不重複配置——利用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大哥的批評:

 

downpour 寫道
在beans level上可以設置autowired的方式,sessionFactory這段配置從來就是可以省略的。
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"));
    }
}
 

此類只實現基本的通用配置,歡迎大家提供想法並完善這個工具類。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章