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 命名beans

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

基于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定义外定义别名

In a bean definition itself, you can supply more than one name for the bean, by using a combination of up to one name specified by the id attribute, and any number of other names in the name attribute. These names can be equivalent aliases to the same bean, and are useful for some situations, such as allowing each component in an application to refer to a common dependency by using a bean name that is specific to that component itself.

Specifying all aliases where the bean is actually defined is not always adequate, however. It is sometimes desirable to introduce an alias for a bean that is defined elsewhere. This is commonly the case in large systems where configuration is split amongst each subsystem, each subsystem having its own set of object definitions. In XML-based configuration metadata, you can use the <alias/> element to accomplish this.

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

In this case, a bean in the same container which is named fromName, may also, after the use of this alias definition, be referred to as toName.

For example, the configuration metadata for subsystem A may refer to a DataSource via the name subsystemA-dataSource. The configuration metadata for subsystem B may refer to a DataSource via the name subsystemB-dataSource. When composing the main application that uses both these subsystems the main application refers to the DataSource via the name myApp-dataSource. To have all three names refer to the same object you add to the MyApp configuration metadata the following aliases definitions:

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

Now each component and the main application can refer to the dataSource through a name that is unique and guaranteed not to clash with any other definition (effectively creating a namespace), yet they refer to the same bean.

5.3.2 Instantiating beans

A bean definition essentially is a recipe for creating one or more objects. The container looks at the recipe for a named bean when asked, and uses the configuration metadata encapsulated by that bean definition to create (or acquire) an actual object.

If you use XML-based configuration metadata, you specify the type (or class) of object that is to be instantiated in the class attribute of the <bean/> element. This class attribute, which internally is a Class property on a BeanDefinition instance, is usually mandatory. (For exceptions, see the section called “Instantiation using an instance factory method” and Section 5.7, “Bean definition inheritance”.) You use the Class property in one of two ways:

  • Typically, to specify the bean class to be constructed in the case where the container itself directly creates the bean by calling its constructor reflectively, somewhat equivalent to Java code using the new operator.
  • To specify the actual class containing the static factory method that will be invoked to create the object, in the less common case where the container invokes a static factory method on a class to create the bean. The object type returned from the invocation of the static factory method may be the same class or another class entirely.

Instantiation with a constructor

When you create a bean by the constructor approach, all normal classes are usable by and compatible with Spring. That is, the class being developed does not need to implement any specific interfaces or to be coded in a specific fashion. Simply specifying the bean class should suffice. However, depending on what type of IoC you use for that specific bean, you may need a default (empty) constructor.

The Spring IoC container can manage virtually any class you want it to manage; it is not limited to managing true JavaBeans. Most Spring users prefer actual JavaBeans with only a default (no-argument) constructor and appropriate setters and getters modeled after the properties in the container. You can also have more exotic non-bean-style classes in your container. If, for example, you need to use a legacy connection pool that absolutely does not adhere to the JavaBean specification, Spring can manage it as well.

With XML-based configuration metadata you can specify your bean class as follows:

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

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

For details about the mechanism for supplying arguments to the constructor (if required) and setting object instance properties after the object is constructed, see Injecting Dependencies.

Instantiation with a static factory method

When defining a bean that you create with a static factory method, you use the class attribute to specify the class containing the static factory method and an attribute named factory-method to specify the name of the factory method itself. You should be able to call this method (with optional arguments as described later) and return a live object, which subsequently is treated as if it had been created through a constructor. One use for such a bean definition is to call static factories in legacy code.

The following bean definition specifies that the bean will be created by calling a factory-method. The definition does not specify the type (class) of the returned object, only the class containing the factory method. In this example, the createInstance() method must be a static method.

<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;
    }
}

For details about the mechanism for supplying (optional) arguments to the factory method and setting object instance properties after the object is returned from the factory, see Dependencies and configuration in detail.

Instantiation using an instance factory method

Similar to instantiation through a static factory method, instantiation with an instance factory method invokes a non-static method of an existing bean from the container to create a new bean. To use this mechanism, leave the class attribute empty, and in the factory-bean attribute, specify the name of a bean in the current (or parent/ancestor) container that contains the instance method that is to be invoked to create the object. Set the name of the factory method itself with the factory-method attribute.

<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<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;
    }
}

One factory class can also hold more than one factory method as shown here:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</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;
    }

}

This approach shows that the factory bean itself can be managed and configured through dependency injection (DI). See Dependencies and configuration in detail.

[Note]Note

In Spring documentation, factory bean refers to a bean that is configured in the Spring container that will create objects through an instance or static factory method. By contrast, FactoryBean (notice the capitalization) refers to a Spring-specific 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", "ultim