12.1 概述
12.1.1 什么是零配置
在SSH集成一章中大家注意到项目结构和包结构是不是很有规律,类库放到WEB-INF/lib文件夹下,jsp文件放到WEB-INF/jsp文件夹下,web.xml需要放到WEB-INF文件夹下等等,为什么要这么放呢?不这样放可以吗?
所谓零配置,并不是说一点配置都没有了,而是配置很少而已。通过约定来减少需要配置的数量,提高开发效率。
因此SSH集成时的项目结构和包结构完全是任意的,可以通过配置方式来指定位置,因此如web.xml完全可以不放在WEB-INF下边而通过如tomcat配置文件中新指定web.xml位置。
还有在SSH集成中还记得使用在Struts2配置文件中使用模式匹配通配符来定义action,只要我们的URL模式将类似于/{module}/{action}/{method}.action即可自动映射到相应的Action类的方法上,但如果你的URL不对肯定是映射不到的,这就是规约。
零配置并不是没有配置,而是通过约定来减少配置。那如何实现零配置呢?
12.1.2 零配置的实现方式
零配置实现主要有以下两种方式:
- 惯例优先原则:也称为约定大于配置或规约大于配置(convention over configuration),即通过约定代码结构或命名规范来减少配置数量,同样不会减少配置文件;即通过约定好默认规范来提高开发效率;如Struts2配置文件使用模式匹配通配符来定义action就是惯例优先原则。
- 基于注解的规约配置:通过在指定类上指定注解,通过注解约定其含义来减少配置数量,从而提高开发效率;如事务注解@Transaction是不是基于注解的规约,只有在指定的类或方法上使用该注解就表示其需要事务。
对惯例优先原则支持的有项目管理工具Maven,它约定了一套非常好的项目结构和一套合理的默认值来简化日常开发,作者比较喜欢使用Maven构建和管理项目;另外还有Strtus2的convention-plugin也提供了零配置支持等等。
大家还记得【7.5 集成Spring JDBC及最佳实践】时的80/20法则吗?零配置是不是同样很好的体现了这个法则,在日常开发中同样80%时间使用默认配置,而20%时间可能需要特定配置。
12.1.3 Spring3的零配置
Spring3中零配置的支持主要体现在Spring Web MVC框架的惯例优先原则和基于注解配置。
Spring Web MVC框架的惯例优先原则采用默认的命名规范来减少配置。
Spring基于注解的配置采用约定注解含义来减少配置,包括注解实现Bean配置、注解实现Bean定义和Java类替换配置文件三部分:
- 注解实现Bean依赖注入:通过注解方式替代基于XML配置中的依赖注入,如使用@Autowired注解来完成依赖注入。
- 注解实现Bean定义:通过注解方式进行Bean配置元数据定义,从而完全将Bean配置元数据从配置文件中移除。
- Java类替换配置文件:使用Java类来定义所有的Spring配置,完全消除XML配置文件。
12.2 注解实现Bean依赖注入
12.2.1 概述
注解实现Bean配置主要用来进行如依赖注入、生命周期回调方法定义等,不能消除XML文件中的Bean元数据定义,且基于XML配置中的依赖注入的数据将覆盖基于注解配置中的依赖注入的数据。
Spring3的基于注解实现Bean依赖注入支持如下三种注解:
- Spring自带依赖注入注解: Spring自带的一套依赖注入注解;
- JSR-250注解:Java平台的公共注解,是Java EE 5规范之一,在JDK6中默认包含这些注解,从Spring2.5开始支持。
- JSR-330注解:Java 依赖注入标准,Java EE 6规范之一,可能在加入到未来JDK版本,从Spring3开始支持;
- JPA注解:用于注入持久化上下文和尸体管理器。
这三种类型的注解在Spring3中都支持,类似于注解事务支持,想要使用这些注解需要在Spring容器中开启注解驱动支持,即使用如下配置方式开启:
java代码:
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation=" http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd">
- <context:annotation-config/>
- </beans>
这样就能使用注解驱动依赖注入了,该配置文件位于“resources/ chapter12/dependecyInjectWithAnnotation.xml”。
12.2.2 Spring自带依赖注入注解
一、@Required:依赖检查;
对应于基于XML配置中的依赖检查,但XML配置的依赖检查将检查所有setter方法,详见【3.3.4 依赖检查】;
基于@Required的依赖检查表示注解的setter方法必须,即必须通过在XML配置中配置setter注入,如果没有配置在容器启动时会抛出异常从而保证在运行时不会遇到空指针异常,@Required只能放置在setter方法上,且通过XML配置的setter注入,可以使用如下方式来指定:
java代码:
- @Requried
- setter方法
1、准备测试Bean
java代码:
- package cn.javass.spring.chapter12;
- public class TestBean {
- private String message;
- @Required
- public void setMessage(String message) {
- this.message = message;
- }
- public String getMessage() {
- return message;
- }
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean" class="cn.javass.spring.chapter12.TestBean">
- <property name="message" ref="message"/>
- </bean>
- <bean id="message" class="java.lang.String">
- <constructor-arg index="0" value="hello"/>
- </bean>
3、测试类和测试方法如下:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class DependencyInjectWithAnnotationTest {
- private static String configLocation = "classpath:chapter12/dependecyInjectWithAnnotation.xml";
- private static ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocation);
- //1、Spring自带依赖注入注解
- @Test
- public void testRequiredForXmlSetterInject() {
- TestBean testBean = ctx.getBean("testBean", TestBean.class);
- Assert.assertEquals("hello", testBean.getMessage());
- }
- }
在XML配置文件中必须指定setter注入,否则在Spring容器启动时将抛出如下异常:
java代码:
- org.springframework.beans.factory.BeanCreationException:
- Error creating bean with name 'testBean' defined in class path resource [chapter12/dependecyInjectWithAnnotation.xml]: Initialization of bean failed;
- nested exception is org.springframework.beans.factory.BeanInitializationException: Property 'message' is required for bean 'testBean'
二、@Autowired:自动装配
自动装配,用于替代基于XML配置的自动装配,详见【3.3.3 自动装配】。
基于@Autowired的自动装配,默认是根据类型注入,可以用于构造器、字段、方法注入,使用方式如下:
java代码:
- @Autowired(required=true)
- 构造器、字段、方法
@Autowired默认是根据参数类型进行自动装配,且必须有一个Bean候选者注入,如果允许出现0个Bean候选者需要设置属性“required=false”,“required”属性含义和@Required一样,只是@Required只适用于基于XML配置的setter注入方式。
(1)、构造器注入:通过将@Autowired注解放在构造器上来完成构造器注入,默认构造器参数通过类型自动装配,如下所示:
1、准备测试Bean,在构造器上添加@AutoWired注解:
java代码:
- package cn.javass.spring.chapter12;
- import org.springframework.beans.factory.annotation.Autowired;
- public class TestBean11 {
- private String message;
- @Autowired //构造器注入
- private TestBean11(String message) {
- this.message = message;
- }
- //省略message的getter和setter
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean11" class="cn.javass.spring.chapter12.TestBean11"/>
3、测试类如下:
java代码:
- @Test
- public void testAutowiredForConstructor() {
- TestBean11 testBean11 = ctx.getBean("testBean11", TestBean11.class);
- Assert.assertEquals("hello", testBean11.getMessage());
- }
在Spring配置文件中没有对“testBean11”进行构造器注入和setter注入配置,而是通过在构造器上添加@ Autowired来完成根据参数类型完成构造器注入。
(2)、字段注入:通过将@Autowired注解放在构造器上来完成字段注入。
1、准备测试Bean,在字段上添加@AutoWired注解:
java代码:
- package cn.javass.spring.chapter12;
- import org.springframework.beans.factory.annotation.Autowired;
- public class TestBean12 {
- @Autowired //字段注入
- private String message;
- //省略getter和setter
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean12" class="cn.javass.spring.chapter12.TestBean12"/>
3、测试方法如下:
java代码:
- @Test
- public void testAutowiredForField() {
- TestBean12 testBean12 = ctx.getBean("testBean12", TestBean12.class);
- Assert.assertEquals("hello", testBean12.getMessage());
- }
字段注入在基于XML配置中无相应概念,字段注入不支持静态类型字段的注入。
(3)、方法参数注入:通过将@Autowired注解放在方法上来完成方法参数注入。
1、准备测试Bean,在方法上添加@AutoWired注解:
java代码:
- package cn.javass.spring.chapter12;
- import org.springframework.beans.factory.annotation.Autowired;
- public class TestBean13 {
- private String message;
- @Autowired //setter方法注入
- public void setMessage(String message) {
- this.message = message;
- }
- public String getMessage() {
- return message;
- }
- }
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class TestBean14 {
- private String message;
- private List<String> list;
- @Autowired(required = true) //任意一个或多个参数方法注入
- private void initMessage(String message, ArrayList<String> list) {
- this.message = message;
- this.list = list;
- }
- //省略getter和setter
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean13" class="cn.javass.spring.chapter12.TestBean13"/>
- <bean id="testBean14" class="cn.javass.spring.chapter12.TestBean14"/>
- <bean id="list" class="java.util.ArrayList">
- <constructor-arg index="0">
- <list>
- <ref bean="message"/>
- <ref bean="message"/>
- </list>
- </constructor-arg>
- </bean>
3、测试方法如下:
java代码:
- @Test
- public void testAutowiredForMethod() {
- TestBean13 testBean13 = ctx.getBean("testBean13", TestBean13.class);
- Assert.assertEquals("hello", testBean13.getMessage());
- TestBean14 testBean14 = ctx.getBean("testBean14", TestBean14.class);
- Assert.assertEquals("hello", testBean14.getMessage());
- Assert.assertEquals(ctx.getBean("list", List.class), testBean14.getList());
- }
方法参数注入除了支持setter方法注入,还支持1个或多个参数的普通方法注入,在基于XML配置中不支持1个或多个参数的普通方法注入,方法注入不支持静态类型方法的注入。
注意“initMessage(String message, ArrayList<String> list)”方法签名中为什么使用ArrayList而不是List呢?具体参考【3.3.3 自动装配】一节中的集合类型注入区别。
三、@Value:注入SpEL表达式;
用于注入SpEL表达式,可以放置在字段方法或参数上,使用方式如下:
java代码:
- @Value(value = "SpEL表达式")
- 字段、方法、参数
1、可以在类字段上使用该注解:
java代码:
- @Value(value = "#{message}")
- private String message;
2、可以放置在带@Autowired注解的方法的参数上:
java代码:
- @Autowired
- public void initMessage(@Value(value = "#{message}#{message}") String message) {
- this.message = message;
- }
3、还可以放置在带@Autowired注解的构造器的参数上:
java代码:
- @Autowired
- private TestBean43(@Value(value = "#{message}#{message}") String message) {
- this.message = message;
- }
具体测试详见DependencyInjectWithAnnotationTest 类的testValueInject测试方法。
四、@Qualifier:限定描述符,用于细粒度选择候选者;
@Autowired默认是根据类型进行注入的,因此如果有多个类型一样的Bean候选者,则需要限定其中一个候选者,否则将抛出异常,详见【3.3.3 自动装配】中的根据类型进行注入;
@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者,具体使用方式如下:
java代码:
- @Qualifier(value = "限定标识符")
- 字段、方法、参数
(1)、根据基于XML配置中的<qualifier>标签指定的名字进行注入,使用如下方式指定名称:
java代码:
- <qualifier type="org.springframework.beans.factory.annotation.Qualifier" value="限定标识符"/>
其中type属性可选,指定类型,默认就是Qualifier注解类,name就是给Bean候选者指定限定标识符,一个Bean定义中只允许指定类型不同的<qualifier>,如果有多个相同type后面指定的将覆盖前面的。
1、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- public class TestBean31 {
- private DataSource dataSource;
- @Autowired
- //根据<qualifier>标签指定Bean限定标识符
- public void initDataSource(@Qualifier("mysqlDataSource") DataSource dataSource) {
- this.dataSource = dataSource;
- }
- public DataSource getDataSource() {
- return dataSource;
- }
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean31" class="cn.javass.spring.chapter12.TestBean31"/>
我们使用@Qualifier("mysqlDataSource")来指定候选Bean的限定标识符,我们需要在配置文件中使用<qualifier>标签来指定候选Bean的限定标识符“mysqlDataSource”:
java代码:
- <bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <qualifier value="mysqlDataSource"/>
- </bean>
3、测试方法如下:
java代码:
- @Test
- public void testQualifierInject1() {
- TestBean31 testBean31 = ctx.getBean("testBean31", TestBean31.class);
- try {
- //使用<qualifier>指定的标识符只能被@Qualifier使用
- ctx.getBean("mysqlDataSource");
- Assert.fail();
- } catch (Exception e) {
- //找不到该Bean
- Assert.assertTrue(e instanceof NoSuchBeanDefinitionException);
- }
- Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean31.getDataSource());
- }
从测试可以看出使用<qualifier>标签指定的限定标识符只能被@Qualifier使用,不能作为Bean的标识符,如“ctx.getBean("mysqlDataSource")”是获取不到Bean的。
(2)、缺省的根据Bean名字注入:最基本方式,是在Bean上没有指定<qualifier>标签时一种容错机制,即缺省情况下使用Bean标识符注入,但如果你指定了<qualifier>标签将不会发生容错。
1、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class TestBean32 {
- private DataSource dataSource;
- @Autowired
- @Qualifier(value = "mysqlDataSource2") //指定Bean限定标识符
- //@Qualifier(value = "mysqlDataSourceBean")
- //是错误的注入,不会发生回退容错,因为你指定了<qualifier>
- public void initDataSource(DataSource dataSource) {
- this.dataSource = dataSource;
- }
- public DataSource getDataSource() {
- return dataSource;
- }
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean32" class="cn.javass.spring.chapter12.TestBean32"/>
- <bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
3、测试方法如下:
java代码:
- @Test
- public void testQualifierInject2() {
- TestBean32 testBean32 = ctx.getBean("testBean32", TestBean32.class);
- Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean32.getDataSource());
- }
默认情况下(没指定<qualifier>标签)@Qualifier的value属性将匹配Bean 标识符。
(3)、扩展@Qualifier限定描述符注解:对@Qualifier的扩展来提供细粒度选择候选者;
具体使用方式就是自定义一个注解并使用@Qualifier注解其即可使用。
首先让我们考虑这样一个问题,如果我们有两个数据源,分别为Mysql和Oracle,因此注入两者相关资源时就牵扯到数据库相关,如在DAO层注入SessionFactory时,当然可以采用前边介绍的方式,但为了简单和直观我们希望采用自定义注解方式。
1、扩展@Qualifier限定描述符注解来分别表示Mysql和Oracle数据源
java代码:
- package cn.javass.spring.chapter12.qualifier;
- import org.springframework.beans.factory.annotation.Qualifier;
- /** 表示注入Mysql相关 */
- @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Qualifier
- public @interface Mysql {
- }
java代码:
- package cn.javass.spring.chapter12.qualifier;
- import org.springframework.beans.factory.annotation.Qualifier;
- /** 表示注入Oracle相关 */
- @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Qualifier
- public @interface Oracle {
- }
2、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class TestBean33 {
- private DataSource mysqlDataSource;
- private DataSource oracleDataSource;
- @Autowired
- public void initDataSource(@Mysql DataSource mysqlDataSource, @Oracle DataSource oracleDataSource) {
- this.mysqlDataSource = mysqlDataSource;
- this.oracleDataSource = oracleDataSource;
- }
- public DataSource getMysqlDataSource() {
- return mysqlDataSource;
- }
- public DataSource getOracleDataSource() {
- return oracleDataSource;
- }
- }
3、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean33" class="cn.javass.spring.chapter12.TestBean33"/>
4、在Spring修改定义的两个数据源:
java代码:
- <bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <qualifier value="mysqlDataSource"/>
- <qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>
- </bean>
- <bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <qualifier type="cn.javass.spring.chapter12.qualifier.Oracle"/>
- </bean>
5、测试方法如下:
java代码:
- @Test
- public void testQualifierInject3() {
- TestBean33 testBean33 = ctx.getBean("testBean33", TestBean33.class);
- Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean33.getMysqlDataSoruce());
- Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean33.getOracleDataSoruce());
- }
测试也通过了,说明我们扩展的@Qualifier限定描述符注解也能很好工作。
前边演示了不带属性的注解,接下来演示一下带参数的注解:
1、首先定义数据库类型:
java代码:
- package cn.javass.spring.chapter12.qualifier;
- public enum DataBase {
- ORACLE, MYSQL;
- }
2、其次扩展@Qualifier限定描述符注解
java代码:
- package cn.javass.spring.chapter12.qualifier;
- //省略import
- @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Qualifier
- public @interface DataSourceType {
- String ip(); //指定ip,用于多数据源情况
- DataBase database();//指定数据库类型
- }
3、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
- import cn.javass.spring.chapter12.qualifier.DataBase;
- import cn.javass.spring.chapter12.qualifier.DataSourceType;
- public class TestBean34 {
- private DataSource mysqlDataSource;
- private DataSource oracleDataSource;
- @Autowired
- public void initDataSource(
- @DataSourceType(ip="localhost", database=DataBase.MYSQL)
- DataSource mysqlDataSource,
- @DataSourceType(ip="localhost", database=DataBase.ORACLE)
- DataSource oracleDataSource) {
- this.mysqlDataSource = mysqlDataSource;
- this.oracleDataSource = oracleDataSource;
- }
- //省略getter方法
- }
4、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean34" class="cn.javass.spring.chapter12.TestBean34"/>
5、在Spring修改定义的两个数据源:
java代码:
- <bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <qualifier value="mysqlDataSource"/>
- <qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>
- <qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">
- <attribute key="ip" value="localhost"/>
- <attribute key="database" value="MYSQL"/>
- </qualifier>
- </bean>
- <bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <qualifier type="cn.javass.spring.chapter12.qualifier.Oracle"/>
- <qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">
- <attribute key="ip" value="localhost"/>
- <attribute key="database" value="ORACLE"/>
- </qualifier>
- </bean>
6、测试方法如下:
java代码:
- @Test
- public void testQualifierInject3() {
- TestBean34 testBean34 = ctx.getBean("testBean34", TestBean34.class);
- Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean34.getMysqlDataSource());
- Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean34.getOracleDataSoruce());
- }
测试也通过了,说明我们扩展的@Qualifier限定描述符注解也能很好工作。
四、自定义注解限定描述符:完全不使用@Qualifier,而是自己定义一个独立的限定注解;
1、首先使用如下方式定义一个自定义注解限定描述符:
java代码:
- package cn.javass.spring.chapter12.qualifier;
- //省略import
- @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface CustomQualifier {
- String value();
- }
2、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class TestBean35 {
- private DataSource dataSoruce;
- @Autowired
- public TestBean35(@CustomQualifier("oracleDataSource") DataSource dataSource) {
- this.dataSoruce = dataSource;
- }
- public DataSource getDataSoruce() {
- return dataSoruce;
- }
- }
3、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean35" class="cn.javass.spring.chapter12.TestBean35"/>
4、然后在Spring配置文件中注册CustomQualifier自定义注解限定描述符,只有注册了Spring才能识别:
java代码:
- <bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
- <property name="customQualifierTypes">
- <set>
- <value>cn.javass.spring.chapter12.qualifier.CustomQualifier</value>
- </set>
- </property>
- </bean>
5、测试方法如下:
java代码:
- @Test
- public void testQualifierInject5() {
- TestBean35 testBean35 = ctx.getBean("testBean35", TestBean35.class);
- Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean35.getDataSource());
- }
从测试中可看出,自定义的和Spring自带的没什么区别,因此如果没有足够的理由请使用Spring自带的Qualifier注解。
到此限定描述符介绍完毕,在此一定要注意以下几点:
- 限定标识符和Bean的描述符是不一样的;
- 多个Bean定义中可以使用相同的限定标识符;
- 对于集合、数组、字典类型的限定描述符注入,将注入多个具有相同限定标识符的Bean。
12.2.3 JSR-250注解
一、@Resource:自动装配,默认根据类型装配,如果指定name属性将根据名字装配,可以使用如下方式来指定:
java代码:
- @Resource(name = "标识符")
- 字段或setter方法
1、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- import javax.annotation.Resource;
- public class TestBean41 {
- @Resource(name = "message")
- private String message;
- //省略getter和setter
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean41" class="cn.javass.spring.chapter12.TestBean41"/>
3、测试方法如下:
java代码:
- @Test
- public void testResourceInject1() {
- TestBean41 testBean41 = ctx.getBean("testBean41", TestBean41.class);
- Assert.assertEquals("hello", testBean41.getMessage());
- }
使用非常简单,和@Autowired不同的是可以指定name来根据名字注入。
使用@Resource需要注意以下几点:
- @Resource注解应该只用于setter方法注入,不能提供如@Autowired多参数方法注入;
- @Resource在没有指定name属性的情况下首先将根据setter方法对于的字段名查找资源,如果找不到再根据类型查找;
- @Resource首先将从JNDI环境中查找资源,如果没找到默认再到Spring容器中查找,因此如果JNDI环境中有和Spring容器同名的资源时需要注意。
二、@PostConstruct和PreDestroy:通过注解指定初始化和销毁方法定义;
1、在测试类TestBean41中添加如下代码:
java代码:
- @PostConstruct
- public void init() {
- System.out.println("==========init");
- }
- @PreDestroy
- public void destroy() {
- System.out.println("==========destroy");
- }
2、修改测试方法如下:
java代码:
- @Test
- public void resourceInjectTest1() {
- ((ClassPathXmlApplicationContext) ctx).registerShutdownHook();
- TestBean41 testBean41 = ctx.getBean("testBean41", TestBean41.class);
- Assert.assertEquals("hello", testBean41.getMessage());
- }
类似于通过<bean>标签的init-method和destroy-method属性指定的初始化和销毁方法,但具有更高优先级,即注解方式的初始化和销毁方法将先执行。
12.2.4 JSR-330注解
在测试之前需要准备JSR-330注解所需要的jar包,到spring-framework-3.0.5.RELEASE-dependencies.zip中拷贝如下jar包到类路径:
com.springsource.javax.inject-1.0.0.jar |
一、@Inject:等价于默认的@Autowired,只是没有required属性;
二、@Named:指定Bean名字,对应于Spring自带@Qualifier中的缺省的根据Bean名字注入情况;
三、@Qualifier:只对应于Spring自带@Qualifier中的扩展@Qualifier限定描述符注解,即只能扩展使用,没有value属性。
1、首先扩展@Qualifier限定描述符注解来表示Mysql数据源
java代码:
- package cn.javass.spring.chapter12.qualifier;
- //省略部分import
- import javax.inject.Qualifier;
- @Target({ElementType.FIELD, ElementType.PARAMETER})
- @Retention(RetentionPolicy.RUNTIME)
- @Qualifier
- public @interface JSR330Mysql {
- }
2、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- import javax.inject.Inject;
- import javax.inject.Named;
- import javax.sql.DataSource;
- import cn.javass.spring.chapter12.qualifier.JSR330Mysql;
- public class TestBean51 {
- private DataSource mysqlDataSource;
- private DataSource oracleDataSource;
- @Inject
- public void initDataSoruce(
- @JSR330Mysql DataSource mysqlDataSource,
- @Named("oracleDataSource") DataSource oracleDataSource) {
- this.mysqlDataSource = mysqlDataSource;
- this.oracleDataSource = oracleDataSource;
- }
- //省略getter
- }
3、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <bean id="testBean51" class="cn.javass.spring.chapter12.TestBean51"/>
4、在Spring修改定义的mysqlDataSourceBean数据源:
java代码:
- <bean id="mysqlDataSourceBean" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
- <qualifier value="mysqlDataSource"/>
- <qualifier type="cn.javass.spring.chapter12.qualifier.Mysql"/>
- <qualifier type="cn.javass.spring.chapter12.qualifier.DataSourceType">
- <attribute key="ip" value="localhost"/>
- <attribute key="database" value="MYSQL"/>
- </qualifier>
- <qualifier type="cn.javass.spring.chapter12.qualifier.JSR330Mysql"/>
- </bean>
5、测试方法如下:
java代码:
- @Test
- public void testInject() {
- TestBean51 testBean51 = ctx.getBean("testBean51", TestBean51.class);
- Assert.assertEquals(ctx.getBean("mysqlDataSourceBean"), testBean51.getMysqlDataSource());
- Assert.assertEquals(ctx.getBean("oracleDataSource"), testBean51.getOracleDataSource());
- }
测试也通过了,说明JSR-330注解也能很好工作。
从测试中可以看出JSR-330注解和Spring自带注解依赖注入时主要有以下特点:
- Spring自带的@Autowired的缺省情况等价于JSR-330的@Inject注解;
- Spring自带的@Qualifier的缺省的根据Bean名字注入情况等价于JSR-330的@Named注解;
- Spring自带的@Qualifier的扩展@Qualifier限定描述符注解情况等价于JSR-330的@Qualifier注解。
12.2.5 JPA注解
用于注入EntityManagerFactory和EntityManager。
1、准备测试Bean:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class TestBean61 {
- @PersistenceContext(unitName = "entityManagerFactory")
- private EntityManager entityManager;
- @PersistenceUnit(unitName = "entityManagerFactory")
- private EntityManagerFactory entityManagerFactory;
- public EntityManager getEntityManager() {
- return entityManager;
- }
- public EntityManagerFactory getEntityManagerFactory() {
- return entityManagerFactory;
- }
- }
2、在Spring配置文件(chapter12/dependecyInjectWithAnnotation.xml)添加如下Bean配置:
java代码:
- <import resource="classpath:chapter7/applicationContext-resources.xml"/>
- <import resource="classpath:chapter8/applicationContext-jpa.xml"/>
- <bean id="testBean61" class="cn.javass.spring.chapter12.TestBean61"/>
此处需要引用第七章和八章的配置文件,细节内容请参考七八两章。
3、测试方法如下:
java代码:
- @Test
- public void testJpaInject() {
- TestBean61 testBean61 = ctx.getBean("testBean61", TestBean61.class);
- Assert.assertNotNull(testBean61.getEntityManager());
- Assert.assertNotNull(testBean61.getEntityManagerFactory());
- }
测试也通过了,说明JPA注解也能很好工作。
JPA注解类似于@Resource注解同样是先根据unitName属性去JNDI环境中查找,如果没找到在到Spring容器中查找。
12.3 注解实现Bean定义
12.3.1 概述
前边介绍的Bean定义全是基于XML方式定义配置元数据,且在【12.2注解实现Bean依赖注入】一节中介绍了通过注解来减少配置数量,但并没有完全消除在XML配置文件中的Bean定义,因此有没有方式完全消除XML配置Bean定义呢?
Spring提供通过扫描类路径中的特殊注解类来自动注册Bean定义。同注解驱动事务一样需要开启自动扫描并注册Bean定义支持,使用方式如下(resources/chapter12/ componentDefinitionWithAnnotation.xml):
java代码:
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:context="http://www.springframework.org/schema/context"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd">
- <aop:aspectj-autoproxy />
- <context:component-scan base-package="cn.javass.spring.chapter12"/>
- </beans>
使用<context:component-scan>标签来表示需要要自动注册Bean定义,而通过base-package属性指定扫描的类路径位置。
<context:component-scan>标签将自动开启“注解实现Bean依赖注入”支持。
此处我们还通过<aop:aspectj-autoproxy/>用于开启Spring对@AspectJ风格切面的支持。
Spring基于注解实现Bean定义支持如下三种注解:
- Spring自带的@Component注解及扩展@Repository、@Service、@Controller,如图12-1所示;
- JSR-250 1.1版本中中定义的@ManagedBean注解,是Java EE 6标准规范之一,不包括在JDK中,需要在应用服务器环境使用(如Jboss),如图12-2所示;
- JSR-330的@Named注解,如图12-3所示。
图12-1 Spring自带的@Component注解及扩展
图12-2 JSR-250中定义的@ManagedBean注解及自定义扩展
图12-3 JSR-330的@Named注解及自定义扩展
图12-2和图12-3中的自定义扩展部分是为了配合Spring自带的模式注解扩展自定义的,并不包含在Java EE 6规范中,在Java EE 6中相应的服务层、DAO层功能由EJB来完成。
在Java EE中有些注解运行放置在多个地方,如@Named允许放置在类型、字段、方法参数上等,因此一般情况下放置在类型上表示定义,放置在参数、方法等上边一般代表使用(如依赖注入等等)。
12.3.2 Spring自带的@Component注解及扩展
一、@Component:定义Spring管理Bean,使用方式如下:
java代码:
- @Component("标识符")
- POJO类
在类上使用@Component注解,表示该类定义为Spring管理Bean,使用默认value(可选)属性表示Bean标识符。
1、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.ApplicationContext;
- import org.springframework.stereotype.Component;
- @Component("component")
- public class TestCompoment {
- @Autowired
- private ApplicationContext ctx;
- public ApplicationContext getCtx() {
- return ctx;
- }
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试类和测试方法:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- public class ComponentDefinitionWithAnnotationTest {
- private static String configLocation = "classpath:chapter12/componentDefinitionWithAnnotation.xml";
- private static ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocation);
- @Test
- public void testComponent() {
- TestCompoment component = ctx.getBean("component", TestCompoment.class);
- Assert.assertNotNull(component.getCtx());
- }
- }
测试成功说明被@Component注解的POJO类将自动被Spring识别并注册到Spring容器中,且自动支持自动装配。
@AspectJ风格的切面可以通过@Compenent注解标识其为Spring管理Bean,而@Aspect注解不能被Spring自动识别并注册为Bean,必须通过@Component注解来完成,示例如下:
java代码:
- package cn.javass.spring.chapter12.aop;
- //省略import
- @Component
- @Aspect
- public class TestAspect {
- @Pointcut(value="execution(* *(..))")
- private void pointcut() {}
- @Before(value="pointcut()")
- public void before() {
- System.out.println("=======before");
- }
- }
通过@Component将切面定义为Spring管理Bean。
二、@Repository:@Component扩展,被@Repository注解的POJO类表示DAO层实现,从而见到该注解就想到DAO层实现,使用方式和@Component相同;
1、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12.dao.hibernate;
- import org.springframework.stereotype.Repository;
- @Repository("testHibernateDao")
- public class TestHibernateDaoImpl {
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试方法:
java代码:
- @Test
- public void testDao() {
- TestHibernateDaoImpl dao =
- ctx.getBean("testHibernateDao", TestHibernateDaoImpl.class);
- Assert.assertNotNull(dao);
- }
测试成功说明被@Repository注解的POJO类将自动被Spring识别并注册到Spring容器中,且自动支持自动装配,并且被@Repository注解的类表示DAO层实现。
三、@Service:@Component扩展,被@Service注解的POJO类表示Service层实现,从而见到该注解就想到Service层实现,使用方式和@Component相同;
1、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12.service.impl;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.stereotype.Service;
- import cn.javass.spring.chapter12.dao.hibernate.TestHibernateDaoImpl;
- @Service("testService")
- public class TestServiceImpl {
- @Autowired
- @Qualifier("testHibernateDao")
- private TestHibernateDaoImpl dao;
- public TestHibernateDaoImpl getDao() {
- return dao;
- }
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试方法:
java代码:
- @Test
- public void testService() {
- TestServiceImpl service = ctx.getBean("testService", TestServiceImpl.class);
- Assert.assertNotNull(service.getDao());
- }
测试成功说明被@Service注解的POJO类将自动被Spring识别并注册到Spring容器中,且自动支持自动装配,并且被@Service注解的类表示Service层实现。
四、@Controller:@Component扩展,被@Controller注解的类表示Web层实现,从而见到该注解就想到Web层实现,使用方式和@Component相同;
1、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12.action;
- //省略import
- @Controller
- public class TestAction {
- @Autowired
- private TestServiceImpl testService;
- public void list() {
- //调用业务逻辑层方法
- }
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试方法:
java代码:
- @Test
- public void testWeb() {
- TestAction action = ctx.getBean("testAction", TestAction.class);
- Assert.assertNotNull(action);
- }
测试成功说明被@Controller注解的类将自动被Spring识别并注册到Spring容器中,且自动支持自动装配,并且被@Controller注解的类表示Web层实现。
大家是否注意到@Controller中并没有定义Bean的标识符,那么默认Bean的名字将是以小写开头的类名(不包括包名),即如“TestAction”类的Bean标识符为“testAction”。
六、自定义扩展:Spring内置了三种通用的扩展注解@Repository、@Service、@Controller ,大多数情况下没必要定义自己的扩展,在此我们演示下如何扩展@Component注解来满足某些特殊规约的需要;
在此我们可能需要一个缓存层用于定义缓存Bean,因此我们需要自定义一个@Cache的注解来表示缓存类。
1、扩展@Component:
java代码:
- package cn.javass.spring.chapter12.stereotype;
- //省略import
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Cache{
- String value() default "";
- }
扩展十分简单,只需要在扩展的注解上注解@Component即可,@Repository、@Service、@Controller也是通过该方式实现的,没什么特别之处
2、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12.cache;
- @Cache("cache")
- public class TestCache {
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试方法:
java代码:
- @Test
- public void testCache() {
- TestCache cache = ctx.getBean("cache", TestCache.class);
- Assert.assertNotNull(cache);
- }
测试成功说明自定义的@Cache注解也能很好的工作,而且实现了我们的目的,使用@Cache来表示被注解的类是Cache层Bean。
12.3.3 JSR-250中定义的@ManagedBean注解
@javax.annotation.ManagedBean需要在实现Java EE 6规范的应用服务器上使用,虽然Spring3实现了,但@javax.annotation.ManagedBean只有在Java EE 6环境中才有定义,因此测试前需要我们定义ManagedBean类。
1、定义javax.annotation.ManagedBean注解类:
java代码:
- package javax.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface ManagedBean {
- String value() default "";
- }
其和@Component完全相同,唯一不同的就是名字和创建者(一个是Spring,一个是Java EE规范)。
2、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12;
- import javax.annotation.Resource;
- import org.springframework.context.ApplicationContext;
- @javax.annotation.ManagedBean("managedBean")
- public class TestManagedBean {
- @Resource
- private ApplicationContext ctx;
- public ApplicationContext getCtx() {
- return ctx;
- }
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试方法:
java代码:
- @Test
- public void testManagedBean() {
- TestManagedBean testManagedBean = ctx.getBean("managedBean", TestManagedBean.class);
- Assert.assertNotNull(testManagedBean.getCtx());
- }
测试成功说明被@ManagedBean注解类也能正常工作。
自定义扩展就不介绍了,大家可以参考@Component来完成如图12-2所示的自定义扩展部分。
12.3.4 JSR-330的@Named注解
@Named不仅可以用于依赖注入来指定注入的Bean的标识符,还可以用于定义Bean。即注解在类型上表示定义Bean,注解在非类型上(如字段)表示指定依赖注入的Bean标识符。
1、定义测试Bean类:
java代码:
- package cn.javass.spring.chapter12;
- //省略import
- @Named("namedBean")
- public class TestNamedBean {
- @Inject
- private ApplicationContext ctx;
- public ApplicationContext getCtx() {
- return ctx;
- }
- }
2、Spring配置文件使用chapter12/ componentDefinitionWithAnnotation.xml即可且无需修改;
3、定义测试方法:
java代码:
- @Test
- public void testNamedBean() {
- TestNamedBean testNamedBean =
- ctx.getBean("namedBean", TestNamedBean.class);
- Assert.assertNotNull(testNamedBean.getCtx());
- }
测试成功说明被@Named注解类也能正常工作。
自定义扩展就不介绍了,大家可以参考@Component来完成如图12-3所示的自定义扩展部分。
12.3.5 细粒度控制Bean定义扫描
在XML配置中完全消除了Bean定义,而是只有一个<context:component-scan>标签来支持注解Bean定义扫描。
前边的示例完全采用默认扫描设置,如果我们有几个组件不想被扫描并自动注册、我们想更改默认的Bean标识符生成策略该如何做呢?接下来让我们看一下如何细粒度的控制Bean定义扫描,具体定义如下:
java代码:
- <context:component-scan
- base-package=""
- resource-pattern="**/*.class"
- name-generator="org.springframework.context.annotation.AnnotationBeanNameGenerator"
- use-default-filters="true"
- annotation-config="true">
- <context:include-filter type="aspectj" expression=""/>
- <context:exclude-filter type="regex" expression=""/>
- </context:component-scan>
- base-package:表示扫描注解类的开始位置,即将在指定的包中扫描,其他包中的注解类将不被扫描,默认将扫描所有类路径;
- resource-pattern:表示扫描注解类的后缀匹配模式,即“base-package+resource-pattern”将组成匹配模式用于匹配类路径中的组件,默认后缀为“**/*.class”,即指定包下的所有以.class结尾的类文件;
- name-generator:默认情况下的Bean标识符生成策略,默认是AnnotationBeanNameGenerator,其将生成以小写开头的类名(不包括包名);可以自定义自己的标识符生成策略;
- use-default-filters:默认为true表示过滤@Component、@ManagedBean、@Named注解的类,如果改为false默认将不过滤这些默认的注解来定义Bean,即这些注解类不能被过滤到,即不能通过这些注解进行Bean定义;
- annotation-config:表示是否自动支持注解实现Bean依赖注入,默认支持,如果设置为false,将关闭支持注解的依赖注入,需要通过<context:annotation-config/>开启。
默认情况下将自动过滤@Component、@ManagedBean、@Named注解的类并将其注册为Spring管理Bean,可以通过在<context:component-scan>标签中指定自定义过滤器将过滤到匹配条件的类注册为Spring管理Bean,具体定义方式如下:
java代码:
- <context:include-filter type="aspectj" expression=""/>
- <context:exclude-filter type="regex" expression=""/>
- <context:include-filter>:表示过滤到的类将被注册为Spring管理Bean;
- <context:exclude-filter>:表示过滤到的类将不被注册为Spring管理Bean,它比<context:include-filter>具有更高优先级;
- type:表示过滤器类型,目前支持注解类型、类类型、正则表达式、aspectj表达式过滤器,当然也可以自定义自己的过滤器,实现org.springframework.core.type.filter.TypeFilter即可;
- expression:表示过滤器表达式。
一般情况下没必要进行自定义过滤,如果需要请参考如下示例:
1、cn.javass.spring.chapter12.TestBean14自动注册为Spring管理Bean:
java代码:
- <context:include-filter type="assignable" expression="cn.javass.spring.chapter12.TestBean14"/>
2、把所有注解为org.aspectj.lang.annotation.Aspect自动注册为Spring管理Bean:
java代码:
- <context:include-filter type="annotation"
- expression="org.aspectj.lang.annotation.Aspect"/>
3、将把匹配到正则表达式“cn\.javass\.spring\.chapter12\.TestBean2*”排除,不注册为Spring管理Bean:
java代码:
- <context:exclude-filter type="regex" expression="cn\.javass\.spring\.chapter12\.TestBean2*"/>
4、将把匹配到aspectj表达式“cn.javass.spring.chapter12.TestBean3*”排除,不注册为Spring管理Bean:
java代码:
- <context:exclude-filter type="aspectj" expression="cn.javass.spring.chapter12.TestBean3*"/>
具体使用就要看项目需要了,如果以上都不满足需要请考虑使用自定义过滤器。
12.3.6 提供更多的配置元数据
1、@Lazy:定义Bean将延迟初始化,使用方式如下:
java代码:
- @Component("component")
- @Lazy(true)
- public class TestCompoment {
- ……
- }
使用@Lazy注解指定Bean需要延迟初始化。
2、@DependsOn:定义Bean初始化及销毁时的顺序,使用方式如下:
java代码:
- @Component("component")
- @DependsOn({"managedBean"})
- public class TestCompoment {
- ……
- }
3、@Scope:定义Bean作用域,默认单例,使用方式如下:
java代码:
- @Component("component")
- @Scope("singleton")
- public class TestCompoment {
- ……
- }
4、@Qualifier:指定限定描述符,对应于基于XML配置中的<qualifier>标签,使用方式如下:
java代码:
- @Component("component")
- @Qualifier("component")
- public class TestCompoment {
- ……
- }
可以使用复杂的扩展,如@Mysql等等。
5、@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常,使用方式如下:
java代码:
- @Component("component")
- @Primary
- public class TestCompoment {
- ……
- }
12.4 基于Java类定义Bean配置元数据
12.4.1 概述
基于Java类定义Bean配置元数据,其实就是通过Java类定义Spring配置元数据,且直接消除XML配置文件。
基于Java类定义Bean配置元数据中的@Configuration注解的类等价于XML配置文件,@Bean注解的方法等价于XML配置文件中的Bean定义。
基于Java类定义Bean配置元数据需要通过AnnotationConfigApplicationContext加载配置类及初始化容器,类似于XML配置文件需要使用ClassPathXmlApplicationContext加载配置文件及初始化容器。
基于Java类定义Bean配置元数据需要CGLIB的支持,因此要保证类路径中包括CGLIB的jar包。
12.4.2 Hello World
首先让我们看一下基于Java类如何定义Bean配置元数据,具体步骤如下:
1、 通过@Configuration注解需要作为配置的类,表示该类将定义Bean配置元数据;
2、 通过@Bean注解相应的方法,该方法名默认就是Bean名,该方法返回值就是Bean对象;
3、 通过AnnotationConfigApplicationContext或子类加载基于Java类的配置。
接下来让我们先来学习一下如何通过Java类定义Bean配置元数据吧:
1、定义配置元数据的Java类如下所示:
java代码:
- package cn.javass.spring.chapter12.configuration;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- @Configuration
- public class ApplicationContextConfig {
- @Bean
- public String message() {
- return "hello";
- }
- }
2、定义测试类,测试一下Java配置类是否工作:
java代码:
- package cn.javass.spring.chapter12.configuration;
- //省略import
- public class ConfigurationTest {
- @Test
- public void testHelloworld () {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
- Assert.assertEquals("hello", ctx.getBean("message"));
- }
- }
测试没有报错说明测试通过了,那AnnotationConfigApplicationContext是如何工作的呢,接下来让我们分析一下:
- 使用@Configuration注解配置类,该配置类定义了Bean配置元数据;
- 使用@Bean注解配置类中的方法,该方法名就是Bean的名字,该方法返回值就是Bean对象。
- 使用new AnnotationConfigApplicationContext(ApplicationContextConfig.class)创建应用上下文,构造器参数为使用@Configuration注解的配置类,读取配置类进行实例化相应的Bean。
知道如何使用了,接下来就详细介绍每个部分吧。
12.4.3 @Configuration
通过@Configuration注解的类将被作为配置类使用,表示在该类中将定义Bean配置元数据,且使用@Configuration注解的类本身也是一个Bean,使用方式如下所示:
java代码:
- import org.springframework.context.annotation.Configuration;
- @Configuration("ctxConfig")
- public class ApplicationContextConfig {
- //定义Bean配置元数据
- }
因为使用@Configuration注解的类本身也是一个Bean,因为@Configuration被@Component注解了,因此@Configuration注解可以指定value属性值,如“ctxConfig”就是该Bean的名字,如使用“ctx.getBean("ctxConfig")”将返回该Bean。
使用@Configuration注解的类不能是final的,且应该有一个默认无参构造器。
12.4.4 @Bean
通过@Bean注解配置类中的相应方法,则该方法名默认就是Bean名,该方法返回值就是Bean对象,并定义了Spring IoC容器如何实例化、自动装配、初始化Bean逻辑,具体使用方法如下:
java代码:
- @Bean(name={},
- autowire=Autowire.NO,
- initMethod="",
- destroyMethod="")
- name:指定Bean的名字,可有多个,第一个作为Id,其他作为别名;
- autowire:自动装配,默认no表示不自动装配该Bean,另外还有Autowire.BY_NAME表示根据名字自动装配,Autowire.BY_TYPE表示根据类型自动装配;
- initMethod和destroyMethod:指定Bean的初始化和销毁方法。
示例如下所示(ApplicationContextConfig.java)
java代码:
- @Bean
- public String message() {
- return new String("hello");
- }
如上使用方式等价于如下基于XML配置方式
java代码:
- <bean id="message" class="java.lang.String">
- <constructor-arg index="0" value="hello"/>
- </bean>
使用@Bean注解的方法不能是private、final或static的。
12.4.5 提供更多的配置元数据
详见【12.3.6 提供更多的配置元数据】中介绍的各种注解,这些注解同样适用于@Bean注解的方法。
12.4.6 依赖注入
基于Java类配置方式的Bean依赖注入有如下两种方式:
- 直接依赖注入,类似于基于XML配置方式中的显示依赖注入;
- 使用注解实现Bean依赖注入:如@Autowired等等。
在本示例中我们将使用【第三章 DI】中的测试Bean。
1、 直接依赖注入:包括构造器注入和setter注入。
- 构造器注入:通过在@Bean注解的实例化方法中使用有参构造器实例化相应的Bean即可,如下所示(ApplicationContextConfig.java):
java代码:
- @Bean
- public HelloApi helloImpl3() {
- //通过构造器注入,分别是引用注入(message())和常量注入(1)
- return new HelloImpl3(message(), 1); //测试Bean详见【3.1.2 构造器注入】
- }
- setter注入:通过在@Bean注解的实例化方法中使用无参构造器实例化后,通过相应的setter方法注入即可,如下所示(ApplicationContextConfig.java):
java代码:
- @Bean
- public HelloApi helloImpl4() {
- HelloImpl4 helloImpl4 = new HelloImpl4();//测试Bean详见【3.1.3 setter注入】
- //通过setter注入注入引用
- helloImpl4.setMessage(message());
- //通过setter注入注入常量
- helloImpl4.setIndex(1);
- return helloImpl4;
- }
2、使用注解实现Bean依赖注入:详见【12.2 注解实Bean依赖注入】。
具体测试方法如下(ConfigurationTest.java):
java代码:
- @Test
- public void testDependencyInject() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
- ctx.getBean("helloImpl3", HelloApi.class).sayHello();
- ctx.getBean("helloImpl4", HelloApi.class).sayHello();
- }
12.4.7 方法注入
在基于XML配置方式中,Spring支持查找方法注入和替换方法注入,但在基于Java配置方式中只支持查找方法注入,一般用于在一个单例Bean中注入一个原型Bean的情况,具体详见【3.3.5 方法注入】,如下所示(ApplicationContextConfig.java):
java代码:
- @Bean
- @Scope("singleton")
- public HelloApi helloApi2() {
- HelloImpl5 helloImpl5 = new HelloImpl5() {
- @Override
- public Printer createPrototypePrinter() {
- //方法注入,注入原型Bean
- return prototypePrinter();
- }
- @Override
- public Printer createSingletonPrinter() {
- //方法注入,注入单例Bean
- return singletonPrinter();
- }
- };
- //依赖注入,注入单例Bean
- helloImpl5.setPrinter(singletonPrinter());
- return helloImpl5;
- }
java代码:
- @Bean
- @Scope(value="prototype")
- public Printer prototypePrinter() {
- return new Printer();
- }
- @Bean
- @Scope(value="singleton")
- public Printer singletonPrinter() {
- return new Printer();
- }
具体测试方法如下(ConfigurationTest.java):
java代码:
- @Test
- public void testLookupMethodInject() {
- AnnotationConfigApplicationContext ctx =
- new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
- System.out.println("=======prototype sayHello======");
- HelloApi helloApi2 = ctx.getBean("helloApi2", HelloApi.class);
- helloApi2.sayHello();
- helloApi2 = ctx.getBean("helloApi2", HelloApi.class);
- helloApi2.sayHello();
- }
如上测试等价于【3.3.5 方法注入】中的查找方法注入。
12.4.8 @Import
类似于基于XML配置中的<import/>,基于Java的配置方式提供了@Import来组合模块化的配置类,使用方式如下所示:
java代码:
- package cn.javass.spring.chapter12.configuration;
- //省略import
- @Configuration("ctxConfig2")
- @Import({ApplicationContextConfig.class})
- public class ApplicationContextConfig2 {
- @Bean(name = {"message2"})
- public String message() {
- return "hello";
- }
- }
具体测试方法如下(ConfigurationTest.java):
java代码:
- @Test
- public void importTest() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationContextConfig2.class);
- Assert.assertEquals("hello", ctx.getBean("message"));
- }
使用非常简单,在此就不多介绍了。
12.4.9 结合基于Java和基于XML方式的配置
基于Java方式的配置方式不是为了完全替代基于XML方式的配置,两者可以结合使用,因此可以有两种结合使用方式:
- 在基于Java方式的配置类中引入基于XML方式的配置文件;
- 在基于XML方式的配置文件中中引入基于Java方式的配置。
一、在基于Java方式的配置类中引入基于XML方式的配置文件:在@Configuration注解的配置类上通过@ImportResource注解引入基于XML方式的配置文件,示例如下所示:
1、定义基于XML方式的配置文件(chapter12/configuration/importResource.xml):
java代码:
- <bean id="message3" class="java.lang.String">
- <constructor-arg index="0" value="test"></constructor-arg>
- </bean>
2、修改基于Java方式的配置类ApplicationContextConfig,添加如下注解:
java代码:
- @Configuration("ctxConfig") //1、使用@Configuration注解配置类
- @ImportResource("classpath:chapter12/configuration/importResource.xml")
- public class ApplicationContextConfig {
- ……
- }
使用@ImportResource引入基于XML方式的配置文件,如果有多个请使用@ImportResource({"config1.xml", "config2.xml"})方式指定多个配置文件。
二、在基于XML方式的配置文件中中引入基于Java方式的配置:直接在XML配置文件中声明使用@Configuration注解的配置类即可,示例如下所示:
1、定义基于Java方式的使用@Configuration注解的配置类在此我们使用ApplicationContextConfig.java。
2、定义基于XML方式的配置文件(chapter12/configuration/xml-config.xml):
java代码:
- <context:annotation-config/>
- <bean id="ctxConfig" class="cn.javass.spring.chapter12.configuration.ApplicationContextConfig"/>
- <context:annotation-config/>:用于开启对注解驱动支持,详见【12.2 注解实现Bean依赖注入】;
- <bean id="ctxConfig" class="……"/>:直接将使用@Configuration注解的配置类在配置文件中进行Bean定义即可。
3、测试代码如下所示(ConfigurationTest.java)::
java代码:
- public void testXmlConfig() {
- String configLocations[] = {"chapter12/configuration/xml-config.xml"};
- ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);
- Assert.assertEquals("hello", ctx.getBean("message"));
- }
测试成功,说明通过在基于XML方式的配置文件中能获取到基于Java方式的配置文件中定义的Bean,如“message”Bean。
12.4.10 基于Java方式的容器实例化
基于Java方式的容器由AnnotationConfigApplicationContext表示,其实例化方式主要有以下几种:
一、对于只有一个@Configuration注解的配置类,可以使用如下方式初始化容器:
java代码:
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationContextConfig.class);
二、对于有多个@Configuration注解的配置类,可以使用如下方式初始化容器:
java代码:
- AnnotationConfigApplicationContext ctx1 = new AnnotationConfigApplicationContext(ApplicationContextConfig.class, ApplicationContextConfig2.class);
或者
java代码:
- AnnotationConfigApplicationContext ctx2 = new AnnotationConfigApplicationContext();
- ctx2.register(ApplicationContextConfig.class);
- ctx2.register(ApplicationContextConfig2.class);
三、对于【12.3 注解实现Bean定义】中通过扫描类路径中的特殊注解类来自动注册Bean定义,可以使用如下方式来实现:
java代码:
- public void testComponentScan() {
- AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
- ctx.scan("cn.javass.chapter12.confiuration");
- ctx.refresh();
- Assert.assertEquals("hello", ctx.getBean("message"));
- }
以上配置方式等价于基于XML方式中的如下配置:
java代码:
- <context:component-scan base-package="cn.javass.chapter12.confiuration"/>
四、在web环境中使用基于Java方式的配置,通过修改通用配置实现,详见【10.1.2 通用配置】:
1、修改通用配置中的Web应用上下文实现,在此需要使用AnnotationConfigWebApplicationContext:
java代码:
- <context-param>
- <param-name>contextClass</param-name>
- <param-value>
- org.springframework.web.context.support.AnnotationConfigWebApplicationContext
- </param-value>
- </context-param>
2、指定加载配置类,类似于指定加载文件位置,在基于Java方式中需要指定需要加载的配置类:
java代码:
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- cn.javass.spring.chapter12.configuration.ApplicationContextConfig,
- cn.javass.spring.chapter12.configuration.ApplicationContextConfig2
- </param-value>
- </context-param>
- contextConfigLocation:除了可以指定配置类,还可以指定“扫描的类路径”,其加载步骤如下:
1、首先验证指定的配置是否是类,如果是则通过注册配置类来完成Bean定义加载,即如通过ctx.register(ApplicationContextConfig.class)加载定义;
2、如果指定的配置不是类,则通过扫描类路径方式加载注解Bean定义,即将通过ctx.scan("cn.javass.chapter12.confiuration")加载Bean定义。
12.5 综合示例
12.5.1 概述
在第十一章中我们介绍了SSH集成,在进行SSH集成时都是通过基于XML配置文件配置每层的Bean,从而产生许多XML配置文件,本节将通过注解方式消除部分XML配置文件,实现所谓的零配置。
12.5.2 项目拷贝
1、拷贝【第十一章 SSH集成开发】中的“pointShop”项目将其命名为“pointShop2”;
2、修改“pointShop2”项目下的“.settings”文件夹下的“org.eclipse.wst.common.component”文件,将“<property name="context-root" value="pointShop"/>”修改为“<property name="context-root" value="pointShop2"/>”,即该web项目的上下文为“pointShop2”,在浏览器中可以通过http://localhost:8080/pointShop2来访问该web项目。
12.5.3 数据访问层变化
将dao层配置文件中的dao实现Bean定义删除,通过在dao实现类头上添加“@Repository”来定义dao实现Bean,并通过注解@Autowired来完成依赖注入。
1、删除DAO层配置文件(cn/javass/point/dao/applicationContext-hibernate.xml)中的如下配置:
java代码:
- <bean id="abstractDao" abstract="true" init-method="init">
- <property name="sessionFactory" ref="sessionFactory"/>
- </bean>
- <bean id="goodsDao" class="cn.javass.point.dao.hibernate.GoodsHibernateDao"
- parent="abstractDao"/>
- <bean id="goodsCodeDao" class="cn.javass.point.dao.hibernate.GoodsCodeHibernateDao"
- parent="abstractDao"/>
2、修改通用DAO实现cn.javass.commons.dao.hibernate.BaseHibernateDao,通过注解实现依赖注入和指定初始化方法:
java代码:
- public abstract class BaseHibernateDao<M extends Serializable, PK extends Serializable> extends HibernateDaoSupport implements IBaseDao<M, PK>, InitializingBean {
- //省略类字段
- @Autowired @Required
- public void setSf(SessionFactory sf) {
- setSessionFactory(sf);
- }
- @PostConstruct
- @SuppressWarnings("unchecked")
- public void init() {
- //省略具体实现代码
- }
- }
- setSf方法:通过@Autowired注解自动注入SessionFactory实现;
- init方法:通过@PostConstruct注解表示该方法是初始化方法;
3、修改cn.javass.point.dao.hibernate.GoodsHibernateDao,在该类上添加@Repository注解来进行DAO层Bean定义:
java代码:
- @Repository
- public class GoodsHibernateDao extends BaseHibernateDao<GoodsModel, Integer> implements IGoodsDao {
- ……
- }
4、修改cn.javass.point.dao.hibernate.GoodsCodeHibernateDao,在该类上添加@Repository注解来进行DAO层Bean定义:
java代码:
- @Repository
- public class GoodsCodeHibernateDao extends BaseHibernateDao<GoodsCodeModel, Integer> implements IGoodsCodeDao {
- ……
- }
DAO层到此就修改完毕,其他地方无需修改。
12.5.4 业务逻辑层变化
将service层配置文件中的service实现Bean定义删除,通过在service实现类头上添加“@Service”来定义service实现Bean,并通过注解@Autowired来完成依赖注入。
1、删除Service层配置文件(cn/javass/point/service/applicationContext-service.xml)中的如下配置:
java代码:
- <bean id="goodsService" class="cn.javass.point.service.impl.GoodsServiceImpl">
- <property name="dao" ref="goodsDao"/>
- </bean>
- <bean id="goodsCodeService" class="cn.javass.point.service.impl.GoodsCodeServiceImpl">
- <property name="dao" ref="goodsCodeDao"/>
- <property name="goodsService" ref="goodsService"/>
- </bean>
2、修改cn.javass.point.service.impl.GoodsServiceImpl,在该类上添加@Service注解来进行Service层Bean定义:
java代码:
- @Service
- public class GoodsServiceImpl extends BaseServiceImpl<GoodsModel, Integer> implements IGoodsService {
- @Autowired @Required
- public void setGoodsDao(IGoodsDao dao) {
- setDao(dao);
- }
- }
- setGoodsDao方法:用于注入IGoodsDao实现,此处直接委托给setDao方法。
3、修改cn.javass.point.service.impl.GoodsCodeServiceImpl,在该类上添加@Service注解来进行Service层Bean定义:
java代码:
- @Service
- public class GoodsCodeServiceImpl extends BaseServiceImpl<GoodsCodeModel, Integer> implements IGoodsCodeService {
- @Autowired @Required
- public void setGoodsCodeDao(IGoodsCodeDao dao) {
- setDao(dao);
- }
- @Autowired @Required
- public void setGoodsService(IGoodsService goodsService) {
- this.goodsService = goodsService;
- }
- }
- setGoodsCodeDao方法:用于注入IGoodsCodeDao实现,此处直接委托给setDao方法;
- setGoodsService方法:用于注入IGoodsService实现。
Service层到此就修改完毕,其他地方无需修改。
12.5.5 表现层变化
类似于数据访问层和业务逻辑层修改,对于表现层配置文件直接删除,通过在action实现类头上添加“@Controller”来定义action实现Bean,并通过注解@Autowired来完成依赖注入。
1、 删除表现层所有Spring配置文件(cn/javass/point/web):
java代码:
- cn/javass/point/web/pointShop-admin-servlet.xml
- cn/javass/point/web/pointShop-front-servlet.xml
2、修改表现层管理模块的cn.javass.point.web.admin.action.GoodsAction,在该类上添加@Controller注解来进行表现层Bean定义,且作用域为“prototype”:
java代码:
- @Controller("/admin/goodsAction")
- @Scope("prototype")
- public class GoodsAction extends BaseAction {
- private IGoodsService goodsService;
- @Autowired @Required
- public void setGoodsService(IGoodsService goodsService) {
- this.goodsService = goodsService;
- }
- }
- setGoodsService方法:用于注入IGoodsService实现。
3、修改表现层管理模块的cn.javass.point.web.admin.action.GoodsCodeAction,在该类上添加@Controller注解来进行表现层Bean定义,且作用域为“prototype”:
java代码:
- @Controller("/admin/goodsCodeAction")
- @Scope("prototype")
- public class GoodsCodeAction extends BaseAction {
- @Autowired @Required
- public void setGoodsCodeService(IGoodsCodeService goodsCodeService) {
- this.goodsCodeService = goodsCodeService;
- }
- @Autowired @Required
- public void setGoodsService(IGoodsService goodsService) {
- this.goodsService = goodsService;
- }
- }
- setGoodsCodeService方法:用于注入IGoodsCodeService实现;
- setGoodsService方法:用于注入IGoodsService实现。
3、修改表现层前台模块的cn.javass.point.web.front.action.GoodsAction,在该类上添加@Controller注解来进行表现层Bean定义,且作用域为“prototype”:
java代码:
- @Controller("/front/goodsAction")
- @Scope("prototype")
- public class GoodsAction extends BaseAction {
- @Autowired @Required
- public void setGoodsService(IGoodsService goodsService) {
- this.goodsService = goodsService;
- }
- @Autowired @Required
- public void setGoodsCodeService(IGoodsCodeService goodsCodeService) {
- this.goodsCodeService = goodsCodeService;
- }
- }
- setGoodsCodeService方法:用于注入IGoodsCodeService实现;
- setGoodsService方法:用于注入IGoodsService实现。
12.5.6 其他变化
1、定义一个基于Java方法的配置类,用于加载XML配置文件:
java代码:
- package cn.javass.point;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.ImportResource;
- @Configuration
- @ImportResource(
- {"classpath:applicationContext-resources.xml",
- "classpath:cn/javass/point/dao/applicationContext-hibernate.xml",
- "classpath:cn/javass/point/service/applicationContext-service.xml"
- })
- public class AppConfig {
- }
该类用于加载零配置中一般不变的XML配置文件,如事务管理,数据源、SessionFactory,这些在几乎所有项目中都是类似的,因此可以作为通用配置。
2、修改集成其它Web框架的通用配置,将如下配置:
java代码:
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath:applicationContext-resources.xml,
- classpath:cn/javass/point/dao/applicationContext-hibernate.xml,
- classpath:cn/javass/point/service/applicationContext-service.xml,
- classpath:cn/javass/point/web/pointShop-admin-servlet.xml,
- classpath:cn/javass/point/web/pointShop-front-servlet.xml
- </param-value>
- </context-param>
修改为如下配置:
java代码:
- <context-param>
- <param-name>contextClass</param-name>
- <param-value>
- org.springframework.web.context.support.AnnotationConfigWebApplicationContext
- </param-value>
- </context-param>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>cn.javass.point</param-value>
- </context-param>
- contextClass:使用notationConfigWebApplicationContext替换默认的XmlWebApplicationContext;
- contextConfigLocation:指定为“cn.javass.point”,表示将通过扫描该类路径“cn.javass.point”下的注解类来进行加载Bean定义。
启动pointShop2项目,在浏览器输入http://localhost:8080/pointShop2/admin/goods/list.action访问积分商城后台,如果没问题说明零配置整合成功。
到此零配置方式实现SSH集成已经整合完毕,相对于基于XML方式主要减少了配置的数量和配置文件的数量。