随笔-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    
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.propertiesvalue>
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    
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.propertiesvalue>
20          list>
21       property>
22    bean>
23 
24    
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工程的压缩包文件,运行示例或者将代码应用到您的工程之中。该代码不受任何凯发k8网页登录的版权保护,可以随便修改或发布。

posted on 2009-11-20 22:27 max 阅读(24000) 评论(10)  编辑  收藏 所属分类: 方法与技巧(tips & tricks)

评论:
# re: 扩展spring——使用 annotation将配置资源注入到bean中 2009-11-20 23:36 |
终于看到好文章了...

不知道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 |
四大皆空福建的  回复  
  
# re: 扩展spring——使用 annotation将配置资源注入到bean中 2009-11-24 08:55 |
@smildlzj
我也一直找这样的框架 不知道有没有  回复  
  
# re: 扩展spring——使用 annotation将配置资源注入到bean中 2009-11-25 22:21 |
@aslan
想了一下。这样的做法也不怎么好。。

setter,getter生成一下就好,如果用注入,那怎么调用,注入这玩意需要运行阶段生成的。
所以,需要调用的时候,连编译都不通过。

==
后来搞成接口
public void setid(string id);
public string getid();

想用cglib生成,但是不会弄,让这两个方法关联起来。
而且cglib生成的父类必须是接口,不能是抽象类。  回复  
  
# re: 扩展spring——使用 annotation将配置资源注入到bean中 2009-12-23 17:21 |
merry christmas buddy. what you been up to?  回复  
  
# re: 扩展spring——使用 annotation将配置资源注入到bean中 2010-08-24 16:40 |
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 |
@smildlzj
lombok  回复  
  
# re: 扩展spring——使用 annotation将配置资源注入到bean中 2015-08-19 14:50 |
这段代码确实很酷, 但是不能不能讲properties中的参数注入到@controller注解的类中.  回复  
  

只有注册用户后才能发表评论。


网站导航:
              
 
网站地图