之前遇到一个很有意思的问题:我需要批量重定义特定类型的由Spring容器托管的Bean。具体体现在,我有很多控制器类(Controller)和校验器类(Validator),我希望他们都是多例(Prototype)的,而Spring默认创建实例是单例(Singleton)的。有朋友可能要问:为什么不自己在Bean定义时加参数呢@Scope("prototype")?我的回答很简单:懒……。因为我的Bean声明是这样的:
那么我就没法很精确得去设置控制器和校验器的类实例为多例,因为我这里很笼统。
接下来我们详解怎样使用代码实现设置特定Bean定义的修改。
ApplicationListener-ContextRefreshedEvent
我们可以监听一个Spring的ApplicationContext的事件来让Spring的Bean容器配置完成后通知我们来处理一下。
<bean id="beanDefineConfigue" class="com.xx.yy.zz.BeanDefineConfigue"></bean>
1 public class BeanDefineConfigue implements ApplicationListener<ContextRefreshedEvent> { 2 3 @Override 4 public void onApplicationEvent(ContextRefreshedEvent event) { 5 6 } 7 }
ContextRefreshedEvent是“Event raised when an ApplicationContext gets initialized or refreshed.(当ApplicationContext初始化完成或刷新完成后产生的事件)”
当然,我们可以在onApplicationEvent函数内“搞事儿”了!
BeanFactory-BeanDefinition-registerBeanDefinition
1 public void onApplicationEvent(ContextRefreshedEvent event) { 2 ConfigurableApplicationContext context = (ConfigurableApplicationContext) event.getApplicationContext(); 3 DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory(); 4 // 控制器 5 String[] controllers = factory.getBeanNamesForAnnotation(Controller.class); 6 if(controllers != null) { 7 for(String controllerBeanName : controllers) { 8 BeanDefinition beanDefine = factory.getBeanDefinition(controllerBeanName); 9 String scope = beanDefine.getScope(); 10 if(scope == null || !scope.equals(ConfigurableBeanFactory.SCOPE_PROTOTYPE)) { 11 beanDefine.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE); 12 factory.registerBeanDefinition(controllerBeanName, beanDefine); 13 } 14 } 15 } 16 // 校验器 17 Object[] validators = factory.getBeanNamesForType(Validator.class); 18 if(validators != null) { 19 for(Object _validatorBeanName : validators) { 20 String validatorBeanName = String.valueOf(_validatorBeanName); 21 BeanDefinition beanDefine = factory.getBeanDefinition(validatorBeanName); 22 String scope = beanDefine.getScope(); 23 if(scope == null || !scope.equals(ConfigurableBeanFactory.SCOPE_PROTOTYPE)) { 24 beanDefine.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE); 25 factory.registerBeanDefinition(validatorBeanName, beanDefine); 26 } 27 } 28 } 29 }
可以看到,核心代码其实很少,也很容易懂!我针对控制器类和校验器类的所有Bean定义(使用getBeanNamesForType函数可以获取给定类型及其子类型的所有Bean定义;上文对Controller类型的检测是使用了Spring的@Controller,这是因为我个人的业务需求不一样,大家注意,beanfactory中的各种方法大家查看API灵活使用),检测到它们scope不为prototype时强制重设!
说在结尾
先把Spring看成一个Hashtable,它存了很多键值,就是Bean定义(包括Bean关系等等);其次是Spring不会凭空产生,更不会凭空为你托管对象,我们使用Spring的方式最终都是{new XXYYZZApplicationContext().getBean(XXYYZZ)},你在web.xml中定义的ContextLoaderListener,或者是其他中间件(Struts等)。
“万事万物都有其源头。”所以,如果观看此篇博文的朋友进行单元测试时发现自动注入等功能未实现,请看看你是否为Spring容器创建了对象。