`
yufengweb
  • 浏览: 2582 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

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

阅读更多

使用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相对比较容易理解,也就是开发者根据业务需求编写的XxxDaoXxxManagerXxxService等。它们的特点是为数众多,定义起来比较麻烦。Annotation方式的简洁性可以最大程度地减少这方便的繁锁,而且可以避免诸如打错类型名称等常见的小错误。对比清单234的代码大家应该会有更为深刻的理解。

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代码

清单23实现的功能与清单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类型的Beanid可以为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中使用配置资源呢?

实现通过AnnotationBean注入配置资源

解决上述问题的思路很简单。首先,参考Spring注入BeanAnnotation(如@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来注入配置资源。当程序发现@Configvalue为空时,会使用变量域(Field)的名称作为配置名称获取其值。

然后,通过上节配置的propertyConfigurer对象获取配置资源。不过通过阅读SpringAPI文档或 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)中。通过阅读SpringAPI文档,笔者发现 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类,它将演示通过XMLAnnotation的方式获取配置文件的资源。如下清单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时常用的基于接口的编码风格。另外,本示例同时通过XMLAnnotation的方式在Spring中定义Demo类型的Bean,前者通过类中的XML和两个Setter注入配置资源,后者则是通过Annotation和两个私有域变量。

最后,编写SpringXML配置文件,如清单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    <!-- <sp

分享到:
评论

相关推荐

    Spring中文帮助文档

    6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...

    Spring API

    6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...

    Spring.3.x企业应用开发实战(完整版).part2

    12.2 在Spring中使用Hibernate 12.2.1 配置SessionFactory 12.2.2 使用HibernateTemplate 12.2.3 处理LOB类型数据 12.2.4 添加Hibernate事件监听器 12.2.5 使用原生Hibernate API 12.2.6 使用注解配置 12.2.7 事务...

    Spring3.x企业应用开发实战(完整版) part1

    12.2 在Spring中使用Hibernate 12.2.1 配置SessionFactory 12.2.2 使用HibernateTemplate 12.2.3 处理LOB类型数据 12.2.4 添加Hibernate事件监听器 12.2.5 使用原生Hibernate API 12.2.6 使用注解配置 12.2.7 事务...

    springmybatis

    MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录. orm工具的基本思想 无论是用过的hibernate,mybatis,你都可以法相他们有一个...

    java微信公众号MVC开发框架

    spring配置文件中唯一需要配置的bean是WeixinConfigurer类,是可选配置,但里面封装了微信接口服务类,建议一定要配置进spring配置文件中。 1、微信接口服务 微信接口服务类位于...

    java版oa源码下载-zollty-mvc:一个轻量级的JavaMVC框架。简单、快速、灵活!

    它的DI功能非常强大,包括Spring等常用用法,也支持高级用法,如使用方法的返回值创建bean,为方法参数注入bean…… 它的 BeanFactory 是高度可扩展的,支持 ClassPathXmlApplicationContext、...

    java开发常用jar包

    测试可以利用spring的注入功能,引入spring的事物管理,而且对于测试的数据还可以支持回滚,保存数据 velocity-1.6.2.jar velocity-1.6.2-dep.jar velocity-tools-1.4.jar velocity-tools-view-1.4.jar velocity...

    Activiti6.0教程例子下载

    6. ManagementService: ManagementService提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti系统的日常维护。 7. HistoryService: HistoryService用于获取正在...

    JAVA上百实例源码以及开源项目源代码

    在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...

    JAVA上百实例源码以及开源项目

    在有状态SessionBean中,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活和钝化,EJB对象是用完毕,从内存中清除…… Java Socket 聊天...

Global site tag (gtag.js) - Google Analytics