亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Spring動(dòng)態(tài)部署B(yǎng)ean/Controller/Groovy Control

系統(tǒng) 1903 0

最近有好幾個(gè)咨詢?nèi)绾蝿?dòng)態(tài)部署B(yǎng)ean/動(dòng)態(tài)部署Spring mvc 控制器;首先聲明下:基于普通Java/JavaEE環(huán)境的不適合做動(dòng)態(tài)部署;如果你有這種需求請(qǐng)考慮使用如Play Framework/Grails這種框架。但是還是有少量朋友會(huì)有這種需求:我的應(yīng)用中只有少量幾個(gè)需要?jiǎng)討B(tài)部署的組件;好吧,那我來(lái)寫(xiě)一個(gè)能動(dòng)態(tài)部署B(yǎng)ean/Controller的工具類(lèi)吧。

?

注意,因?yàn)镾pring整個(gè)框架非常好的遵循開(kāi)閉原則,所以只能通過(guò)反射來(lái)操作,而且目前不考慮Spring 3.1版本以下的(或者使用DefaultAnnotationHandlerMapping,從Spring3.1開(kāi)始使用RequestMappingHandlerMapping,之前實(shí)現(xiàn)了對(duì)DefaultAnnotationHandlerMapping的支持,但是想了想還是請(qǐng)考慮升級(jí)吧,因?yàn)閟pring向下兼容性非常好),如果想在Spring 3.1之前版本使用請(qǐng)考慮自己修改代碼/升級(jí)框架。

?

對(duì)于動(dòng)態(tài)注冊(cè)Groovy腳本,Spring內(nèi)部提供了支持,使用如<lang:groovy>標(biāo)簽;但是對(duì)于需要?jiǎng)討B(tài)修改的Controller就不那么完美了;

1、如果開(kāi)啟其refresh-check-delay(即多久重載一下腳本),這個(gè)目前實(shí)現(xiàn)很土,即假設(shè)我設(shè)置為500毫秒,不管文件修改/沒(méi)修改都會(huì)自動(dòng)reload,所以請(qǐng)考慮不要使用它的這種刷新腳本機(jī)制;我們需要的是檢查如文件修改否再刷新;

2、如果開(kāi)啟了refresh-check-delay,其內(nèi)部是通過(guò)Aop完成的,如果沒(méi)有設(shè)置其是proxy-target-class="true",那么它是走JDK動(dòng)態(tài)代理,因?yàn)槲覀兇蟛糠挚刂破魇菦](méi)有實(shí)現(xiàn)接口的,所以即使你注冊(cè)到Spring mvc,也會(huì)映射不到的,因此請(qǐng)使用CGLIB代理;創(chuàng)建代理是通過(guò)ScriptFactoryPostProcessor來(lái)完成的;

3、如果你注冊(cè)到Spring MVC了,又刷新了腳本,那么它是通過(guò)ScriptFactoryPostProcessor注冊(cè)到proxy一個(gè)RefreshableScriptTargetSource,通過(guò)這個(gè)TargetSource刷新的;問(wèn)題來(lái)了:

對(duì)于Spring mvc進(jìn)行映射是通過(guò)RequestMappingHandlerMapping實(shí)現(xiàn),那么RequestMappingHandlerMapping通過(guò)如下字段來(lái)保持映射關(guān)系的;

?

      private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>(); //RequestMappingInfo--->HandlerMethod(保持了controllerBean method)
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>(); //url--->RequestMappingInfo

    

因此如果你刷新了腳本,相當(dāng)于又創(chuàng)建了一個(gè)新的controllerBean,因此拿著的是老的controllerBean和Methond(來(lái)的controllerBean類(lèi)的),而當(dāng)我們調(diào)用時(shí)會(huì)把Method最終綁定到新的controllerBean類(lèi)上,所以會(huì)得到如下異常:

?

寫(xiě)道
java.lang.ClassCastException: com.sishuok.spring.controller.GroovyController cannot be cast to com.sishuok.spring.controller.GroovyController
at com.sishuok.spring.controller.GroovyController$$FastClassByCGLIB$$bb52fd90.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:713)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133)
at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:646)
at com.sishuok.spring.controller.GroovyController$$EnhancerByCGLIB$$5c30e5e0.hello(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:214)

"GroovyController cannot be cast to?GroovyController",類(lèi)名一樣,那就是ClassLoader不一樣了,即刷新腳本時(shí)又加載了一個(gè)GroovyController類(lèi)。由于Spring mvc實(shí)現(xiàn)機(jī)制的問(wèn)題,無(wú)法通過(guò)框架本身解決,也就是說(shuō)動(dòng)態(tài)刷新的Groovy腳本不能用作控制器;具體原因請(qǐng)參考: https://jira.springsource.org/browse/SPR-5749 ;怎么辦呢?想到一個(gè)辦法就是在反射調(diào)用Method之前把老的controllerBean類(lèi)替換為新的controllerBean類(lèi)即可:通過(guò)修改ScriptFactoryPostProcessor的postProcessBeforeInstantiation方法中調(diào)用的createRefreshableProxy方法:為proxyFactory添加一個(gè)增強(qiáng):proxyFactory.addAdvice(new ScriptReplaceClassInfoMethodInterceptor()):

          @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        boolean isCglibMi = mi.getClass().getName().equals("org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation");
        if (isCglibMi && mi.getMethod().getDeclaringClass() != mi.getThis().getClass()) {
            MethodProxy methodProxy = (MethodProxy) ReflectionUtils.getField(methodProxyField, mi);
            Object fastClassInfo = ReflectionUtils.getField(fastClassInfoField, methodProxy);
            ReflectionUtils.setField(fastClassInfoF1Field, fastClassInfo, FastClass.create(mi.getThis().getClass()));
        }
        return mi.proceed();
    }

    

