不重复配置——利用Spring通用化配置
还记得 如下这种配置吗:
1、struts2作用域:每一个Action我们必须设置scope为prototype 每次都做重复的配置,而且有时候忘记配置还会出现bug,想不想删掉它?
<bean id="**Action" class="***Action" scope="prototype">
2、在使用spring集成hibernate时,每次都必须注入sessionFactory,虽然可以用父子bean解决 但还是要写parent="abstractHibernateDao"之类的。
<bean id="***Dao" class="***DaoImpl">
<property name="sessionFactory" ref="sessionFactory">
</bean>
受够了这种配置,得想法解决这个重复配置,怎么解决呢?
补充:
首先感谢downpour大哥的批评:
prototype属性不能也不该省略,配置是给人看的,要是人看不懂就是垃圾。
综上所述,此方案纯粹脱裤子放屁多此一举。
1、sessionFactory注入问题:
如果是注解这个可以写个通用的基类就很容易搞定;
如果是XML 也可以通过在beans标签上使用 default-autowire="byName" default-autowire-candidates="*Dao" 也能解决问题,当我们通过类似于如下方式时,必须在每个相关的配置文件中都写上。
<context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring-common-config.xml, classpath:spring-budget-config.xml </param-value> </context-param>
2、struts2 Action scope问题
如果使用StrutsPrepareAndExecuteFilter可以通过:
<init-param> <param-name>actionPackages</param-name> <param-value>Action所在包前缀</param-value> </init-param>
scope会自动是prototype
使用我说的这种设置方式:我觉得因为只要会Struts2+Spring集成都知道struts2的Action是prototype,可以用;『prototype属性不能也不该省略,配置是给人看的,要是人看不懂就是垃圾。』这个是这么回事,需要仔细考虑下;当然我可以考虑在配置文件中加上注释 说明一下 告诉其他人是怎么回事。
另外这个功能我想可以改建为检查配置是否正确 类似于spring的依赖检查。欢迎大家拍砖。
思路:
在BeanFactory创建Bean之前查找所有我们需要通用化配置的Bean 然后修改BeanDefinition注入我们的通用数据就可以解决我们这个问题。
Spring提供了BeanFactoryPostProcessor扩展点,用于提供给我们修改BeanDefinition数据的。
还记得org.springframework.beans.factory.config.PropertyPlaceholderConfigurer吗? 替换占位符数据,它就是一个BeanFactoryPostProcessor的实现。
好了思路有了,接下来我们实现一下吧:
1、XML配置方式
*
* 使用方法:<br/>
* <pre>
* <bean class="cn.javass.common.spring.CommonConfigureProcessor">
<property name="config">
<map>
<!-- aspectj表达式 选择所有Action结尾的Bean 注入scope数据 为 prototype -->
<entry key="cn.javass..*Action">
<props>
<prop key="scope">prototype</prop>
</props>
</entry>
<!-- aspectj表达式 选择所有的HibernateDaoSupport实现Bean 注入sessionFactory -->
<entry key="org.springframework.orm.hibernate3.support.HibernateDaoSupport+">
<props>
<prop key="property-ref">sessionFactory=sessionFactory</prop>
</props>
</entry>
</map>
</property>
</bean>
* </pre>
*
* 目前支持三种配置:
* scope:注入作用域
* property-ref:注入Bean引用 如之上的sessionFactory
* propertyName=beanName
* property-value:注入常量值
* propertyName=常量
2、CommonConfigureProcessor源码
package cn.javass.common.spring; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import org.aspectj.bridge.IMessageHandler; import org.aspectj.weaver.ResolvedType; import org.aspectj.weaver.World; import org.aspectj.weaver.bcel.BcelWorld; import org.aspectj.weaver.patterns.Bindings; import org.aspectj.weaver.patterns.FormalBinding; import org.aspectj.weaver.patterns.IScope; import org.aspectj.weaver.patterns.PatternParser; import org.aspectj.weaver.patterns.SimpleScope; import org.aspectj.weaver.patterns.TypePattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanNameReference; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.util.StringUtils; /** * * 设置通用配置<br/> * * 使用方法:<br/> * <pre> * <bean class="cn.javass.common.spring.CommonConfigureProcessor"> <property name="config"> <map> <!-- aspectj表达式 选择所有Action结尾的Bean 注入scope数据 为 prototype --> <entry key="cn.javass..*Action"> <props> <prop key="scope">prototype</prop> </props> </entry> <!-- aspectj表达式 选择所有的HibernateDaoSupport实现Bean 注入sessionFactory --> <entry key="org.springframework.orm.hibernate3.support.HibernateDaoSupport+"> <props> <prop key="property-ref">sessionFactory=sessionFactory</prop> </props> </entry> </map> </property> </bean> * </pre> * * 目前支持三种配置: * scope:注入作用域 * property-ref:注入Bean引用 如之上的sessionFactory * propertyName=beanName * property-value:注入常量值 * propertyName=常量 * * @author Zhangkaitao * @version 1.0 * */ public class CommonConfigureProcessor implements BeanFactoryPostProcessor { private Logger log = LoggerFactory.getLogger(CommonConfigureProcessor.class); private Map<String, Properties> config = new HashMap<String, Properties>(); public void setConfig(Map<String, Properties> config) { this.config = config; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException { log.debug("apply common config start"); for(Entry<String, Properties> entry : config.entrySet()) { String aspectjPattern = entry.getKey(); Properties props = entry.getValue(); List<BeanDefinition> bdList = findBeanDefinition(aspectjPattern, factory); apply(bdList, props); } log.debug("apply common config end"); } private void apply(List<BeanDefinition> bdList, Properties props) { for(Entry<Object, Object> entry : props.entrySet()) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); switch(SupportedConfig.keyToEnum(key)) { case scope : applyScope(bdList, value); break; case propertyRef: applyPropertyRef(bdList, value); break; case propertyValue: applyPropertyValue(bdList, value); break; default: throw new IllegalArgumentException(String.format("错误的配置:[%s]", key)); } } } private void applyPropertyValue(List<BeanDefinition> bdList, String value) { for(BeanDefinition bd : bdList) { String propertyName = value.split("=")[0]; String propertyValue = value.substring(propertyName.length()+1); bd.getPropertyValues().add(propertyName, propertyValue); log.debug("apply property value {} to {}", value, bd.getBeanClassName()); } } private void applyPropertyRef(List<BeanDefinition> bdList, String value) { for(BeanDefinition bd : bdList) { String propertyName = value.split("=")[0]; String propertyValue = value.substring(propertyName.length()+1); bd.getPropertyValues().addPropertyValue(propertyName, new RuntimeBeanReference(propertyValue)); log.debug("apply property ref {} to {}", value, bd.getBeanClassName()); } } private void applyScope(List<BeanDefinition> bdList, String value) { for(BeanDefinition bd : bdList) { bd.setScope(value); log.debug("apply scope {} to {}", value, bd.getBeanClassName()); } } private List<BeanDefinition> findBeanDefinition(String aspectjPattern, ConfigurableListableBeanFactory factory) { List<BeanDefinition> bdList = new ArrayList<BeanDefinition>(); for(String beanName : factory.getBeanDefinitionNames()) { BeanDefinition bd = factory.getBeanDefinition(beanName); if(matches(aspectjPattern, bd.getBeanClassName())) { bdList.add(bd); } } return bdList; } private boolean matches(String aspectjPattern, String beanClassName) { if(!StringUtils.hasLength(beanClassName)) { return false; } return new AspectJTypeMatcher(aspectjPattern).matches(beanClassName); } //支持的操作 private static enum SupportedConfig { scope("scope"), propertyRef("property-ref"), propertyValue("property-value"), error("error"); //出错的情况 private final String key; private SupportedConfig(String key) { this.key = key; } public static SupportedConfig keyToEnum(String key) { if(key == null) { return error; } for(SupportedConfig config : SupportedConfig.values()) { if(config.key.equals(key.trim())) { return config; } } return error; } } public static interface TypeMatcher { public boolean matches(String className); } static class AspectJTypeMatcher implements TypeMatcher { private final World world; private final TypePattern typePattern; public AspectJTypeMatcher(String pattern) { this.world = new BcelWorld(Thread.currentThread().getContextClassLoader(), IMessageHandler.THROW, null); this.world.setBehaveInJava5Way(true); PatternParser patternParser = new PatternParser(pattern); TypePattern typePattern = patternParser.parseTypePattern(); typePattern.resolve(this.world); IScope scope = new SimpleScope(this.world, new FormalBinding[0]); this.typePattern = typePattern.resolveBindings(scope, Bindings.NONE, false, false); } @Override public boolean matches(String className) { ResolvedType resolvedType = this.world.resolve(className); return this.typePattern.matchesStatically(resolvedType); } } public static void main(String[] args) { //System.out.println(new AspectJTypeMatcher("cn.javass..*Action").matches("cn.javass.test.web.action.AbcAction")); //System.out.println(new AspectJTypeMatcher("com.opensymphony.xwork2.ActionSupport+").matches("cn.javass.erp.web.action.MoneyAction")); } }
此类只实现基本的通用配置,欢迎大家提供想法并完善这个工具类。