收藏私塾在线
 

欢迎您来到私塾在线网!   

请登录! 

免费注册 


zhang的笔记
状态: 离线
人气:5090732
访问用户量:4227
笔记经验:
总积分:261656
级别:VIP5
搜索本笔记
ta的交流分类
ta的交流主题贴(544)
ta的所有交流贴(1049)
ta的全部笔记
全部笔记(255)
未分类笔记(1)
Java Web(9)
并发实践(1)
课程问题(0)
Java(22)
架构(1)
缓存(5)
JavaEE(0)
JVM(12)
跟我学spring3(68)
Spring Sec……(43)
Spring 3.x……(25)
Spring Sec……(20)
跟开涛学Spring……(17)
深入剖析Spring……(18)
性能调优(10)
前端(2)
Tomcat源码解读(1)
spring sec……(0)
存档
2014-01(7)
2013-12(10)
2012-10(4)
2012-09(2)
2012-08(31)
2012-07(10)
2012-06(5)
2012-05(41)
2012-04(3)
2012-03(41)
2012-02(54)
2011-11(17)
2011-10(30)

2014-01-07 11:03:55
Spring动态部署Bean/Controller/Groovy Controller
浏览(14014)|评论(0)   交流分类:Java|笔记分类: 跟我学spring3

最近有好几个咨询如何动态部署Bean/动态部署Spring mvc 控制器;首先声明下:基于普通Java/JavaEE环境的不适合做动态部署;如果你有这种需求请考虑使用如Play Framework/Grails这种框架。但是还是有少量朋友会有这种需求:我的应用中只有少量几个需要动态部署的组件;好吧,那我来写一个能动态部署Bean/Controller的工具类吧。

 

注意,因为Spring整个框架非常好的遵循开闭原则,所以只能通过反射来操作,而且目前不考虑Spring 3.1版本以下的(或者使用DefaultAnnotationHandlerMapping,从Spring3.1开始使用RequestMappingHandlerMapping,之前实现了对DefaultAnnotationHandlerMapping的支持,但是想了想还是请考虑升级吧,因为spring向下兼容性非常好),如果想在Spring 3.1之前版本使用请考虑自己修改代码/升级框架。

 

对于动态注册Groovy脚本,Spring内部提供了支持,使用如<lang:groovy>标签;但是对于需要动态修改的Controller就不那么完美了;

1、如果开启其refresh-check-delay(即多久重载一下脚本),这个目前实现很土,即假设我设置为500毫秒,不管文件修改/没修改都会自动reload,所以请考虑不要使用它的这种刷新脚本机制;我们需要的是检查如文件修改否再刷新;

2、如果开启了refresh-check-delay,其内部是通过Aop完成的,如果没有设置其是proxy-target-class="true",那么它是走JDK动态代理,因为我们大部分控制器是没有实现接口的,所以即使你注册到Spring mvc,也会映射不到的,因此请使用CGLIB代理;创建代理是通过ScriptFactoryPostProcessor来完成的;

3、如果你注册到Spring MVC了,又刷新了脚本,那么它是通过ScriptFactoryPostProcessor注册到proxy一个RefreshableScriptTargetSource,通过这个TargetSource刷新的;问题来了:

对于Spring mvc进行映射是通过RequestMappingHandlerMapping实现,那么RequestMappingHandlerMapping通过如下字段来保持映射关系的;

 

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

因此如果你刷新了脚本,相当于又创建了一个新的controllerBean,因此拿着的是老的controllerBean和Methond(来的controllerBean类的),而当我们调用时会把Method最终绑定到新的controllerBean类上,所以会得到如下异常:

 

写道
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",类名一样,那就是ClassLoader不一样了,即刷新脚本时又加载了一个GroovyController类。由于Spring mvc实现机制的问题,无法通过框架本身解决,也就是说动态刷新的Groovy脚本不能用作控制器;具体原因请参考:https://jira.springsource.org/browse/SPR-5749;怎么办呢?想到一个办法就是在反射调用Method之前把老的controllerBean类替换为新的controllerBean类即可:通过修改ScriptFactoryPostProcessor的postProcessBeforeInstantiation方法中调用的createRefreshableProxy方法:为proxyFactory添加一个增强: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();
    }

该增强通过反射替换老的controllerBean类为新的controllerBean类即可,这也是没有办法的办法皱眉

 

4、如果你的Groovy Controller又有依赖注入,如@Autowired private UserController userController;又完蛋了,因为对于@Autowired注解是通过AutowiredAnnotationBeanPostProcessor实现,而其又缓存了注入信息;如果刷新了脚本就会得到如下异常:

写道
java.lang.IllegalArgumentException: Can not set com.sishuok.spring.controller.UserController field com.sishuok.spring.controller.GroovyController.userController to com.sishuok.spring.controller.GroovyController

