5. IoC 容器

5.1 Spring IoC容器和Bean概述

本章介绍了Spring框架实现控制反转(IoC) [1] 的原理。IoC也被称作 依赖注入 (DI)。它是一个处理对象依赖项的过程,也就是说,和他们一起工作的其他的对象,只有通过构造参数、工厂方法参数或者(属性注入)通过构造参数实例化或通过工厂方法返回对象后再设置属性。当创建bean后,IoC容器再将这些依赖项注入进去。这个过程基本上是反转的,因此得名 控制反转 (IoC), This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes, or a mechanism such as the Service Locator pattern.

org.springframework.beansorg.springframework.context包是Spring框架IoC容器的基础。 BeanFactory接口提供了一个先进的配置机制能够管理任何类型的对象。 ApplicationContext(应用上下文)BeanFactory的一个子接口。它增加了更方便的集成Spring的AOP功能、消息资源处理(使用国际化)、事件发布和特定的应用层,如在web应用层中使用的WebApplicationContext

总之,BeanFactory提供了配置框架和基本功能,ApplicationContext则添加了更多的企业特定的功能。ApplicationContextBeanFactory的一个完整的超集,并且在本章专门用于指代Spring容器。关于更多使用BeanFactory替代ApplicationContext的信息,参考Section 5.16, “The BeanFactory”

在Spring中,被Spring IoC 容器 管理的这些来自于应用主干的这些对象称作 beans 。bean是一个由Spring IoC容器进行实例化、装配和管理的对象。此外,bean只是你应用中许多对象中的一个。Beans以及他们之间的 依赖关系 是通过容器使用 配置元数据 反应出来。

5.2 容器概述

org.springframework.context.ApplicationContext接口代表了Spring IoC容器,并且负责上面提到的Beans的实例化、配置和装配。容器通过读取配置元数据获取对象如何实例化、配置和装配的指示。配置元数据可以用XML、Java注解或Java代码来描述。它允许你表示组成你应用的对象,以及对象间丰富的依赖关系。

Spring提供了几个开箱即用的ApplicationContext接口的实现。在独立的应用程序中,通常创建 ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例。 虽然XML是定义配置元数据的传统格式,但是你可以指示容器使用Java注解或者代码作为元数据格式,你需要通过提供少量XML配置声明支持这些额外的元数据格式。

在大多数的应用场景,不需要显式的代码来实例化一个或多个Spring IoC容器。例子,在wei应用中,在应用的web.xml文件中,简单的8行样板式的xml配置文件就足够了。如果你使用 Spring Tool Suite 的Eclipse开发环境,你只需要点几下鼠标或者键盘就可以轻松的创建这个配置。

下面的图表是一个Spring工作的高级别视图。你的应用程序类都通过配置元数据进行关联,所以在ApplicationContext创建和初始化后,你就有了一个完全配置和可执行的系统或应用程序。

Figure 5.1. Spring IoC容器

container magic

5.2.1 配置元数据

如上图所示,Spring IoC容器使用了一种 配置元数据 的形式,这些配置元数据代表了你作为一个应用开发者告诉Spring容器如何去实例化、配置和装备你应用中的对象。

配置元数据通常使用一个简单和直观的XML格式,本章大部分都使用这种格式来表达Spring IoC容器概念和特性。

[Note]Note

基于XML配置的元数据 不是 唯一允许用来配置元数据的一种形式。Spring IoC容器本身是 完全 和元数据配置书写的形式解耦的。这些天,许多开发者在他们的Spring应用中选择使用基于Java的配置的元数据形式。

更多有关在Spring容器中使用其他形式的元数据的内容,请查阅:

  • 基于注解的配置: Spring 2.5引入基于注解的配置元数据。
  • 基于Java的配置: 从Spring 3.0开始,由Spring JavaConfig提供的许多功能已经成为Spring框架的一部分。因此,你可以通过Java而不是XML文件来定义外部应用程序的bean类。使用这些新的功能,请看@Configuration@Bean@Import@DependsOn 注解。

Spring配置包括至少一个且通常多个由容器管理的bean定义。在基于XML配置的元数据中,这些beans配置成一个<bean/>元素,这些<bean/>元素定义在顶级元素<beans/>的里面。在Java配置中通常在一个@Configuration注解的类中,在方法上使用@Bean注解。

这些bean定义对应的实际对象组成了你的应用。通常你会定义服务层对象、数据访问层对象(DAO),展现层对象如Struts的Action实例,底层对象如Hibernate的SessionFactories,JMS的Queues等等。一般很少会在容器中配置细粒度的领域对象,因为通常是DAO和业务逻辑负责创建和加载领域对象。但是你可以使用Spring集成AspectJ来配置IoC容器之外创建的对象。查看在Spring中使用AspectJ依赖注入领域对象

下面的例子演示了基于XML的配置元数据的基础结构:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
        <!-- 在这里写 bean 的配置和相关引用 -->
    </bean>

    <bean id="..." class="...">
        <!-- 在这里写 bean 的配置和相关引用 -->
    </bean>

    <!-- 更多bean的定义写在这里 -->

</beans>

id属性是一个用来识别每一个独立bean定义的字符串。class属性定义了bean的类型,这个属性需要使用bean类的全限定名称。id属性的值可以被其他的bean对象引用。这个例子中没有引用其他bean,查看bean依赖获取更多信息。

5.2.2 实例化容器

实例化Spring IoC容器很容易。将一个或多个位置路径提供给ApplicationContext的构造方法就可以让容器加载配制元数据,可以从多种外部资源进行获取,例如文件系统、Java的CLASSPATH等等。

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
[Note]Note

在你了解Spring的IoC容器之后,你可能想知道更多有关Spring的Resource的更多内容,它的介绍在Chapter 6, Resources,它提供了一个方便的机制来读取URI中的InputStream。Resource路径是用来构建应用程序上下文的,详细内容请看Section 6.7, “应用上下文和资源路径”

下面的例子是服务层对象(services.xml)的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- 在这里写额外的bean的配置和相关引用 -->
    </bean>

    <!-- 更多Service层的bean定义写在这里 -->

</beans>

下面的例子是数据访问层daos.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- 在这里写额外的bean的配置和相关引用 -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- 在这里写额外的bean的配置和相关引用 -->
    </bean>

    <!-- 更多数据访问层的bean定义写在这里 -->

</beans>

在上面的例子中,服务层包含了PetStoreServiceImpl类和两个类型为JpaAccountDaoJpaItemDao(基于JPA对象/关系映射标准)的数据访问对象。property name元素指代JavaBean属性的名称,ref元素引用了另一个bean定义的名称。idref直接的这种关系表达出了这两个合作对象间的依赖关系。配置对象直接依赖关系的详细信息,请参见依赖关系

编写基于XML的配置元数据

bean定义可以跨越多个XML文件是非常有用的。通常每个独立的XML配置文件表示一个逻辑层或者是你架构中的一个模块。

你可以使用应用上下文的构造方法从多个XML片段中加载bean的定义。像上面例子中出现过的一样,构造方法可以接收多个Resource位置。或者可以在bean定义中使用一个或多个<import/>从其他的配置文件引入bean定义。例如:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

上面的例子中,外部的bean定义从services.xmlmessageSource.xmlthemeSource.xml这三个文件中加载。所有的位置路径都是相对于定义执行导入的文件,所以 services.xml必须和当前定义导入的文件在相同的路径下。而messageSource.xmlthemeSource.xml必须在当前定义导入的文件路径下的resources路径下。你可以看到,这里忽略了反斜杠,由于这里的路径是相对的,因此建议 不使用反斜杠。这些被引入文件的内容会被导入进来,包含顶层的<beans/>元素,它必须是一个符合Spring架构的有效的XML bean定义。

[Note]Note

使用一个相对"../"路径引用父目录中的配置是允许的,但是不推荐这么做。如果这么做就产生了一个当前应用外的引用依赖。特别不推荐在使用"classpath:"路径的时候,在运行的时候解析选择“最近”的classpath跟路径,然后在找父目录。Classpath配置的更改可能会导致选择一个不同的、错误的目录。

通常情况下,你可以使用完全限定的资源位置来代替相对路径,例如:"file:C:/config/services.xml"或"classpath:/config/services.xml"。但是请注意,你的应用可能和一个特定的绝对路径耦合了。通常更合适的方式是通过间接的方式来使用绝对路径,例如通过"${…}"占位符,在运行时解析JVM的系统属性。

5.2.3 使用容器

ApplicationContext是智能的工厂接口,它能够维护注册不同beans和它们的依赖。通过使用 T getBean(String name, Class<T> requiredType) 方法,你可以取得这些beans的实例。

'ApplicationContext`使您可以读取的bean定义和像下面这样使用:

// 创建并配置beans
ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// 取得配置的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 使用实例
List<String> userList = service.getUsernameList();

使用getBean()来获取您beans的实例,ApplicationContext接口还有几个其他的可以获取beans的方法,但是理想情况下,你最好不要使用这些方法。 事实上,您的应用程序代码不应调用getBean()所有方法,因而不会和Spring的接口产生依赖。 例如,Spring的与web框架的集成提供了对各种web框架类的依赖注入,如控制器和JSF管理的bean。

5.3 Bean概述

一个Spring IoC容器管理了一个或者多个 beans。这些beans通过你提供给容器的配置元数据进行创建,例如通过XML形式的<bean/>定义。

在容器内本身,这些bean定义表示为BeanDefinition对象,它包含了如下的元数据:

  • 包限定的类名: 通常是bean定义的实现类。
  • Bean行为配置元素,这些状态指示bean在容器中的行为(范围,生命周期回调函数,等等)。
  • bean工作需要引用的其他beans;这些引用也称为 协作者依赖者
  • 其他配置设置应用于新创建的对象中设置,例如连接池中的连接数或者连接池的大小限制。

这些元数据转换成组成每个bean定义的一组属性。


除了bean定义外,它还包含有关如何创建特定bean的信息,ApplicationContext实现还允许由用户在容器外创建注册现有的对象。 这是通过访问ApplicationContext的工厂方法,通过getBeanFactory()返回DefaultListableBeanFactory工厂方法的实现。 DefaultListableBeanFactory支持通过registerSingleton(..)方法和registerBeanDefinition(..)方法进行注册。 然而,典型的应用程序的工作仅仅通过元数据定义的bean定义beans。

5.3.1 bean的命名

每个bean都有一个或多个标识符,这些bean的标识符在它所在的容器中必须唯一。 一个bean通常只有一个标识符,但如果它有一个以上的id标识符,多余的标识符将被认为是别名。

基于xml的配置元数据中,你可以使用id 或(和) name 属性来指定bean的标识符。 id属性允许您只指定一个id。通常这些名字是字母数字组成的(myBean,fooService等等),但也可能包含特殊字符。 如果你想给bean添加其他的别名,你可以通过name属性来指定这些别名,可以使用逗号(,),分号(;)或者空格来分割这些别名。 这里需要特别注意的是,在Spring3.1版本以前,id属性被定义成xsd:ID类型(可以通过xml规则限制唯一), 在Spring3.1以及以后的版本中,id被定义成了xsd:string类型,id属性不在通过XML解析器限制为唯一,而是通过容器强制限制为唯一。

bean的id和name不是必须的。如果没有明确的name或者id,容易会给bean生成一个唯一的名字。 但是,如果你想通过名称引用这个bean,通过使用ref元素或服务定位器模式 查找,你就必须提供一个名字。

不提供名称的原因和内部beans自动装配有关。

bean的别名

在对bean定义时,除了使用id属性指定一个唯一的名称外,为了提供多个名称,需要通过name属性加以指定,所有这个名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用每一个组件都能更容易的对公共组件进行引用。然而,在定义bean时就指定所有的别名并不总是很恰当。有时我们期望能够在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可以通过<alias/>元素来完成bean别名的定义,例如:

<alias name="fromName" alias="toName"/>

在这种情况下,如果容易中存在名为fromName的bean定义,在增加别名定义后,也可以用toName来引用。

例如,在子系统A中通过名字subsystemA-dataSource配置的数据源。在子系统B中可能通过名字subsystemB-dataSource来引用。当两个子系统构成主应用的时候,主应用可能通过名字myApp-dataSource引用数据源,将全部三个名字引用同一个对象,你可以将下面的别名定义添加到应用配置中:

<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />

现在每个子系统和主应用都可以通过唯一的名称来引用相同的数据源,并且可以保证他们的定义不与任何其他的定义冲突。

5.3.2 实例化bean

bean定义基本上就是用来创建一个或多个对象的配方(recipe,有更合适的翻译?),当需要一个bean的时候,容器查看配方并且根据bean定义封装的配置元数据创建(或获取)一个实际的对象。

如果你使用基于XML的配置,你可以在<bean/>元素通过class属性来指定对象的类型。这个class属性,实际上是BeanDefinition实例中的一个Class属性。这个class属性通常是必须的(例外情况,查看the section called “使用实例工厂方法实例化”Section 5.7, “Bean定义的继承”),使用Class属性的两种方式:

  • 通常情况下,直接通过反射调用构造方法来创建bean,和在Java代码中使用new有点像。
  • 通过静态工厂方法创建,类中包含静态方法。通过调用静态方法返回对象的类型可能和Class一样,也可能完全不一样。

通过构造函数实例化

当你使用构造方法来创建bean的时候,Spring对class没有特殊的要求。也就是说,正在开发的类不需要实现任何特定的接口或者以特定的方式进行编码。但是,根据你使用那种类型的IoC来指定bean,你可能需要一个默认(无参)的构造方法。

Spring IoC 容器可以管理几乎所有你想让它管理的类,它不限于管理POJO。大多数Spring用户更喜欢使用POJO(一个默认无参的构造方法和setter,getter方法)。但在容器中使用非bean形式(non-bean style)的类也是可以的。比如遗留系统中的连接池,很显然它与JavaBean规范不符,但Spring也能管理它。

当使用基于XML的元数据配置文件,可以这样来指定bean类:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

给构造方法指定参数以及为bean实例化设置属性将在后面的依赖注入中详细说明。

使用静态工厂方法实例化

当采用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。

下面的bean定义展示了如何通过工厂方法来创建bean实例。注意,此定义并未指定返回对象的类型,仅指定该类包含的工厂方法。在此例中,createInstance()必须是一个static方法。

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

给工厂方法指定参数以及为bean实例设置属性的详细内容请查阅依赖和配置详解

使用实例工厂方法实例化

与通过 静态工厂方法 实例化类似,通过调用工厂实例的非静态方法进行实例化。 使用这种方式时,class属性必须为空,而factory-bean属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该工厂bean的工厂方法本身必须通过factory-method属性来设定。

<!-- 工厂bean,包含createInstance()方法 -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- 其他需要注入的依赖项 -->
</bean>

<!-- 通过工厂bean创建的ben -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂类也可以有多个工厂方法,如下代码所示:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- 其他需要注入的依赖项 -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private static AccountService accountService = new AccountServiceImpl();

    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }

}

这种做法表明工厂bean本身也可以通过依赖注入(DI)进行管理配置。查看依赖和配置详解

[Note]Note

在Spring文档中,factory bean是指在Spring容器中配置的工厂类通过 实例静态 工厂方法来创建对象。相比而言, FactoryBean (注意大小写) 代表了Spring中特定的 FactoryBean.

5.4 依赖

典型的企业应用不会单一得由一个对象组成(或者说Spring术语中的bean)。即便是最简单的系统也需要多个对象共同协作来展示给终端用户一个条理分明的应用。接下来的这一节内容会阐述如何定义多个独立于应用程序的bean一起协同工作完成目标。

5.4.1 依赖注入

依赖注入 (DI) 是指对象之间的依赖关系,也就是说,一起协作的其他对象只通过构造器的参数、工厂方法的参数或者由构造函数或者工厂方法创建的对象设置属性。因此容器的工作就是创建bean并注入那些依赖关系。这个过程实质通过直接使用类的构造函数或者服务定位模式来反转控制bean的实例或者其依赖关系的位置,因此它有另外一个名字叫控制反转 (IoC)。

运用了DI原理代码会更加清晰并且由依赖关系提供对象也将使各层次的松耦合变得更加容易。对象不需要知道其依赖关系,也不需要知道它的位置或者类之间的依赖。因此,你的类会更容易测试,尤其是当依赖关系是在接口或者抽象基本类,

DI主要有两种注入方式,即构造器注入Setter注入

构造器注入

基于构造器注入 DI通过调用带参数的构造器来实现,每个参数代表着一个依赖关系。此外,还可通过给 静态 工厂方法传参数来构造bean。接下来的介绍将认为给构造器传参数和给静态工厂方法传参数是类似的。下面展示了只能使用构造器来注入依赖关系的例子。请注意这个类并没有什么 特别 之处,它只是个普通的POJO,不依赖于特殊的接口,抽象类或者注解。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}
构造器参数解析

构造器参数通过参数类型进行匹配。如果构造器参数的类型定义没有潜在的歧义,那么bean被实例化的时候,bean定义中构造器参数的定义顺序就是这些参数的顺序并依次进行匹配。看下面的代码:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }

}

上述代码中参数类型定义不存在潜在的歧义,我们假设BarBaz之间不存在继承关系。因此,下面代码中在元素<constructor-arg/>的配置即使没有明确指定构造参数顺序或者类型也会起作用。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

当另一个bean被引用,它的类型是已知的,并且匹配也没问题(跟前面的例子一样)。当我们使用简单类型,比如<value>true</value>。Spring并不能知道该值的类型,不借助其他帮助Spring将不能通过类型进行匹配。看下面的类:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

针对上面的场景可以使用type属性来显式指定那些简单类型那个的构造参数类型,比如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

使用index属性来显式指定构造参数的索引,比如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

使用索引可以解决多个简单值的混淆,还能解决构造方法有两个相同类型的参数的混淆问题,注意index是从0开始的

你也可以使用构造器参数命名来指定值的类型:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住为了使这个起作用,你的代码编译时要打开编译模式,这样Spring可以检查构造方法的参数。如果你不打开调试模式(或者不想打开),也可以使用 @ConstructorProperties JDK注解明确指出构造函数的参数。下面是简单的例子:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

Setter注入

在调用了无参的构造方法或者无参的静态工厂方法实例化bean之后,容器通过回调bean的setter方法来完成setter注入。接下来的例子将展示只使用setter注入依赖。这个类是个普通的Java类

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}

ApplicationContext所管理的beans支持构造函数注入和setter注入,在一些依赖已经使用构造器注入之后它还支持setter注入。你可以以BeanDefinition的形式配置依赖,它能根据指定的PropertyEditor实现将属性从一种格式转化为另外一种格式。但是,大多数Spring的使用者不会直接使用这些类(也就是通过编程的形式),而是采用XML配置这些bean,注解的组件(即用@Component@Controller等注解类),或者基于@Configuration类的@Bean方法。本质上这些资源会转换成BeanDefinition的实例并且用于加载整个Spring IoC容器实例。

依赖解决步骤

容器解决依赖问题通常有以下几个步骤:

  • 描述所有bean的ApplicationContext创建并根据配置的元数据初始化。配置的元数据可以通过置顶的XML,Java代码或者注解。
  • 每个bean的依赖将以属性,构造器参数或者静态工厂方法的形式出现。当这些bean被创建时,这些依赖将会提供给该bean。
  • 每个属性或者构造器参数既可以是一个实际的值也可以是容器中的另一个引用。
  • 每个指定的属性或者构造器参数值必须能够被转换成特定的格式或构造参数所需的类型。默认情况下Spring会以String类型转换为各种内置类型,比如int,long, String, boolean 等。

