扩展Spring——使用 Annotation将配置资源注入到Bean中

 

来源:http://www.blogjava.net/max/archive/2009/11/20/303112.html

使用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工程的压缩包文件,运行示例或者将代码应用到您的 工程之中。该代码不受任何版权保护,可以随便修改或发布。

 

你可能感兴趣的:(annotation)