隨筆-34  評論-1965  文章-0  trackbacks-0

          好久沒有更新BLOG了,這幾天經常有朋友留言或EMAIL關心或鼓勵我。其實這篇文章我大約兩星期前就寫好了,本來想投稿IBM的 developerWork中國。算啦,還是不等IBM的回復啦,發到我的自己的博客吧。

          使用XML還是Annotation定義Bean

          自從Spring 2.5開始引入使用Annotation定義Bean的方式之后,業界時常會有一些關于“到底是應該使用XML還是Annotation定義Bean呢?”的討論。筆者本人就比較中庸,喜歡兩者結合使用——對于一些框架性的基礎型的Bean使用XML,對于業務性的Bean則使用Annotation。

          然而,什么是“框架性的基礎型的Bean”呢?這些Bean可以理解為由第三方開源組件提供的基礎Java類的、又或者開發者在其基礎上擴展而來的Bean,如數據源org.apache.commons.dbcp.BasicDataSource、事務管理器org.springframework.orm.hibernate3.HibernateTransactionManager等。這些Bean一般在應用程序中數量較少,卻起著框架性和全局性的作用,對于此類Bean使用XML的好處是必要時可以通過修改一個或幾個XML文件即可改變應用程序行為滿足實際的項目需求,如下清單1所示。

           1 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
           2       destroy-method="close">
           3    <property name="driverClassName" value="${jdbc.driverClassName}" />
           4    <property name="url" value="${jdbc.url}" />
           5    <property name="username" value="${jdbc.username}" />
           6    <property name="password" value="${jdbc.password}" />
           7 </bean>
           8 <bean id="transactionManager" 
           9       class="org.springframework.orm.hibernate3.HibernateTransactionManager">
          10    <property name="sessionFactory" ref="sessionFactory" />
          11 </bean>
          清單 1. 使用XML定義框架性的Bean

          此外,我們再來解釋一下什么是“業務性的Bean”。這些Bean相對比較容易理解,也就是開發者根據業務需求編寫的XxxDao、XxxManager或XxxService等。它們的特點是為數眾多,定義起來比較麻煩。Annotation方式的簡潔性可以最大程度地減少這方便的繁鎖,而且可以避免諸如打錯類型名稱等常見的小錯誤。對比清單2、3和4的代碼大家應該會有更為深刻的理解。

          1 <bean id="myService" class="net.blogjava.max.service.MyServiceImpl">
          2    <property name="myDao1" ref="myDao1" />
          3    <!-- 其它DAO引用  -->
          4    <property name="myDaoN" ref="myDaoN" />
          5 </bean>
          清單 2. 使用XML定義業務性的Bean

           1 public class MyServiceImpl implements MyService {
           2    private MyDao1 myDao1;
           3    // 其它DAO
           4    private MyDaoN myDaoN;
           5 
           6    public void setMyDao1(MyDao1 myDao1) {
           7       this.myDao1 = myDao1;
           8    }
           9 
          10    public void setMyDaoN(MyDaoN myDaoN) {
          11       this.myDaoN = myDaoN;
          12    }
          13    // 其它業務代碼
          14 }
          清單 3. 使用XML方式時Bean的代碼

           1 @Service("myService")
           2 public class MyServiceImpl implements MyService {
           3    @Resource
           4    private MyDao1 myDao1;
           5    // 其它DAO
           6    @Resource
           7    private MyDaoN myDaoN;
           8 
           9    // 其它業務代碼
          10 }
          清單 4. 使用Annotation方式的Bean代碼

          清單2、3實現的功能與清單4一樣,都是在Spring容器中定義一個MyServiceImpl類型的Bean。孰優孰劣?一目了然!

          在Spring中配置應用程序

          大家可以從清單1看到有${xxx.xxx}的寫法,有Spring開發經驗的朋友可能已經知道這是使用Spring框架時配置應用程序的方式之一。為了方便一些不甚了解的朋友,筆者在此也大概講述一下這種配置方式的步驟。

          首先,在工程中新建一個資源(Property)文件(筆者建議放在源代碼目錄下),通過“名稱=取值”的方式定義應用的配置,如下清單5所示。

          1 jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
          2 jdbc.url=jdbc\:oracle\:thin\:@localhost\:1521\:ORCL
          3 jdbc.username=max
          4 jdbc.password=secret
          清單 5. 配置代碼片段

          然后,定義一個 org.springframework.beans.factory.config.PropertyPlaceholderConfigurer類型 的Bean,id可以為propertyConfigurer。通常我們需要通過設定它的locations屬性指明應用程序配置文件的路徑。例如,以下清單6的代碼就是指明配置在構建路徑(Build Path)的根目錄下的config.properties文件里。

          1 <bean id="propertyConfigurer" 
          2       class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
          3    <property name="locations">
          4    <list>
          5       <value>classpath:config.properties</value>
          6    </list>
          7    </property>
          8 </bean>
          清單 6. Spring配置代碼片段

          最后,在XML中定義Bean時,使用${xxx}引用配置資源來初始化對象,如清單1所示。然而這種配置方式僅限于XML,如果我們需要在通過Annotation定義的業務性的Bean中使用配置資源呢?

          實現通過Annotation向Bean注入配置資源

          解決上述問題的思路很簡單。首先,參考Spring注入Bean的Annotation(如@Resource等)編寫一個類似的Annotation類,如下清單7所示。

           1 package net.blogjava.max.spring;
           2 
           3 import java.lang.annotation.ElementType;
           4 import java.lang.annotation.Retention;
           5 import java.lang.annotation.RetentionPolicy;
           6 import java.lang.annotation.Target;
           7 
           8 @Retention(RetentionPolicy.RUNTIME)
           9 @Target(ElementType.FIELD)
          10 public @interface Config {
          11    String value() default "";
          12 }
          清單 7. Config.java

          上述Config類有只一個屬性,所以用默認的“value”作為名稱,而且此屬性是可選的,換而言之,開發者可以通過@Config("配置名稱")或簡單地直接使用@Config來注入配置資源。當程序發現@Config的value為空時,會使用變量域(Field)的名稱作為配置名稱獲取其值。

          然后,通過上節配置的propertyConfigurer對象獲取配置資源。不過通過閱讀Spring的API文檔或 org.springframework.beans.factory.config.PropertyPlaceholderConfigurer的源代碼,筆者發現此對象并沒有一個公共方法可以滿足以上需求,但是它有一個受保護的方法,protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException,作用是將從配置中讀入的配置資源應用到Bean的生產工廠對象中。因此,我們可以繼承此類,然后改寫該方法,將參數 props的引用放到類的全局變量里,接著通過它提供一個公共方法返回對應名稱的配置資源,如下清單8所示。

           1 package net.blogjava.max.spring;
           2 
           3 import java.util.Properties;
           4 
           5 import org.springframework.beans.BeansException;
           6 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
           7 import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
           8 
           9 public class ExtendedPropertyPlaceholderConfigurer extends
          10       PropertyPlaceholderConfigurer {
          11    private Properties props;
          12 
          13    @Override
          14    protected void processProperties(
          15          ConfigurableListableBeanFactory beanFactory, Properties props)
          16          throws BeansException {
          17       super.processProperties(beanFactory, props);
          18       this.props = props;
          19    }
          20 
          21    public Object getProperty(String key) {
          22       return props.get(key);
          23    }
          24 }
          清單 8. ExtendedPropertyPlaceholderConfigurer.java

          最后,我們需要通過實現Spring的某此生命周期回調方法,在Bean實例化之后將配置資源注入到標記有@Config的變量域(Field)中。通過閱讀Spring的API文檔,筆者發現 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口的方法boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException非常符合我們的需求,而且Spring的@Autowire就是通過實現此方法工作的。當然,在此大家已經可以著手編寫該接口的實現類了。不過,由于該接口還不少其它方法,而這些方法跟我們的目標是毫無瓜葛的,直接實現它就不得不被迫編寫一堆空的實現代碼,所以筆者選擇繼承 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter虛基類,改寫其postProcessAfterInstantiation方法。該虛基類是提供了一些接口(當然其中包括 InstantiationAwareBeanPostProcessor)的空實現,因此開發者只需改寫自己需要的方法即可,如下清單9所示。

           1 package net.blogjava.max.spring;
           2 
           3 import java.lang.reflect.Field;
           4 import java.lang.reflect.Modifier;
           5 
           6 import org.springframework.beans.BeansException;
           7 import org.springframework.beans.SimpleTypeConverter;
           8 import org.springframework.beans.factory.annotation.Autowired;
           9 import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
          10 import org.springframework.stereotype.Component;
          11 import org.springframework.util.ReflectionUtils;
          12 
          13  @Component //定義一個匿名Spring組件
          14 public class ConfigAnnotationBeanPostProcessor extends
          15       InstantiationAwareBeanPostProcessorAdapter {
          16    @Autowired //自動注入  ExtendedPropertyPlaceholderConfigurer對象,用于獲取配置資源
          17    private ExtendedPropertyPlaceholderConfigurer propertyConfigurer;
          18 
          19    //創建簡單類型轉換器
          20    private SimpleTypeConverter typeConverter = new SimpleTypeConverter();
          21 
          22    @Override
          23    public boolean postProcessAfterInstantiation(final Object bean, String beanName) 
          24          throws BeansException {
          25       ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
          26          public void doWith(Field field) throws IllegalArgumentException, 
          27                IllegalAccessException {
          28             Config cfg = field.getAnnotation(Config.class);
          29             if (cfg != null) {
          30                if (Modifier.isStatic(field.getModifiers())) {
          31                   throw new IllegalStateException("@Config annotation is not supported 
          32                            on static fields");
          33                }
          34 
          35             //如果開發者沒有設置@Config的 value,則使用變量域的名稱作為鍵查找配置資源
          36             String key = cfg.value().length() <= 0 ? field.getName() : cfg.value();
          37             Object value = propertyConfigurer.getProperty(key);
          38 
          39             if (value != null) {
          40                //轉換配置值成其它非String類型
          41                Object _value = typeConverter.convertIfNecessary(value, field.getType());
          42                //使變量域可用,并且轉換后的配置值注入其中
          43                ReflectionUtils.makeAccessible(field);
          44                field.set(bean, _value);
          45             }
          46          }
          47       }
          48    });
          49 
          50    //通常情況下返回true即可
          51    return true;
          52    }
          53 }
          清單 9. ConfigAnnotationBeanPostProcessor.java

          @Config使用示例

          完成了上述步驟之后,下面我們用一個完整的例子來演示一下@Config的使用。首先,創建配置文件,如下清單10所示。

          1 demo.config1=Demo Config \#1
          2 config2=314159
          清單 10. src/config.properties

          接著,編寫Demo類,它將演示通過XML和Annotation的方式獲取配置文件的資源。如下清單11所示。

           1 package net.blogjava.max.spring;
           2 
           3 import org.springframework.context.ApplicationContext;
           4 import org.springframework.context.support.ClassPathXmlApplicationContext;
           5 import org.springframework.stereotype.Service;
           6 
           7 @Service("demoAnn")//通過Annotation的方式定義Bean
           8 public class Demo {
           9    @Config("demo.config1"//演示最常見的用法
          10    private String config1;
          11 
          12    @Config //演示通過域變量名字獲取配置資源和數據類型轉換
          13    private Integer config2;
          14 
          15    //演示通過XML方式注入配置資源
          16    private String config3;
          17    private Integer config4;
          18 
          19    public void setConfig3(String config3) {
          20       this.config3 = config3;
          21    }
          22 
          23    public void setConfig4(Integer config4) {
          24       this.config4 = config4;
          25    }
          26 
          27    public void printConfigAnn() {
          28       System.out.println("{ config1 = " + config1 + ", config2 = " + config2
          29       + "}");
          30    }
          31 
          32    public void printConfigXML() {
          33       System.out.println("{ config3 = " + config3 + ", config4 = " + config4
          34       + "}");
          35    }
          36 
          37    public static void main(String[] args) {
          38       ApplicationContext appCtx = new ClassPathXmlApplicationContext(
          39             "applicationContext.xml");
          40 
          41       Demo demoAnn = (Demo) appCtx.getBean("demoAnn");
          42       demoAnn.printConfigAnn();
          43 
          44       Demo demoXML = (Demo) appCtx.getBean("demoXML");
          45       demoXML.printConfigXML();
          46    }
          47 }
          清單 11. Demo.java

          由于本示例的目的是演示@Config的使用,所以采取了最簡單編碼風格,而并非大家使用Spring時常用的基于接口的編碼風格。另外,本示例同時通過XML和Annotation的方式在Spring中定義Demo類型的Bean,前者通過類中的XML和兩個Setter注入配置資源,后者則是通過Annotation和兩個私有域變量。

          最后,編寫Spring的XML配置文件,如清單12所示。

           1 <?xml version="1.0" encoding="UTF-8"?>
           2 
           3 <beans xmlns=http://www.springframework.org/schema/beans
           4    xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
           5    xmlns:context=http://www.springframework.org/schema/context
           6    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           7       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           8       http://www.springframework.org/schema/context 
           9       http://www.springframework.org/schema/context/spring-context-2.5.xsd">
          10 
          11    <!-- 指明需要進行Annotation掃描的包 -->
          12    <context:component-scan base-package="net.blogjava.max" />
          13 
          14    <!-- 讀入配置文件  -->
          15    <bean id="propertyConfigurer"
          16          class="net.blogjava.max.spring.ExtendedPropertyPlaceholderConfigurer">
          17       <property name="locations">
          18          <list>
          19             <value>classpath:config.properties</value>
          20          </list>
          21       </property>
          22    </bean>
          23 
          24    <!-- 通過XML配置Demo的Bean,并注入配置資源 -->
          25    <bean id="demoXML" class="net.blogjava.max.spring.Demo">
          26       <property name="config3" value="${demo.config1}" />
          27       <property name="config4" value="${config2}" />
          28    </bean>
          29 
          30 </beans>
          清單 12. src/applicationContext.xml

          完成了配置之后,大家可以運行Demo類的main方法,控制臺會有如清單13的輸出。這就證明了通過XML或Annotation可以注入相同配置資源,而且對比兩者的代碼,Annotation比XML更為簡便和快捷。

          1 { config1 = Demo Config #1, config2 = 314159}
          2 { config3 = Demo Config #1, config4 = 314159}
          清單 13. 示例控制臺輸出

          結束語

          本文再三強調定義Bean時Annotation對比XML的優越性,尤其是針對業務性的對象;而配置又是每個應用程序必不可少的一部分,通過擴展Spring框架,開發者可以輕松地使用Annotation的方式實現應用程序配置。同時,筆者也希望Spring社區能夠意識到這方面的需求,將其整合在以后發行的Spring版本之中。在此之前,大家可以通過文章后面的下載鏈接獲得本文Eclipse工程的壓縮包文件,運行示例或者將代碼應用到您的工程之中。該代碼不受任何版權保護,可以隨便修改或發布。

          下載代碼

          posted on 2009-11-20 22:27 Max 閱讀(24110) 評論(10)  編輯  收藏 所屬分類: 方法與技巧(Tips & tricks)

          評論:
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2009-11-20 23:36 | smildlzj
          終于看到好文章了...

          不知道Annotation能不能實現這樣?

          @Set
          @Get
          private String id;

          這樣的功能,這樣就不用寫Setter,Getter.  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2009-11-21 15:59 | 咖啡妝
          感覺還是配置文件好 自少東西不零散  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2009-11-22 15:07 | 99讀書人俱樂部
          四大皆空福建的  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2009-11-24 08:55 | aslan
          @smildlzj
          我也一直找這樣的框架 不知道有沒有  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2009-11-25 22:21 | smildlzj
          @aslan
          想了一下。這樣的做法也不怎么好。。

          setter,getter生成一下就好,如果用注入,那怎么調用,注入這玩意需要運行階段生成的。
          所以,需要調用的時候,連編譯都不通過。

          ==
          后來搞成接口
          public void setId(String id);
          public String getId();

          想用cglib生成,但是不會弄,讓這兩個方法關聯起來。
          而且cglib生成的父類必須是接口,不能是抽象類。  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2009-12-23 17:21 | koumei
          Merry Christmas buddy. What you been up to?  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2010-08-24 16:40 | sad
          The value of hands-on experience as compared to book learning is smaller in software development than in many other fields.   回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2011-06-14 09:10 | 墨龍
          @smildlzj
          使用Autowired 就可以自動注入 並且有get set方法  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2014-09-09 13:53 | xfan
          @smildlzj
          lombok  回復  更多評論
            
          # re: 擴展Spring——使用 Annotation將配置資源注入到Bean中 2015-08-19 14:50 | 紅色芒果
          這段代碼確實很酷, 但是不能不能講properties中的參數注入到@controller注解的類中.  回復  更多評論
            
          主站蜘蛛池模板: 东山县| 天镇县| 新源县| 临清市| 保亭| 襄汾县| 高淳县| 翁源县| 巫溪县| 亚东县| 平武县| 镇雄县| 山阳县| 土默特左旗| 翁牛特旗| 桐梓县| 六枝特区| 江油市| 北安市| 洪洞县| 汝城县| 资兴市| 太谷县| 德令哈市| 武冈市| 台安县| 湟中县| 集贤县| 珠海市| 凤山市| 栖霞市| 五常市| 鞍山市| 响水县| 大英县| 南陵县| 中山市| 正阳县| 涟源市| 兴城市| 衡水市|