当Spring容器创建时容器会校验每个bean的配置。但是在bean被实际创建 前,bean的值并不会被设置。那些单例类型的bean和被设置为预安装(默认)的bean会在容器创建时与容器同时创建。Scopes是在Section 5.5, “Bean作用域”中定义的。同时,另外的bean只会在被需要时创建。伴随着bean被实际创建,作为该bean的依赖和它的依赖的依赖(以此类推)会被创建和分配。注意 这些依赖之间的解决会显示迟一些

通常你可以信赖Spring。在容器加载时Spring会检查配置,比如不存在的bean和循环依赖。当bean创建时,Spring尽可能迟得设置属性和依赖关系。这意味着即使Spring正常加载,在你需要一个存在问题或者它的依赖存在问题的对象时,Spring会报出异常。举个例子,bean因设置缺少或者无效的属性会抛出一个异常。因为一些配置问题存在将会导致潜在的可见性被延迟,所以默认ApplicationContext的实现bean采用提前实例化的单例模式。在实际需要之前创建这些bean会带来时间和内存的开销,当ApplicationContext创建完成时你会发现配置问题,而不是之后。你也可以重写默认的行为使得单例bean延迟实例化而不是提前实例化。

如果不存在循环依赖,当一个或者多个协助bean会被注入依赖bean时,每个协助bean必须在注入依赖bean之前 完全 配置好。这意味着如果bean A对bean B存在依赖关系, 那么Spring Ioc容器在调用bean A的setter方法之前会完全配置bean B。换句话说,bean会被实例化(如果不是采用提前实例化的单例模式),相关的依赖会被设置好,相关的lifecycle方法(比如configured init 方法或者InitializingBean callback 方法)会被调用

一些依赖注入的例子

接下来的Setter注入例子使用基于XML的配置元数据的方式。相应的Spring XML配置文件:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }

}

在前面的例子,我们看到Setter会匹配定义在XML里的属性,接下来的例子会使用构造器注入:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }

}

在bean定义中指定的构造器参数会被用作ExampleBean的构造器参数。

现在来看使用构造器的例子,Spring调用静态工厂方法来返回对象的实例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }

}

静态工厂方法参数由<constructor-arg/>元素提供,实际上这和使用构造器是一样的。工厂方法 返回的类的类型并不一定要与包含静态工厂方法的类类型一致,虽然在这个例子中是一样的。 实例工厂方法(不是静态的)与此相同(除了使用factory-bean属性代替class属性外),所以这里不作详细讨论。

5.4.2 依赖配置详解

正如在前面章节所提到的,你可以定义bean的属性和构造器参数作为其他所管理的bean的依赖(协作), 或者是内联的bean。基于XML的Spring配置元数据支持使用<property/><constructor-arg/>元素定义。

直接变量 (基本类型, String类型等)

<property/>元素的value值通过可读的字符串形式来指定属性和构造器参数。Spring的conversion serviceString转换成属性或者构造器实际需要的类型。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <!-- results in a setDriverClassName(String) call -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    <property name="username" value="root"/>
    <property name="password" value="masterkaoli"/>
</bean>

接下来的例子使用p 命名空间简化XML配置

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

以上的XML更加简洁;但是,编码的错误只有在运行时才会被发现而不是编码设计的时候,除非你在定义bean的时候, 使用 IntelliJIDEA 或者 Spring Tool Suite (STS) 支持动态属性补全的IDE。IDE的帮助是非常值得推荐的。

你也可以配置java.util.Properties实例,就像这样:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring容器使用JavaBeans PropertyEditor把元素<value/>内的文本转换为java.util.Properties 实例。由于这种做法非常简单,所以这是Spring团队在很多地方采用内嵌的<value/>元素代替value属性。

idref元素

idref元素用来将容器内其他bean的id(值是字符串-不是引用)传给元素<constructor-arg/> 或者 <property/>

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean" />
    </property>
</bean>

上面的bean定义片段完全等同于(在运行时)下面片段:

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean" />
</bean>

第一种形式比第二种形式更好,因为使用idref标签允许容器在部署时验证引用的bean是否存在。 在第二种形式中,传给 client bean中属性targetName的值并没有被验证。 只有当 client bean完全实例化的时候错误才会被发现(可能伴随着致命的结果)。如果 client bean是原型 bean。那么这个错误和异常可能只有再容器部署很长一段时间后才能被发现。

[Note]Note

idref元素上的local属性在4.0之后不再支持,因为它不再提供普通bean的价值。当你升级到4.0 schema时需要修改存在的idref localidref bean

ProxyFactoryBean bean定义中使用<idref/>元素指定AOP 拦截器配置 (版本不低于Spring 2.0)相同之处在于:当你指定拦截器名称的时候使用<idref/>元素可以防止你拼错拦截器的id。

引用其他bean(协作者)

<constructor-arg/> 或者 <property/>可以使用ref 元素。该元素用来将bean中指定属性的值设置为对容器的另外一个bean(协作者)的引用。 该引用bean将被作为依赖注入,而且再注入之前会被初始化(如果协作者是单例)。所有的引用最终都是另一个对象的引用。 bean的范围和验证依赖于你指定的beanlocal,或者 parent属性的id/name。

通过<ref/>标签的bean属性定义目标bean是最常见的形式,通过该标签可以引用同一容器或者父容器任何bean,无论是否在 相同的xml文件中。xml的bean元素的值既可以目标bean的id属性也可以是其中一个目标bean的name 属性值。

<ref bean="someBean"/>

通过parent属性指定目标bean来创建bean的引用,该bean是当前容器下的父级容器。parent属性值既可以是目标bean的id也可以是 name属性值。而且目标bean必须在当前容器的父级容器中。使用parent属性的主要用途是为了用某个与父级容器中的bean同名的代理来包装父级容器中的一个bean。

<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>
[Note]Note

ref元素上的local属性4.0 beans xsd之后不在支持,因为它能提供的值不比普通bean多。当你升级到4.0schema时需要修改存在的ref local 为ref bean`。

内部bean

A <bean/> element inside the <property/> or <constructor-arg/> elements defines a so-called inner bean. 所谓的内部bean就是指在 <property/> 或者 <constructor-arg/> 元素内部使用<bean/>定义bean。

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

内部bean的定义不需要id或者name属性。容器会忽略这些属性值。同时容器也会忽略scope标志位。内部bean 总是 匿名的 并且他们总是伴随着外部bean创建。同时将内部bean注入到包含该内部bean之外的bean是不可能的。

集合

<list/>, <set/>, <map/>, 和<props/>元素中,你可以设置值和参数分别对应Java的集合类型List, Set, Map, 和 Properties

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

map的key或者value值,或者set的value值还可以是以下任意元素:

bean | ref | idref | list | set | map | props | value | null
集合合并

Spring容器也支持集合的 合并。开发者可以定义parent-style`<list/>, `<map/>, <set/> 或者<props/> 元素并, child-style 的<list/>, <map/>, <set/> 或者 <props/>元素继承和覆盖自父集合。也就是说。父集合元素合并后的值就是子集合的最终结果,而且子集中的元素值将覆盖父集中对应的值。

关于合并的章节涉及到了parent-child bean机制。不熟悉父子bean的读者可参见relevant section.

接下来的例子展示了集合的合并:

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

注意child`bean的定义中<props/>元素上的`merge=true属性的用法。当child`bean被解析并且被容器初始化,产生的实例包含了`adminEmailsProperties集合,其adminEmails将与父集合的adminEmails属性进行合并。

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

子bean的Properties集合将从父<props/>集成所有属性元素。同时子bean的support值将覆盖父集合的相应值。

<list/>, <map/>, 和 <set/>集合类型的合并处理都基本类似。<list/>元素某个方面有点特殊,这和List集合类型的语义学有关 换句话说,比如维护一个有序集合的值,父bean的列表内容将排在子bean李彪内容的前面。对于Map, Set, 和 Properties集合类型没有顺序的概念, 因此作为相关的Map, Set, 和 Properties实现基础的集合类型在容器内部排序的语义。

集合合并的限制

你不能合并两种不能类型的集合(比如MapList),如果你这么做了将会抛出相应的异常merge属性必须在继承 的子bean中定义。定义在父bean的集合属性上指定的merge属性是多余的并且得不到期望的合并结果。

强类型集合

Java 5 引入了泛型,这样你可以使用强类型集合。换句话说绳命一个只能包含String类型元素的Collection是可能的(比如)。 如果使用Spring来给bean注入强类型的Collection,你可以利用Spring的类型转换,在向强类型Collection添加元素前,这些元素将被转换。

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

foo bean的accounts 属性准备注入时,通过反射获得强类型Map<String, Float>元素类型的泛型信息。Spring的底层类型转换 机制会把各种value元素值转换为Float,因此字符串9.99, 2.753.99将会转换为实际的Float 类型。

Null和空字符串

Spring会把空属性当做空字符串处理。以下的基于XML配置的片段将email属性设置为空字符串。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

先前的例子等同于以下Java代码:

exampleBean.setEmail("")

<null/>元素处理null值,例如:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面的配置等同于下面的Java代码:

exampleBean.setEmail(null)

XML使用p命名空间简化

使用p命名空间可以用bean 元素的属性代替<property/>` 元素来描述属性值或者协作bean。

Spring支持名称空间的可扩展配置with namespaces,这些名称空间基于一种XML Schema定义。这章节涉及到的beans 配置都是定义在一个XML Schema文档理。但是p命名空间不是定义在XSD文件而是存在于Spring内核中。

下面的例子展示了两种XML片段,其结果是一样的:第一个使用了标准XML格式,第二种使用了p命名空间。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="foo@bar.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>

在例子中,使用p命名空间的bean定义有了一个叫email的属性。这告诉Spring要包含这个属性的声明。正如前面所说的, p命名空间不需要schema定义,因此你可以设置属性的名字作为bean的property的名字。

接下来的例子包括了两种以上bean的定义,都引用了另外一个bean。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">
        <property name="name" value="John Doe"/>
        <property name="spouse" ref="jane"/>
    </bean>

    <bean name="john-modern"
        class="com.example.Person"
        p:name="John Doe"
        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">
        <property name="name" value="Jane Doe"/>
    </bean>
</beans>

正如你看到的,例子不仅使用p命名空间包含了一个属性值,而且使用了一个特殊的格式声明了一个属性的引用。在第一个bean 定义中使用了<property name="spouse" ref="jane"/>创建一个john bean 对jane bean的引用,第二个bean的定义使用了p:spouse-ref="jane",它们做了同样一件事情。在这个例子中spouse是属性名,而-ref部分声明了这不是一个直接的值而是另一个bean的引用。

[Note]Note

p命名空间没有标准XML格式那么灵活。举个例子,声明属性的引用是以Ref结尾的,采用p命名空间将会产生冲突,但是采用标准XML 格式则不会。我们建议你小心的选择并和团队成员交流你的想法,避免在XML文档中同时使用所有的三种方法。

XML shortcut with the c-namespace

和p命名空间the section called “XML使用p命名空间简化”类似,Spring3.1 引入了c命名空间,使用内联的构造参数代替嵌套的constructor-arg元素

让我们回顾一下the section called “构造器注入”使用c命名空间的例子:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

和p命名空间约定的一样(bean的引用以-ref结尾),c命名空间使用它们的名称作为构造器参数。同时它需要声明即使它没有 XSD schema中定义(但是它存在于Spring内核中)

极少数的情况下构造器参数的名称不可用(通常字节码没有经过调试信息编译),可以使用备份进行参数索引。

<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
[Note]Note

因为XML的语法,索引标记需要_主导作为XML属性名称,这不能已数字开头(即使某些IDE这么允许)

在实践中,构造器解析机智机制在匹配参数上相当高效。除非你需要这么做,我们建议您在配置中使用符号名称。

组合属性名称

当你设置bean属性时可以使用组合或者嵌套属性名称,只要路径上所有组件除了最终属性不为空。看以下bean的定义:

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

foo bean有一个fred属性,fred属性又有个bob属性,bob属性又有个属性,最后把 sammy属性设置值为123。为了是这个起作用,foofred属性和 fredbob属性在bean被构造后都不能为空,否则会抛出NullPointerException异常。

5.4.3 使用 depends-on

如果一个bean是另外一个bean的依赖,这通常意味着这个bean可以设置成为另外一个bean的属性。在XML配置文件中你可以使用<<beans-ref-element, <ref/>`element>> 实现依赖。但是某些时候bean之间的依赖并不是那么直接。举个例子:类的静态块初始化,比如数据库驱动的注册。`depends-on属性 可以同于当前bean初始化之前显式地强制一个或多个bean被初始化。下面的例子中使用了depends-on属性来指定一个bean的依赖。

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

为了实现多个bean的依赖,你可以在depends-on中将指定的多个bean名字用分隔符进行分隔,分隔符可以是逗号,空格以及分号等。

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
[Note]Note

depends-on属性在bean的定义中可以指定初始化时的依赖和指定相应的销毁时的依赖,该依赖只针对于singleton bean 这样depends-on可以控制销毁顺序。

5.4.4 延迟初始化bean

ApplicationContext实现的默认行为就是再启动时将所有singleton bean提前进行实例化。 通常这样的提前实例化方式是好事,因为配置中或者运行环境的错误就会被立刻发现,否则可能要花几个小时甚至几天。如果你不想 这样,你可以将单例bean定义为延迟加载防止它提前实例化。延迟初始化bean会告诉Ioc容器在第一次需要的时候才实例化而不是在容器启动时就实例化。

在XML配置文件中,延迟初始化通过<bean/>元素的lazy-init属性进行控制,比如:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

ApplicationContext实现上面的配置时,设置为lazy的bean将不会在ApplicationContext启动时提前实例化,而`not.lazy`bean 却会被提前实例化。

但是当一个延迟加载的bean是单例bean的依赖,但这个单例bean又不是 延迟加载,ApplicationContext在启动时创建了延迟加载 的bean,因为它必须满足单例bean的依赖。因此延迟加载的bean会被注入单例bean,然而在其他地方它不会延迟加载。

你也可以使用<beans/>元素上的default-lazy-init属性在容器层次上控制延迟加载。比如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

5.4.5 自动装配协作者

Spring容器可以自动装配相互协作bean的关联关系。因此,如果可能的话,可以自动让Spring检测ApplicationContext的内容自动 处理协作者(其他bean)。自动装配有以下好处:

  • 自动装配可以显著得减少指定属性或者构造器参数的需求。(其他的机制比如bean模板discussed elsewhere in this chapter在这方面也是由价值的)
  • 当对象发生变化时自动装配可以更新配置。比如如果你需要给一个类添加依赖,那么这个依赖可以被自动满足而不需要你去修改配置。 因此自动依赖在开发时尤其有用,当系统趋于稳定时改为显式装配。

当使用XML配置脚注:[See Section 5.4.1, “依赖注入”],可以使用<bean/>元素的autowire属性 为定义的bean指定自动装配模式。你可以指定自动装配per bean,选择那种方式来自动装配。

Table 5.2. 自动装配模式

Mode

Explanation 模式解释

no

(默认)不自动装配。Bean的引用必须用ref元素定义。对于较大的部署不建议改变默认设置,因为明确指定协作者能更好控制和维护系统。 在某种程度上,它记录了系统的结构。

byName

通过属性名称自动装配。Spring会寻找相同名称的bean并将其与属性自动装配。譬如,如果bean的定义设置了根据名称自动装配, 并且包含了一个master 属性(换句话说,它有setMaster(..)方法),Spring会寻找名为master的bean的定义,并用它来装配属性

byType

如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,将会抛出异常,并指出 不能使用byType自动装配这个bean。如果没有找到相同类型的,什么也不会发生。属性不会被设置。

constructor

byType类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,就会抛出异常。


byType 或者 constructor 自动装配模式也可以应用于数组和指定类型的集合。在这种情况下容器中的所有匹配的自动装配对象将 被应用于满足各种依赖。对于key值类型为String的强类型Map也可以自动装配。一个自动装配的Map value值将由所匹配类型的bean所填充。

你可以结合自动装配和依赖检查,后者将会在自动装配完成之后进行。

自动装配的局限性和缺点

在工程里一致使用自动装配,这将会工作得很好。如果自动装配并不常使用,只使用在一个或两个bean的定义上, 它可能会对开发者产生困扰。

考虑一下自动装配的局限性和缺点:

  • propertyconstructor-arg 显式的依赖设置总是会覆盖自动装配。你不能装配所谓的简单属性比如原始的Strings, 和 Classes (这样的简单属性数组也是)。这种缺陷是故意设计的。
  • 自动装配没有显示编写精确。虽然在上面的表格提到的,Spring很小心得避免猜测模糊的情况,这可能会导致意想不到的结果。 Spring管理的对象之间的关系不再记录明确。
  • 自动装配的依赖信息可能不能用于根据Spring容器生成文档的的工具。
  • 在容器内部可能存在多个bean的定义与自动装配的setter方法或者构造器参数匹配。对于数组,集合或者Map来说,这不是问题。 但是对于单值依赖来说,就会存在模棱两可的问题。如果bean定义不唯一,装配时就会抛出异常。

