最近有好几个咨询如何动态部署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类上,所以会得到如下异常:
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实现,而其又缓存了注入信息;如果刷新了脚本就会得到如下异常:
原因和之前的类似,因为AutowiredAnnotationBeanPostProcessor缓存了InjectionMetadata,即注入的元数据;而这些元数据又存储了目标类、注入的字段/方法信息;所以会得到如上信息;只能通过Hack清除缓存信息了;通过重载RefreshableScriptTargetSource得到一个ReplaceAndRefreshableScriptTargetSource:然后在其刷新时调用的方法obtainFreshBean中调用removeInjectCache(beanFactory, beanName)清除注入元数据缓存即可完美工作了。
涉及的类:
ScriptFactoryPostProcessor.java
ScriptReplaceClassInfoMethodInterceptor.java
ReplaceAndRefreshableScriptTargetSource.java
这种方式不推荐使用:
需要覆盖重写其ScriptFactoryPostProcessor,如果未来发生变化需要跟着维护;
如果在Groovy Controller里添加新的方法是无法注册到RequestMappingHandlerMapping中的;还需要自己手工注册一遍;
所以以上Hack意义不是特别大了,接下来再给大家另一种比较完美的方案。即完全自己定制注册逻辑,不依赖于Spring相关的基础组件:
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
如无必要请不要这样用,请尽量考虑动态脚本语言/框架。
相关推荐
Spring会根据类型匹配找到合适的Bean进行注入,如果存在多个匹配的Bean,则可以通过`@Qualifier`注解指定具体哪一个。 3. `@Scope`:用来定义Bean的作用域,例如单例(`prototype`)、原型(`singleton`)、请求(`...
Groovy是一种基于Java平台的动态编程语言,它与Java语法高度兼容,但提供了更简洁的语法和一些额外的特性,如闭包和元编程。在这个项目中,Groovy可能被用来编写控制器、服务层和领域模型类,以减少代码量并提高...
这个版本的Spring核心支持了更多的类加载策略,以及对动态语言的支持,比如Groovy和JRuby,使得在Java应用中使用这些语言编写业务逻辑成为可能。 最后,`spring-web-2.5.6.jar`专注于Web相关的功能,包括MVC(Model...
在现代Web开发中,Spring框架是Java领域中最受欢迎的全栈解决方案之一,而Spring MVC作为其一部分,提供了强大的MVC(Model-View-Controller)架构支持。Spring Mobile是Spring框架的扩展,专为移动设备优化,它使得...
spring-boot-starter-groovy-templates spring-boot-starter-hateoas spring-boot-starter-hornetq spring-boot-starter-integration spring-boot-starter-jdbc spring-boot-starter-...
创建`applicationContext.xml`或使用Java配置类(从Spring 3.0开始引入)来定义bean的实例化规则和依赖关系。这里我们可以声明Bean的定义,使用`<bean>`标签或`@Component`注解。 5. **Spring Boot简化项目搭建**...
Spring AOP通过动态代理(JDK Proxy或CGLIB)创建目标对象的代理,实现切面的织入。Pointcut定义切入点,Advice定义增强处理,Advisor结合两者,Aspect则封装了多个Advisor。 6. **Spring 声明式事务处理**:基于...
- Spring MVC 是用于构建Web应用的模块,`@RequestMapping` 注解用于映射HTTP请求,`@Controller` 定义控制器bean。 总的来说,Spring API 提供了一个全面的框架,用于构建模块化、可测试和可维护的Java应用。通过...
7. **Groovy 支持**:Spring 3.0 添加了对 Groovy 脚本语言的支持,开发者可以用 Groovy 编写 Spring 配置,简化配置编写过程。 8. **Spring Expression Language(SpEL)**:Spring 3.0 引入了 SpEL,这是一种强大...
在Spring 3.0中,对依赖注入(Dependency Injection, DI)进行了优化,支持了基于注解的配置,开发者可以使用@Component、@Service、@Repository和@Controller等注解来声明bean,并通过@Autowired自动装配依赖。...
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. 使用 ...
- **Groovy支持**:Spring 3.0开始支持使用Groovy编写配置,提供了更简洁的语法。 - **更多模块化**:Spring 3.0将框架拆分为多个模块,便于选择和部署。 在提供的"spring3.0.2-jar包"中,包含了Spring框架的所有...
在Spring 3.0中,注解的应用更加广泛,如@Controller、@Service、@Repository和@Transactional等,极大地简化了XML配置,提高了开发效率。开发者可以通过注解直接在类或方法上声明其在应用程序中的角色和行为。 3....
- **动态语言支持**:支持Groovy、Ruby等动态语言,便于开发者使用这些语言进行开发。 - **测试支持增强**:改进了测试支持,提供了更丰富的测试工具和API。 - **JMX支持**:增强了与Java Management Extensions ...
通过以上步骤,可以成功地在Spring MVC项目中集成JasperReport,实现动态报表的生成和展示。这不仅提升了应用程序的功能性,也为开发者提供了一个灵活的报表解决方案。希望本指南能帮助到正在学习或使用JasperReport...
Spring Boot通过`@EnableJpaRepositories`注解自动配置了数据源,但如果我们需要配置多个数据源,可以创建自定义的`DataSource` bean。例如,创建两个数据源`primaryDataSource`和`secondaryDataSource`: ```java...
5. **Groovy支持**:Spring 3.0 添加了对Groovy的支持,允许在配置中使用Groovy脚本,提高了配置的灵活性。 6. **Expression Language(SpEL)增强**:Spring Expression Language在3.0版本中得到增强,提供更丰富...
8. **Groovy支持**:Spring 4增加了对Groovy的集成,允许开发者使用Groovy语言编写配置和脚本,提高开发效率。 9. **Spring Boot**:虽然不直接包含在Spring 4中,但Spring Boot是Spring 4时代的明星产品,它简化了...
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(控制反转)...
6. **lang**: `lang`模块提供了对动态语言的支持,比如Groovy或JavaScript,使得这些语言能够无缝集成到Spring应用中。 7. **aop**: AOP(面向切面编程)模块是Spring的一大特色,它允许开发者定义“切面”来封装横...