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
(未完 待續)