好久沒有更新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 閱讀(24107)
評論(10) 編輯 收藏 所屬分類:
方法與技巧(Tips & tricks)