Spring Framwork 官方文档5.0.8翻译 第一章:The IoC Container

最近想系统的学习一下Spring的相关知识,在这里分享一下。也希望能帮助那些对英文技术文档阅读比较困难的朋友。
接下来会持续更新,如果文中有解释的不当之处,欢迎指正,谢谢!~

2018/11/2 ,在翻译的过程中,我发现有些地方英文表达的有些冗余,我可能不会直译,请注意。

英文文档

1.1. 介绍IoC容器和Bean

本章涵盖了 SpringFramwork 中 IoC (控制反转)的实现原理。IoC 也被叫做 DI (依赖注入),它是一个对象定义依赖关系的过程,也就是对象只能通过构造函数参数,工厂方法参数,或者(对象通过new或者工厂方法返回以后)设置属性来定义与其他对象的依赖关系,然后容器在创建Bean时注入这些依赖项。这个过程本质是反转的,因此得名控制反转,其含义是bean自身通过类的构造函数控制对象的初始化或者依赖发现,还是通过像 Service Locator Pattern机制。(关于什么服务定位模式,可以阅读这篇博客Service Locator Pattern)。

org.springframework.beansorg.springframework.contextIoC 容器的基础包,BeanFactory接口提供了高级配置机制,可以管理任何类型的对象。ApplicationContextBeanFactory 的子接口,它添加了易与 Spring AOP 集成,消息资源处理(如:国际化),事件发布以及应用层特定的的contexts,比如web应用的WebApplicationContext等功能。

简而言之,BeanFactory接口提供了配置框架和基本功能,ApplicationContext添加了企业级应用特用的功能点。ApplicationContextBeanFactory 一个完整的超集,也是在本章中专门用于解释 IoC Container的contexts,有关使用 BeanFactory 而不是使用 ApplicationContext 的更多信息,请参考 Bean Factory

在 Spring 中,Spring IoC Container 管理了所有组成应用主干的对象,这些对象叫做 Beans,Bean 要么是由 Spring IoC Container 实例化、装载以及管理的对象,要么就是一个应用中简单的对象。Beans,以及它们之间的依赖关系是在容器使用的配置元数据中配置的。

总结:

  1. 什么是控制反转。
  2. BeanFactoryApplicationContext 的关系。
1.2. 容器概述

org.springframework.context.ApplicationContext 接口表示 Spring IoC Container,负责实例化、配置以及装载 beans。容器通过读取配置元数据实例始化、配置以及装载对象。配置有三种形式,分别是:XML、Java annotations 或者 Java code,它允许你定义应用程序的对象以及这些对象的相互依赖关系。

Spring 提供了 ApplicationContext 接口的几个开箱即用的实现,在独立应用中,通常会创建 ClassPathXmlApplicationContextFileSystemXmlApplicationContext 的实例。虽然 XML 一直是定义配置的传统方式,但是你也可以通过提供少量的 XML 配置来启用 Java annoations 或 Java Code 的配置方式。

大多数场景下,不需要显示创建 Spring IoC Container 的实例,比如:Web 应用场景中,web.xml 文件用简单的大约8行描述就足够。如果你使用 Spring Tool Suite,那么这个配置将会很容易创建,只需要几次键盘鼠标操作。

下图是 Spring 工作原理的高级视图,应用程序类和配置元数据组合起来,在 ApplicationContext 创建和初始化以后,就有一个完整的可执行系统或应用程序。

Figure 1. The Spring IoC Container

1.2.1. 配置元数据

如上图所示,Spring IoC Container 需要一份配置元数据,由应用程序开发人员编写用来告诉容器如何实例化、配置和装载对象。

配置元数据传统方式是由简单、直观的XML格式配置,本章中阐述Spring IoC Container 的关键概念和特性都是用这种方式。

注意:基于XML并不是配置的唯一途径。Spring IoC容器与配置文件完全解耦。现在,许多开发者在他们的Spring应用中选择使用基于Java的配置(后续章节会介绍此部分内容)。

关于配置元数据的其他形式,请参考:

  • Annotation-based configuration:Spring 2.5引入的基于注解配置的元数据。
  • Java-based configuration:Spring 3.0开始,Spring JavaConfig提供的许多特性已经成为了Spring Framework的核心。你可以使用Java而不是XML文件来定义程序类外部的bean。要使用这些新特性,可以参考 @Configuration@Bean@Import@DependsOn 等注解。

Spring 配置至少由一个,通常有多个容器管理的 bean 组成,XML 通过 <bean/> 元素来配置 bean,其父元素,也是顶级元素是 <beans />。基于 Java 配置 bean 通常是在 @Configuration 标注的类中,用 @Bean 标注的方法,表示这个方法会返回一个 bean。