针对于上述场景,你会有多个选项:

  • 放弃自动装配以便于明确依赖关系。
  • 在bean定义中通过设置autowire-candidate属性为false避免该bean自动装配,这将会在下一节中详细描述。
  • 在bean定义中设置<bean/>元素上的`primary属性为true,将该bean设置为首选自动装配bean。
  • 使用注解配置实现更加细粒度的控制,详情见Section 5.9, “Annotation-based container configuration”

将bean排除在自动装配之外

在提前实例化bean的基础上,你可以将bean排除在自动装配之外。在Spring XML格式中,将<bean/> 元素中的autowire-candidate属性 设置为false。容器会使特定的bean定义不可于自动装配(包括注解配置比如@Autowired

你也可以对使用bean名字进行模式匹配来对自动装配进行限制。顶层的<beans/> 元素在它的default-autowire-candidates属性 接受一个或多个模式。譬如,为了限制

对于那些从来就不会被其他bean采用自动装配的方式注入的bean而言,这是有用的。不过这并不意味这被排除的bean自己就 不能使用自动装配来注入其他bean。更确切得说,该bean本身不会被考虑作为其他bean自动装配的候选者。

5.4.6 方法注入

在大部分的应用场景中,容器中的大部分bean是singletons类型的。当一个单例bean需要和另外一个单例bean, 协作时,或者一个费单例bean要引用另外一个非单例bean时,通常情况下将一个bean定义为另外一个bean的属性值就行了。不过对于具有不同生命周期的bean 来说这样做就会有问题了,比如在调用一个单例类型bean A的某个方法,需要引用另一个非单例(prototype)类型bean B,对于bean A来说,容器只会创建一次,这样就没法 在需要的时候每次让容器为bean A提供一个新的bean B实例。

上面问题的一个解决方法是放弃控制反转,你可以实现ApplicationContextAware接口来让bean A感知到容器, 并且在需要的时候通过使用使用getBean("B")向容器请求一个(新的)bean B实例。下面的例子使用了这个方法:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

上面的例子并没有达到期望的效果,因为业务代码和Spring框架产生的耦合。方法注入,作为Spring Ioc容器的高级特性,可以以一种 干净的方法来处理这种情况。

Lookup 方法注入

Lookup方法具有使容器覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。在上述场景中,Lookup方法注入适用于原型bean。 Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码的功能,通过动态创建Lookup方法bean的子类从而达到复写Lookup方法的目的。

[Note]Note

为了使动态子类起作用,Spring容器要子类化的类不能是final,并且需要复写的方法也不能是final。同样的,要测试一个包含 抽象方法的类也稍微有些不同,你需要子集编写它的子类提供该抽象方法的实现。最后,作为方法注入目标的bean不能是序列化的。 在Spring 3.2之后再也没必要添加CGLIB到classpath,因为CGLIB的类打包在了org.springframework下并且在Spring核心JAR中有所描述。 这样做既方便,又避免了与其他使用了不同版本CGLIB的项目的冲突。

再看一下在之前代码片段中的CommandManager类,你可以发现Spring容器会自动复写createCommand()方法的实现。CommandManager类 将不会有任何的Spring依赖,下面返工的例子可以看出:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

在包含被注入方法的客户类中(这个例子中是CommandManager),此方法的定义需要按以下形式进行:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是抽象,动态生成的子类会实现该方法。沟则,动态生成的子类会覆盖类里的具体方法。譬如:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

标识为commandManager的bean在需要一个新的command bean实例时会调用createCommand()方法。你必须将`command`bean部署为 原型(prototype)类型,如果这是实际需要的话。如果部署为singleton。那么每次将返回相同的 `command`bean。

[Tip]Tip

感兴趣的读者也许发现了ServiceLocatorFactoryBean(在包org.springframework.beans.factory.config下)可以使用。ServiceLocatorFactoryBean的用法 与另一个实用类ObjectFactoryCreatingFactoryBean类似,但是它允许你指定子集的lookup接口,不一定非要用Spring的lookup接口。要详细了解这种方法参考 这些类的javadoc。

自定义方法的替代方案

比起Lookup方法注入来,还有一种较少用到的方法注入形式,该注入能使用bean的另一个方法实现去替换自定义方法的方法。 除非你真的需要该功能,否则可以略过本节。

使用基于XML配置文件时,你可以使用replaced-method元素来达到用另一个方法来取代已有方法的目的。考虑下面的类,我们将覆盖 computeValue方法。

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...

}

实现org.springframework.beans.factory.support.MethodReplacer接口的类提供了新的方法定义。

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

下面的bean定义中指定了要配置的原始类和将要复写的方法:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

你可以在<replaced-method/>元素中可以包含多个<arg-type/>元素,这些元素用来标明被复写的方法签名。只有被复写的方法 存在重载的情况和同名的多个方法变体。为了方便,参数的类型字符可以采用全限定类名的简写。例如,下面的字符串都标识参数类型 为java.lang.String

java.lang.String
String
Str

因为参数的数目通常足够用来区别每个可能的选择,这个结晶能减少很多键盘输入的工作,它允许你只输入最短的匹配参数类型的字符串。

5.5 Bean作用域

当你创建一个 bean 的定义,实际上是创建了一个产生真实实例的配方(recipe)。bean 定义是一个配方(recipe)这种概念是很重要的,它的意思是指,和class一样,你可以从一个配方(recipe)创建多个对象实例。

You can control not only the various dependencies and configuration values that are to be plugged into an object that is created from a particular bean definition, but also the scope of the objects created from a particular bean definition. This approach is powerful and flexible in that you can choose the scope of the objects you create through configuration instead of having to bake in the scope of an object at the Java class level. Beans can be defined to be deployed in one of a number of scopes: out of the box, the Spring Framework supports five scopes, three of which are available only if you use a web-aware ApplicationContext.

你不仅可以控制被插入到一个从特定 bean 定义创建的对象的各种依赖和配置值,还可以控制从这个特定 bean 定义创建的对象的作用域(scope)。这种方法是强大而灵活的,你可以选择(choose)通过配置创建的对象作用域而不必在 Java class level 的作用域(scope)预热。Bean 可以被多种作用域之一定义:Spring 框架支持五种开箱即用的作用域,其中三种仅在基于 web 的 ApplicationContext 中使用。

下面的作用域是开箱即用的。同时你也可以创建 自定义作用域.

Table 5.3. Bean作用域

作用域描述

singleton

(默认的) 每个 String IoC 容器作用域中一个 bean 定义只对应一个对象实例。

prototype

一个 bean 定义对应多个对象实例。

request

一个 bean 定义作用于 HTTP request 生命周期;是指每个 HTTP request 拥有自己的通过一个 bean 定义创建的实例。仅在基于 web 的 Spring ApplicationContext 中有效。

session

一个 bean 定义作用于 HTTP session 生命周期。仅在基于 web 的 Spring ApplicationContext 中有效。

global session

一个 bean 定义作用于全局的 HTTP session 生命周期。仅在 portlet context 中使用才有效。仅在基于 web 的 Spring ApplicationContext 中有效。

application

一个 bean 定义作用于整个 ServletContext 生命周期。仅在基于 web 的 Spring ApplicationContext 中有效。


[Note]Note

从 Spring 3.0 开始,新增了一个thread scope,但是默认是不被注册的。要获取更多相关信息,请看文档 SimpleThreadScope。 要了解关于如何注册这个作用域或者其它自定义的作用域,请看 自定义 bean 作用域.

5.5.1 单例作用域

仅管理一个单例 bean 的共享实例,并且所有通过 id 或者 ids 获得 bean 定义的请求,都会从 Spring 容器中得到同一个特定的 bean 实例。

换句话说,当你定义一个 bean 定义,并且它的作用域为单例的,Spring IoC 容器就会精确地(exactly)创建一个(one)对象实例。这个实例被存储在一个包含很多单例 bean 的缓存中。并且所有的后来的请求和引用(all subsequent requests and references)都会返回缓存的对象。

Figure 5.2. 

singleton

Spring 中单例 bean 的概念与四人帮(GoF)设计模式一书中的定义是不同的。GoF 中的单例是硬编码方式的对象的作用域以至于每个特定的类在每个类加载器(per ClassLoader)中有且仅有一个(and only one)实例。Spring 单例作用域更好的描述了每个容器和每个 bean(per container and per bean)。它的意思是说,如果你在一个 Spring 容器中定义了一个特定类的 bean,然后这个 Spring 容器就会创建一个且仅仅一个(and only one)通过指定 bean 定义的实例。单例作用域在 Spring 中是默认的作用域(The singleton scope is the default scope in Spring)_。在 XML 中定义一个单例的 bean,你需要写如下的配置,例如:

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- 通过显示的冗余配置是等价的 (单例作用域是默认的) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

5.5.2 原型作用域

bean使用原型作用域而不是单例作用域的话,会在每次请求该bean,也就是bean被注入至另一个bean、 或通过调用Spring容器的 getBean() 方法时,创建一个新的bean实例 。 通常,对于有状态的bean使用原型作用域,无状态的bean则使用单例作用域。

下图展示了Spring的原型作用域。一般来说,数据访问对象(data access object, DAO )不会保存任何会话状态, 因此是无状态的,不该被配置为原型作用域。作者之所以在下图使用DAO,只是为了能重用上面单例的那张图,节约工作量。

Figure 5.3. 

prototype

这段样例代码展示了如何在xml配置文件中定义一个原型作用域的bean:

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

与其他作用域不同的是,Spring容器不会管理原型域bean的完整生命周期:Spring容器会初始化、 配置,亦或者组装原型域的bean对象,然后交给客户端,之后就再也不会管这个bean对象了。 因此,对于bean的生命周期方法来说,尽管所有作用域的 初始化方法 都会被调用, 但是原型域bean的 销毁方法 不会 被Spring容器调用。客户端代码要自己负责销毁原型域bean 以及和bean相关的资源(特别是开销大的资源)。如果想让Spring负责这些事(销毁bean、释放资源), 就得自定义bean的后处理器 bean post-processor ,它会持用原型域bean的引用。

从某种意义上说,对于原型域bean,Spring容器代替了Java 的 new 操作符。所有在 new 之后的生命周期管理任务, 都要由客户端自行处理。(想了解Spring容器具体如何管理bean的生命周期,请参考 Section 5.6.1, “生命周期回调”. )

5.5.3 依赖原型bean的单例bean

如果你的单例bean依赖了原型bean,谨记这些依赖(的原型bean) 只在初始化时解析 。 因此,假如你将原型bean依赖注入至单例bean,在注入时会初始化一个新的原型bean实例, 这个被注入的原型bean实例是一个独立的实例。

不过,假如你希望单例bean在运行时能够反复获得一个新的原型bean实例,你就不能用依赖注入的方式, 因为这个注入只发生一次 ,即当Spring容器初始化单例bean并解析依赖时。 如果你需要在运行时多次获得新的原型bean实例,请参阅 Section 5.4.6, “方法注入”

5.5.4 请求作用域、会话作用域和全局会话作用域

仅当 你使用web相关的Spring ApplicationContext(例如 XmlWebApplicationContext)时, 请求 request 、会话 session 和全局会话 global session 作用域才会起作用。如果你在普通的Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用这几个作用域,会抛出异常 IllegalStateException ,告知你这是一个未知的bean作用域

The request, session, and global session scopes are only available if you use a web-aware Spring ApplicationContext implementation (such as XmlWebApplicationContext). If you use these scopes with regular Spring IoC containers such as the ClassPathXmlApplicationContext, you get an IllegalStateException complaining about an unknown bean scope.

基本的web配置

为了使用请求 request 、会话 session 和全局会话 global session 等作用域(web作用域), 在定义bean之前要做一些最基本的初始化配置。(如果使用单例和原型这类标准作用域,是 不需要 这些初始化配置的)。

具体如何组装这些初始化配置与你使用的特定的Servlet环境有关..

事实上,如果你使用Spring Web MVC ,在 DispatcherServletDispatcherPortlet 处理的请求内去访问这些作用域的bean, 那就不用做任何专门的配置:DispatcherServletDispatcherPortlet 已经帮你做了这些事情。

如果你使用的是 Servlet2.5 标准的web容器,而且请求不是由 Spring的 DispatcherServlet 处理 (例如使用了Struts或JSF),你就得注册 org.springframework.web.context.request.RequestContextListener 这一 ServletRequestListener 。 对于Servlet 3.0 以上的环境,可以通过 WebApplicationInitializer 接口以代码的方式去实现。 或者,对于比较老旧的web 容器,在 web.xml 配置文件中加入以下的声明:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

又或者,如果你在配置监听器时有问题,也可以使用 RequestContextFilter 。过滤器的映射与具体的web应用有关,所以你得自行修改映射的内容。

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServlet, RequestContextListenerRequestContextFilter 做的事情是一样的,都是把每个http请求对象绑定到处理线程 Thread 上, 那么,请求、会话和全局会话作用域bean就可以沿着调用链继续往下走了。

请求作用域

考虑如下的bean定义:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

对于每个http请求,Spring容器会创建一个 LoginAction bean 的新实例。也就是说,loginAction bean 的作用域限于 HTTP 请求范围。 你可以在请求内随意修改这个bean实例的状态,因为其他 loginAction bean实例看不到这些变化,bean实例是与特定的请求相关的。 当请求处理完毕,对应的bean实例也就销毁(被回收)了。

会话作用域

考虑如下的bean定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

在每个HTTP Session的生命周期内,Spring容器会根据id为 userPreferences 的bean定义创建一个 UserPreferences bean 的新实例。 也就是说,userPreferences bean 的作用域限于 HTTP Session范围。和请求作用域 request-scoped bean 类似, 因为每个会话域 session-scoped bean的范围限于特定的 HTTP Session 内部,所以一个 Session 内的 userPreferences bean也是可以被随意修改, 而不会影响到其他 Session 中的 userPreferences bean。当一个HTTP Session 最终用完被JVM回收时,相关的会话域 session-scoped bean也被一起回收。

全局会话作用域

考虑如下的bean定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

全局会话作用域 global session上文 提到的标准HTTP Session 作用域类似,但是仅适用于基于门户(portlet)的web应用程序上下文。 Portlet规范定义了如何在组成一个门户的多个portlet组件之间共享全局 Session 。 因此,全局会话作用域 global session bean的生命周期也就与全局门户 Session 紧密关联。

假如你开发了一个标准的、基于Servlet的web应用,并且定义了一个或多个全局会话作用域 global session bean, Spring容器会使用 HTTP Session 作用域(来代替),不会发生错误。

应用程序作用域

考虑如下的bean定义:

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

Spring容器依据 appPreferences 的bean 定义,在整个web应用启动时创建了一个 AppPreferences bean 的实例。 也就是说, appPreferences bean 的作用域限于 ServletContext 范围,作为一个普通的 ServletContext 属性进行存储。 这跟Spring的单例作用域有些相似,但有两个重大差异:1. 这是某个 ServletContext 的单例, 不是某个Spring ApplicationContext 的单例(任何给定的web应用都可能有多个 ApplicationContext )。 2. 这是作为一个 ServletContext 属性被暴露和访问的。

将上述作用域的bean作为依赖

Spring IoC 容器不仅负责管理对象(beans)的创建,也负责有合作(依赖)关系对象之间的组装。 如果你想将一个HTTP 请求作用域的bean注入到另一个 bean,必须注入一个 AOP 代理来取代请求作用域的bean本身。 也就是说,你得有一个和作用域bean实现了相同接口、能在相应作用域(例如,HTTP 请求) 访问真正的请求域bean目标对象的代理对象,而且这个代理对象要能把方法调用委派给真正的请求域bean, 然后把代理对象注入到请求域bean该注入的地方。

[Note]Note

对于作用域为 singletons (单例)或 prototypes(原型)的bean , 不需要 使用 <aop:scoped‐proxy/> 元素

下述代码虽然只有一行,但读者不仅要“知其然”,更要“知其所以然”。

<?xml version="1.0" encoding="UTF-8"?>
<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">


    <!-- 将一个HTTP Session bean 暴露为一个代理bean -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">

        <!-- 通知Spring容器去代理这个bean -->
        <aop:scoped-proxy/>
    </bean>


    <!-- 将上述bean 的代理注入到一个单例bean -->
    <bean id="userService" class="com.foo.SimpleUserService">

        <!-- 引用被代理的 userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

为了创建上文提到的代理对象,要在 request 请求、 session 会话、 globalSession 全局会话 和自定义作用域的bean声明中加入 <aop:scoped‐proxy/> 子元素。 (参考 the section called “选择要创建的代理类型”Chapter 33, XML Schema-based configuration) 。为什么要这么做呢?让我们将这几个作用域的bean定义与单例作用域的bean定义做个对比。 (下列代码的 userPreferences bean定义实际是 不完整的 )。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在上述例子中,将HTTP Session 会话域的 userPreferences bean 注入进了单例域 userManager。 代码的关键在于userManager  是个单例bean,它在每个Spring 容器中只会被初始化 一次, 它的依赖对象(在这个例子中是 userPreferences bean)也只会被注入一次。这就意味着 userManager   每次操作的都是在最开始注入进来的同一个 userPreferences 对象。

当你把一个生命周期较短的bean注入至一个生命周期较长的bean时,例如把HTTP Session bean注入到单例bean, 这种情况肯定 不是 你所期望的。相反,你希望有个唯一的 userManager 单例对象,在每个HTTP Session的生命周期内, 都能有一个专门的 userPreferences 对象供 userManager 使用。因此,Spring容器会创建一个代理类, 使之与 UserPreferences 类实现相同的接口(理论上也是一个 UserPreferences 对象),并能根据作用域(HTTP 请求、 Session,等等)去获得真正的 UserPreferences 对象。 Spring容器将这个代理类的对象注入到 userManager , 但是 userManager 并不清楚它获得的 UserPreferences 其实是个代理。 在这个例子中,当UserManager 实例调用注入的 UserPreferences 对象的某个方法时,实际上调用的是代理对象的方法。 然后,代理对象从HTTP Session 中获取、并将方法调用委派给真正的 UserPreferences 对象。

所以,当你需要将 request-, session-, and globalSession-scoped 作用域的 bean 注入至其他合作者bean的时候,要按照下面的方法去正确地配置。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型

默认情况下,对于使用了 <aop:scoped-proxy/> 元素的bean,Spring容器会创建一个 基于CGLIB类代理机制的代理类

[Note]Note

CGLIB 代理只能拦截 public方法!不要在代理类上调用 非 public方法,那样不会委派给真正的目标对象。

或者,你也可以指定 <aop:scoped-proxy/> 元素的 proxy-target-class 属性为 false , 从而让Spring容器创建标准的、基于接口机制的JDK 代理类。使用 JDK 代理类意味着你不需要在应用程序的classpath 指定额外的类库。 但是,这也意味着你的 scoped-bean 至少得实现一个接口,而且 所有 合作者必须通过其中一个接口来引用被注入的scoped-bean。

<!-- DefaultUserPreferences 实现了 UserPreferences 接口 -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

到底该选择基于类的还是基于接口的代理机制呢?可参考 aop代理 了解更多细节信息。

5.5.5 Custom scopes

The bean scoping mechanism is extensible; You can define your own scopes, or even redefine existing scopes, although the latter is considered bad practice and you cannot override the built-in singleton and prototype scopes.

Creating a custom scope

To integrate your custom scope(s) into the Spring container, you need to implement the org.springframework.beans.factory.config.Scope interface, which is described in this section. For an idea of how to implement your own scopes, see the Scope implementations that are supplied with the Spring Framework itself and the Scope javadocs, which explains the methods you need to implement in more detail.

The Scope interface has four methods to get objects from the scope, remove them from the scope, and allow them to be destroyed.

The following method returns the object from the underlying scope. The session scope implementation, for example, returns the session-scoped bean (and if it does not exist, the method returns a new instance of the bean, after having bound it to the session for future reference).

Object get(String name, ObjectFactory objectFactory)

The following method removes the object from the underlying scope. The session scope implementation for example, removes the session-scoped bean from the underlying session. The object should be returned, but you can return null if the object with the specified name is not found.

Object remove(String name)

The following method registers the callbacks the scope should execute when it is destroyed or when the specified object in the scope is destroyed. Refer to the javadocs or a Spring scope implementation for more information on destruction callbacks.

void registerDestructionCallback(String name, Runnable destructionCallback)

The following method obtains the conversation identifier for the underlying scope. This identifier is different for each scope. For a session scoped implementation, this identifier can be the session identifier.

String getConversationId()

Using a custom scope

After you write and test one or more custom Scope implementations, you need to make the Spring container aware of your new scope(s). The following method is the central method to register a new Scope with the Spring container:

void registerScope(String scopeName, Scope scope);

This method is declared on the ConfigurableBeanFactory interface, which is available on most of the concrete ApplicationContext implementations that ship with Spring via the BeanFactory property.

The first argument to the registerScope(..) method is the unique name associated with a scope; examples of such names in the Spring container itself are singleton and prototype. The second argument to the registerScope(..) method is an actual instance of the custom Scope implementation that you wish to register and use.

Suppose that you write your custom Scope implementation, and then register it as below.

[Note]Note

The example below uses SimpleThreadScope which is included with Spring, but not registered by default. The instructions would be the same for your own custom Scope implementations.

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

You then create bean definitions that adhere to the scoping rules of your custom Scope:

<bean id="..." class="..." scope="thread">

With a custom Scope implementation, you are not limited to programmatic registration of the scope. You can also do the Scope registration declaratively, using the CustomScopeConfigurer class:

<?xml version="1.0" encoding="UTF-8"?>
<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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="foo" class="x.y.Foo">
        <property name="bar" ref="bar"/>
    </bean>

</beans>
[Note]Note

When you place <aop:scoped-proxy/> in a FactoryBean implementation, it is the factory bean itself that is scoped, not the object returned from getObject().

5.6 定制bean特性

5.6.1 生命周期回调

Spring提供了几个标志接口(marker interface),这些接口用来改变容器中bean的行为;它们包括InitializingBean和DisposableBean。 实现这两个接口的bean在初始化和析构时容器会调用前者的afterPropertiesSet()方法,以及后者的destroy()方法。

[Tip]Tip

在现代的Spring应用中,The JSR-250 @PostConstruct and @PreDestroy 接口一般认为是接收生命周期回调的最佳做法。 使用这些注解意味着bean没有耦合到Spring具体的接口。详情见Section 5.9.7, “@PostConstruct and @PreDestroy”

如果你不想使用JSR-250 注解,但你还是寻找消除耦合,考虑使用对象的init方法和destroy方法定义元数据。

Spring在内部使用 BeanPostProcessor 实现来处理它能找到的任何回调接口并调用相应的方法。如果你需要自定义特性或者生命周期行为,你可以实现自己的 BeanPostProcessor 。更多信息,详情见Section 5.8, “容器拓展点”

除了初始化和销毁回调之外,Spring管理对象可能还实现了Lifecycle接口,这些对象可以参与由容器自身驱动的启动和关闭过程。

本节中描述了生命周期回调接口。

初始化回调函数

实现 org.springframework.beans.factory.InitializingBean 接口,允许容器在设置好bean的所有必要属性后,执行初始化事宜。 InitializingBean 接口仅指定了一个方法:

void afterPropertiesSet() throws Exception;

通常,要避免使用 InitializingBean 接口并且不鼓励使用该接口,因为这样会将代码和Spring耦合起来。 使用@PostConstruct注解或者指定一个POJO的初始化方法。 在XML配置元数据的情况下,使用 init-method 属性去指定方法名,并且该方法无参数签名。 例如,下面的定义:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }

}

…是完全一样的…

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }

}

但是没有将代码与Spring耦合在一起。

析构回调函数

实现 org.springframework.beans.factory.DisposableBean 接口,允许一个bean当容器需要其销毁时获得一次回调。 DisposableBean 接口也只规定了一个方法:

void destroy() throws Exception;

建议不使用 DisposableBean 回调接口,因为会与Spring耦合。使用@PreDestroy 注解或者指定一个普通的方法,但能由bean定义支持。基于XML配置的元数据,使用 <bean/>destroy-method 属性。例如,下面的定义:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }

}

与下面效果相同:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }

}

但是不与Spring耦合。

缺省的初始化和析构方法

如果有人没有采用Spring所指定的 InitializingBeanDisposableBean 回调接口来编写初始化和析构方法回调,会发现自己正在编写的方法, 其名称莫过于 init(), initialize(), dispose() 等等。这种生命周期回调方法的名称最好在一个项目范围内标准化, 这样团队中的开发人员就可以使用同样的方法名称,并且确保了某种程度的一致性。

Spring容器通过配置可以实现对每个 bean初始化时的查找和销毁时的回调调用。这也就是说,一个应用的开发者可以借助于初始化的回调方法 init() 轻松的写一个类(不必想XML配置文件那样为每个bean都配置一个init-method="init"属性)。Spring IoC容器在创建bean的时候将调用这个方法( 这和之前描述的标准生命周期回调一致)。

为了完全弄清如何使用该特性,让我们看一个例子。出于示范的目的,假设一个项目的编码规范中约定所有的初始化回调方法都被命名为 init() 而析构回调方法被命名为 destroy() 。遵循此规则写成的类如下所示:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }

}
<beans default-init-method="init">

    <bean id="blogService" class="com.foo.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

注意在顶级的 <beans/> 元素中的 default-init-method 属性。这个属性的含义是 Spring IOC容器在bean创建和装配的时候会将 init 方法 作为实例化回调方法。如果类有这个方法,则会在适当的时候执行。

销毁回调方法配置是相同的 (XML配置),在顶级的<beans/>元素中使用 default-destroy-method 属性。

当已经存在的类的初始化方法的命名规则与惯例有差异的时候,你应该始终使用<bean/>元素中的init-methoddestroy-method属性(在XML配置中)来覆盖默认的方式。

最后,请注意Spring容器保证在bean的所有依赖都满足后立即执行配置的初始化回调。这意味着初始化回调在原生bean上调用,这也意味着这个时候任何诸如AOP拦截器之类的将不能被应用。 一个目标bean是首先完全创建,然后才应用诸如AOP代理等拦截器链。注意,如果目标bean和代理是分开定义了,你的代码甚至可以绕开代理直接和原生bean通信。 因此,在初始化方法上使用拦截器将产生未知的结果,因为这将目标bean和它的代理/拦截器的生命周期绑定并且留下了和初始bean直接通信这样奇怪的方式。

组合生命周期机制

截至 Spring 2.5,有三种选择控制bean生命周期行为:InitializingBeanDisposableBean 回调接口;自定义init()destroy() 方法; @PostConstruct and `@PreDestroy`annotations。 你可以组合这些机制去控制给定的bean。

[Note]Note

如果bean存在多种的生命周期机制配置并且每种机制都配置为不同的方法名, 那所有配置的方法将会按照上面的顺利执行。然而如果配置了相同的方法名 - 例如, init()初始化方法 - 采用多种机制配置后,只会执行一次。

为同一个bean配置多个生命周期机制,不同的初始化方法,调用如下:

  • @PostConstruct 元注释
  • InitializingBeanafterPropertiesSet() 定义
  • 自定义 init() 方法

析构方法调用顺序是相同的:

  • @PreDestroy 元注释
  • DisposableBeandestroy() 定义
  • 自定义 destroy() 方法

启动和关闭回调

Lifecycle 接口 为任何有它自己生命周期要求的对象定义基本方法(例如 开始和停止一些后台处理):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

任何Spring管理的对象可能实现那个接口。然后,当 ApplicationContext 开始和停止的时候,它会将那些调用的所有生命周期的实现 定义在这样的上下文中。通过 LifecycleProcessor

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

注意, LifecycleProcessor 本身扩展于 Lifecycle 接口。它还增加了两个其他的方法,用于对上下文进行刷新和关闭。

Notice that the LifecycleProcessor is itself an extension of the Lifecycle interface. It also adds two other methods for reacting to the context being refreshed and closed.

启动和关闭的顺序调用也很重要。如果任何两个对象之间存在依赖关系,依赖方将会在依赖后开始,在依赖前停止。然而,有时候直接依赖关系是未知的。 你可能只知道某个类型的对象应该在另一种类型的对象之前开始。在这种情况下, SmartLifecycle 接口定义另一种选择,换句话说, 作为其超级接口,Phased 定义 getPhase() 方法。

public interface Phased {

    int getPhase();

}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);

}

当开始的时候,最低阶段的对象首先开始,并且当停止的时候,是反向的顺序。因此,实现 SmartLifecycle 接口和返回值是 Integer.MIN_VALUEgetPhase() 方法 的对象将在第一个开始和最后一个停止。在另一方面,相位值Integer.MAX_VALUE 将表明对象应该第一个停止和最后开始( 可能是因为它依赖于其他进程的运行)。当考虑相位值的时候,同样重要的是要知道任何没有实现 SmartLifecycleLifecycle 对象默认的相位是0。 因此,任何负相值都会显示一个对象应该在那些标准组件前开始(并且在他们之后停止),反之为任何正相位值。

正如你所看到的, SmartLifecycle 接受回调,定义了停止方法。任何实现必须调用回调的 run() 方法,在实现的关闭进程完成之后。 那使得异步关闭,在默认实现的 lifecycleprocessor 接口开始,DefaultLifecycleProcessor ,将等待其在每个阶段中的对象组的超时值来调用这个回调。 默认的 per-phase 超时时间是30秒。你可以在上下文中定义一个名为"lifecycleProcessor"的bean来重写默认生命周期处理器实例。如果你打算修改超时时间, 然后定义以下的就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如上所述, LifecycleProcessor 接口对于上下文的刷新和关闭定义了回调方法。如果` stop() 被显式调用,后者将简单的驱动关闭进程, 但当上下文关闭时会发生。 'refresh' 回调在另一方面来说是 `SmartLifecycle bean的另一个特点。当上下文被刷新的时候(在所有对象被实例化和 初始化之后),回调将被调用,而在这一点上,默认生命周期处理器将检查通过每一个 SmartLifecycle 对象的 isAutoStartup() 方法返回的布尔值。 如果返回 "true",这个对象将在这一点上开始,而不是等待上下文的或者本身的 start() 方法的显示调用(与上下文刷新不同,上下文开始不会 为一个标准的上下文实现自动发生)。 "phase" 值以及 "depends-on" 关系将以相同的方式确定启动顺序,如上所述。

在非web应用中优雅地关闭Spring IoC容器

[Note]Note

本节仅适用于非web应用程序。在基于web的ApplicationContext实现中已有相应的代码来处理关闭web应用时如何恰当地关闭Spring IoC容器。

This section applies only to non-web applications. Spring’s web-based ApplicationContext implementations already have code in place to shut down the Spring IoC container gracefully when the relevant web application is shut down.

如果你正在一个非web应用的环境下使用Spring的IoC容器;例如在桌面富客户端环境下,你想让容器优雅的关闭,并调用singleton bean上的相应析构回调方法, 你需要在JVM里注册一个“关闭钩子”(shutdown hook)。这一点非常容易做到,并且将会确保你的Spring IoC容器被恰当关闭,以及所有由单例持有的资源都会 被释放(当然,为你的单例配置销毁回调,并正确实现销毁回调方法,依然是你的工作)。

为了注册“关闭钩子”,你只需要简单地调用在 AbstractApplicationContext 实现中的 registerShutdownHook() 方法即可。也就是:

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

    public static void main(final String[] args) throws Exception {

        AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
                new String []{"beans.xml"});

        // add a shutdown hook for the above context...
        ctx.registerShutdownHook();

        // app runs here...

        // main method exits, hook is called prior to the app shutting down...

    }
}

5.6.2 ApplicationContextAware and BeanNameAware

ApplicationContext 创建一个实现 org.springframework.context.ApplicationContextAware 接口的对象的实例, 该实例提供一个参考,ApplicationContext

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

When an ApplicationContext creates an object instance that implements the org.springframework.context.ApplicationContextAware interface, the instance is provided with a reference to that ApplicationContext.

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

因此,bean可以通过编程方式操纵 ApplicationContext 来创建,通过 ApplicationContext 接口,或者通过向这个接口的一个已知的子类的引用, 如 ConfigurableApplicationContext ,公开附加功能。一个应用程序将是其他bean的方案检索。有时候这种性能是有用的,然而,一般来说, 你应该避免它,因为他会与Spirng耦合,并且不遵循反转控制方式,在合作者被提供给bean作为属性的地方。 ApplicationContext 的其他方法提供 获取文件资源,发布应用程序事件,和获取一个MessageSource。这些附加功能在Section 5.15, “ApplicationContext 的附加功能”中描述。

截止Spring 2.5,自动装配是获取 ApplicationContext 索引的另一种选择。传统的 constructorbyType自动模式(在 Section 5.4.5, “自动装配协作者”中描述)可以分别为 ApplicationContext 类型的构造函数参数或setter方法参数提供依赖。为了更灵活, 包括自动装配成员和多参数方法的能力,使用新的基于注释的自动功能,如果你这样做, ApplicationContext 被自动装配到成员,构造函数参数,或者 方法参数,希望` ApplicationContext 类型,如果成员,构造函数,方法在问题进行 `@Autowired 注释。 更多信息请看Section 5.9.2, “@Autowired”

ApplicationContext 创建一个实现 org.springframework.beans.factory.BeanNameAware 接口的类,该类为 定义在其相关对象定义中的名称提供一个索引。

public interface BeanNameAware {

    void setBeanName(string name) throws BeansException;

}

回调是在所有正常bean属性之后,但是在如 InitializingBean afterPropertiesSet 或者一个自定义的初始化方法 的初始化回调之前,被调用。

5.6.3 其他 Aware 接口

除了上述的 ApplicationContextAwareBeanNameAware ,Spring提供一系列的 Aware 接口,允许bean表示他们需要一定基础设施依赖的容器。 最重要的 Aware 接口概括如下,作为一般规则,这个名称是依赖类型的一个很好的指示:

Table 5.4. Aware 接口

名称注入依赖解释…

ApplicationContextAware

声明 ApplicationContext

Section 5.6.2, “ApplicationContextAware and BeanNameAware”

ApplicationEventPublisherAware

封闭的事件发布者 ApplicationContext

Section 5.15, “ApplicationContext 的附加功能”

BeanClassLoaderAware

用于装载bean class 的装载器.

Section 5.3.2, “实例化bean”

BeanFactoryAware

声明 BeanFactory

Section 5.6.2, “ApplicationContextAware and BeanNameAware”

BeanNameAware

声明bean的名称

Section 5.6.2, “ApplicationContextAware and BeanNameAware”

BootstrapContextAware

Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts

Chapter 25, JCA CCI

LoadTimeWeaverAware

在加载时定义weaver处理类的定义

Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”

MessageSourceAware

解决消息的配置策略 (用参数化和国际化支持)

Section 5.15, “ApplicationContext 的附加功能”

NotificationPublisherAware

Spring JMX 通知发布者

Section 24.7, “Notifications”

PortletConfigAware

目前 PortletConfig 容器运行. 仅在一个web-aware Spring中有效 ApplicationContext

Chapter 19, Portlet MVC Framework

PortletContextAware

目前 PortletContext 容器运行. 仅在一个web-aware Spring中有效 ApplicationContext

Chapter 19, Portlet MVC Framework

ResourceLoaderAware

为低级别访问资源配置的加载程序

Chapter 6, Resources

ServletConfigAware

目前 ServletConfig 容器运行. 仅在一个web-aware Spring中有效 ApplicationContext

Chapter 16, Web MVC 框架

ServletContextAware

目前 ServletContext 容器运行. 仅在一个web-aware Spring中有效 ApplicationContext

Chapter 16, Web MVC 框架


再次说明,这些接口的使用将您的代码联系到Spring API,并且不遵循反转控制方式。因此,它们被推荐用于要求对容器进行编程访问的基础bean。

5.7 Bean定义的继承

在一个bean定义中,可以包含配置信息,包括构造器参数,属性值,以及容器的特定信息,比如初始化方法, 静态工厂方法名等等。子bean定义,从它的父bean定义继承配置数据;子bean定义可以覆盖一些值, 或者根据需要添加一些其他值。使用父子bean定义,可以避免很多配置填写。事实上,这是一种模板设计模式。

如果在工作中,你编程式地使用ApplicationContext接口,子bean定义由ChildBeanDefinition 类来表示; 大多数的使用者,在工作中不在这个层面上使用它,而是声明式配置bean定义,比如使用ClassPathXmlApplicationContext 当使用基于XML的配置元数据,你可以指明一个子bean定义使用parent属性,指定父类bean作为这个属性的值

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">
    <property name="name" value="override"/>
    <!--age属性的值1,将会从父类继承-->
</bean>

一个子bean定义如果没有指定class,他可以使用父bean定义的class,也可以覆盖它;在后一种情形中子bean定义, 必须兼容父类,也就是说,他要接受父类的属性值

子bean定义从父类继承构造器参数,属性值,和可以重写的方法,另外也可以增加新的值。你指定的任何初始化方法, 销毁方法,和/或者静态工厂方法设置,都会覆盖相应的父类设置。

其余的设置 总是 取自子bean定义:depends on, autowire mode, dependency check, singleton, scope, lazy init.

下面的例子,通过使用abstract属性,明确地标明这个父类bean定义是抽象的。如果,父类bean定义 没有明确地指出所属的类,那么标记父bean定义为为abstract是必须的,如下:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!--age属性的值1,将会从父类继承-->
</bean>

这个父bean不能自主实例化,因为它是不完整的,同时它也明确地被标注为abstract;像这样, 一个bean定义为abstract 的,它只能作为一个纯粹的bean模板,为子bean定义,充当父bean定义。 尝试独立地使用这样一个abstract的父bean,把他作为另一个bean 的引用,或者根据这个父bean的id显式调用getBean()方法, 将会返回一个错误。类似地,容器内部的preInstantiateSingletons() 方法,也忽略定义为抽象的bean定义。

[注意]

ApplicationContext默认地预实例化所有单例bean。因此,如果你打算把一个(父)bean定义仅仅作为模板来使用, 同时给它指定了类属性,你必须确保设置 abstract 属性为 true,否则,应用程序上下文,会(尝试) 预实例化这个abstract bean。

5.8 容器拓展点

通常,应用程序开发者,不需要继承ApplicationContext的实现类。相反,Spring IoC容器可以通过插入特殊的集成接口的实现,进行拓展。新的一节中,描述了这些集成接口。

5.8.1 使用BeanPostProcessor自定义beans

BeanPostProcessor 定义了回调方法,通过实现这个回调方法,你可以提供你自己的(或者重写容器默认的) 实例化逻辑,依赖分析逻辑等等。如果你想在Spring容器完成实例化配置,实例化一个bean之后,实现一些自定义逻辑 你可以插入一个或多个 BeanPostProcessor 的实现。

你可以配置多个BeanPostProcessor实例,同时你也能通过设置 order 属性来控制这些BeanPostProcessors 的执行顺序。只有BeanPostProcessor实现了Ordered 接口,你才可以设置 order 属性。如果,你编写了自己的BeanPostProcessor 也应当考虑实现 Ordered 接口。欲知详情,请参考BeanPostProcessorOrdered接口的javadoc。也可以看看下面的要点programmatic registration of BeanPostProcessors

[注意]

BeanPostProcessors 作用在一个bean(或者对象)的实例上;也就是说,Spring IoC实例化一个bean实例之后, BeanPostProcessors,才开始进行处理。

BeanPostProcessors作用范围是每一个容器。这仅仅和你正在使用容器有关。如果你在一个容器中定义了一个BeanPostProcessor ,它将 仅仅 后置处理那个容器中的beans。换言之,一个容器中的beans不会被另一个,容器中的BeanPostProcessor处理,即使这两个容器,具有相同的父类。

为了改变实际的bean定义(例如, blueprint 定义的bean),你反而需要使用BeanFactoryPostProcessor,就像在Section 5.8.2, “Customizing configuration metadata with a BeanFactoryPostProcessor”中描述的那样。

org.springframework.beans.factory.config.BeanPostProcessor 接口,恰好有两个回调方法组成。 当这样的一个类注册为容器的一个后置处理器,由于每一个bean实例都是由容器创建的,这个后置处理器会从容器得到一个回调方法 在容器的初始化方法(比如InitializingBean的afterPropertiesSet()和任何生命的初始化方法)被调用之前和任何bean实例化回调之后。 后置处理器,可以对bean采取任何措施,包括忽略回到方法是否完成。一个bean后置处理器,通常会检查回调接口或者使用代理包装一个bean。一些 Spring AOP基础设施类,为了提供包装式的代理逻辑,被实现为bean后置处理器,

一个ApplicationContext自动地检测所有定义在配置元文件中,并实现了BeanPostProcessor接口的bean。 该ApplicationContext注册这些beans作为后置处理器,使他们可以在bean创建完成之后,被调用。 bean后置处理器可以像其他bean一样部署到容器中。

注意,当在一个配置类上,使用@Bean工厂方法声明一个 BeanPostProcessor ,返回类型应该是实现类自身, 至少也要是 org.springframework.beans.factory.config.BeanPostProcessor接口,要清楚地表明这个bean的后置处理器本质特点。 此外,在它完全创建之前,ApplicationContext将不能通过类型自动探测它。由于一个BeanPostProcessor,早期就需要被实例化, 以适应上下文中其他bean的实例化,因此这个早期的类型检查是至关重要的。

[注意]

编程式注册 BeanPostProcessors 虽然推荐使用ApplicationContext的自动检测来注册BeanPostProcessor,但是对于使用了addBeanPostProcessor方法的ConfigurableBeanFactory也可以编程式地注册他们。 在注册之前,或者是在继承层次的上下文之间复制bean后置处理器,需要对逻辑进行条件式的评估时,这是有用的。但是请注意,编程地添加的BeanPostProcessors 不需要考虑Ordered接口 。 也就是注册的顺序决定了执行的顺序。也要注意,编程式注册的BeanPostProcessors,总是预先被处理----早于通过自动检测方式注册的,同时忽略 任何明确的排序

[注意]

BeanPostProcessors 和 AOP 自动代理 实现了BeanPostProcessor接口的类是特殊的,会被容器特殊处理。所有BeanPostProcessors`__和他们直接引用的 beans__都会在容器启动的时候被实例化,作为`ApplicationContext特殊启动阶段的一部分。接着,所有的BeanPostProcessors 以一个有序的方式,进行注册,并应用于容器中的一切bean。因为AOP自动代理本身被实现为BeanPostProcessor, 这个BeanPostProcessors 和它直接应用的beans都没有资格进行自动代理,这样就没有切面编织到他们里面。

对于所有这样的bean,你会看到一个info日志:"Bean foo 没有资格获取任何BeanPostProcessor接口的处理(例如,没有资格获取自动代理的)".

注意,如果你有beans使用自动装配或者@Resource 装配到了你的BeanPostProcessor中,当根据候选依赖,搜索匹配类型时,Spring也许会访问意外类型的bean; 因此,使它们没有资格进行自动代理,或者其他类型的bean后置处理。例如,你使用@Resource注解一个依赖,其中字段或者set方法名,不是和bean声明的名字直接对应 同时没有name属性被使用,然后,Spring将会根据类型,访问其他beans进行匹配。

下面的示例显示了如何编写,注册和在一个ApplicationContext中使用BeanPostProcessor

Example: Hello World, BeanPostProcessor-style

This first example illustrates basic usage. The example shows a custom BeanPostProcessor implementation that invokes the toString() method of each bean as it is created by the container and prints the resulting string to the system console.

Find below the custom BeanPostProcessor implementation class definition:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean,
            String beanName) throws BeansException {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean,
            String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

Notice how the InstantiationTracingBeanPostProcessor is simply defined. It does not even have a name, and because it is a bean it can be dependency-injected just like any other bean. (The preceding configuration also defines a bean that is backed by a Groovy script. The Spring dynamic language support is detailed in the chapter entitled Chapter 28, Dynamic language support.)

The following simple Java application executes the preceding code and configuration:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

The output of the preceding application resembles the following:

Bean messenger created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

Example: The RequiredAnnotationBeanPostProcessor

Using callback interfaces or annotations in conjunction with a custom BeanPostProcessor implementation is a common means of extending the Spring IoC container. An example is Spring’s RequiredAnnotationBeanPostProcessor - a BeanPostProcessor implementation that ships with the Spring distribution which ensures that JavaBean properties on beans that are marked with an (arbitrary) annotation are actually (configured to be) dependency-injected with a value.

5.8.2 Customizing configuration metadata with a BeanFactoryPostProcessor

The next extension point that we will look at is the org.springframework.beans.factory.config.BeanFactoryPostProcessor. The semantics of this interface are similar to those of the BeanPostProcessor, with one major difference: BeanFactoryPostProcessor operates on the bean configuration metadata; that is, the Spring IoC container allows a BeanFactoryPostProcessor to read the configuration metadata and potentially change it before the container instantiates any beans other than BeanFactoryPostProcessors.

You can configure multiple BeanFactoryPostProcessors, and you can control the order in which these BeanFactoryPostProcessors execute by setting the order property. However, you can only set this property if the BeanFactoryPostProcessor implements the Ordered interface. If you write your own BeanFactoryPostProcessor, you should consider implementing the Ordered interface too. Consult the javadocs of the BeanFactoryPostProcessor and Ordered interfaces for more details.

[Note]Note

If you want to change the actual bean instances (i.e., the objects that are created from the configuration metadata), then you instead need to use a BeanPostProcessor (described above in Section 5.8.1, “使用BeanPostProcessor自定义beans”). While it is technically possible to work with bean instances within a BeanFactoryPostProcessor (e.g., using BeanFactory.getBean()), doing so causes premature bean instantiation, violating the standard container lifecycle. This may cause negative side effects such as bypassing bean post processing.

Also, BeanFactoryPostProcessors are scoped per-container. This is only relevant if you are using container hierarchies. If you define a BeanFactoryPostProcessor in one container, it will only be applied to the bean definitions in that container. Bean definitions in one container will not be post-processed by BeanFactoryPostProcessors in another container, even if both containers are part of the same hierarchy.

A bean factory post-processor is executed automatically when it is declared inside an ApplicationContext, in order to apply changes to the configuration metadata that define the container. Spring includes a number of predefined bean factory post-processors, such as PropertyOverrideConfigurer and PropertyPlaceholderConfigurer. A custom BeanFactoryPostProcessor can also be used, for example, to register custom property editors.

An ApplicationContext automatically detects any beans that are deployed into it that implement the BeanFactoryPostProcessor interface. It uses these beans as bean factory post-processors, at the appropriate time. You can deploy these post-processor beans as you would any other bean.

[Note]Note

As with BeanPostProcessors , you typically do not want to configure BeanFactoryPostProcessors for lazy initialization. If no other bean references a Bean(Factory)PostProcessor, that post-processor will not get instantiated at all. Thus, marking it for lazy initialization will be ignored, and the Bean(Factory)PostProcessor will be instantiated eagerly even if you set the default-lazy-init attribute to true on the declaration of your <beans /> element.

Example: the Class name substitution PropertyPlaceholderConfigurer

You use the PropertyPlaceholderConfigurer to externalize property values from a bean definition in a separate file using the standard Java Properties format. Doing so enables the person deploying an application to customize environment-specific properties such as database URLs and passwords, without the complexity or risk of modifying the main XML definition file or files for the container.

Consider the following XML-based configuration metadata fragment, where a DataSource with placeholder values is defined. The example shows properties configured from an external Properties file. At runtime, a PropertyPlaceholderConfigurer is applied to the metadata that will replace some properties of the DataSource. The values to replace are specified as placeholders of the form ${property-name} which follows the Ant / log4j / JSP EL style.

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

The actual values come from another file in the standard Java Properties format:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

Therefore, the string ${jdbc.username} is replaced at runtime with the value sa, and the same applies for other placeholder values that match keys in the properties file. The PropertyPlaceholderConfigurer checks for placeholders in most properties and attributes of a bean definition. Furthermore, the placeholder prefix and suffix can be customized.

With the context namespace introduced in Spring 2.5, it is possible to configure property placeholders with a dedicated configuration element. One or more locations can be provided as a comma-separated list in the location attribute.

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

The PropertyPlaceholderConfigurer not only looks for properties in the Properties file you specify. By default it also checks against the Java System properties if it cannot find a property in the specified properties files. You can customize this behavior by setting the systemPropertiesMode property of the configurer with one of the following three supported integer values:

  • never (0): Never check system properties
  • fallback (1): Check system properties if not resolvable in the specified properties files. This is the default.
  • override (2): Check system properties first, before trying the specified properties files. This allows system properties to override any other property source.

Consult the PropertyPlaceholderConfigurer javadocs for more information.

[Tip]Tip

You can use the PropertyPlaceholderConfigurer to substitute class names, which is sometimes useful when you have to pick a particular implementation class at runtime. For example:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <value>classpath:com/foo/strategy.properties</value>
    </property>
    <property name="properties">
        <value>custom.strategy.class=com.foo.DefaultStrategy</value>
    </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

If the class cannot be resolved at runtime to a valid class, resolution of the bean fails when it is about to be created, which is during the preInstantiateSingletons() phase of an ApplicationContext for a non-lazy-init bean.

Example: the PropertyOverrideConfigurer

The PropertyOverrideConfigurer, another bean factory post-processor, resembles the PropertyPlaceholderConfigurer, but unlike the latter, the original definitions can have default values or no values at all for bean properties. If an overriding Properties file does not have an entry for a certain bean property, the default context definition is used.

Note that the bean definition is not aware of being overridden, so it is not immediately obvious from the XML definition file that the override configurer is being used. In case of multiple PropertyOverrideConfigurer instances that define different values for the same bean property, the last one wins, due to the overriding mechanism.

Properties file configuration lines take this format:

beanName.property=value

For example:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

This example file can be used with a container definition that contains a bean called dataSource, which has driver and url properties.

Compound property names are also supported, as long as every component of the path except the final property being overridden is already non-null (presumably initialized by the constructors). In this example…

foo.fred.bob.sammy=123
  1. the sammy property of the bob property of the fred property of the foo bean is set to the scalar value 123.
[Note]Note

Specified override values are always literal values; they are not translated into bean references. This convention also applies when the original value in the XML bean definition specifies a bean reference.

With the context namespace introduced in Spring 2.5, it is possible to configure property overriding with a dedicated configuration element:

<context:property-override location="classpath:override.properties"/>

5.8.3 Customizing instantiation logic with a FactoryBean

Implement the org.springframework.beans.factory.FactoryBean interface for objects that are themselves factories.

The FactoryBean interface is a point of pluggability into the Spring IoC container’s instantiation logic. If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.

The FactoryBean interface provides three methods:

  • Object getObject(): returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.
  • boolean isSingleton(): returns true if this FactoryBean returns singletons, false otherwise.
  • Class getObjectType(): returns the object type returned by the getObject() method or null if the type is not known in advance.

The FactoryBean concept and interface is used in a number of places within the Spring Framework; more than 50 implementations of the FactoryBean interface ship with Spring itself.

When you need to ask a container for an actual FactoryBean instance itself instead of the bean it produces, preface the bean’s id with the ampersand symbol ( &) when calling the getBean() method of the ApplicationContext. So for a given FactoryBean with an id of myBean, invoking getBean("myBean") on the container returns the product of the FactoryBean; whereas, invoking getBean("&myBean") returns the FactoryBean instance itself.

5.9 Annotation-based container configuration

An alternative to XML setups is provided by annotation-based configuration which rely on the bytecode metadata for wiring up components instead of angle-bracket declarations. Instead of using XML to describe a bean wiring, the developer moves the configuration into the component class itself by using annotations on the relevant class, method, or field declaration. As mentioned in the section called “Example: The RequiredAnnotationBeanPostProcessor”, using a BeanPostProcessor in conjunction with annotations is a common means of extending the Spring IoC container. For example, Spring 2.0 introduced the possibility of enforcing required properties with the @Required annotation. Spring 2.5 made it possible to follow that same general approach to drive Spring’s dependency injection. Essentially, the @Autowired annotation provides the same capabilities as described in Section 5.4.5, “自动装配协作者” but with more fine-grained control and wider applicability. Spring 2.5 also added support for JSR-250 annotations such as @PostConstruct, and @PreDestroy. Spring 3.0 added support for JSR-330 (Dependency Injection for Java) annotations contained in the javax.inject package such as @Inject and @Named. Details about those annotations can be found in the relevant section.

[Note]Note

Annotation injection is performed before XML injection, thus the latter configuration will override the former for properties wired through both approaches.

As always, you can register them as individual bean definitions, but they can also be implicitly registered by including the following tag in an XML-based Spring configuration (notice the inclusion of the context namespace):

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

(The implicitly registered post-processors include AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, as well as the aforementioned RequiredAnnotationBeanPostProcessor.)

[Note]Note

<context:annotation-config/> only looks for annotations on beans in the same application context in which it is defined. This means that, if you put <context:annotation-config/> in a WebApplicationContext for a DispatcherServlet, it only checks for @Autowired beans in your controllers, and not your services. See Section 16.2, “The DispatcherServlet” for more information.

5.9.1 @Required

The @Required annotation applies to bean property setter methods, as in the following example:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Required
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

This annotation simply indicates that the affected bean property must be populated at configuration time, through an explicit property value in a bean definition or through autowiring. The container throws an exception if the affected bean property has not been populated; this allows for eager and explicit failure, avoiding NullPointerExceptions or the like later on. It is still recommended that you put assertions into the bean class itself, for example, into an init method. Doing so enforces those required references and values even when you use the class outside of a container.

5.9.2 @Autowired

As expected, you can apply the @Autowired annotation to "traditional" setter methods:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}
[Note]Note

JSR 330’s @Inject annotation can be used in place of Spring’s @Autowired annotation in the examples below. See here for more details

You can also apply the annotation to methods with arbitrary names and/or multiple arguments:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}

You can apply @Autowired to constructors and fields:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}

It is also possible to provide all beans of a particular type from the ApplicationContext by adding the annotation to a field or method that expects an array of that type:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...

}

The same applies for typed collections:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...

}
[Tip]Tip

Your beans can implement the org.springframework.core.Ordered interface or either use the @Order or standard @Priority annotation if you want items in the array or list to be sorted into a specific order.

Even typed Maps can be autowired as long as the expected key type is String. The Map values will contain all beans of the expected type, and the keys will contain the corresponding bean names:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...

}

By default, the autowiring fails whenever zero candidate beans are available; the default behavior is to treat annotated methods, constructors, and fields as indicating required dependencies. This behavior can be changed as demonstrated below.

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired(required=false)
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}
[Note]Note

Only one annotated constructor per-class can be marked as required, but multiple non-required constructors can be annotated. In that case, each is considered among the candidates and Spring uses the greediest constructor whose dependencies can be satisfied, that is the constructor that has the largest number of arguments.

@Autowired's required attribute is recommended over the @Required annotation. The required attribute indicates that the property is not required for autowiring purposes, the property is ignored if it cannot be autowired. @Required, on the other hand, is stronger in that it enforces the property that was set by any means supported by the container. If no value is injected, a corresponding exception is raised.

You can also use @Autowired for interfaces that are well-known resolvable dependencies: BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, and MessageSource. These interfaces and their extended interfaces, such as ConfigurableApplicationContext or ResourcePatternResolver, are automatically resolved, with no special setup necessary.

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...

}
[Note]Note

@Autowired, @Inject, @Resource, and @Value annotations are handled by a Spring BeanPostProcessor implementations which in turn means that you cannot apply these annotations within your own BeanPostProcessor or BeanFactoryPostProcessor types (if any). These types must be wired up explicitly via XML or using a Spring @Bean method.

5.9.3 Fine-tuning annotation-based autowiring with qualifiers

Because autowiring by type may lead to multiple candidates, it is often necessary to have more control over the selection process. One way to accomplish this is with Spring’s @Qualifier annotation. You can associate qualifier values with specific arguments, narrowing the set of type matches so that a specific bean is chosen for each argument. In the simplest case, this can be a plain descriptive value:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...

}

The @Qualifier annotation can also be specified on individual constructor arguments or method parameters:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}

The corresponding bean definitions appear as follows. The bean with qualifier value "main" is wired with the constructor argument that is qualified with the same value.

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/>

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/>

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

For a fallback match, the bean name is considered a default qualifier value. Thus you can define the bean with an id "main" instead of the nested qualifier element, leading to the same matching result. However, although you can use this convention to refer to specific beans by name, @Autowired is fundamentally about type-driven injection with optional semantic qualifiers. This means that qualifier values, even with the bean name fallback, always have narrowing semantics within the set of type matches; they do not semantically express a reference to a unique bean id. Good qualifier values are "main" or "EMEA" or "persistent", expressing characteristics of a specific component that are independent from the bean id, which may be auto-generated in case of an anonymous bean definition like the one in the preceding example.

Qualifiers also apply to typed collections, as discussed above, for example, to Set<MovieCatalog>. In this case, all matching beans according to the declared qualifiers are injected as a collection. This implies that qualifiers do not have to be unique; they rather simply constitute filtering criteria. For example, you can define multiple MovieCatalog beans with the same qualifier value "action"; all of which would be injected into a Set<MovieCatalog> annotated with @Qualifier("action").

[Tip]Tip

If you intend to express annotation-driven injection by name, do not primarily use @Autowired, even if is technically capable of referring to a bean name through @Qualifier values. Instead, use the JSR-250 @Resource annotation, which is semantically defined to identify a specific target component by its unique name, with the declared type being irrelevant for the matching process.

As a specific consequence of this semantic difference, beans that are themselves defined as a collection or map type cannot be injected through @Autowired, because type matching is not properly applicable to them. Use @Resource for such beans, referring to the specific collection or map bean by unique name.

@Autowired applies to fields, constructors, and multi-argument methods, allowing for narrowing through qualifier annotations at the parameter level. By contrast, @Resource is supported only for fields and bean property setter methods with a single argument. As a consequence, stick with qualifiers if your injection target is a constructor or a multi-argument method.

You can create your own custom qualifier annotations. Simply define an annotation and provide the @Qualifier annotation within your definition:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

Then you can provide the custom qualifier on autowired fields and parameters:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;
    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...

}

Next, provide the information for the candidate bean definitions. You can add <qualifier/> tags as sub-elements of the <bean/> tag and then specify the type and value to match your custom qualifier annotations. The type is matched against the fully-qualified class name of the annotation. Or, as a convenience if no risk of conflicting names exists, you can use the short class name. Both approaches are demonstrated in the following example.

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        _<qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

In Section 5.10, “Classpath scanning and managed components”, you will see an annotation-based alternative to providing the qualifier metadata in XML. Specifically, see Section 5.10.8, “Providing qualifier metadata with annotations”.

In some cases, it may be sufficient to use an annotation without a value. This may be useful when the annotation serves a more generic purpose and can be applied across several different types of dependencies. For example, you may provide an offline catalog that would be searched when no Internet connection is available. First define the simple annotation:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

Then add the annotation to the field or property to be autowired:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...

}

Now the bean definition only needs a qualifier type:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/>
    <!-- inject any dependencies required by this bean -->
</bean>

You can also define custom qualifier annotations that accept named attributes in addition to or instead of the simple value attribute. If multiple attribute values are then specified on a field or parameter to be autowired, a bean definition must match all such attribute values to be considered an autowire candidate. As an example, consider the following annotation definition:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();

}

In this case Format is an enum:

public enum Format {
    VHS, DVD, BLURAY
}

The fields to be autowired are annotated with the custom qualifier and include values for both attributes: genre and format.

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...

}

Finally, the bean definitions should contain matching qualifier values. This example also demonstrates that bean meta attributes may be used instead of the <qualifier/> sub-elements. If available, the <qualifier/> and its attributes take precedence, but the autowiring mechanism falls back on the values provided within the <meta/> tags if no such qualifier is present, as in the last two bean definitions in the following example.

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>

5.9.4 Using generics as autowiring qualifiers

In addition to the @Qualifier annotation, it is also possible to use Java generic types as an implicit form of qualification. For example, suppose you have the following configuration:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }

}

Assuming that beans above implement a generic interface, i.e. Store<String> and Store<Integer>, you can @Autowire the Store interface and the generic will be used as a qualifier:

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean

@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

Generic qualifiers also apply when autowiring Lists, Maps and Arrays:

// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;

5.9.5 CustomAutowireConfigurer

The CustomAutowireConfigurer is a BeanFactoryPostProcessor that enables you to register your own custom qualifier annotation types even if they are not annotated with Spring’s @Qualifier annotation.

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

The AutowireCandidateResolver determines autowire candidates by:

  • the autowire-candidate value of each bean definition
  • any default-autowire-candidates pattern(s) available on the <beans/> element
  • the presence of @Qualifier annotations and any custom annotations registered with the CustomAutowireConfigurer

When multiple beans qualify as autowire candidates, the determination of a "primary" is the following: if exactly one bean definition among the candidates has a primary attribute set to true, it will be selected.

5.9.6 @Resource

Spring also supports injection using the JSR-250 @Resource annotation on fields or bean property setter methods. This is a common pattern in Java EE 5 and 6, for example in JSF 1.2 managed beans or JAX-WS 2.0 endpoints. Spring supports this pattern for Spring-managed objects as well.

@Resource takes a name attribute, and by default Spring interprets that value as the bean name to be injected. In other words, it follows by-name semantics, as demonstrated in this example:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder")
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

}

If no name is specified explicitly, the default name is derived from the field name or setter method. In case of a field, it takes the field name; in case of a setter method, it takes the bean property name. So the following example is going to have the bean with name "movieFinder" injected into its setter method:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

}
[Note]Note

The name provided with the annotation is resolved as a bean name by the ApplicationContext of which the CommonAnnotationBeanPostProcessor is aware. The names can be resolved through JNDI if you configure Spring’s SimpleJndiBeanFactory explicitly. However, it is recommended that you rely on the default behavior and simply use Spring’s JNDI lookup capabilities to preserve the level of indirection.

In the exclusive case of @Resource usage with no explicit name specified, and similar to @Autowired, @Resource finds a primary type match instead of a specific named bean and resolves well-known resolvable dependencies: the BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, and MessageSource interfaces.

Thus in the following example, the customerPreferenceDao field first looks for a bean named customerPreferenceDao, then falls back to a primary type match for the type CustomerPreferenceDao. The "context" field is injected based on the known resolvable dependency type ApplicationContext.

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...

}

5.9.7 @PostConstruct and @PreDestroy

The CommonAnnotationBeanPostProcessor not only recognizes the @Resource annotation but also the JSR-250 lifecycle annotations. Introduced in Spring 2.5, the support for these annotations offers yet another alternative to those described in initialization callbacks and destruction callbacks. Provided that the CommonAnnotationBeanPostProcessor is registered within the Spring ApplicationContext, a method carrying one of these annotations is invoked at the same point in the lifecycle as the corresponding Spring lifecycle interface method or explicitly declared callback method. In the example below, the cache will be pre-populated upon initialization and cleared upon destruction.

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }

}
[Note]Note

For details about the effects of combining various lifecycle mechanisms, see the section called “组合生命周期机制”.

5.10 Classpath scanning and managed components

Most examples in this chapter use XML to specify the configuration metadata that produces each BeanDefinition within the Spring container. The previous section (Section 5.9, “Annotation-based container configuration”) demonstrates how to provide a lot of the configuration metadata through source-level annotations. Even in those examples, however, the "base" bean definitions are explicitly defined in the XML file, while the annotations only drive the dependency injection. This section describes an option for implicitly detecting the candidate components by scanning the classpath. Candidate components are classes that match against a filter criteria and have a corresponding bean definition registered with the container. This removes the need to use XML to perform bean registration, instead you can use annotations (for example @Component), AspectJ type expressions, or your own custom filter criteria to select which classes will have bean definitions registered with the container.

[Note]Note

Starting with Spring 3.0, many features provided by the Spring JavaConfig project are part of the core Spring Framework. This allows you to define beans using Java rather than using the traditional XML files. Take a look at the @Configuration, @Bean, @Import, and @DependsOn annotations for examples of how to use these new features.

5.10.1 @Component and further stereotype annotations

The @Repository annotation is a marker for any class that fulfills the role or stereotype (also known as Data Access Object or DAO) of a repository. Among the uses of this marker is the automatic translation of exceptions as described in Section 14.2.2, “异常转化”.

Spring provides further stereotype annotations: @Component, @Service, and @Controller. @Component is a generic stereotype for any Spring-managed component. @Repository, @Service, and @Controller are specializations of @Component for more specific use cases, for example, in the persistence, service, and presentation layers, respectively. Therefore, you can annotate your component classes with @Component, but by annotating them with @Repository, @Service, or @Controller instead, your classes are more properly suited for processing by tools or associating with aspects. For example, these stereotype annotations make ideal targets for pointcuts. It is also possible that @Repository, @Service, and @Controller may carry additional semantics in future releases of the Spring Framework. Thus, if you are choosing between using @Component or @Service for your service layer, @Service is clearly the better choice. Similarly, as stated above, @Repository is already supported as a marker for automatic exception translation in your persistence layer.

5.10.2 Meta-annotations

Many of the annotations provided by Spring can be used as "meta-annotations" in your own code. A meta-annotation is simply an annotation, that can be applied to another annotation. For example, The @Service annotation mentioned above is meta-annotated with with @Component:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // Spring will see this and treat @Service in the same way as @Component
public @interface Service {

    // ....

}

Meta-annotations can also be combined together to create composed annotations. For example, the @RestController annotation from Spring MVC is composed of @Controller and @ResponseBody.

With the exception of value(), meta-annotated types may redeclare attributes from the source annotation to allow user customization. This can be particularly useful when you want to only expose a subset of the source annotation attributes. For example, here is a custom @Scope annotation that defines session scope, but still allows customization of the proxyMode.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope("session")
public @interface SessionScope {

    ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT

}

5.10.3 Automatically detecting classes and registering bean definitions

Spring can automatically detect stereotyped classes and register corresponding BeanDefinitions with the ApplicationContext. For example, the following two classes are eligible for such autodetection:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

To autodetect these classes and register the corresponding beans, you need to add @ComponentScan to your @Configuration class, where the basePackages attribute is a common parent package for the two classes. (Alternatively, you can specify a comma-separated list that includes the parent package of each class.)

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}
[Note]Note

for concision, the above may have used the value attribute of the annotation, i.e. ComponentScan("org.example")

The following is an alternative using XML

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>
[Tip]Tip

The use of <context:component-scan> implicitly enables the functionality of <context:annotation-config>. There is usually no need to include the <context:annotation-config> element when using <context:component-scan>.

[Note]Note

The scanning of classpath packages requires the presence of corresponding directory entries in the classpath. When you build JARs with Ant, make sure that you do not activate the files-only switch of the JAR task. Also, classpath directories may not get exposed based on security policies in some environments, e.g. standalone apps on JDK 1.7.0_45 and higher (which requires Trusted-Library setup in your manifests; see http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).

Furthermore, the AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor are both included implicitly when you use the component-scan element. That means that the two components are autodetected and wired together - all without any bean configuration metadata provided in XML.

[Note]Note

You can disable the registration of AutowiredAnnotationBeanPostProcessor and CommonAnnotationBeanPostProcessor by including the annotation-config attribute with a value of false.

5.10.4 Using filters to customize scanning

By default, classes annotated with @Component, @Repository, @Service, @Controller, or a custom annotation that itself is annotated with @Component are the only detected candidate components. However, you can modify and extend this behavior simply by applying custom filters. Add them as includeFilters or excludeFilters parameters of the @ComponentScan annotation (or as include-filter or exclude-filter sub-elements of the component-scan element). Each filter element requires the type and expression attributes. The following table describes the filtering options.

Table 5.5. Filter Types

Filter TypeExample ExpressionDescription

annotation (default)

org.example.SomeAnnotation

An annotation to be present at the type level in target components.

assignable

org.example.SomeClass

A class (or interface) that the target components are assignable to (extend/implement).

aspectj

org.example..*Service+

An AspectJ type expression to be matched by the target components.

regex

org\.example\.Default.*

A regex expression to be matched by the target components class names.

custom

org.example.MyTypeFilter

A custom implementation of the org.springframework.core.type .TypeFilter interface.


The following example shows the configuration ignoring all @Repository annotations and using "stub" repositories instead.

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

and the equivalent using XML

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
[Note]Note

You can also disable the default filters by setting useDefaultFilters=false on the annotation or providing use-default-filters="false" as an attribute of the <component-scan/> element. This will in effect disable automatic detection of classes annotated with @Component, @Repository, @Service, or @Controller.

5.10.5 Defining bean metadata within components

Spring components can also contribute bean definition metadata to the container. You do this with the same @Bean annotation used to define bean metadata within @Configuration annotated classes. Here is a simple example:

@Component
public class FactoryMethodComponent {

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    public void doWork() {
        // Component method implementation omitted
    }

}

This class is a Spring component that has application-specific code contained in its doWork() method. However, it also contributes a bean definition that has a factory method referring to the method publicInstance(). The @Bean annotation identifies the factory method and other bean definition properties, such as a qualifier value through the @Qualifier annotation. Other method level annotations that can be specified are @Scope, @Lazy, and custom qualifier annotations.

[Tip]Tip

In addition to its role for component initialization, the @Lazy annotation may also be placed on injection points marked with @Autowired or @Inject. In this context, it leads to the injection of a lazy-resolution proxy.

Autowired fields and methods are supported as previously discussed, with additional support for autowiring of @Bean methods:

@Component
public class FactoryMethodComponent {

    private static int i;

    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }

    // use of a custom qualifier and autowiring of method parameters

    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }

    @Bean
    @Scope(BeanDefinition.SCOPE_SINGLETON)
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }

    @Bean
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }

}

The example autowires the String method parameter country to the value of the Age property on another bean named privateInstance. A Spring Expression Language element defines the value of the property through the notation #{ <expression> }. For @Value annotations, an expression resolver is preconfigured to look for bean names when resolving expression text.

The @Bean methods in a Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects; such methods are not invoked with normal Java semantics. In contrast, invoking a method or field in an @Bean method within a @Component class has standard Java semantics.

5.10.6 Naming autodetected components

When a component is autodetected as part of the scanning process, its bean name is generated by the BeanNameGenerator strategy known to that scanner. By default, any Spring stereotype annotation ( @Component, @Repository, @Service, and @Controller) that contains a name value will thereby provide that name to the corresponding bean definition.

If such an annotation contains no name value or for any other detected component (such as those discovered by custom filters), the default bean name generator returns the uncapitalized non-qualified class name. For example, if the following two components were detected, the names would be myMovieLister and movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
[Note]Note

If you do not want to rely on the default bean-naming strategy, you can provide a custom bean-naming strategy. First, implement the BeanNameGenerator interface, and be sure to include a default no-arg constructor. Then, provide the fully-qualified class name when configuring the scanner:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

As a general rule, consider specifying the name with the annotation whenever other components may be making explicit references to it. On the other hand, the auto-generated names are adequate whenever the container is responsible for wiring.

5.10.7 Providing a scope for autodetected components

As with Spring-managed components in general, the default and most common scope for autodetected components is singleton. However, sometimes you need other scopes, which Spring 2.5 provides with a new @Scope annotation. Simply provide the name of the scope within the annotation:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
[Note]Note

To provide a custom strategy for scope resolution rather than relying on the annotation-based approach, implement the ScopeMetadataResolver interface, and be sure to include a default no-arg constructor. Then, provide the fully-qualified class name when configuring the scanner:

@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
            scope-resolver="org.example.MyScopeResolver" />
</beans>

When using certain non-singleton scopes, it may be necessary to generate proxies for the scoped objects. The reasoning is described in the section called “将上述作用域的bean作为依赖”. For this purpose, a scoped-proxy attribute is available on the component-scan element. The three possible values are: no, interfaces, and targetClass. For example, the following configuration will result in standard JDK dynamic proxies:

@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        scoped-proxy="interfaces" />
</beans>

5.10.8 Providing qualifier metadata with annotations

The @Qualifier annotation is discussed in Section 5.9.3, “Fine-tuning annotation-based autowiring with qualifiers”. The examples in that section demonstrate the use of the @Qualifier annotation and custom qualifier annotations to provide fine-grained control when you resolve autowire candidates. Because those examples were based on XML bean definitions, the qualifier metadata was provided on the candidate bean definitions using the qualifier or meta sub-elements of the bean element in the XML. When relying upon classpath scanning for autodetection of components, you provide the qualifier metadata with type-level annotations on the candidate class. The following three examples demonstrate this technique:

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
[Note]Note

As with most annotation-based alternatives, keep in mind that the annotation metadata is bound to the class definition itself, while the use of XML allows for multiple beans of the same type to provide variations in their qualifier metadata, because that metadata is provided per-instance rather than per-class.

5.11 Using JSR 330 Standard Annotations

Starting with Spring 3.0, Spring offers support for JSR-330 standard annotations (Dependency Injection). Those annotations are scanned in the same way as the Spring annotations. You just need to have the relevant jars in your classpath.

[Note]Note

If you are using Maven, the javax.inject artifact is available in the standard Maven repository ( http://repo1.maven.org/maven2/javax/inject/javax.inject/1/). You can add the following dependency to your file pom.xml:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

5.11.1 Dependency Injection with @Inject and @Named

Instead of @Autowired, @javax.inject.Inject may be used as follows:

import javax.inject.Inject;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

As with @Autowired, it is possible to use @Inject at the class-level, field-level, method-level and constructor-argument level. If you would like to use a qualified name for the dependency that should be injected, you should use the @Named annotation as follows:

import javax.inject.Inject;
import javax.inject.Named;

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

5.11.2 @Named: a standard equivalent to the @Component annotation

Instead of @Component, @javax.inject.Named may be used as follows:

import javax.inject.Inject;
import javax.inject.Named;

@Named("movieListener")
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

It is very common to use @Component without specifying a name for the component. @Named can be used in a similar fashion:

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // ...

}

When using @Named, it is possible to use component-scanning in the exact same way as when using Spring annotations:

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    ...
}

5.11.3 Limitations of the standard approach

When working with standard annotations, it is important to know that some significant features are not available as shown in the table below:

Table 5.6. Spring annotations vs. standard annotations

Springjavax.inject.*javax.inject restrictions / comments

@Autowired

@Inject

@Inject has no required attribute

@Component

@Named

-

@Scope("singleton")

@Singleton

The JSR-330 default scope is like Spring’s prototype. However, in order to keep it consistent with Spring’s general defaults, a JSR-330 bean declared in the Spring container is a singleton by default. In order to use a scope other than singleton, you should use Spring’s @Scope annotation.

javax.inject also provides a @Scope annotation. Nevertheless, this one is only intended to be used for creating your own annotations.

@Qualifier

@Named

-

@Value

-

no equivalent

@Required

-

no equivalent

@Lazy

-

no equivalent


5.12 基于Java(Java-based)的容器配置

5.12.1 基本概念: @Bean and @Configuration

Spring新的Java配置支持中核心构件是 @Configuration 注释类和 @Bean 注释方法。

@Bean 注释是用来表示一个方法实例化,配置和初始化一个由Spring IoC容器管理的新的对象。对于那些熟悉Spring的 <beans/> XML 配置, @Bean 注释和 <bean/> 一样起着同样的作用。你可以使用 @Bean 注释任何Spring @Component 的方法,但是, 最经常使用的是 @Configuration 注释bean。

@Configuration 注释一个类表明它的主要目的是作为bean定义的来源。此外, @Configuration 注释的类允许inter-bean依赖关系 在相同的类中,通过简单地调用其他 @Bean 方法被定义。最简单的 @Configuration 类定义如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }

}

上面的 AppConfig 类相当于下面的Spring <beans/> XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

The @Bean and @Configuration annotations will be discussed in depth in the sections below. First, however, we’ll cover the various ways of creating a spring container using Java-based configuration.

5.12.2 Instantiating the Spring container using AnnotationConfigApplicationContext

The sections below document Spring’s AnnotationConfigApplicationContext, new in Spring 3.0. This versatile ApplicationContext implementation is capable of accepting not only @Configuration classes as input, but also plain @Component classes and classes annotated with JSR-330 metadata.

When @Configuration classes are provided as input, the @Configuration class itself is registered as a bean definition, and all declared @Bean methods within the class are also registered as bean definitions.

When @Component and JSR-330 classes are provided, they are registered as bean definitions, and it is assumed that DI metadata such as @Autowired or @Inject are used within those classes where necessary.

Simple construction

In much the same way that Spring XML files are used as input when instantiating a ClassPathXmlApplicationContext, @Configuration classes may be used as input when instantiating an AnnotationConfigApplicationContext. This allows for completely XML-free usage of the Spring container:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

As mentioned above, AnnotationConfigApplicationContext is not limited to working only with @Configuration classes. Any @Component or JSR-330 annotated class may be supplied as input to the constructor. For example:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

The above assumes that MyServiceImpl, Dependency1 and Dependency2 use Spring dependency injection annotations such as @Autowired.

Building the container programmatically using register(Class<?>…)

An AnnotationConfigApplicationContext may be instantiated using a no-arg constructor and then configured using the register() method. This approach is particularly useful when programmatically building an AnnotationConfigApplicationContext.

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

Enabling component scanning with scan(String…)

To enable component scanning, just annotate your @Configuration class as follows:

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
    ...
}
[Tip]Tip

Experienced Spring users will be familiar with the XML declaration equivalent from Spring’s context: namespace

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>

In the example above, the com.acme package will be scanned, looking for any @Component-annotated classes, and those classes will be registered as Spring bean definitions within the container. AnnotationConfigApplicationContext exposes the scan(String...) method to allow for the same component-scanning functionality:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
[Note]Note

Remember that @Configuration classes are meta-annotated with @Component, so they are candidates for component-scanning! In the example above, assuming that AppConfig is declared within the com.acme package (or any package underneath), it will be picked up during the call to scan(), and upon refresh() all its @Bean methods will be processed and registered as bean definitions within the container.

使用AnnotationConfigWebApplicationContext支持Web应用

AnnotationConfigWebApplicationContext是一个AnnotationConfigApplicationContextWebApplicationContext变种。这个实现可以用于当配置Spring ContextLoaderListener servlet监听,Spring MVC的DispatcherServlet等等。下面是一个web.xml的片段,配置了一个典型的Spring MVC web应用,注意contextClass的context-param和init-param:

<web-app>
    <!-- 配置ContextLoaderListener使用AnnotationConfigWebApplicationContext
        替换默认的XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- 配置多个路径必须使用逗号或空格分隔的全限定名称的@Configuration注解类,
        也可以使用包名来自动扫描组件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- 像平常一样使用ContextLoaderListener启动根应用上下文 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 像平常一样定义Spring MVC DispatcherServlet -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext
            替换默认的XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- 同上面一样配置路径 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- 将所有/app/* 的请求映射到dispatcher -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

5.12.3 使用@Bean注解

@Bean是一个方法级别的注解,类似于XML`<bean/>元素。 这个注解支持一些由<bean/>提供的属性,例如: <<beans-factory-lifecycle-initializingbean,init-method>>, <<beans-factory-lifecycle-disposablebean,destroy-method>>, <<beans-factory-autowire,autowiring>> 和 `name.

你可以在使用@Configuration@Component的注解类中使用@Bean注解。

Declaring a bean

To declare a bean, simply annotate a method with the @Bean annotation. You use this method to register a bean definition within an ApplicationContext of the type specified as the method’s return value. By default, the bean name will be the same as the method name. The following is a simple example of a @Bean method declaration:

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }

}

The preceding configuration is exactly equivalent to the following Spring XML:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

Both declarations make a bean named transferService available in the ApplicationContext, bound to an object instance of type TransferServiceImpl:

transferService -> com.acme.TransferServiceImpl

Receiving lifecycle callbacks

Any classes defined with the @Bean annotation support the regular lifecycle callbacks and can use the @PostConstruct and @PreDestroy annotations from JSR-250, see JSR-250 annotations for further details.

The regular Spring lifecycle callbacks are fully supported as well. If a bean implements InitializingBean, DisposableBean, or Lifecycle, their respective methods are called by the container.

The standard set of *Aware interfaces such as BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware, and so on are also fully supported.

The @Bean annotation supports specifying arbitrary initialization and destruction callback methods, much like Spring XML’s init-method and destroy-method attributes on the bean element:

public class Foo {
    public void init() {
        // initialization logic
    }
}

public class Bar {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }

}

Of course, in the case of Foo above, it would be equally as valid to call the init() method directly during construction:

@Configuration
public class AppConfig {
    @Bean
    public Foo foo() {
        Foo foo = new Foo();
        foo.init();
    return foo;
    }

    // ...

}
[Tip]Tip

When you work directly in Java, you can do anything you like with your objects and do not always need to rely on the container lifecycle!

Specifying bean scope

Using the @Scope annotation

You can specify that your beans defined with the @Bean annotation should have a specific scope. You can use any of the standard scopes specified in the Bean Scopes section.

The default scope is singleton, but you can override this with the @Scope annotation:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }

}
@Scope and scoped-proxy

Spring offers a convenient way of working with scoped dependencies through scoped proxies. The easiest way to create such a proxy when using the XML configuration is the <aop:scoped-proxy/> element. Configuring your beans in Java with a @Scope annotation offers equivalent support with the proxyMode attribute. The default is no proxy ( ScopedProxyMode.NO), but you can specify ScopedProxyMode.TARGET_CLASS or ScopedProxyMode.INTERFACES.

If you port the scoped proxy example from the XML reference documentation (see preceding link) to our @Bean using Java, it would look like the following:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

Customizing bean naming

By default, configuration classes use a @Bean method’s name as the name of the resulting bean. This functionality can be overridden, however, with the name attribute.

@Configuration
public class AppConfig {

    @Bean(name = "myFoo")
    public Foo foo() {
        return new Foo();
    }

}

Bean aliasing

As discussed in Section 5.3.1, “bean的命名”, it is sometimes desirable to give a single bean multiple names, otherwise known asbean aliasing. The name attribute of the @Bean annotation accepts a String array for this purpose.

@Configuration
public class AppConfig {

    @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }

}

Bean description

Sometimes it is helpful to provide a more detailed textual description of a bean. This can be particularly useful when beans are exposed (perhaps via JMX) for monitoring purposes.

To add a description to a @Bean the @Description annotation can be used:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Foo foo() {
        return new Foo();
    }

}

5.12.4 Using the @Configuration annotation

@Configuration is a class-level annotation indicating that an object is a source of bean definitions. @Configuration classes declare beans via public @Bean annotated methods. Calls to @Bean methods on @Configuration classes can also be used to define inter-bean dependencies. See Section 5.12.1, “基本概念: @Bean and @Configuration” for a general introduction.

Injecting inter-bean dependencies

When @Beans have dependencies on one another, expressing that dependency is as simple as having one bean method call another:

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }

}

In the example above, the foo bean receives a reference to bar via constructor injection.

[Note]Note

This method of declaring inter-bean dependencies only works when the @Bean method is declared within a @Configuration class. You cannot declare inter-bean dependencies using plain @Component classes.

Lookup method injection

As noted earlier, lookup method injection is an advanced feature that you should use rarely. It is useful in cases where a singleton-scoped bean has a dependency on a prototype-scoped bean. Using Java for this type of configuration provides a natural means for implementing this pattern.

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();

        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
    return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

Using Java-configuration support , you can create a subclass of CommandManager where the abstract createCommand() method is overridden in such a way that it looks up a new (prototype) command object:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

Further information about how Java-based configuration works internally

The following example shows a @Bean annotated method being called twice:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }

}

clientDao() has been called once in clientService1() and once in clientService2(). Since this method creates a new instance of ClientDaoImpl and returns it, you would normally expect having 2 instances (one for each service). That definitely would be problematic: in Spring, instantiated beans have a singleton scope by default. This is where the magic comes in: All @Configuration classes are subclassed at startup-time with CGLIB. In the subclass, the child method checks the container first for any cached (scoped) beans before it calls the parent method and creates a new instance. Note that as of Spring 3.2, it is no longer necessary to add CGLIB to your classpath because CGLIB classes have been repackaged under org.springframework and included directly within the spring-core JAR.

[Note]Note

The behavior could be different according to the scope of your bean. We are talking about singletons here.

[Note]Note

There are a few restrictions due to the fact that CGLIB dynamically adds features at startup-time:

  • Configuration classes should not be final
  • They should have a constructor with no arguments

5.12.5 Composing Java-based configurations

Using the @Import annotation

Much as the <import/> element is used within Spring XML files to aid in modularizing configurations, the @Import annotation allows for loading @Bean definitions from another configuration class:

@Configuration
public class ConfigA {

     @Bean
    public A a() {
        return new A();
    }

}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }

}

Now, rather than needing to specify both ConfigA.class and ConfigB.class when instantiating the context, only ConfigB needs to be supplied explicitly:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

This approach simplifies container instantiation, as only one class needs to be dealt with, rather than requiring the developer to remember a potentially large number of @Configuration classes during construction.

Injecting dependencies on imported @Bean definitions

The example above works, but is simplistic. In most practical scenarios, beans will have dependencies on one another across configuration classes. When using XML, this is not an issue, per se, because there is no compiler involved, and one can simply declare ref="someBean" and trust that Spring will work it out during container initialization. Of course, when using @Configuration classes, the Java compiler places constraints on the configuration model, in that references to other beans must be valid Java syntax.

Fortunately, solving this problem is simple. Remember that @Configuration classes are ultimately just another bean in the container - this means that they can take advantage of @Autowired injection metadata just like any other bean!

Let’s consider a more real-world scenario with several @Configuration classes, each depending on beans declared in the others:

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }

}

@Configuration
public class RepositoryConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

In the scenario above, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do you know exactly where the @Autowired AccountRepository bean is declared? It’s not explicit in the code, and this may be just fine. Remember that the Spring Tool Suite provides tooling that can render graphs showing how everything is wired up - that may be all you need. Also, your Java IDE can easily find all declarations and uses of the AccountRepository type, and will quickly show you the location of @Bean methods that return that type.

In cases where this ambiguity is not acceptable and you wish to have direct navigation from within your IDE from one @Configuration class to another, consider autowiring the configuration classes themselves:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate through the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }

}

In the situation above, it is completely explicit where AccountRepository is defined. However, ServiceConfig is now tightly coupled to RepositoryConfig; that’s the tradeoff. This tight coupling can be somewhat mitigated by using interface-based or abstract class-based @Configuration classes. Consider the following:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();

}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }

}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

Now ServiceConfig is loosely coupled with respect to the concrete DefaultRepositoryConfig, and built-in IDE tooling is still useful: it will be easy for the developer to get a type hierarchy of RepositoryConfig implementations. In this way, navigating @Configuration classes and their dependencies becomes no different than the usual process of navigating interface-based code.

Conditionally including @Configuration classes or @Beans

It is often useful to conditionally enable to disable a complete @Configuration class, or even individual @Bean methods, based on some arbitrary system state. One common example of this it to use the @Profile annotation to active beans only when a specific profile has been enabled in the Spring Environment (see Section 5.13.1, “Bean定义配置文件” for details).

The @Profile annotation is actually implemented using a much more flexible annotation called @Conditional. The @Conditional annotation indicates specific org.springframework.context.annotation.Condition implementations that should be consulted before a @Bean is registered.

Implementations of the Condition interface simply provide a matches(...) method that returns true or false. For example, here is the actual Condition implementation used for @Profile:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

See the @Conditional javadocs for more detail.

Combining Java and XML configuration

Spring’s @Configuration class support does not aim to be a 100% complete replacement for Spring XML. Some facilities such as Spring XML namespaces remain an ideal way to configure the container. In cases where XML is convenient or necessary, you have a choice: either instantiate the container in an "XML-centric" way using, for example, ClassPathXmlApplicationContext, or in a "Java-centric" fashion using AnnotationConfigApplicationContext and the @ImportResource annotation to import XML as needed.

XML-centric use of @Configuration classes

It may be preferable to bootstrap the Spring container from XML and include @Configuration classes in an ad-hoc fashion. For example, in a large existing codebase that uses Spring XML, it will be easier to create @Configuration classes on an as-needed basis and include them from the existing XML files. Below you’ll find the options for using @Configuration classes in this kind of "XML-centric" situation.

Remember that @Configuration classes are ultimately just bean definitions in the container. In this example, we create a @Configuration class named AppConfig and include it within system-test-config.xml as a <bean/> definition. Because <context:annotation-config/> is switched on, the container will recognize the @Configuration annotation, and process the @Bean methods declared in AppConfig properly.

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }

}
system-test-config.xml
<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
[Note]Note

In system-test-config.xml above, the AppConfig<bean/> does not declare an id element. While it would be acceptable to do so, it is unnecessary given that no other bean will ever refer to it, and it is unlikely that it will be explicitly fetched from the container by name. Likewise with the DataSource bean - it is only ever autowired by type, so an explicit bean id is not strictly required.

Because @Configuration is meta-annotated with @Component, @Configuration-annotated classes are automatically candidates for component scanning. Using the same scenario as above, we can redefine system-test-config.xml to take advantage of component-scanning. Note that in this case, we don’t need to explicitly declare <context:annotation-config/>, because <context:component-scan/> enables all the same functionality.

system-test-config.xml
<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
@Configuration class-centric use of XML with @ImportResource

In applications where @Configuration classes are the primary mechanism for configuring the container, it will still likely be necessary to use at least some XML. In these scenarios, simply use @ImportResource and define only as much XML as is needed. Doing so achieves a "Java-centric" approach to configuring the container and keeps XML to a bare minimum.

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }

}
properties-config.xml
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

5.13 Environment抽象

Environment 是集成在容器中的抽象,他包含了两个两个方面:profilesproperties.

profile是一个命名,是一组逻辑上bean定义的组,只有相应的profile被激活的情况下才会起作用。可以通过XML或者注解将bean分配给一个profile,Environment对象在profile中的角色是判断哪一个profile应该在当前激活和哪一个profile应该在默认情况下激活。

属性在几乎所有应用中都扮演了非常重要的角色,并且可能来源于各种各样的资源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,点对点的属性对象,映射等等。Environment对象在属性中的角色是提供一个方便的服务接口来配置属性资源和解决它们的属性。

5.13.1 Bean定义配置文件

Beand定义配置文件是核心容器的一种机制,它允许为不同的bean在不同的环境中注册。 environment这个词可以意味着不同的事情不同的用户,而且这个功能可以帮助很多用例,包括:

  • 在工作中使用内存数据源并且在QA和生产环境中通过JDNI查找相同的数据源
  • 只有当部署应用到一个性能测试环境时注册监视工具
  • 给客户A注册定制的bean实现而不需要给客户B时

我们考虑第一种情况,在一个需要DataSource的实际应用中。在测试环境时,配置可能像下面这样:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("my-schema.sql")
        .addScript("my-test-data.sql")
        .build();
}

现在我们考虑如何将这个应用部署到QA或者生产环境,假设应用程序的数据源将在生产应用服务器的JNDI目录中注册,我们的dataSource bean现在看起来像这样:

@Bean
public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

现在的问题是如何基于当前环境在这两个变体之间切换。随着时间的推移,Spring用户设计了多种方法来完成这件事。通常依靠系统环境变量的组合和XML`<import/>语句包含${placeholder}`标记来解决正确的配置文件路径。Bean定义配置文件是一个核心容器的功能,它提供了解决这个问题的方法。

如果我们归纳上面这个基于环境bean定义的例子,我们需要在某些情况下注册某些bean,而不是其他的。你可能想在A情况下注册一个bean定义的配置,在B情况下注册另一个配置。我们先看看如何更新配置来反映这一需求。

@Profile

@Profile 注解用于当一个或多个配置文件激活的时候,用来指定组件是否有资格注册。使用上面的例子,我们可以按如下方式重写dataSource配置:

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

@Profile 可以用作元注解,可以用来编写自定义的注解。下面的例子定义了一个自定义的@Production注解,可以用来替代@Profile("production"):

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

@Profile 可以用在方法级别,可以只包含配置类中的一个特殊的bean:

@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }

    @Bean
    @Profile("production")
    public DataSource productionDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}

[提示]

如果带@Configuration的类被标记了@Profile,那么只有当这个配置是激活状态的时候,这个类中标记@Bean的方法和@Import关联的类才有效,否则就会被忽略。 如果一个@Component@Configuration类标记了@Profile({"p1", "p2"}),这样的类只有当p1和(或)p2激活的时候才有效。如果一个配置使用了!前缀,只有当这个配置不激活的时候才有效。例如@Profile({"p1", "!p2"}),只有当p1激活,p2不激活的时候才有效。

5.13.2 XML的Bean定义配置文件

XML对应的配置元素<beans>新增了profile属性,我们上面的配置示例可以重写为下面的两个XML文件:

<beans profile="dev"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xsi:schemaLocation="...">

    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
        <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
</beans>
<beans profile="production"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>

还可以避免分开写,将<beans/>元素都写到同一个文件中:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <!-- 其他 bean 定义,beans只能写在最后面 -->

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>
</beans>

spring-bean.xsd强制允许将<beans>元素定义在文件的最后面,这有助于在XML文件中提供灵活的方式而又不引起混乱。

启用配置文件

现在我们已经更新了我们的配置,我们还需要指示那个配置文件处于激活状态。如果我们现在启动我们的示例程序,我们会看到抛出NoSuchBeanDefinitionException异常,因为我们的容器找不到名为dataSource的bean对象。

激活配置文件可以采取多种方式,但是最直接的方式就是以编程的方式使用ApplicationContext API:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();

此外,配置文件还可以以声明的方式通过spring.profiles.active属性来激活,可以通过系统环境变量,JVM系统属性,web.xml中的servlet上下文参数,甚至是JNDI中的一个条目来设置(Section 5.13.3, “PropertySource的抽象”)。

注意,配置文件不是“二选一”的;你可以一次激活多个配置文件。以编程方式,只需要在setActiveProfiles()方法提供多个配置文件的名字即可,这里接收的String...可变参数:

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");

声明形式中,spring.profiles.active可以接收逗号隔开的配置名字列表:

-Dspring.profiles.active="profile1,profile2"

默认配置

default配置文件代表的是默认启用的配置文件。考虑以下:

@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}

如果没有配置文件激活,上面的dataSource就会被创建。这提供了一种默认的方式。如果有任何一个配置文件启用,default配置就不会生效。

默认配置文件的名字(default)可以通过EnvironmentsetDefaultProfiles方法或者spring.profiles.default属性修改。

5.13.3 PropertySource的抽象

Spring的抽象环境提供了一个可配置的属性源的层次结构的搜索操作。为了充分说明,考虑下面的情况:

ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);

在上面的代码片段中,我们看到了一种高层次的方式来询问Spring当前的环境中是否定义了foo属性。 为了解答这个问题,Environment对象执行了一组http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/env/PropertySource.html[PropertySource]对象上的搜索。 PropertySource是一个简单的抽象在任何源上的键值对,Spring的http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/env/StandardEnvironment.html[StandardEnvironment] 配置了两个属性源对象 — 一组是JVM系统属性(a la System.getProperties()),另一组是系统环境变量(a la System.getenv())。

[注意]

These default property sources are present for StandardEnvironment, for use in standalone applications. StandardServletEnvironment is populated with additional default property sources including servlet config and servlet context parameters. StandardPortletEnvironment similarly has access to portlet config and portlet context parameters as property sources. Both can optionally enable a JndiPropertySource. See Javadoc for details.

Concretely, when using the StandardEnvironment, the call to env.containsProperty("foo") will return true if a foo system property or foo environment variable is present at runtime.

[Tip]Tip

The search performed is hierarchical. By default, system properties have precedence over environment variables, so if the foo property happens to be set in both places during a call to env.getProperty("foo"), the system property value will win and be returned preferentially over the environment variable.

Most importantly, the entire mechanism is configurable. Perhaps you have a custom source of properties that you’d like to integrate into this search. No problem — simply implement and instantiate your own PropertySource and add it to the set of PropertySources for the current Environment:

ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());

In the code above, MyPropertySource has been added with highest precedence in the search. If it contains a foo property, it will be detected and returned ahead of any foo property in any other PropertySource. The MutablePropertySources API exposes a number of methods that allow for precise manipulation of the set of property sources.

5.13.4 @PropertySource

The @PropertySource annotation provides a convenient and declarative mechanism for adding a PropertySource to Spring’s Environment.

Given a file "app.properties" containing the key/value pair testbean.name=myTestBean, the following @Configuration class uses @PropertySource in such a way that a call to testBean.getName() will return "myTestBean".

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

Any ${...} placeholders present in a @PropertySource resource location will be resolved against the set of property sources already registered against the environment. For example:

@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

Assuming that "my.placeholder" is present in one of the property sources already registered, e.g. system properties or environment variables, the placeholder will be resolved to the corresponding value. If not, then "default/path" will be used as a default. If no default is specified and a property cannot be resolved, an IllegalArgumentException will be thrown.

5.13.5 Placeholder resolution in statements

Historically, the value of placeholders in elements could be resolved only against JVM system properties or environment variables. No longer is this the case. Because the Environment abstraction is integrated throughout the container, it’s easy to route resolution of placeholders through it. This means that you may configure the resolution process in any way you like: change the precedence of searching through system properties and environment variables, or remove them entirely; add your own property sources to the mix as appropriate.

Concretely, the following statement works regardless of where the customer property is defined, as long as it is available in the Environment:

<beans>
    <import resource="com/bank/service/${customer}-config.xml"/>
</beans>

5.14 注册一个LoadTimeWeaver

Spring使用LoadTimeWeaver动态转换已经加载到JVM虚拟机中的类。

你可以在@Configuration类上增加@EnableLoadTimeWeaving注解来启用该功能:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {

}

或者在XML配置中使用context:load-time-weaver元素:

<beans>
    <context:load-time-weaver/>
</beans>

ApplicationContext启用该功能后,所有ApplicationContext中的bean都可以实现LoadTimeWeaverAware接口,从而获得对该加载时织入器实例的引用。 当结合Spring的JPA 支持的时候特别的有用,JPA类转换可能需要加载时的织入。查阅LocalContainerEntityManagerFactoryBean帮助文档获得更多细节,更多AspectJ装载时织入,查看Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”

5.15 ApplicationContext 的附加功能

我们在本章的介绍中讨论过,org.springframework.beans.factory 包提供了管理和操作 bean 的基本功能,包括编程方式。org.springframework.context 包增加了 ApplicationContext 接口。它继承了 BeanFactory 接口,除了扩展其他接口外还提供了更为 application framework-oriented style 的附加功能。很多人通过完全的声明方式来使用 ApplicationContext,而非编程式地创建它,但是还可以依靠类似 ContextLoader 这样的的支持类来自动初始化 ApplicationContext,并将它作为 Java EE web 应用程序的普通的启动过程的一部分。

在更为面向框架的风格上,为了增强 BeanFactory 的功能,context 包还提供了以下功能:

  • 通过 MessageSource 接口,访问 i18n 样式的信息
  • 通过 ResourceLoader 接口,访问资源,比如 URL 和文件。
  • Event publication to beans implementing the ApplicationListener interface, through the use of the ApplicationEventPublisher interface.
  • 通过 HierarchicalBeanFactory 接口,加载多个(分层)上下文,这样使得每一个上下文可以关注特定的层,比如 web 层。

5.15.1 Internationalization using MessageSource

The ApplicationContext interface extends an interface called MessageSource, and therefore provides internationalization (i18n) functionality. Spring also provides the interface HierarchicalMessageSource, which can resolve messages hierarchically. Together these interfaces provide the foundation upon which Spring effects message resolution. The methods defined on these interfaces include:

  • String getMessage(String code, Object[] args, String default, Locale loc): The basic method used to retrieve a message from the MessageSource. When no message is found for the specified locale, the default message is used. Any arguments passed in become replacement values, using the MessageFormat functionality provided by the standard library.
  • String getMessage(String code, Object[] args, Locale loc): Essentially the same as the previous method, but with one difference: no default message can be specified; if the message cannot be found, a NoSuchMessageException is thrown.
  • String getMessage(MessageSourceResolvable resolvable, Locale locale): All properties used in the preceding methods are also wrapped in a class named MessageSourceResolvable, which you can use with this method.

When an ApplicationContext is loaded, it automatically searches for a MessageSource bean defined in the context. The bean must have the name messageSource. If such a bean is found, all calls to the preceding methods are delegated to the message source. If no message source is found, the ApplicationContext attempts to find a parent containing a bean with the same name. If it does, it uses that bean as the MessageSource. If the ApplicationContext cannot find any source for messages, an empty DelegatingMessageSource is instantiated in order to be able to accept calls to the methods defined above.

Spring provides two MessageSource implementations, ResourceBundleMessageSource and StaticMessageSource. Both implement HierarchicalMessageSource in order to do nested messaging. The StaticMessageSource is rarely used but provides programmatic ways to add messages to the source. The ResourceBundleMessageSource is shown in the following example:

<beans>
    <bean id="messageSource"
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>format</value>
                <value>exceptions</value>
                <value>windows</value>
            </list>
        </property>
    </bean>
</beans>

In the example it is assumed you have three resource bundles defined in your classpath called format, exceptions and windows. Any request to resolve a message will be handled in the JDK standard way of resolving messages through ResourceBundles. For the purposes of the example, assume the contents of two of the above resource bundle files are…

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

A program to execute the MessageSource functionality is shown in the next example. Remember that all ApplicationContext implementations are also MessageSource implementations and so can be cast to the MessageSource interface.

public static void main(String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("message", null, "Default", null);
    System.out.println(message);
}

The resulting output from the above program will be…

Alligators rock!

So to summarize, the MessageSource is defined in a file called beans.xml, which exists at the root of your classpath. The messageSource bean definition refers to a number of resource bundles through its basenames property. The three files that are passed in the list to the basenames property exist as files at the root of your classpath and are called format.properties, exceptions.properties, and windows.properties respectively.

The next example shows arguments passed to the message lookup; these arguments will be converted into Strings and inserted into placeholders in the lookup message.

<beans>

    <!-- this MessageSource is being used in a web application -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="exceptions"/>
    </bean>

    <!-- lets inject the above MessageSource into this POJO -->
    <bean id="example" class="com.foo.Example">
        <property name="messages" ref="messageSource"/>
    </bean>

</beans>
public class Example {

    private MessageSource messages;

    public void setMessages(MessageSource messages) {
        this.messages = messages;
    }

    public void execute() {
        String message = this.messages.getMessage("argument.required",
            new Object [] {"userDao"}, "Required", null);
        System.out.println(message);
    }

}

The resulting output from the invocation of the execute() method will be…

The userDao argument is required.

With regard to internationalization (i18n), Spring’s various MessageResource implementations follow the same locale resolution and fallback rules as the standard JDK ResourceBundle. In short, and continuing with the example messageSource defined previously, if you want to resolve messages against the British (en-GB) locale, you would create files called format_en_GB.properties, exceptions_en_GB.properties, and windows_en_GB.properties respectively.

Typically, locale resolution is managed by the surrounding environment of the application. In this example, the locale against which (British) messages will be resolved is specified manually.

# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) {
    MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
    String message = resources.getMessage("argument.required",
        new Object [] {"userDao"}, "Required", Locale.UK);
    System.out.println(message);
}

The resulting output from the running of the above program will be…

Ebagum lad, the userDao argument is required, I say, required.

You can also use the MessageSourceAware interface to acquire a reference to any MessageSource that has been defined. Any bean that is defined in an ApplicationContext that implements the MessageSourceAware interface is injected with the application context’s MessageSource when the bean is created and configured.

[Note]Note

As an alternative to ResourceBundleMessageSource, Spring provides a ReloadableResourceBundleMessageSource class. This variant supports the same bundle file format but is more flexible than the standard JDK based ResourceBundleMessageSource implementation. In particular, it allows for reading files from any Spring resource location (not just from the classpath) and supports hot reloading of bundle property files (while efficiently caching them in between). Check out the ReloadableResourceBundleMessageSource javadocs for details.

5.15.2 Standard and Custom Events

Event handling in the ApplicationContext is provided through the ApplicationEvent class and ApplicationListener interface. If a bean that implements the ApplicationListener interface is deployed into the context, every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified. Essentially, this is the standard Observer design pattern. Spring provides the following standard events:

Table 5.7. Built-in Events

EventExplanation

ContextRefreshedEvent

Published when the ApplicationContext is initialized or refreshed, for example, using the refresh() method on the ConfigurableApplicationContext interface. "Initialized" here means that all beans are loaded, post-processor beans are detected and activated, singletons are pre-instantiated, and the ApplicationContext object is ready for use. As long as the context has not been closed, a refresh can be triggered multiple times, provided that the chosen ApplicationContext actually supports such "hot" refreshes. For example, XmlWebApplicationContext supports hot refreshes, but GenericApplicationContext does not.

ContextStartedEvent

Published when the ApplicationContext is started, using the start() method on the ConfigurableApplicationContext interface. "Started" here means that all Lifecycle beans receive an explicit start signal. Typically this signal is used to restart beans after an explicit stop, but it may also be used to start components that have not been configured for autostart , for example, components that have not already started on initialization.

ContextStoppedEvent

Published when the ApplicationContext is stopped, using the stop() method on the ConfigurableApplicationContext interface. "Stopped" here means that all Lifecycle beans receive an explicit stop signal. A stopped context may be restarted through a start() call.

ContextClosedEvent

Published when the ApplicationContext is closed, using the close() method on the ConfigurableApplicationContext interface. "Closed" here means that all singleton beans are destroyed. A closed context reaches its end of life; it cannot be refreshed or restarted.

RequestHandledEvent

A web-specific event telling all beans that an HTTP request has been serviced. This event is published after the request is complete. This event is only applicable to web applications using Spring’s DispatcherServlet.


You can also create and publish your own custom events. This example demonstrates a simple class that extends Spring’s ApplicationEvent base class:

public class BlackListEvent extends ApplicationEvent {

    private final String address;
    private final String test;

    public BlackListEvent(Object source, String address, String test) {
        super(source);
        this.address = address;
        this.test = test;
    }

    // accessor and other methods...

}

To publish a custom ApplicationEvent, call the publishEvent() method on an ApplicationEventPublisher. Typically this is done by creating a class that implements ApplicationEventPublisherAware and registering it as a Spring bean. The following example demonstrates such a class:

public class EmailService implements ApplicationEventPublisherAware {

    private List<String> blackList;
    private ApplicationEventPublisher publisher;

    public void setBlackList(List<String> blackList) {
        this.blackList = blackList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String text) {
        if (blackList.contains(address)) {
            BlackListEvent event = new BlackListEvent(this, address, text);
            publisher.publishEvent(event);
            return;
        }
        // send email...
    }

}

At configuration time, the Spring container will detect that EmailService implements ApplicationEventPublisherAware and will automatically call setApplicationEventPublisher(). In reality, the parameter passed in will be the Spring container itself; you’re simply interacting with the application context via its ApplicationEventPublisher interface.

To receive the custom ApplicationEvent, create a class that implements ApplicationListener and register it as a Spring bean. The following example demonstrates such a class:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlackListEvent event) {
        // notify appropriate parties via notificationAddress...
    }

}

Notice that ApplicationListener is generically parameterized with the type of your custom event, BlackListEvent. This means that the onApplicationEvent() method can remain type-safe, avoiding any need for downcasting. You may register as many event listeners as you wish, but note that by default event listeners receive events synchronously. This means the publishEvent() method blocks until all listeners have finished processing the event. One advantage of this synchronous and single-threaded approach is that when a listener receives an event, it operates inside the transaction context of the publisher if a transaction context is available. If another strategy for event publication becomes necessary, refer to the JavaDoc for Spring’s ApplicationEventMulticaster interface.

The following example shows the bean definitions used to register and configure each of the classes above:

<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</bean>

Putting it all together, when the sendEmail() method of the emailService bean is called, if there are any emails that should be blacklisted, a custom event of type BlackListEvent is published. The blackListNotifier bean is registered as an ApplicationListener and thus receives the BlackListEvent, at which point it can notify appropriate parties.

[Note]Note

Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context. However, for more sophisticated enterprise integration needs, the separately-maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build upon the well-known Spring programming model.

5.15.3 Convenient access to low-level resources

For optimal usage and understanding of application contexts, users should generally familiarize themselves with Spring’s Resource abstraction, as described in the chapter Chapter 6, Resources.

An application context is a ResourceLoader, which can be used to load Resources. A Resource is essentially a more feature rich version of the JDK class java.net.URL, in fact, the implementations of the Resource wrap an instance of java.net.URL where appropriate. A Resource can obtain low-level resources from almost any location in a transparent fashion, including from the classpath, a filesystem location, anywhere describable with a standard URL, and some other variations. If the resource location string is a simple path without any special prefixes, where those resources come from is specific and appropriate to the actual application context type.

You can configure a bean deployed into the application context to implement the special callback interface, ResourceLoaderAware, to be automatically called back at initialization time with the application context itself passed in as the ResourceLoader. You can also expose properties of type Resource, to be used to access static resources; they will be injected into it like any other properties. You can specify those Resource properties as simple String paths, and rely on a special JavaBean PropertyEditor that is automatically registered by the context, to convert those text strings to actual Resource objects when the bean is deployed.

The location path or paths supplied to an ApplicationContext constructor are actually resource strings, and in simple form are treated appropriately to the specific context implementation. ClassPathXmlApplicationContext treats a simple location path as a classpath location. You can also use location paths (resource strings) with special prefixes to force loading of definitions from the classpath or a URL, regardless of the actual context type.

5.15.4 web 应用程序中 ApplicationContext 的便利实例化

你可以声明式地创建 ApplicationContext 实例,比如通过 ContextLoader。当然你也可以编程式地创建 ApplicationContext 实例,只需要使用一个 ApplicationContext 实现。

你可以使用 ContextLoaderListener 来注册一个 ApplicationContext,如下:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

该监听器会检查 contextConfigLocation 参数。如果该参数不存在,监听器会使用 /WEB-INF/applicationContext.xml 作为默认值。如果该参数存在,该监听器通过使用预置的分隔符(逗号,分号和空白符)来分隔字符串,并使用分隔后的值作为要查找的应用上下文的路径。同时支持 Ant 风格的路径模式。比如 /WEB-INF/*Context.xml 表示位于 "WEB-INF" 目录下且名称以 "Context.xml" 作为结尾的所有文件,/WEB-INF/**/*Context.xml 表示 "WEB-INF" 下的任意子目录下的所有这类文件。

5.15.5 Deploying a Spring ApplicationContext as a Java EE RAR file

It is possible to deploy a Spring ApplicationContext as a RAR file, encapsulating the context and all of its required bean classes and library JARs in a Java EE RAR deployment unit. This is the equivalent of bootstrapping a standalone ApplicationContext, just hosted in Java EE environment, being able to access the Java EE servers facilities. RAR deployment is more natural alternative to scenario of deploying a headless WAR file, in effect, a WAR file without any HTTP entry points that is used only for bootstrapping a Spring ApplicationContext in a Java EE environment.

RAR deployment is ideal for application contexts that do not need HTTP entry points but rather consist only of message endpoints and scheduled jobs. Beans in such a context can use application server resources such as the JTA transaction manager and JNDI-bound JDBC DataSources and JMS ConnectionFactory instances, and may also register with the platform’s JMX server - all through Spring’s standard transaction management and JNDI and JMX support facilities. Application components can also interact with the application server’s JCA WorkManager through Spring’s TaskExecutor abstraction.

Check out the JavaDoc of the SpringContextResourceAdapter class for the configuration details involved in RAR deployment.

For a simple deployment of a Spring ApplicationContext as a Java EE RAR file: package all application classes into a RAR file, which is a standard JAR file with a different file extension. Add all required library JARs into the root of the RAR archive. Add a "META-INF/ra.xml" deployment descriptor (as shown in SpringContextResourceAdapter's JavaDoc) and the corresponding Spring XML bean definition file(s) (typically "META-INF/applicationContext.xml"), and drop the resulting RAR file into your application server’s deployment directory.

[Note]Note

Such RAR deployment units are usually self-contained; they do not expose components to the outside world, not even to other modules of the same application. Interaction with a RAR-based ApplicationContext usually occurs through JMS destinations that it shares with other modules. A RAR-based ApplicationContext may also, for example, schedule some jobs, reacting to new files in the file system (or the like). If it needs to allow synchronous access from the outside, it could for example export RMI endpoints, which of course may be used by other application modules on the same machine.

5.16 The BeanFactory

The BeanFactory provides the underlying basis for Spring’s IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring. The BeanFactory and related interfaces, such as BeanFactoryAware, InitializingBean, DisposableBean, are still present in Spring for the purposes of backward compatibility with the large number of third-party frameworks that integrate with Spring. Often third-party components that can not use more modern equivalents such as @PostConstruct or @PreDestroy in order to remain compatible with JDK 1.4 or to avoid a dependency on JSR-250.

This section provides additional background into the differences between the BeanFactory and ApplicationContext and how one might access the IoC container directly through a classic singleton lookup.

5.16.1 BeanFactory or ApplicationContext?

Use an ApplicationContext unless you have a good reason for not doing so.

Because the ApplicationContext includes all functionality of the BeanFactory, it is generally recommended over the BeanFactory, except for a few situations such as in an Applet where memory consumption might be critical and a few extra kilobytes might make a difference. However, for most typical enterprise applications and systems, the ApplicationContext is what you will want to use. Spring makes heavy use of the BeanPostProcessor extension point (to effect proxying and so on). If you use only a plain BeanFactory, a fair amount of support such as transactions and AOP will not take effect, at least not without some extra steps on your part. This situation could be confusing because nothing is actually wrong with the configuration.

The following table lists features provided by the BeanFactory and ApplicationContext interfaces and implementations.

Table 5.8. Feature Matrix

FeatureBeanFactoryApplicationContext

Bean instantiation/wiring

Yes

Yes

Automatic BeanPostProcessor registration

No

Yes

Automatic BeanFactoryPostProcessor registration

No

Yes

Convenient MessageSource access (for i18n)

No

Yes

ApplicationEvent publication

No

Yes


To explicitly register a bean post-processor with a BeanFactory implementation, you must write code like this:

ConfigurableBeanFactory factory = new XmlBeanFactory(...);

// now register any needed BeanPostProcessor instances
MyBeanPostProcessor postProcessor = new MyBeanPostProcessor();
factory.addBeanPostProcessor(postProcessor);

// now start using the factory

To explicitly register a BeanFactoryPostProcessor when using a BeanFactory implementation, you must write code like this:

XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));

// bring in some property values from a Properties file
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// now actually do the replacement
cfg.postProcessBeanFactory(factory);

In both cases, the explicit registration step is inconvenient, which is one reason why the various ApplicationContext implementations are preferred above plain BeanFactory implementations in the vast majority of Spring-backed applications, especially when using BeanFactoryPostProcessors and BeanPostProcessors. These mechanisms implement important functionality such as property placeholder replacement and AOP.

5.16.2 Glue code and the evil singleton

It is best to write most application code in a dependency-injection (DI) style, where that code is served out of a Spring IoC container, has its own dependencies supplied by the container when it is created, and is completely unaware of the container. However, for the small glue layers of code that are sometimes needed to tie other code together, you sometimes need a singleton (or quasi-singleton) style access to a Spring IoC container. For example, third-party code may try to construct new objects directly ( Class.forName() style), without the ability to get these objects out of a Spring IoC container.If the object constructed by the third-party code is a small stub or proxy, which then uses a singleton style access to a Spring IoC container to get a real object to delegate to, then inversion of control has still been achieved for the majority of the code (the object coming out of the container). Thus most code is still unaware of the container or how it is accessed, and remains decoupled from other code, with all ensuing benefits. EJBs may also use this stub/proxy approach to delegate to a plain Java implementation object, retrieved from a Spring IoC container. While the Spring IoC container itself ideally does not have to be a singleton, it may be unrealistic in terms of memory usage or initialization times (when using beans in the Spring IoC container such as a Hibernate SessionFactory) for each bean to use its own, non-singleton Spring IoC container.

Looking up the application context in a service locator style is sometimes the only option for accessing shared Spring-managed components, such as in an EJB 2.1 environment, or when you want to share a single ApplicationContext as a parent to WebApplicationContexts across WAR files. In this case you should look into using the utility class ContextSingletonBeanFactoryLocator locator that is described in this Spring team blog entry.