該增強(qiáng)通過(guò)反射替換老的controllerBean類(lèi)為新的controllerBean類(lèi)即可,這也是沒(méi)有辦法的辦法 皺眉

?

4、如果你的Groovy Controller又有依賴注入,如@Autowired private UserController userController;又完蛋了,因?yàn)閷?duì)于@Autowired注解是通過(guò)AutowiredAnnotationBeanPostProcessor實(shí)現(xiàn),而其又緩存了注入信息;如果刷新了腳本就會(huì)得到如下異常:

寫(xiě)道
java.lang.IllegalArgumentException: Can not set com.sishuok.spring.controller.UserController field com.sishuok.spring.controller.GroovyController.userController to com.sishuok.spring.controller.GroovyController

原因和之前的類(lèi)似,因?yàn)锳utowiredAnnotationBeanPostProcessor緩存了InjectionMetadata,即注入的元數(shù)據(jù);而這些元數(shù)據(jù)又存儲(chǔ)了目標(biāo)類(lèi)、注入的字段/方法信息;所以會(huì)得到如上信息;只能通過(guò)Hack清除緩存信息了;通過(guò)重載RefreshableScriptTargetSource得到一個(gè)ReplaceAndRefreshableScriptTargetSource:然后在其刷新時(shí)調(diào)用的方法obtainFreshBean中調(diào)用removeInjectCache(beanFactory, beanName)清除注入元數(shù)據(jù)緩存即可完美工作了。

?

涉及的類(lèi):

DynamicDeployBeans2.java

ScriptFactoryPostProcessor.java?

ScriptReplaceClassInfoMethodInterceptor.java?

ReplaceAndRefreshableScriptTargetSource.java?

?

?

這種方式不推薦使用:

需要覆蓋重寫(xiě)其ScriptFactoryPostProcessor,如果未來(lái)發(fā)生變化需要跟著維護(hù);

如果在Groovy Controller里添加新的方法是無(wú)法注冊(cè)到RequestMappingHandlerMapping中的;還需要自己手工注冊(cè)一遍;

?

所以以上Hack意義不是特別大了,接下來(lái)再給大家另一種比較完美的方案。即完全自己定制注冊(cè)邏輯,不依賴于Spring相關(guān)的基礎(chǔ)組件:

?

DynamicDeployBeans.java

      dynamicDeployBeans.registerBean(DynamicService1.class); //注冊(cè)一般的Class類(lèi)
dynamicDeployBeans.registerBean(DynamicService2.class); //注冊(cè)一般的Class類(lèi) 注意DynamicService2依賴于DynamicService1

dynamicDeployBeans.registerController(DynamicController.class); //注冊(cè)一般的控制器(可以重復(fù)注冊(cè))

dynamicDeployBeans2.registerGroovyController("classpath:com/sishuok/spring/dynamic/GroovyController.groovy"); //注冊(cè)Groovy Controller 注冊(cè)后根據(jù)scriptCheckInterval會(huì)定期檢查腳本有沒(méi)有更新
    

?

這種方式可以對(duì)控制器的動(dòng)態(tài)修改提供更好的支持:

動(dòng)態(tài)修改代碼;

動(dòng)態(tài)增/刪/改方法,即可以刪除一個(gè)已有的映射,或者添加一個(gè)新的映射,不會(huì)拋出映射二義性錯(cuò)誤;

依賴注入的支持。

?

具體請(qǐng)參考我的github

https://github.com/zhangkaitao/spring4-showcase/tree/master/spring-dynamic ?

?

如無(wú)必要請(qǐng)不要這樣用,請(qǐng)盡量考慮動(dòng)態(tài)腳本語(yǔ)言/框架。

?

?

Spring動(dòng)態(tài)部署B(yǎng)ean/Controller/Groovy Controller


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 奇米影视四色中文字幕 | 日本一本久 | 成人欧美一区二区三区视频 | 亚洲欧美日韩激情在线观看 | 日本一本二本免费播放视频 | 全部精品孕妇色视频在线 | 青青青永久免费视频 | 奇米欧美成人综合影院 | 国产美女a做受大片免费 | 日韩免费精品一级毛片 | 粗大猛烈进出呻吟声的视频 | 天天伊人 | 国内视频一区 | 国产91在线免费 | 中文字幕欧美日韩 | 狠狠操夜夜操 | 天天做天天爱天天一爽一毛片 | 久久伊人中文字幕 | 欧美激情亚洲精品日韩1区2区 | 国产这里只有精品 | 亚洲香蕉国产高清在线播放 | 国产成年网站v片在线观看 国产成人 免费观看 | 国内精品自在自线在免费 | 久久久久国产精品四虎 | 亚洲欧美一区二区三区不卡 | 爱爱一区 | 国产高清福利91成人 | 久久久香蕉 | 国产欧美日韩亚洲 | 免费看国产精品久久久久 | 黄频网站在线观看视频 | 综合在线视频精品专区 | 毛片免费视频播放 | 亚洲高清免费 | 一区二区三区欧美视频 | 久久视屏这里只有精品6国产 | 伊人色综合琪琪久久社区 | 综合久久一区二区三区 | 青草伊伊| 91久久99| 免费鲁丝片一级观看 |