原因和之前的类似,因为AutowiredAnnotationBeanPostProcessor缓存了InjectionMetadata,即注入的元数据;而这些元数据又存储了目标类、注入的字段/方法信息;所以会得到如上信息;只能通过Hack清除缓存信息了;通过重载RefreshableScriptTargetSource得到一个ReplaceAndRefreshableScriptTargetSource:然后在其刷新时调用的方法obtainFreshBean中调用removeInjectCache(beanFactory, beanName)清除注入元数据缓存即可完美工作了。

 

涉及的类:

DynamicDeployBeans2.java

ScriptFactoryPostProcessor.java 

ScriptReplaceClassInfoMethodInterceptor.java 

ReplaceAndRefreshableScriptTargetSource.java 

 

 

这种方式不推荐使用:

需要覆盖重写其ScriptFactoryPostProcessor,如果未来发生变化需要跟着维护;

如果在Groovy Controller里添加新的方法是无法注册到RequestMappingHandlerMapping中的;还需要自己手工注册一遍;

 

所以以上Hack意义不是特别大了,接下来再给大家另一种比较完美的方案。即完全自己定制注册逻辑,不依赖于Spring相关的基础组件:

 

DynamicDeployBeans.java

dynamicDeployBeans.registerBean(DynamicService1.class); //注册一般的Class类
dynamicDeployBeans.registerBean(DynamicService2.class); //注册一般的Class类 注意DynamicService2依赖于DynamicService1

dynamicDeployBeans.registerController(DynamicController.class); //注册一般的控制器(可以重复注册)

dynamicDeployBeans2.registerGroovyController("classpath:com/sishuok/spring/dynamic/GroovyController.groovy"); //注册Groovy Controller 注册后根据scriptCheckInterval会定期检查脚本有没有更新

 

这种方式可以对控制器的动态修改提供更好的支持:

动态修改代码;

动态增/删/改方法,即可以删除一个已有的映射,或者添加一个新的映射,不会抛出映射二义性错误;

依赖注入的支持。

 

具体请参考我的github

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

 

如无必要请不要这样用,请尽量考虑动态脚本语言/框架。

 

 

精品视频课程推荐

Java数据结构和算法精讲版
本课程专注于数据结构和算法的内容,使用Java来进行代码示例,不空洞的讲解概念和理论,重点放在代码的实现和示例上。 从零开始、全面系统、成体系的讲解数据结构和基本算法,循序渐进的讲述构建软件系统所常见的数据结构和算法。

研磨设计模式——跟着cc学设计系列视频教程
本视频课程是北京Java私塾原创精品书籍《研磨设计模式》一书的配套学习视频,由《研磨设计模式》的第一作者CC录制 课程目标:全面、系统的掌握GoF设计模式的知识,达到可以在实际项目开发中运用的能力 技术要点:如何实现可配置、如何实现缓存以及缓存的管理、如何实现用缓存来控制多实例的创建、如何实现参数化工厂、 如何实现可扩展工厂、如何实现原型管理器、如何实现Java的静态代理和动态代理、如何实现多线程处理队列请求、 如何实现命令的参数化配置、可撤销的操作、宏命令、队列请求和日志请求、如何实现翻页迭代、如何检测环状结构、 如何实现通用的增删改查、如何模拟工作流来处理流程、如何实现简单又通用的XML读取、如何实现模拟AOP的功能......

ssh+jbpm项目(某集团OA)视频教程
达到能综合使用Struts2+Spring3+Hibernate3+Jbpm4来进行实际项目开发的能力。 包括:ssh和jbpm的整合;数据字典;通用DAO(Spring+Hibernate+泛型+反射+SpEL+模板方法模式);自动生成UUID的加强版;分层开发、SSH联合的基本开发;翻页的taglib;示范真实值和表现值,数据参照的实现;文件上传下载;主子表操;登录验证码;登录控制的拦截器

高级软件架构师实战培训阶段一
内容概述:本课程专注于构建:高可扩展性、高性能、大数据量、高并发、分布式的系统架构。 从零开始、全面系统、成体系的软件架构课程,循序渐进的讲述构建上述系统架构所需要的各种技术知识和技能。
技术要点: 1:构建基本的业务功能块,基于Maven+Git+Spring mvc+spring+mybatis+ehcache+mysql+X-gen代码生成
 2:高扩展性的分布式体系架构(基于Nginx+Varnish+Memcache+ActiveMQ)
 3:NoSQL的合理使用和架构优化(基于MongoDB)
 4:分布式文件存储和架构优化(基于MogileFS)

深入浅出学Spring Web MVC视频教程
系统、完整的学习Spring Web MVC开发的知识。包括:Spring Web MVC入门;理解DispatcherServlet;注解式控制器开发详解;数据类型转换;数据格式化;数据验证; 拦截器;对Ajax的支持;文件上传下载;表单标签等内容;最后以一个综合的CRUD带翻页的应用示例来综合所学的知识

浏览(14014)|评论(0)   交流分类:Java|笔记分类: 跟我学spring3

评论(0)
请登录后评论 登录

关于我们 | 联系我们 | 用户协议 | 私塾在线服务协议 | 版权声明 | 隐私保护

版权所有 Copyright(C)2009-2012 私塾在线学习网