这些 bean 的定义对应组成应用程序的实际对象,通常你可以定义服务层对象,数据访问层对象,表示层对象(Struct Action 实例),底层对象(Hibrenate SessionFactories),JMS Queues等等,但不需要配置一些细粒度的域对象,因为这些对象通常是由 DAOs 和业务逻辑创建和加载。然而,你也可以通过 Spring`s integration with AspectJ 来配置在 IoC Container 之外创建的对象,请参考:Using AspectJ to dependency-inject domain objects with Spring.

下面的例子展示了基于 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="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

id 属性是一个字符串,唯一标识一个 bean,可以被其他对象引用。class 属性定义 bean 的类型,使用类的完全限定名。关于依赖引用配置,请参考:Dependencies

1.2.2. 初始化容器

初始化一个容器很简单,给 ApplicationContext 构造函数传递一个或多个资源路径,容器也可以加载各种外部资源(文件系统、Java 类路径等)。

ApplicationContext  context = new ClassPathXmlApplicationContext("services.xml","daos.xml");

学习了Spring IoC容器之后,你可能会想去知道更多有关于Spring的Resource,像在Resource章节描述的一样,Spring Resource提供了方便的机制来使用URI规则读取InputStream。尤其是,Resource路径被用来构建应用程序context,下面章节会给出具体描述。

下面的例子展示了服务层bean的配置(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"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

下面的例子展示了数据接入层bean的配置(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">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

在上面的例子中,服务层由类 PetStoreServiceImpl 以及两个数据接入层的对象 JpaAccountDaoJpaItemDao 组成。<property /> 元素的 name 属性关联bean对象的属性名称,ref 关联依赖的 bean 的 id

基于XML配置的组合使用
将bean的定义按逻辑层或者功能模块拆分到多个XML配置文件中是很有用的。

你可以给 ApplicationContext 构造函数中传递多个路径来加载配置,也可以通过 <import />标签,将多个配置文件组合在一起使用,比如:configuration.xml

<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 三个文件加载。所有的路径都是 configuration.xml 的相对路径,所以 services.xml 必须在
configuration.xml 的相同目录下或者类路径下,messageSource.xmlthemeSource.xml 必须在和 configuration.xml 同级目录下的 resources 目录下。正如你所看到的,messageSource.xml 文件路径的前置斜杠被忽略,因为这些路径都是相对的,所以最好使用这种形式。被导入文件的内容,包括顶级元素 <beans /> 通过 Spring Schema 的校验是一个有效的 XML bean 的定义。

引用父目录文件的时候,使用相对路径“../”,这样做是可以的,但是并不推荐这样做。这样做会创建一个相对当前应用程序之外的文件的依赖关系。尤其是,这样引用不推荐用于"classpath:"路径(例如"classpath:../services.xml"),在运行时解析过程中会选择“最近的”类路径的根目录,然后再寻找它的父目录。classPath配置的改变,可能会导致选择不同的、错误的目录。

你也可以使用绝对路径来代替相对路径:例如“file:C:/config/services.xml” 或 “classpath:/config/services.xml”。然而,你可能意识到,这样做会将你的应用程序的配置,耦合到一个特定的位置上。一般不会直接直接配置这种绝对路径,例如,可以使用"${…}"占位符来获得JVM运行时的系统属性。

<import /> 标签是 beans 命名空间自身提供的特性。在Spring 提供的 XML 命名空间里,还是很多其他的配置特性,例如 contextutil 等。

1.2.3. 使用容器

ApplicationContext 接口是一个高级工厂接口,它能维护不同 bean 以及它们的依赖的注册表。通过 T getBean(String name,Class<T> requiredType) 方法,可以获取bean的实例。

ApplicationContext 允许你读取bean 的定义以及访问它们,比如:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml","daos.xml");

// retireve configured instance
PetStoreService service = context.getBean("petStore",PetStoreService.class);

// use configured instance
List<String> userList = service.getUserNameList();

最灵活的方式是使用 GenericApplicationContext ,它整合了具有代表性的几个 reader ,比如,为 XML 文件准备的 XmlBeanDefinitionReader

GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml","daos.xml");
context.refresh();

这样同一个 ApplicationContext 可以读取多个不同配置源的 bean 的定义。

你可以通过 getBean 方法获取 bean 的实例,ApplicationContext 接口还提供了一个获取 bean 的实例的方法,不过,理想状态下,你的应用代码中不需要使用它们。实际上,你的应用代码中不会调用 getBean 方法,因此不需用依赖 Spring APIs。例如,Spring与web框架的集成,为各种web框架组件(例如controller,JSF)提供了依赖注入,允许您通过配置声明对特定bean的依赖(例如:autowiring注解)。

总结:

  1. 容器、Bean、配置元数据之间的关系。
  2. 如何初始化和使用容器。
  3. 如何加载多个配置。
  4. 配置元数据有那些形式。
1.3. Bean概述

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

Container 内部,这些 bean 的定义被表示为 BeanDefinition 对象,其中包含了一下元数据(以及其他信息)。

  • 类的包限定名,通常是所定义 bean 的具体实现类。
  • bean 的行为配置元素,这些元素声明了 bean 在容器中的行为,例如:作用域、生命周期回调函数等等。
  • 对其他 bean 的引用,这些引用叫做协作者或者依赖。
  • 其他配置项设置新创建的对象,例如:管理连接池的 bean ,可以设置这个 bean 的连接数以及池的大小。

这些元数据转化为 BeanDefinition 对象的属性。

Table 1. Bean 定义

属性 详述
class Instantiating beans
name Naming beans
scope Bean scopes
constructor arguments Dependency Injection
properties Dependency Injection
autowiring mode Autowiring collaborators
lazy-initializtion mode Lazy-initialized beans
initializtion method Initialization callbacks
desturction method Destruction callbacks

除了通过 bean 定义的方式创建 bean 以外,ApplicationContext 也允许注册在容器外用户创建的对象,通过调用 ApplicationContext 接口的 getBeanFactory 方法,返回一个实现了 BeanFactory 接口的 DefaultListableBeanFactory 对象,这个对象提供了两个注册方法 registerSingleton(...)registerBeanDefinition(...) 。 然而,通常是通过配置元数据定义需要创建的 bean

Bean元数据和手动提供的单例需要尽早注册,以便容器在自动注入和其他自省步骤期间正确地推断它们。
虽然在一定程度上支持覆盖现有的元数据和现有的单例,但是在运行时注册新bean(与对工厂的实时访问同时进行)并没有得到官方的支持,并且可能导致bean容器中的并发访问异常和/或不一致状态。

1.3.1. beans 命名

每个 bean 可以有一个或多个标识符,这些标识符必须在 bean 所在的容器中唯一。通过一个 bean 只有一个标识符,其他标识符可以作为 bean 的别名。

基于 XML 配置的元数据中,使用 idname 来指定 bean 的标识符。通常这些标识符由数字和字母组成(myBean,fooService等等),但是也可以使用特殊字符。通过 name 属性给 bean 指定一个或多个别名,用逗号、分号或者空格分割。在 Spring 3.1 之前的版本中, id 属性被定义为 xsd:ID 类型,限制了一些字符的使用,截止 Spring 3.1,它被定义为 xsd:string 类型,虽然不在需要 XML 解析器校验唯一性了,但是 id 在容器内仍然强制要求必须唯一。

可以不用显示的指定 beanid 或者 name ,容器会给 bean 生成一个唯一的名称,但是如果需要通过名称引用 (ref 或者 Service Locator Patternbean ,就必须手动提供一个名称。如果被用作 inner beans 或者 autowiring collaborators.,可以不用手动提供名称。

Bean 命名规范
bean 的命名规则和 java 中类的字段的命名规则一致,使用首字母小写的驼峰命名,比如:accountManageraccountService,等等。

classPath 下扫描组件时,Spring 会给未命名的bean生成名称,遵循上面说的规则:实质上就是取简单类型并将首字母小写。然而在特殊情况下,如果类名的前两个字符都是大写的话,Spring会采用原始的命名,不会做改动,具体的逻辑可以参考 Spring 命名的方法 java.beans.Introspector.decapitalize

定义之外给 bean 引入别名
bean 定义时,可以通过设置一个 id 和 多个name 给bean 命名,这些名称都是指向同一个 bean 的等价别名,在有些场景下这很有用,比如:应用程序中的每个组件希望自己指定一个 bean 名称来引用通用组件。

bean 定义的地方设置所有的别名不总是很适合,有时希望在 bean 定义之外给 bean 设置别名。一个常用的场景是:在一个大型系统中,配置分布在每个子系统中,每个子系统定义他们所需要的 bean ,基于 XML 配置中,可以通过 <alias />标签实现。

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

在这种情况下, 同一个容器中有一个叫做 fromNamebean ,通过上面的别名定义,可以通过 toName 去引用这个 bean

比如,子系统 A 通过 subsystemA-dataSource 引用 DataSource ,子系统 B 通过 subsystemB-dataSource 引用 DataSource ,由子系统 A 和 子系统 B 组成的应用使用 myApp-dataSource 引用 DataSource,将下面的代码添加到应用的配置中,将会有三个名称指向同一个对象。

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

现在每个组件和应用通过一个唯一的名称引用相同的 DataSource ,这个名称也不会和其他定义冲突。

Java-configuration
如果使用 Java-configuration 的形式配置,@Bean 标签可以提供别名

1.3.2. 实例化 beans

bean 定义本质是为了创建一个或多个对象,当需要一个 bean 时,容器会根据配置元数据创建实际对象。

基于 XML 的配置,需要通过 <bean /> 元素的 class 属性指定对象的类型,这个属性通常是必须指定的,在容器内部,它是 BeanDefinition 实例的 Class 属性。(更多的信息请参考: Instantiation using an instance factory method and Bean definition inheritance),有两种使用 Class 属性的方式:

  • 典型的,指定需要实例化的 beanclass,容器通过反射调用 bean 的构造方法实例化 bean,这种方式等价于 java 中使用 new 操作符。
  • 指定包含创建对象的 static factory method 的类,容器调用类上面的静态工厂方法创建 bean ,静态工厂方法返回当前类型对象或者其他类的对象,这种方式不常用。

内部类名称
如果需要配置一个静态的内部类,需要使用内部类的另外一个名称。
例如:如果在 com.example 包中有一个 Foo 类,这个类包含一个静态的内部了 Bar ,静态内部类的 bean 定义中 <bean /> 元素的 class 属性应该是:com.example.Foo$Bar,内部类和外部类需要用 $ 分割。

构造方法实例化bean
使用构造方法的方式创建 bean 时,正常的 class 都能使用,也就是说,这个类不需要实现特定的接口,也不要按指定的方式去编写,简单的指定 beanclass 就足够了。然而,你也许需要指定一个默认的构造方法,这取决于使用的容器。

Spring IoC Container 几乎能管理所有你想要管理的类,不限于真正的 JavaBeans,大多数用户喜欢使用只有一个默认构造方法以及操作属性的 getter/setter 方法的 JavaBeans,如果需要,也可以在容器中使用不是 bean 风格的类。例如:Spring 可以管理一个不符合 JavaBean 规范的遗留连接池。

基于 XML 的配置,可以使用下面的方式指定 beanclass:

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

如果想了解给构造方法传递参数(如果需要)以及给构造的对象设置属性的原理,可以参考:Injecting Dependencies.

静态工厂方法实例化bean
当需要静态工厂方法定义 bean 时,需要将 class 属性指定为包含静态工厂方法的类,将 factory-method 指定为静态工厂方法,你可以调用这个方法(可能带参数)并且返回一个对象,随后这个对象可以当做是通过构造方法创建的。静态工厂方法可以用在一些遗留的代码中。

以下的 bean 定义通过调用 factory-method 方法创建 bean ,定义中没有指定静态工厂方法返回的类型,只是指定了包含静态工厂方法的类,这个例子中,createInstance() 是一个静态方法。

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

关于给静态工程方法传递参数,工厂方法返回对象后给对象实例属性赋值的详细说明,请参考:Dependencies and configuration in detail.

实例工厂方法实例化bean
类似于上节中的静态工厂方法,实例工厂方法是通过容器中已经存在的一个 bean 上面的实例方法创建一个 bean ,要使用这种方式创建 bean ,不需要设置 classfactory-bean 属性设置为在当前容器(或者父容器)中包含工厂方法的 beanfactory-method 属性设置为工厂方法的名称。

<!-- 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();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

一个工厂类中可以有多个工厂方法:

<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();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

如果需要了解如何通过依赖注入配置和管理 factory bean ,请参考:Dependencies and configuration in detail.

在 Spring 文档中,factory bean 指的是包含用于创建其他 bean 的实例或静态工厂方法的 bean ,相反,FactoryBean 指定是 Spring 中的一个接口。
总结

  1. BeanDefinitionbean 的关系
  2. 如何配置 beanbean 有哪些属性
  3. bean 命名
  4. 如何创建 bean
1.4 依赖

一个典型的企业级应用不会由一个对象(也就是Spring 中的 bean)组成,即使很简单的应用,也需要好几个对象相互协作展现给终端用户。下一节中会解释如何通过单独定义的多个 bean 相互协作实现这个目标。

依赖注入(DI)是一个对象定义依赖关系的过程,也就是对象只能通过构造方法参数,工厂方法参数,或者(对象通过new或者工厂方法返回以后)设置属性来定义与其他对象的依赖关系,然后容器在创建Bean时注入这些依赖项。这个过程本质是反转的,因此得名控制反转,其含义是bean自己通过类的构造函数控制对象的初始化或者依赖发现,还是通过像 Service Locator Pattern机制。(关于什么服务定位模式,可以阅读这篇博客Service Locator Pattern)。

遵循 DI 原则可以让代码更简洁,当对象提供依赖关系时,解耦更彻底。对象不需要查找它的依赖,也不需要知道依赖的位置和类。这样,类变的更容易测试,如果依赖的是接口或者抽象类,在单元测试中可以通过打桩或者Mock来实现。

DI 存在两种主要的变体,分别是:Constructor-based dependency injection and Setter-based dependency injection.

构造方法参数注入
容器通过调用类的构造方法,传递参数,每个参数代表一个依赖来实现基于构造方法参数的注入,给静态工厂方法传递参数来创建对象也是类似的。接下来的讨论对于参数传递给构造方法还是工厂方法是类似的。下面的例子展示了通过构造方法参数注入,这个类没有特别的设定,它是一个不需要依赖容器的特定接口、抽象类或者标签的POJO类。


今天工作比较忙,只能现在来更新(2018/10/31 20:44),感谢各位捧场~~。


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

构造方法参数解析
构造方法的参数是通过类型解析和匹配的。如果 bean 定义的构造方法参数没用潜在的歧义,容器会使用 bean 定义的构造方法参数的顺序使用合适的构造方法实例化对象。请考虑下面定义的类。

package x.y;

public class ThingOne{
    
    public ThingOne(ThingTwo thingTwo, ThingThree thingThree){
          //......
    }
}

假如上面的类 ThingTwoThingThree 之间没有继承关系,就不存在潜在的歧义。因此,不需要显示的在 <constructor-arg /> 元素中指定 typeindex 属性,下面的配置就可以正常工作。

<beans>
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
    </bean>

    <bean id="thingTwo" class="x.y.ThingTwo"/>

    <bean id="thingThree" class="x.y.ThingThree"/>
</beans>

当引用另一个bean时,类型是已知的,并且可以进行匹配(就像前面的例子那样)。当使用简单类型(如<value>true</value>)时,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 的依赖注入
容器调用无参的构造方法或者静态工厂方法创建对象,然后调用 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 不仅支持 bean 通过 setter 方法注入或者构造方法注入,也支持这两种方式混合注入。你可以用 BeanDefinition 的形式配置依赖关系,并将其与 PropertyEditor 实例一起使用,以将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户并不直接使用这些类(即通过编程方式),而是使用XML bean 定义、带注解的组件(@Component@Controller等注释的类)或基于java的 @Configuration 类中的 @Bean 方法。然后,这些源在内部转换为 BeanDefinition 实例,并用于加载整个Spring IoC容器实例。

构造参数注入和 setter 方法注入比较
由于可以混合使用基于构造方法参数和基于setter 方法的DI,因此使用构造方法处理强制依赖关系,setter方法或配置方法(?)处理可选依赖关系是一个不错的经验。但是请注意,在setter方法上使用 @Required 注释可以使属性成为必需的依赖项。
Spring 团队通常支持构造方法注入,因为它允许你将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造方法注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为补充说明,大量的构造方法参数是一种糟糕的代码风格,这意味着类可能有太多的职责,为了更好地处理关注点的适当分离,应该重构。
Setter注入主要应用于可选的依赖项,这些依赖项可以在类中设置合适的默认值。否则,在代码使用依赖项的任何地方都必须执行非空检查。setter注入的一个好处是,setter方法使该类的对象可以稍后重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个不错的用例。
对特定类使用最有意义的DI风格。有时候,在处理没有源代码的第三方类时,会为你做出选择。例如,如果第三方类不公开任何 setter 方法,那么构造函数注入可能是惟一可用的 DI 形式。

依赖解析过程说明
容器是通过以下几个步骤完成 bean 的依赖解析。

  • ApplicationContext 是通过所有 bean 的配置元数据创建和初始化的。配置元数据可以通过 XML 、Java Annoations以及Java Code 指定。
  • 对于每个 bean ,它们的依赖项是通过 属性(setter)、构造方法参数或者静态构造方法参数表示。当对象真正创建以后,这些依赖会注入到对象中。
  • 每个属性或者构造函数参数都是要设置的值的定义,或者容器中其他 bean 的引用。
  • 每个属性或者构造函数的参数的值的格式(类型)会被转化为属性或者构造函数参数的真实类型。默认情况下,Spring 可以将字符串格式的值转化为任何内置类型,必须 intlongStringboolean 等等。

Spring 容器在创建的时候会校验每个 bean 的配置,然而, bean 在只有创建以后才会设置自己的属性。 如果 beanscopesingleton-scope 并且设置了提前实例化(默认)时,则容器创建以后就会创建 bean,否则,只有在需要的时候才会被创建。创建一个 bean 时可能因为依赖关系会创建出一系列其他 bean。找不到依赖的 bean 的情况只会在正真创建 bean 的时候发生。

循环依赖
如果使用构造函数注入,可能会遇到循环依赖的问题。
例如:类 A 使用构造函数注入类 B,类 B 使用构造函数注入类 A ,这种情况下容器在运行时会检测到循环依赖,并且抛出一个 BeanCurrentlyInCreationException 的异常。
一种可行的方案是使用 setter 方法注入而不是构造函数注入,虽然这种方法是不推荐的,但是确实可以解决循环依赖的问题。
与一般的情况(没有循环依赖)不同,bean A和bean B之间的循环依赖关系迫使其中一个 bean 在被 完全初始化之前 注入另一个 bean(典型的鸡生蛋,蛋生鸡场景)。

通常,可以相信 Spring 会正确的事情,在容器加载期间,它会检测到诸如依赖的 bean 不存在或者循环依赖等配置问题。当创建真实的对象时,Spring 会尽可能晚的设置属性和解析依赖。这意味着 Spring 容器正常加载以后,只有在正真创建对象或者对象的依赖时,可能抛出异常,而不是容器加载以后就抛出异常,这样都导致一些配置问题不能马上暴露,这也是为什么 ApplicationContext 实现默认实例化单例 bean。在 bean 需要使用之前,花费一些时间和内存提前创建,这样一些问题就会在容器创建的时候发现,而不是之后。也可以重写默认的行为,让单例 bean 延迟实例化而不是预实例化。

如果不存在循环依赖,当一个或多个协作者被注入到一个 bean 时,每个协作者在注入之前需要完全被配置。这也就是如果 bean A 需要依赖一个 bean B,在调用 bean A 的 setter 方法之前,需要优先配置好 bean B,

换句话说,bean 被实例化(非预实例化bean) ,它的依赖被设置,然后才是调用相关的生命周期函数(比如:configured init method or the InitializingBean callback method

依赖注入示例
下面的例子展示了基于 XML 的 setter 注入配置,有一小部分配置文件指定了 bean 的定义。

<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 方法注入设置的。下面的例子使用构造方法注入:

<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 的构造函数。
现在考虑一个衍生示例,用静态构造方法代替构造函数创建对象:

<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 /> 标签提供,和构造方法一样。静态工厂方法返回的类型不一定和拥有它的类一样,虽然这里是一样的。实例工厂方法也是一样方式,只不过将 class 属性替换为包含实例方法的 bean,详请就不在这里讨论了。

1.4.2 依赖和配置详细说明

正如上节中提到的,可以通过定义 bean 的属性或构造参数来引用其他的 bean 或者简单值。基于 XML 的配置可以通过元素 <property /><constructor-arg /> 实现这个目的。
简单值(基本类型、Strings等等)
<property />元素的 value 属性通过一个可读的字符串指定属性或者构造参数。利用 Spring 的 conversion service 将这些 String 值转化为属性或构造参数所需类型值。下面的例子展示设置各种值:

<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-namespace 简化 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 更加简洁,但是,拼写错误只能在运行时发现而不是编辑时,除非你使用 IDE(比如: IntelliJ IDEA 或者 Spring Tool Suite)。

你也可以使用下面的方式配置一个 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 容器使用 PropertyEditor 机制将 <value />元素中的文本转化为 java.util.Properties 实例。这是一个完美的快捷方式,这也是 Spring 团队喜欢使用 <value /> 元素内嵌文本风格而不是 value 属性风格的少有的地方之一。

idref 元素
元素 idref通过 bean 属性(是一个字符串,而不是引用)来验证 <constructor-arg /> 或者 <property /> 元素中引用的其他 bean 是否存在(请记住,idref只是验证bean是否存在,并不是引用bean)。下面展示了如何使用。

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

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

上面的配置中,targetName 在类中是一个字符串字段,而不是 theTargetBean 类型的对象引用。
它与下面的配置等价:

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

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

第一种配置形式比第二种好,因为,idref 标签可以使容器在部署阶段端验证指定名称的 bean 是否存在。第二种方式没有验证传递给 targetName 属性的 bean(是否存在),对于拼写错误只能到 bean 真正实例化时才能发现,如果 beanscopeprototype ,这个错误可能会在容器部署完很长一段时间之后才能发现。

标签idref上的local属性,在4.0版本中的xsd就已经不在支持了,因为它不能再为bean提供一个引用值了。当升级至4.0版本时,只要将你项目中的idref local替换为idref bean即可。

一个通用的方式(至少是在 Spring 2.0之前),<idref /> 元素携带值在 ProxyFactoryBean定义中配置 AOP interceptors ,使用 <idref />可以防止拼错拦截器的ID。

引用其他bean(协作者)
ref 元素是一个嵌在 <constructor-args /><property /> 元素中的终子元素(没有子元素的元素),可以通过设置 bean 属性来引用容器中的其他 bean,被引用的 bean 是这个 bean 的依赖,协作(被引用)bean 需要在属性设置之前实例化。(如果协作 bean是一个单例 bean,也许已经被容器实例化)。基本上所有的 bean 都有一个引用的 bean,作用域和校验依赖于你是否通过 beanlocalparent 属性指定了其他对象的 ID 或者 name

通过 <ref /> 元素的 bean 属性来创建相同容器或者父容器中的其他 bean 的引用是一种常用的方式,不管他们是否在同一个 XML 文件里。bean 属性可能是协作 beanID 或者 name 值。下面展示如何使用 ref 元素:

<ref bean="someBean"/>

通过 ref 元素的 parent 属性创建一个引用父容器的 beanparent 属性的值是协作 beanID 或者其中一个 name 值,协作 bean 必须在父容器中存在。这种引用指定的方式主要使用在容器继承中,如果子容器想要创建一个与父容器 的 bean 具有相同名称的代理。下面的例子展示了如何使用 parent 属性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.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>

标签 local 上的local属性,在4.0版本中的xsd就已经不在支持了,因为它不能再为bean提供一个引用值了。当升级至4.0版本时,只要将你项目中的ref local替换为ref 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 不需要指定 IDname,即便是指定了,容器也不会把它当做 bean 的标识符。容器在创建时也会忽略 scope 标志,因为内部 bean 常常是匿名的并且是伴随外部 bean 创建的。内部 bean 是不可能被单独访问,或者注入到除了外部 bean 的其他 bean 中。

一个特例,内部 bean 可以接受自定义 scope 的销毁回调,举个例子,在一个 singleton bean 中包含一个 request-scoped 内部 bean ,虽然内部 bean 的创建和包含它的外部 bean 绑定到一起,但是销毁回调允许它参与 request scope 的生命周期。

集合
元素 <list /><set /><map /><props> 分别设置 Java 集合类型 ListSetMapProperties 的属性或方法参数。下面展示如何使用它们:

<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 的键值或者集合的值可以通过以下元素设置:

bean | idref | ref | null | value | list | set | map | props

集合合并
Spring 容器也支持合并集合。应用开发人员可以定义继承自父元素 <list /><set /><map /> 或者 <props /> 的子元素 <list /><set /><map /> 或者 <props /> 来覆盖父集合的值。也就是说,子集合的值是父集合和子集合合并后的值。

这节会涉及到父子 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>

请注意在定义子 beanadminEmails 属性时需要在元素 <props /> 上面使用 merge=true 属性。当容器解析和实例化 child bean 的时候,实例有一个类型 Properties 集合的属性 adminEmails 的结果是子集合 adminEmails 和 父集合 adminEmails 合并后的结果,下面是合并后集合的值:

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

这个合并行为也可以应用于 <list /><set /><map /> 类型的集合,<list /> 有一个特殊情况,它的元素的顺序在合并以后不会改变,父集合的值排在子集合的前面。对于 MapSetProperties 类型的集合不存在排序的问题。也就是说,在容器内部,对于实现 MapSetProperties 的类型没有排序的语义。

集合合并的限制
不能合并不同类型的集合(比如:MapList),如果试图这样做,会得到一个对应的 Exceptionmerge 属性必须在 child bean 的定义中指定,如果在 parent bean 中指定了,那么你不会得到你想要的结果。

强类型集合
自从 Java 5 中引入了泛型类型之后,你可以使用强类型的集合。也就是说,可以定义一个只包含 String 类型元素的集合(举例)。如果你使用 Spring 来向 bean 中注入一个强类型的集合,Spring 会在元素添加到集合之前将定义的元素转化为合适的类型。下面展示如何使用:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <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>

something bean 的属性 accounts 准备被注入时,强类型集合 Map<String,Float> 的元素类型的泛型信息会通过反射拿到,因此,Spring 的类型转化功能会把这些元素值认为是 Float 类型,字符串值(9.99,2.75 和 3.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.setEamil(null);

p-namespace

c-namespace
p-namespace 允许你使用 bean 元素的属性(代替内嵌的 <property /> 元素)来描述属性的值(简单值或引用)。

Spring 支持通过基于 XML 模式定义的 with namespaces 来扩展配置格式。在本章中 bean 的配置格式是在 XML 模式文档中定义,然而 p-namespace 不是在 XSD 文件中定义的,它只存在于 Spring 的核心。

下面展示了两段 XML 片段(第一段使用标准的 XML 格式,第二段使用 p-namespace),它们解析后的结果是相同的。

<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="someone@somewhere.com"/>
    </bean>

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

上面的例子展示了在 p-namespace 中有一个叫 email 的属性,这就告诉 Spring 包含一个属性的声明。上面提到,p-namespace 不是一个模式定义,所以类字段的名称可以设置为属性的名称。

下面的例子展示了两个 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-namespace 设置简单属性值,也包含了设置引用属性值。第一个 bean 定义使用 <property name="spouse" ref="jane"> 创建了一个从 bean johnbean jane 的引用,第二个 bean 定义使用 p:spouse-ref="jane" 做了同样的事情。在这种情况下,spouse 是属性的名称,而后缀 -ref 表示这不是一个简单值而是一个引用(这里的引用不包含 String)。

p-namespace并不如标准XML定义灵活。例如声明引用属性时与以Ref结尾的属性冲突。我们建议你慎重选择,并与团队成员做好沟通,避免同时使用全部三种方式(<property />的属性、<property /> 的子元素以及 <bean /> 的属性)。

c-namespace
类似于 XML Shortcut with the p-namespacec-namespace 是在 Spring 3.1 引入,允许使用行内属性配置构造参数而不是内嵌元素 <constructor-arg>

下面展示了使用 c: 命名空间做和 Constructor-based Dependency Injection 一样的事情:

<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="thingOne" class="x.y.ThingTwo"/>
    <bean id="thingTwo" class="x.y.ThingThree"/>

    <!-- traditional declaration -->
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
        <constructor-arg value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="thingOne" class="x.y.ThingOne" c:thingTwo-ref="thingTwo" c:thingThree-ref="thingThree" c:email="something@somewhere.com"/>

</beans>

c: 命名空间使用了和 p: 相同的规则(以 -ref 后缀表示引用)通过名称来定义构造参数。类似的,它可以声明参数名称即使不在 XSD 模式中定义(它存在于 Spring core 中)。

在少数情况下,构造参数名称不能获取(如果字节码文件是在没有调试信息的情况下编译的),你可以退而求次使用参数的索引,比如:

<!-- c-namespace index declaration -->
<bean id="thingOne" class="x.y.ThingOne" c:_0-ref="thingTwo" c:_1-ref="thingThree"/>

由于 XML 语法规定属性名不能以数字开口,所以索引值需要前缀 _

在实践中,构造函数解析机制在匹配参数方面是非常不错的(一般情况都可以处理),所以除非逼不得已才像上面这么做,我们建议在配置中使用名称表示法来贯穿整个配置。

组合属性值
当设置 bean 的属性时,你可以使用组合的,嵌套的属性名称,只要除了最终需要设置的属性,其他每一级的属性都不为null。请看下面的配置:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

something bean 有一个 fred 的属性,fred 有一个 bob 属性,bob 有一个 sammy 属性,然后最终的属性 sammy被设置为123。为了能正常工作, something bean 被构建后,属性 fredfred 的属性bob 不能为null,否则,就会抛出NullPointerException(空指针异常)。

1.4.3. 使用 depends-on

如果一个 bean 是另外一个 bean 的依赖,通常是这个 bean 是作为另外一个 bean 的属性。典型情况下,在基于 XML 的配置中是通过 <ref />元素实现的。然而,有些情况下,两个 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" />

bean定义中的depends-on属性,可以指定初始化时候的依赖,在单例bean中也可以指定销毁时间的依赖。在此bean本身被销毁之前,被指定的依赖bean首先被销毁,因此,depends-on也可以控制关闭的顺序。

1.4.4 懒加载 bean

默认,ApplicationContext 的实现会尽早的创建和配置所有的单例 bean,并将此作为初始化过程的一部分。通常,预实例化是你需要的,因为配置错误或环境错误会被立即发现,而不是几个小时或几天之后。当这个默认行为不是你想要的,可以在 <bean /> 元素上指定 lazy-init 属性来阻止单例 bean 预实例化。懒加载 bean 告诉容器只有在它被需要的时候再实例化,而不是启动的时候。

在 XML 中,这个行为可以通过 <bean /> 元素的 lazy-init 属性控制,示例如下:

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

当上面的配置被一个 ApplicationContext 使用时, ApplicationContext 启动时,lazy bean 不会被尽早的实例化,而 not.lazy bean 会被尽早的预实例化。

然而,如果一个懒加载的 bean 是一个非懒加载的 bean 的依赖,ApplicationContext 会在启动的时候创建懒加载的 bean ,因为它必须满足单例的依赖。懒加载的 bean 被注入到单例 bean 中,它在其他地方也就成了非懒加载的 bean 了。

也可以通过 <beans /> 元素的 default-lazy-init 属性,在容器级控制懒加载。例如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 自动装配协作者
1.4.6 方法注入
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260