`
jinnianshilongnian
  • 浏览: 21504010 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2418674
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3008770
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5639488
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:259916
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1597311
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:250216
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5858947
Group-logo
跟我学Nginx+Lua开...
浏览量:702000
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:785223
社区版块
存档分类
最新评论

Spring动态部署Bean/Controller/Groovy Controller

阅读更多

最近有好几个咨询如何动态部署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 

 

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

 

 

14
12
分享到:
评论
7 楼 feiweiwei 2017-08-24  
涛神,最近有个地方要动态更新controller,正好参考了您的文章,您例子里使用的是spring4.0,但是我们用的springboot使用的spring是4.3.9,里面RequestMappingHandlerMapping类的handlerMethods和urlMap都没有了,后来看了4.3.9的源码发现,这两个Map变量都没有了,而是在AbstractHandlerMethodMapping类中实现了一个MappingRegistry内部类里找到了Map<T, HandlerMethod> mappingLookup和MultiValueMap<String, T> urlLookup,怀疑是原来的handlerMethod和urlMap,但是这两个因为是protected内部类的对象无法反射出来,请问如何处理,还有个问题就是例子里spring4.0的源码也看了下,handlerMethod的map源码里是定义成final的,remove不会报错吗?谢谢解答。
6 楼 biran1980 2016-01-27  
涛ge,拜读了代码,想请教下,如果我不使用注解RequestMapping,如何能自定义设置请求,比如假设用这种方式,dynamicDeployBeans2.registerController("/hello".clazz);
5 楼 jacky_zz 2014-01-07  
jinnianshilongnian 写道
jacky_zz 写道
通过与开涛几天的讨论以及自己的一些尝试,解决了项目的一些技术需求,感谢开涛!
PS:开涛对spring的理解太深入了。

:oops: ,可别这么说;有问题再讨论


经过这几天的尝试,觉得将groovy脚本放在service层更加合适一点,放在controller层还是不太合适,当然开涛你的方案也是可行的。
4 楼 jinnianshilongnian 2014-01-06  
jacky_zz 写道
通过与开涛几天的讨论以及自己的一些尝试,解决了项目的一些技术需求,感谢开涛!
PS:开涛对spring的理解太深入了。

:oops: ,可别这么说;有问题再讨论
3 楼 jinnianshilongnian 2014-01-06  
hngmduyi 写道
支持一个。

2 楼 jacky_zz 2014-01-06  
通过与开涛几天的讨论以及自己的一些尝试,解决了项目的一些技术需求,感谢开涛!
PS:开涛对spring的理解太深入了。
1 楼 hngmduyi 2014-01-06  
支持一个。

相关推荐

    Spring注解IOC所用的jar包

    Spring会根据类型匹配找到合适的Bean进行注入,如果存在多个匹配的Bean,则可以通过`@Qualifier`注解指定具体哪一个。 3. `@Scope`:用来定义Bean的作用域,例如单例(`prototype`)、原型(`singleton`)、请求(`...

    Groovy+Tapestry5+Spring2.5+Hibernate3.2实现CRUD

    Groovy是一种基于Java平台的动态编程语言,它与Java语法高度兼容,但提供了更简洁的语法和一些额外的特性,如闭包和元编程。在这个项目中,Groovy可能被用来编写控制器、服务层和领域模型类,以减少代码量并提高...

    spring2.5.6

    这个版本的Spring核心支持了更多的类加载策略,以及对动态语言的支持,比如Groovy和JRuby,使得在Java应用中使用这些语言编写业务逻辑成为可能。 最后,`spring-web-2.5.6.jar`专注于Web相关的功能,包括MVC(Model...

    将spring mobile集成到spring mvc

    在现代Web开发中,Spring框架是Java领域中最受欢迎的全栈解决方案之一,而Spring MVC作为其一部分,提供了强大的MVC(Model-View-Controller)架构支持。Spring Mobile是Spring框架的扩展,专为移动设备优化,它使得...

    springboot学习思维笔记.xmind

    spring-boot-starter-groovy-templates spring-boot-starter-hateoas spring-boot-starter-hornetq spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-...

    Spring(三)如何创建一个spring项目

    创建`applicationContext.xml`或使用Java配置类(从Spring 3.0开始引入)来定义bean的实例化规则和依赖关系。这里我们可以声明Bean的定义,使用`&lt;bean&gt;`标签或`@Component`注解。 5. **Spring Boot简化项目搭建**...

    spring源码分析(1-10)

    Spring AOP通过动态代理(JDK Proxy或CGLIB)创建目标对象的代理,实现切面的织入。Pointcut定义切入点,Advice定义增强处理,Advisor结合两者,Aspect则封装了多个Advisor。 6. **Spring 声明式事务处理**:基于...

    spring API

    - Spring MVC 是用于构建Web应用的模块,`@RequestMapping` 注解用于映射HTTP请求,`@Controller` 定义控制器bean。 总的来说,Spring API 提供了一个全面的框架,用于构建模块化、可测试和可维护的Java应用。通过...

    spring3开发文档

    7. **Groovy 支持**:Spring 3.0 添加了对 Groovy 脚本语言的支持,开发者可以用 Groovy 编写 Spring 配置,简化配置编写过程。 8. **Spring Expression Language(SpEL)**:Spring 3.0 引入了 SpEL,这是一种强大...

    spring3.0轻量级框架

    在Spring 3.0中,对依赖注入(Dependency Injection, DI)进行了优化,支持了基于注解的配置,开发者可以使用@Component、@Service、@Repository和@Controller等注解来声明bean,并通过@Autowired自动装配依赖。...

    Spring-Reference_zh_CN(Spring中文参考手册)

    Controller 代码 14.6.2.3. Excel视图子类 14.6.2.4. PDF视图子类 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.2.1. 配置ViewResolver 14.7.2.2. 配置View 14.7.2.3. 关于报表文件 14.7.2.4. 使用 ...

    spring3.0.2所有jar包

    - **Groovy支持**:Spring 3.0开始支持使用Groovy编写配置,提供了更简洁的语法。 - **更多模块化**:Spring 3.0将框架拆分为多个模块,便于选择和部署。 在提供的"spring3.0.2-jar包"中,包含了Spring框架的所有...

    spring3.0 jar包

    在Spring 3.0中,注解的应用更加广泛,如@Controller、@Service、@Repository和@Transactional等,极大地简化了XML配置,提高了开发效率。开发者可以通过注解直接在类或方法上声明其在应用程序中的角色和行为。 3....

    Spring2.5.6 参考文档

    - **动态语言支持**:支持Groovy、Ruby等动态语言,便于开发者使用这些语言进行开发。 - **测试支持增强**:改进了测试支持,提供了更丰富的测试工具和API。 - **JMX支持**:增强了与Java Management Extensions ...

    springmvc 集成jasperReport.docx

    通过以上步骤,可以成功地在Spring MVC项目中集成JasperReport,实现动态报表的生成和展示。这不仅提升了应用程序的功能性,也为开发者提供了一个灵活的报表解决方案。希望本指南能帮助到正在学习或使用JasperReport...

    Spring Boot中整合MyBatis

    Spring Boot通过`@EnableJpaRepositories`注解自动配置了数据源,但如果我们需要配置多个数据源,可以创建自定义的`DataSource` bean。例如,创建两个数据源`primaryDataSource`和`secondaryDataSource`: ```java...

    Spring3.0 MVC的初次尝试

    5. **Groovy支持**:Spring 3.0 添加了对Groovy的支持,允许在配置中使用Groovy脚本,提高了配置的灵活性。 6. **Expression Language(SpEL)增强**:Spring Expression Language在3.0版本中得到增强,提供更丰富...

    北京动力节点-Spring4讲义

    8. **Groovy支持**:Spring 4增加了对Groovy的集成,允许开发者使用Groovy语言编写配置和脚本,提高开发效率。 9. **Spring Boot**:虽然不直接包含在Spring 4中,但Spring Boot是Spring 4时代的明星产品,它简化了...

    Spring中文帮助文档

    2.6.4. 将Spring 应用程序上下文部署为JCA adapter 2.6.5. 计划任务 2.6.6. 对Java 5 (Tiger) 支持 2.7. 移植到Spring 2.5 2.7.1. 改变 2.8. 更新的样例应用 2.9. 改进的文档 I. 核心技术 3. IoC(控制反转)...

    spring-framework-4.1.4.release-schema

    6. **lang**: `lang`模块提供了对动态语言的支持,比如Groovy或JavaScript,使得这些语言能够无缝集成到Spring应用中。 7. **aop**: AOP(面向切面编程)模块是Spring的一大特色,它允许开发者定义“切面”来封装横...

Global site tag (gtag.js) - Google Analytics