`

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

 

分享到:
评论

相关推荐

    SSH笔记-annotation配置注入关系1

    在本文中,我们将深入探讨SSH笔记中的一个关键概念——基于注解的配置注入关系,特别是在Spring框架4.x版本中的应用。SSH(Struts、Spring、Hibernate)是Java Web开发中的经典组合,而Spring框架以其强大的依赖注入...

    Spring boot——@DeclareParents例子

    当我们在一个切面类中使用`@DeclareParents`时,Spring会为匹配到的bean创建代理,并将指定的接口实现注入到这些bean中。 首先,我们需要了解`@Aspect`的使用。在Spring Boot项目中,创建一个带有`@Aspect`注解的类...

    Spring2的IOC利器统管Bean世界.rar

    本教程将通过三个部分——SpringHelloWorld、SpringXmlIOC和SpringAnnotationIOC,深入探讨Spring的IOC机制。 首先,我们从SpringHelloWorld开始。这是一个简单的Spring入门示例,展示了如何使用Spring来创建和管理...

    Spring IoC简单示例-注解配置-Maven构建

    在本文中,我们将深入探讨Spring框架的核心特性——控制反转(Inversion of Control,简称IoC)和依赖注入(Dependency Injection,简称DI),以及如何通过注解配置和Maven项目构建来实现这一概念。Spring框架是Java...

    Spring_2900_Registration_7

    这种方式使得配置文件中的属性能被方便地注入到其他Bean中,增强了配置的灵活性。 4. **Spring_2900_Registration_7可能的含义**:这个标识可能指的是一个特定版本或迭代的Spring注册相关教程或案例。"2900"可能是...

    Spring Annotaion Support详细介绍及简单实例

    要使用BeanPostProcessor,开发者需要编写一个实现了该接口的类,并且将该类的实例作为一个Bean注册到Spring容器中。当Spring容器启动时,它会检测到实现了BeanPostProcessor接口的Bean,并将其特殊处理。这些...

    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 事务...

    CXF集成spring hibernate

    【CXF集成Spring Hibernate】是将三个核心的Java技术——CXF、Spring和Hibernate整合到一个应用程序中的实践。这样的集成可以构建出强大的企业级服务,其中CXF用于提供Web服务,Spring作为应用的管理和依赖注入容器...

    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 事务...

    org.springframework.core.jar

    5. **排序与比较**:`org.springframework.core.order`和`org.springframework.core.type`包提供了排序算法和类型比较机制,对于Spring容器中bean的排序和类型检查起到了关键作用。 6. **事件驱动**:`org.spring...

    spring-framework 中文文档.pdf

    Spring 支持多种配置方式,包括 XML、注解(Annotation)和基于 Java 的配置。 - **实例化一个容器**:为了使用 Spring 容器,首先需要实例化一个容器。可以通过多种方式完成此操作,例如使用 `new ...

    SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

    MyBatis 使用简单的 XML 或注解进行配置和原始映射,将接口和 Java POJOs 映射到数据库中的记录。 - **SQL Maps**: 提供 SQL 查询语句和 Java 类型之间的映射关系。 - **Data Access Objects (DAO)**: 提供对数据的...

    SpringIOC文档.zip

    3. **@Autowired**:自动装配依赖,Spring会根据类型或名称自动将Bean注入到需要的地方,如: ```java @Autowired private MyService myService; ``` 4. **@Qualifier**:当有多个相同类型的Bean需要选择注入...

    Spring3_权威开发指南

    - **将DI容器宿主到Web容器中**:讲解了如何将Spring容器集成到Web容器中,使得Spring管理的Bean可以在Web环境中使用。 - **外在化配置应用参数**:介绍了如何将应用程序的配置信息从代码中分离出来,放置在外部...

    spring框架案例学习文档笔记

    - **XML配置**:可以在Spring的配置文件中使用`&lt;aop:config&gt;`等标签来配置AOP。 #### 第九课:DataSource - **配置**:在Spring中可以通过`&lt;bean&gt;`标签配置数据源。 - **使用**:通常会使用C3P0或DBCP等连接池来...

    SSM框架——详细整合教程.docx

    - **机制**:通过简单的XML配置文件或注解将接口和Java POJOs映射到数据库记录。 #### 三、开发环境搭建 - **环境要求**:Spring 4.0.2 RELEASE、Spring MVC 4.0.2 RELEASE。 - **工具选择**:采用Maven进行项目...

    java5.0新特性总结--Annotation系统学习整理笔记

    这篇博客是关于Java 5.0新特性——Annotation的系统学习与整理笔记,通过讲解与实例,深入探讨了注解在Junit、Spring和Hibernate中的应用。 首先,注解是一种声明式的编程元素,它可以附加到类、接口、方法、变量等...

    Spring+SpringMVC+MyBatis整合教程

    ### SSM框架——详细整合教程(Spring+SpringMVC+MyBatis) #### 1、基本概念 **1.1、Spring** Spring 是一个开源框架,最初由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的...

    JavaEE多层架构Struts2+Spring3+Hibernate3+Ajax的整合

    Spring 框架的核心特性包括依赖注入(DI)和面向切面编程(AOP),这些特性极大地简化了 Bean 的装配和事务管理等工作。Spring 还为不同的数据访问技术提供了一个统一的接口,这意味着开发者可以在不改变业务逻辑的...

Global site tag (gtag.js) - Google Analytics