Spring中Bean多种实现切换方案
一个公共工程中的Spring配置文件,可能会被多个工程引用。因为每个工程可能只需要公共工程中的一部分Bean,所以这些工程的Spring容器启动时,需要区分开哪些Bean要创建出来。另一种场景是:想通过Properties文件中的配置开关,就将Spring配置文件中Bean的实现切换成另一套。
方法一:Qulifier区分Bean
1.1应用实例
以Apache开源框架Jetspeed中的一段配置为例:page-manager.xml
===============================================================================
<bean name="xmlPageManager"class="org.apache.jetspeed.page.psml.CastorXmlPageManager"init-method="init" destroy-method="destroy"> <meta key="j2:cat" value="xmlPageManager orpageSerializer" /> <constructor-arg index="0"> <ref bean="IdGenerator"/> </constructor-arg> <constructor-arg index="1"> <refbean="xmlDocumentHandlerFactory" /> </constructor-arg> …… </bean> <bean id="dbPageManager"class="org.apache.jetspeed.page.impl.DatabasePageManager"init-method="init" destroy-method="destroy"> <meta key="j2:cat" value="dbPageManager orpageSerializer" /> <!-- OJB configuration file resourcepath --> <constructor-arg index="0"> <value>JETSPEED-INF/ojb/page-manager-repository.xml</value> </constructor-arg> <!-- fragment id generator --> <constructor-arg index="1"> <ref bean="IdGenerator"/> </constructor-arg> …… </bean>
1.2 Bean过滤器
JetspeedBeanDefinitionFilter在Spring容器解析每个Bean定义时,会取出上面Bean配置中j2:cat对应的值,例如dbPageManageror pageSerializer。然后将这部分作为正则表达式与当前的Key(从配置文件中读出)进行匹配。只有匹配上的Bean,才会被Spring容器创建出来。
JetspeedBeanDefinitionFilter
===============================================================================
public boolean match(BeanDefinition bd) { String beanCategoriesExpression = (String)bd.getAttribute(CATEGORY_META_KEY); boolean matched = true; if (beanCategoriesExpression != null) { matched = ((matcher != null)&& matcher.match(beanCategoriesExpression)); } return matched; } public void registerDynamicAlias(BeanDefinitionRegistry registry, String beanName,BeanDefinition bd) { String aliases =(String)bd.getAttribute(ALIAS_META_KEY); if (aliases != null) { StringTokenizer st = newStringTokenizer(aliases, " ,"); while (st.hasMoreTokens()) { String alias = st.nextToken(); if (!alias.equals(beanName)) { registry.registerAlias(beanName, alias); } } } }
===============================================================================
match()方法中的CATEGORY_META_KEY的值就是j2:cat,matcher类中保存的就是当前的Key,并负责将当前Key与每个Bean的进行正则表达式匹配。
registerDynamicAlias的作用是:在Bean匹配成功后,定制的Spring容器会调用此方法为Bean注册别名。详见下面1.3中的源码。
1.3定制Spring容器
定制一个Spring容器,重写registerBeanDefinition()方法,在Spring注册Bean时进行拦截。
===============================================================================
public class FilteringXmlWebApplicationContextextends XmlWebApplicationContext { private JetspeedBeanDefinitionFilterfilter; publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext) { this(filter, configLocations,initProperties, servletContext, null); } publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext,ApplicationContext parent) { super(); if (parent != null) { this.setParent(parent); } if (initProperties != null) { PropertyPlaceholderConfigurer ppc =new PropertyPlaceholderConfigurer(); ppc.setIgnoreUnresolvablePlaceholders(true); ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK); ppc.setProperties(initProperties); addBeanFactoryPostProcessor(ppc); } setConfigLocations(configLocations); setServletContext(servletContext); this.filter = filter; } protected DefaultListableBeanFactorycreateBeanFactory() { return new FilteringListableBeanFactory(filter,getInternalParentBeanFactory()); } } public classFilteringListableBeanFactory extends DefaultListableBeanFactory { private JetspeedBeanDefinitionFilterfilter; public FilteringListableBeanFactory(JetspeedBeanDefinitionFilterfilter, BeanFactory parentBeanFactory) { super(parentBeanFactory); this.filter = filter; if (this.filter == null) { this.filter = newJetspeedBeanDefinitionFilter(); } this.filter.init(); } /** * Override of the registerBeanDefinitionmethod to optionally filter out a BeanDefinition and * if requested dynamically register anbean alias */ public void registerBeanDefinition(StringbeanName, BeanDefinition bd) throws BeanDefinitionStoreException { if (filter.match(bd)) { super.registerBeanDefinition(beanName, bd); if (filter != null) { filter.registerDynamicAlias(this, beanName, bd); } } } }
1.4为Bean起别名
使用BeanReferenceFactoryBean工厂Bean,将上面配置的两个Bean(xmlPageManager和dbPageManager)包装起来。将Key配成各自的,实现通过配置当前Key来切换两种实现。别名都配成一个,这样引用他们的Bean就直接引用这个别名就行了。例如下面的PageLayoutComponent。
page-manager.xml
===============================================================================
<bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean"> <meta key="j2:cat"value="xmlPageManager" /> <meta key="j2:alias"value="org.apache.jetspeed.page.PageManager" /> <propertyname="targetBeanName" value="xmlPageManager" /> </bean> <bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean"> <meta key="j2:cat"value="dbPageManager" /> <meta key="j2:alias"value="org.apache.jetspeed.page.PageManager" /> <propertyname="targetBeanName" value="dbPageManager" /> </bean> <bean id="org.apache.jetspeed.layout.PageLayoutComponent" class="org.apache.jetspeed.layout.impl.PageLayoutComponentImpl"> <meta key="j2:cat"value="default" /> <constructor-arg index="0"> <refbean="org.apache.jetspeed.page.PageManager" /> </constructor-arg> <constructor-arg index="1"> <value>jetspeed-layouts::VelocityOneColumn</value> </constructor-arg> </bean>
方法二:使用注解区分Bean
(未完 待续)