本章涵盖了 Spring Framework实现控制翻转 (IoC) 的原则。 IoC 有时也被称为依赖注入 (DI)。这是一个对象定义他们依赖的过程,其中对象之间的相关性,也就是说,它们一起工作,只能通过构造函数参数,参数工厂方法或设置在其构造后的对象实例或者是从一个工厂方法返回的对象实例的属性上。容器在创建的 bean 注入这些依赖。这个过程是根本的反转,因此称为控制反转(IoC),bean 本身通过直接构造类,或作为 Service Locator(服务定位器)模式的机制,来控制其依赖的实例或依赖的位置。

org.springframework.beans 和 org.springframework.context 是Sprig 框架的 IoC 容器的基础包的。 BeanFactory 提供能够管理任何类型的对象的高级配置机制。 ApplicationContext 是 BeanFactory 的一个子接口。它增加了与 Spring AOP 功能的整合更容易;消息资源处理(用于国际化),事件发布;和应用层的上下文,如WebApplicationContext 中的 Web 应用程序使用。

简而言之,BeanFactory 提供了配置框架和基本功能,而ApplicationContext 则增加了更多支持企业特定的功能。一个ApplicationContext 是 BeanFactory 的一个完整的超集,本章将专对 门 Spring 的 IoC 容器进行描述。有关使用 BeanFactory 来代替ApplicationContext 的更多信息,请参见第6.16节,“BeanFactory”

在 Spring 中,形成了应用程序的骨干,该对象由 Spring IoC 容器管理被称为 bean。一个 bean 是由 Spring IoC 容器实例化,组装,并以其他方式管理的对象。否则,一个 bean 只是简单的应用程序中的许多对象之一。bean,以及它们之间的相关性,反映在所使用的容器的配置元数据中(configuration metadata )。

容器总览

org.springframework.context.ApplicationContext 代表 Spring IoC 容器,并负责实例化,配置和组装上述 bean 的接口。容器是通过对象实例化,配置,和读取配置元数据汇编得到对象的构建。配置元数据可以是用 XML,Java 注解,或 Java 代码来展示。它可以让你描述组成应用程序的对象和对象间丰富的相互依赖。

Spring ApplicationContext 接口提供了几种即装即用的实现方式。在独立应用中,通常以创建 ClassPathXmlApplicationContext 或FileSystemXmlApplicationContext 的实例。虽然 XML 一直是传统的格式来定义配置元数据,但也可以指示容器使用 Java 注解或代码作为元数据格式,并通过提供少量的XML配置以声明方式启用这些额外的元数据格式的支持。

在大多数应用场合,不需要明确的用户代码来实例化一个 Spring IoC 容器的一个或多个实例。例如,在 web 应用程序中,在应用程序的 web.xml文件中一个简单的样板网站的 XML 描述符通常就足够了(见第6.15.4,“便捷的 ApplicationContext 实例化 Web 应用程序”)。如果您使用的是基于Eclipse的 Spring Tool Suite开发环境,该样板配置可以很容易地用点击几下鼠标或键盘创建。

下面的图展示了 Spring 是如何工作的高级视图。您的应用程序的类是通过配置元数据来结合的,以便 ApplicationContext 需要创建和初始化后,你有一个完全配置和可执行的系统或应用程序。

Figure 6.1. The Spring IoC container


配置元数据

如上述图所示,Spring IoC 容器使用 配置元数据( configuration metadata);这个配置元数据代表了应用程序开发人员告诉 Spring 容器在应用程序中如何来实例化,配置和组装对象。

传统的配置元数据是一个简单而直观的 XML 格式,这是大多数本章用来传达关键概念和 Spring IoC 容器的功能。

基于 XML 的元数据并不是配置元数据的唯一允许的形式。 Spring IoC容器本身是完全从此配置元数据实际写入格式脱钩。现在,许多开发商选择适合自己的 Spring 应用程序的基于 Java 的配置

更多其他格式的元数据见:

  • 基于注解的配置:Spring 2.5 的推出了基于注解的配置元数据支持。
  • 基于Java的配置:Spring3.0 开始,由 Spring JavaConfig 项目提供了很多功能成为核心 Spring 框架的一部分。因此,你可以通过使用Java,而不是 XML 文件中定义外部 bean 到你的应用程序类。要使用这些新功能,请参阅 @Configuration,@Bean,@Import 和 @DependsOn 注解。

Spring 配置至少一个,通常不止一个 bean 来由容器来管理。基于 XML 的配置元数据显示这些 bean 配置的<bean/> 包含于顶层元素<beans/>元素。 Java 配置通常使用 @Configuration 类中 @Bean 注解的方法。

这些 bean 定义对应于构成应用程序的实际对象。通常,您定义服务层对象,数据访问对象(DAO),展示对象,如 Struts Action 的情况下,基础设施的对象,如 Hibernate 的 SessionFactories,JMS Queues,等等。通常一个不配置细粒度域对象在容器中,因为它通常负责 DAO 和业务逻辑来创建和负载域对象。但是,你可以使用 Spring 和 AspectJ的集成配置在 IoC 容器的控制之外创建的对象。请参阅使用 AspectJ 在 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 的类型,并使用完整的类名。 id 属性的值是指协作对象。将XML 用于参照协作对象未在本例中示出;请参阅 依赖以获取更多信息。

实例化容器

实例化 Spring IoC 容器是简单的。提供给 ApplicationContext构造器的路径就是实际的资源字符串,使容器装入从各种外部资源的配置元数据,如本地文件系统, Java CLASSPATH,等等。

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

当你了解 Spring 的 IoC 容器,你可能想知道更多关于 Spring 的Resource 抽象,如第7章,资源,它提供了一种方便的从一个 URI 语法定义的位置读取一个InputStream 描述。具体地,资源路被用作在第7.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"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

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

</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">
        <!-- 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,以及类型的两个数据访问对象 JpaAccountDao 和 JpaItemDao(基于JPA对象/关系映射标准)组成。property name 元素是指 JavaBean 属性的名称,而 ref 元素引用另一个 bean 定义的名称。 id 和 ref 元素之间的这种联系表达了合作对象之间的依赖关系。对于配置对象的依赖关系的详细信息,请参阅依赖

基于XML的元数据配置

它可以让 bean 定义跨越多个 XML 文件,这样做非常有用。通常,每个单独的 XML 配置文件代表你的架构一个逻辑层或模块。

您可以使用应用程序上下文构造从所有这些 XML 片段加载 bean 定义。这个构造函数的多个 Resource 位置,作为上一节中被证明。另外,使用 <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>

services.xml,messageSource.xml 及 themeSource.xml 在前面的例子中,外部 bean 定义是由三个文件加载而来。所有位置路径是相对于导入文件的,因此 services.xml 是必须和导入文件在同一目录或类路径中的位置,而 messageSource.xml 和 themeSource.xml 来必须在导入文件的 resources 以下位置。正如你所看到的,前面的斜线被忽略,但考虑到这些路径是相对的,它更好的形式是不使用斜线。该文件的内容被导入,包括顶级<beans />元素,必须根据 Spring Schema 是有效的XML bean 定义。

这是可能的,但不推荐,引用在使用相对“../”的路径的父目录中的文件。这样将创建一个文件,该文件是当前应用程序之外的依赖。特别是,该引用不推荐“classpath:” URL(例如,“classpath:../services.xml”),在运行时解决过程中选择了“就近”的类路径的根,然后查找到它的父目录。类路径配置的变化可能导致不同的,不正确的目录的选择。 您可以随时使用完全合格的资源位置,而不是相对路径:例如,file:C:/config/services.xml" 或 "classpath:/config/services.xml"。但是,要知道,你这是是在耦合应用程序的配置到特定的绝对位置。通常优选间接的方式应对这种绝对路径,例如,通过“${…​}”在运行时解决了对JVM系统属性占位符。

使用容器

ApplicationContext 是能够保持 bean 定义以及相互依赖关系的高级工厂接口。使用方法 T getBean(String name, Class requiredType)就可以取得 bean 的实例。

ApplicationContext 中可以读取 bean 定义并访问它们,如下所示:

// create and configure beans
ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

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

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

您可以使用 getBean() 方法来获取 bean 的实例。 ApplicationContext 接口有一些其他的方法来获取 bean,但理想的应用程序代码不应该使用它们。事实上,你的应用程序代码不应该调用的getBean() 方法,也不应该对 Spring 的 API 有任何依赖性。正如,Spring为各种框架类提供的依赖注入,如控制器和 JSF 管理的bean。

Bean 总览

Spring IoC 容易管理一个或者多个 bean。 bean 由你提供给容器的配置元数据创建,例如,在 XML 中的<bean/> 定义。

容器内部,这些 bean 定义表示为 BeanDefinition 对象,其中包含(其他信息)以下元数据:

  • 限定包类名称:作为已经被定义bean的实际实现的代表。
  • bean 行为配置元素,定义了容器中的Bean应该如何行为(范围、生命周期回调,等等)。
  • bean 需要引用其他 bean 来完成工作,这些引用也称为合作者或依赖关系。
  • 其他配置设置来设置新创建的对象,例如,连接使用 bean 的数量管理连接池,或者池的大小限制。

以下是每个 bean 定义的属性。

Table 6.1. The bean definition

属性 解释
class Section 6.3.2, “Instantiating beans”
name Section 6.3.1, “Naming beans”
scope Section 6.5, “Bean scopes”
constructor arguments Section 6.4.1, “Dependency Injection”
properties Section 6.4.1, “Dependency Injection”
autowiring mode Section 6.4.5, “Autowiring collaborators”
lazy-initialization mode Section 6.4.4, “Lazy-initialized beans”
initialization method the section called “Initialization callbacks”
destruction method the section called “Destruction callbacks”

除 bean 定义里包含的如何创建一个特定的bean的信息, ApplicationContext 的实现还允许由用户注册现有创建在容器之外的对象。这是通过访问 ApplicationContext 的 BeanFactory 的 getBeanFactory() 方法 返回 BeanFactory 的实现 DefaultListableBeanFactory 。DefaultListableBeanFactory 支持这种通过 registerSingleton(..) 和registerBeanDefinition(..) 方法来注册。然而,典型的应用程序只能通过元数据定义的 bean 来定义。

需要尽早注册 Bean 元数据和手动实现单例的实例,这是为了使容器正确推断它们在自动装配和其他内省的步骤。虽然覆盖现有的元数据和现有的单例实例在某种程度上是支持的,新 bean 在运行时(同时动态访问工厂)注册不是官方支持,可能会导致并发访问 bean 容器中的异常和/或不一致的状态。

命名Bean

每个 bean 都有一个或多个标识符。这些标识符在容器托管 bean 必须是唯一的。bean 通常只有一个标识符,但如果它需要不止一个,可以考虑额外的别名。

在基于 xml 的配置元数据,您可以使用 id 和 / 或名称属性指定 bean 标识符(。id 属性允许您指定一个 id。通常这些名字字母数字(“myBean”、“fooService”,等等),但可以包含特殊字符。如果你想介绍其他别名 bean,您还可以指定属性名称,由逗号分隔(,),分号(;),或白色空格。作为一个历史因素的要注意,在 Spring 3.1 版本之前,id 属性被定义为 xsd:ID类型,它限制可能的字符。3.1,它被定义为一个 xsd:string 类型。注意,bean id 独特性仍由容器执行,虽然不再由 XML 解析器。

你不需要提供一个 bean 的名称或id。如果没有显式地提供名称或id, 容器生成一个唯一的名称给 bean 。然而,如果你想引用 bean 的名字,通过使用 ref 元素或使用 Service Locator(服务定位器)风格查找,你必须提供一个名称。不使用名称的原因是,内部 bean 自动装配的合作者

bean 名约定

约定是使用标准 Java 实例字段名称命名 bean 时的约定。也就是说,bean 名称开始以小写字母开头,后面采用“骆峰式”。例如“accountManager”、“accountService’,‘userDao’,‘loginController’,等等。

一致的beans命名可以让您的配置容易阅读和理解,如果你正在使用Spring AOP,当你通过 bean 名称应用到 advice 时,这会对你帮助很大。

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" />

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

基于 Java 的配置

如果你想使用基于 Java 的配置,@Bean 注解可以用来提供别名,详细信息请看 Section 6.12.3, “Using the @Bean annotation”

实例化bean

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

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

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

内部类名。如果你想配置使用静态的内部类,你必须用内部类的二进制名称。例如,在 com.example 包下有个 Foo 类,这里类里面有个静态的内部类Bar,这种情况下bean定义的class属性应该…com.example.Foo$Bar 注意,使用$字符来分割外部类和内部类的名称。

通过构造函数实例化

当你使用构造方法来创建 bean 的时候,Spring 对类来说并没有什么特殊。也就是说,正在开发的类不需要实现任何特定的接口或者以特定的方式进行编码。但是,根据你使用那种类型的 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 方法。

<beanid="clientService"class="examples.ClientService"factory-method="createInstance"/>
publicclassClientService{
    privatestatic ClientService clientService = new ClientService();
    privateClientService(){}

    publicstatic ClientService createInstance(){
        return clientService;
    }
}

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

使用实例工厂方法实例化

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

<!-- 工厂bean,包含createInstance()方法 --><beanid="serviceLocator"class="examples.DefaultServiceLocator"><!-- 其他需要注入的依赖项 --></bean><!-- 通过工厂bean创建的ben --><beanid="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/>
publicclassDefaultServiceLocator{

    privatestatic ClientService clientService = new ClientServiceImpl();
    privateDefaultServiceLocator(){}

    public ClientService createClientServiceInstance(){
        return clientService;
    }
}

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

<beanid="serviceLocator"class="examples.DefaultServiceLocator"><!-- 其他需要注入的依赖项 --></bean><beanid="clientService"factory-bean="serviceLocator"factory-method="createClientServiceInstance"/><beanid="accountService"factory-bean="serviceLocator"factory-method="createAccountServiceInstance"/>
publicclassDefaultServiceLocator{

    privatestatic ClientService clientService = new ClientServiceImpl();
    privatestatic AccountService accountService = new AccountServiceImpl();

    privateDefaultServiceLocator(){}

    public ClientService createClientServiceInstance(){
        return clientService;
    }

    public AccountService createAccountServiceInstance(){
        return accountService;
    }

}

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

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

依赖

一个典型的企业应用不会仅包含一个对象(用Spring的叫法就是一个Bean),甚至最简单的应用都包含一些互相工作的对象来为最终用户呈现一个完整的应用程序。接下来的部分会讲解如何从定义一些单独的Bean到完整的实现一个对象间相互合作实现统一目标的应用。

依赖注入

依赖注入(DI)是对象定义他们的依赖关系的一个过程,这些依赖关系就是对象之间相互作用关系,这些关系只有通过构造方法参数,工厂方法参数,或者是属性,在对象创建以后或者是工厂方法返回以后被设置到对象上。容器在创建Bean的时候把这些依赖关系注入进去。这个过程是Bean反转的基础,这个Bean通过直接使用类构造函数或者是Service Locator模式来控制Bean的实例化和依赖的定位。因此被叫做控制反转。
使用DI使代码更简洁并且当对象由依赖提供的时候使解耦更高效。对象不会去寻找他的依赖,不会知道依赖的类或者是位置。这样使你的类更方便测试,尤其是依赖于接口或者是抽象类的时候,在单元测试的时候允许你模拟实现。
DI存在两个主要的变种,基于构造函数的依赖注入和基于Setter方法的依赖注入

基于构造函数的依赖注入

基于构造函数的依赖注入是通过容器调用带参数的构造函数来实现的,每一个参数代表一个依赖。这和调用带参的静态工厂方法构造Bean是几乎一样的,这次讨论中我们把带参的构造函数和带参的静态工厂方法认为是一样的。接下来的例子展示了使用构造函数进行依赖注入。注意到这个类没有什么特殊,只是一个简单的JAVA类(POJO),没有依赖于容器的指定接口、基类、或者是有任何声明。
publicclass SimpleMovieLister {

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

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

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

}

构造参数解析

构造参数解析匹配时使用参数的类型。如果Bean定义的构造函数参数不存在潜在的歧义,那么Bean定义文件中定义的构造参数的顺序就是Bean被实例化时提供给相应构造函数参数的顺序。考虑下面的类:
package x.y;

publicclass Foo {

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

}
没有潜在歧义的存在,假设 Bar    Baz 没有相应的继承关系。因此下面的定义文件可以正常解析,你不用在<constructor-arg/> 元素里 指定构造函数参数的索引或者是明确的类型。
<beans><beanid="foo"class="x.y.Foo"><constructor-argref="bar"/><constructor-argref="baz"/></bean><beanid="bar"class="x.y.Bar"/><beanid="baz"class="x.y.Baz"/></beans>
当一个类型明确的Bean被引用,就能正确的匹配(正如之前的例子)。当用到如 <value>true</value> 的简单类型时,Spring不能决定值得类型,所以在没有其他帮助的情况下是无法匹配的参数的。考虑下面的情况:
package examples;

publicclass ExampleBean {

    // Number of years to calculate the Ultimate Answerprivateint years;

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

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

}
在上面的场景中,只有你用 type 元素明确指定了构造参数的类型,框架才能正确的匹配。例如:
<beanid="exampleBean"class="examples.ExampleBean"><constructor-argtype="int"value="7500000"/><constructor-argtype="java.lang.String"value="42"/></bean>
或者是用 index 标签明确指定构造参数的索引。例如:
<beanid="exampleBean"class="examples.ExampleBean"><constructor-argindex="0"value="7500000"/><constructor-argindex="1"value="42"/></bean>
除了解决构造参数存在多个简单值模棱两可的情况,指定一个索引还可以解决构造参数是两个相同类型的情况。注意索引是从0开始的。

你也可以用构造参数的名字来处理这种值模棱两可的情况:
<beanid="exampleBean"class="examples.ExampleBean"><constructor-argname="years"value="7500000"/><constructor-argname="ultimateAnswer"value="42"/></bean>
记住,你必须把Debug表示设置为enabled来编译才能让你的代码正常工作,因为只有这样Spring才能搜索构造函数的参数名字。如果你不能或者是不想以Debug模式来编译你的代码,你可以使用 @ConstructorProperties  
JDK声明来明确你构造参数的名字。如下面例子所示:
package examples;

publicclass ExampleBean {

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

}

基于setter的依赖注入

基于setter方法的依赖注入是在调用无参构造方法或者无参静态工厂方法实例化你的Bean以后通过调用setter方法来实现的。接下来的例子展示了可以仅仅通过纯setter方法实现依赖注入。这是一个普通的JAVA类,没有依赖于容器特定的接口、基类或者是声明。
publicclass SimpleMovieLister {

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

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

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

}

对于 ApplicationContext 管理的Bean,它支持基于构造方法和基于setter方法的依赖注入。它也支持在一些依赖已经通过构造方法注入后再通过setter方法注入。你可以通过 BeanDefinition 的形式配置依赖,这样你可以连同 PropertyEditor 一个属性转换匹配为另一个 。但是,大多数Spring用户不这样直接做,而是用XMLbean 定义、声明组件(例如用 @Component ,  @Controller 来声明类 ),或者是用基于JAVA @Configuration 类的 @Bean 方法。这些资源然后在内部被转换为 BeanDefinition 实例,然后被用来加载整个Spring IoC容器实例。


基于构造函数还是基于setter方法依赖注入?
如果你会混淆 基于构造函数和基于setter方法的依赖注入。一个好的方法是,对于必须的依赖就用构造函数,对于可选的依赖就用setter方法或者是配置方法。注意,在setter方法上用 @Required  声明,可以是属性成为必须的依赖。
Spring团队通常建议构造函数注入,因为这样允许把应用程序组件时间为不可变的对象以此来保证必须的依赖不为空。此外构造函数注入组件会在类完全初始化状态时返回客户端代码。作为边注,大量的构造参数是一个坏的习惯,意味着类有太多的职责应该被重构来重新处理关注点的划分。
Setter注入应该主要被用于可选依赖的注入,这样可以给类分配一个合理的默认值。否则,就需要在代码用到依赖的地方都进行非空校验。Setter注入的一个好处就是setter方法使类的对象可以在稍后进行重新配置或者重新注入。所以JMX Mbeans管理就是一个在编译期通过setter注入的例子。
对于个人的类使用DI是很好的方式。有时候,对于那些你没有源码的第三方类库,你就别无选择了。例如如果第三方类库没有暴露任何的setter方法,那么构造函数注入只能是唯一的方式。

依赖解析过程

容器执行Bean依赖的解析如下:
  • ApplicationContext 类被创建并初始化描述所有Bean的元数据配置。元数据配置是通过XML,JAVA代码,或者是生命来指定的。
  • 对于每一个Bean,他的依赖都会以属性的形式、构造参数的形式、或者是静态工厂方法的方式(如果你用静态工厂方法代替默认构造函数)被表达。当Bean被创建的时候,这些依赖就会提供给Bean。
  • 每一个属性或者构造参数都是一个实际要被设置值的定义,或者是容器中另一个Bean的引用。
  • 每一个属性或者是构造参数的值都会被转换为一个构造参数或者是属性的实际类型,默认情况下,Spring可以把一个字符串值转换为任何的基础类型,像int,long,String,boolean等等。
当容器被创建时,Spring容器校验每一个Bean的配置。但是,直到Bean真正被创建,Bean的属性才会真正被设置。对于单例范围的Bean默认会在容器创建时被预先初始化。范围会在 Section 6.5, “Bean scopes”中讲解。另外,Bean只有在被请求的时候才会创建。Bean的创建可能造成一系列Bean的创建。比如Bean的依赖和它依赖的依赖被创建。注意对于这些Bean的创建解析不匹配的情况会在以后展示,例如在第一个被影响Bean的创建。

循环依赖

如果你主要用构造函数注入,就很可能遇到循环依赖无法解析的情况。
例如:A通过构造函数注入B实例,B也通过构造函数注入一个A实例。如果你为A和B互相配置一个注入,Spring IoC容器会在运行期探测到这个循环引用,并抛出 BeanCurrentlyInCreationException 异常。
一个可行的方案是更改源码设置为Setter赋值而不是通过构造参数。另外,避免仅仅通过构造参数和setter赋值。换句话说,尽管不推荐,你可以通过setter配置循环注入。
不像典型的情况(非循环依赖),Bean A和Bean B的循环依赖强制只有当另一个完全被初始化以后才能注入进去(典型的鸡和蛋的场景)。
通常情况下你可以相信Spring做了正确的事情。在容器加载时它会探测到你的配置问题,例如引用了不存在的Bean和循环依赖。只有当Bean被真正创建时,Spring才会设置属性和尽可能晚的解决依赖关系。这就意味着,当Spring正确被加载后,你请求一个创建时有问题的对象或者是依赖时,Spring也会产生错误。例如,bean抛出一个类不存在或者是类属性无效的错误。这个配置问题潜在延后的暴漏,就是为什么ApplicationContext 默认的预先加载单例Bean的原因。这些Bean在它被需要的时候提前的创建时间和内存的花费,使你在ApplicationContext 被创建时就能发现配置的错误,而不是之后。你可以重写默认的行为使单例Bean被延迟初始化,而不是提前初始化。
如果没有依赖注入的存在,当一个或多个写作的bean被注入到依赖的bean中时,每一个协作的bean都是在被注入之前完全初始化的。这意味着,如果A对B存在依赖,Spring IoC容器在调用A的setter方法之前就会完全实例化B。换句话说,Bean是在调用设置依赖和相关声明周期方法被调用之前就被初始化了(如果不是提前初始化的单例模式)。

依赖注入的例子

下面的例子使用基于XML的配置来使用setter方法依赖注入。一下是Spring XML配置的一部分,指定了一些bean的定义:
<beanid="exampleBean"class="examples.ExampleBean"><!-- setter injection using the nested ref element --><propertyname="beanOne"><refbean="anotherExampleBean"/></property><!-- setter injection using the neater ref attribute --><propertyname="beanTwo"ref="yetAnotherBean"/><propertyname="integerProperty"value="1"/></bean><beanid="anotherExampleBean"class="examples.AnotherBean"/><beanid="yetAnotherBean"class="examples.YetAnotherBean"/>
publicclass ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    privateint i;

    publicvoid setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    publicvoid setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    publicvoid setIntegerProperty(int i) {
        this.i = i;
    }

}
在上例中,setter方法被声明以用来匹配XML文件中指定的属性。下面的例子用了基于构造函数的依赖注入:
<beanid="exampleBean"class="examples.ExampleBean"><!-- constructor injection using the nested ref element --><constructor-arg><refbean="anotherExampleBean"/></constructor-arg><!-- constructor injection using the neater ref attribute --><constructor-argref="yetAnotherBean"/><constructor-argtype="int"value="1"/></bean><beanid="anotherExampleBean"class="examples.AnotherBean"/><beanid="yetAnotherBean"class="examples.YetAnotherBean"/>
publicclass ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    privateint i;

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

}
Bean定义中指定的构造参数将被用来作为 ExampleBean 的构造函数参数。
现在考虑这个例子的一个变种,在这里不用构造函数,而是告诉Spring调用静态工厂方法来返回一个对象实例:
<beanid="exampleBean"class="examples.ExampleBean"factory-method="createInstance"><constructor-argref="anotherExampleBean"/><constructor-argref="yetAnotherBean"/><constructor-argvalue="1"/></bean><beanid="anotherExampleBean"class="examples.AnotherBean"/><beanid="yetAnotherBean"class="examples.YetAnotherBean"/>
publicclass ExampleBean {

    // a private constructorprivate 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.publicstatic ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

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

}
静态工厂的方法是通过 <constructor-arg/> 标签提供的,就和使用构造函数一样。工厂方法返回的类型不需要和静态工厂方法包含的类型一致,尽管例子是一致的。工厂方法(非静态)本质上会用相同的方式(除了用factory-bean
标签来替代 class 标签 ),所以细节就不在这讨论了。

依赖和配置细节

正如上面部分提到的,你可以通过引用被管理的bean或者是内部定义的值来定义bean属性和构造参数。Spring XML配置还支持定义在 <property/> <constructor-arg/> 标签内的子属性。

直接的值(基础类型,String等等)

<property/> value 属性用一个可读的字符串指定一个属性或者是构造参数。Spring的转换服务将会把这些String值转换为实际的属性或参数类型。
<beanid="myDataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"><!-- results in a setDriverClassName(String) call --><propertyname="driverClassName"value="com.mysql.jdbc.Driver"/><propertyname="url"value="jdbc:mysql://localhost:3306/mydb"/><propertyname="username"value="root"/><propertyname="password"value="masterkaoli"/></bean>
下面用了p-namespace来展示一种更简洁的方式:
<beansxmlns="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"><beanid="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足够简洁,但是拼写错误是在运行时而不是设计时被检测出来,除非你用了如Intellij IDEA或者是Spring Tool Suite来支持在创建bean定义时自动属性完成功能。这些IDE提示是高度推荐的。

你也可以这样配置java.util.Properties
<beanid="mappings"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><!-- typed as a java.util.Properties --><propertyname="properties"><value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value></property></bean>
Spring容器运用Java的 PropertyEditor   机制把 <value/> 标签内的文本转换为 java.util.Properties 实例,这是一个不错的捷径,而且是一个少有Spring团队喜欢嵌套 <value/> 标签胜过 value 属性的方式。

idref属性

idref 是一个简单错误校验方式,通过传递容器中的bean给<constructor-arg/>  或者 <property/>   标签。
<beanid="theTargetBean"class="..."/><beanid="theClientBean"class="..."><propertyname="targetName"><idrefbean="theTargetBean" /></property></bean>
上面的bean配置片段完全等同于下面的片段(在运行期):
<beanid="theTargetBean"class="..." /><beanid="client"class="..."><propertyname="targetName"value="theTargetBean" /></bean>
第一种方式比第二种更好,因为用 idref 标签允许容器在部署期间校验引用或者命名的bean是否真实存在。在第二个变种里,传递给客户端bean的targetName 属性的值是不会校验的。只有在客户端bean被实例化的时候错误才会被发现(会伴随一些致命的错误)。如果bean是一个prototype  范围的bean,只有在容器被加载以后才会发现拼写错误和导致的异常。

[Note]

local 标签的idref 在4.0版本中不再被支持, 因为它不再支持提冲常规bean引用的值。当更新到4.0版本后,简单把你的idref local改为idref bean就行

<idref/> 元素的值 是在AOP的配置拦截器ProxyFactoryBean bean定义的。当你指定一个拦截器的名字校验拦截器ID拼写错误时,使用<idref/>元素。

其他Bean的引用(协作的bean)

 ref  元素是在<constructor-arg/>   <property/> 中定义的元素的最终元素。用来让你通过配置另一个被容器管理的bean的引用来设置指定bean属性的值。这些bean引用是将被设置bean的属性的依赖。只有在设置属性之前,需要这些属性的时候,依赖bean才会被实例化(如果以来的bean是单例范围的bean,她会在容器初始化时就被实例化)。所有的依赖最终都是另一个对象的引用。范围和校验取决于你指定的对象的bean,local或者是parent标签的id/name属性。
通过bean的<ref/> 属性指定一个bean是大多通常的方式。并且允许在相同容器或者是父容器中任何bean引用的创建,不管是否是同一个XML定义文件。bean属性的值应该和目标bean的id属性一致,或者和目标bean的name属性一致。
<refbean="someBean"/>
通过 parent 属性指定一个目标bean来创建一个在当前容器父容器中的bean的引用。parent 属性的值应该也和目标bean id 属性的值一致,或者和目标bean的name属性一致,并且目标bean必须在当前容器的父容器中。当你有容器的继承关系或你想用一个在父容器中有相同名字的代理作为父bean来包裹你现在bean时,可以用这种bean引用的变种。
<!-- in the parent context --><beanid="accountService"class="com.foo.SimpleAccountService"><!-- insert dependencies as required as here --></bean>
<!-- in the child (descendant) context --><beanid="accountService"<!--beannameisthesameastheparentbean-->class="org.springframework.aop.framework.ProxyFactoryBean"><propertyname="target"><refparent="accountService"/><!-- notice how we refer to the parent bean --></property><!-- insert other configuration and dependencies as required here --></bean>
[Note]

local 标签的idref 在4.0版本中不再被支持, 因为它不再支持提冲常规bean引用的值。当更新到4.0版本后,简单把你的idref local改为idref bean就行

内部bean

<property/> <constructor-arg/> 内部定义的内部bean。
<beanid="outer"class="..."><!-- instead of using a reference to a target bean, simply define the target bean inline --><propertyname="target"><beanclass="com.example.Person"><!-- this is the inner bean --><propertyname="name"value="Fiona Apple"/><propertyname="age"value="25"/></bean></property></bean>
内部bean不需要定义id或name;如果指定了,容器不会使用那个值作为标识。容器创建内部bean时也会忽视掉 scope
标签:内部bean常常是匿名的,他们常常伴随着外部bean一起创建。不可能像单独的像注入闭合的bean一样来注入内部bean来访问他们。
从自定义的范围接受一个销毁回调是可能的。例如,对于一个包含在单例bean中的请求范围内的内部bean:它的创建时和包含它的bean有关系的,但是它的销毁回调允许作为请求范围的生命周期的一部分。这不是通常的情况;内部bean通常情况和包含它的bean有相同的范围。

集合

你分别用<list/>, <set/>, <map/> ,和 <props/> 标签来设置JAVA类型 List Set Map Properties 的属性和参数。
<beanid="moreComplexObject"class="example.ComplexObject"><!-- results in a setAdminEmails(java.util.Properties) call --><propertyname="adminEmails"><props><propkey="administrator">administrator@example.org</prop><propkey="support">support@example.org</prop><propkey="development">development@example.org</prop></props></property><!-- results in a setSomeList(java.util.List) call --><propertyname="someList"><list><value>a list element followed by a reference</value><refbean="myDataSource" /></list></property><!-- results in a setSomeMap(java.util.Map) call --><propertyname="someMap"><map><entrykey="an entry"value="just some string"/><entrykey ="a ref"value-ref="myDataSource"/></map></property><!-- results in a setSomeSet(java.util.Set) call --><propertyname="someSet"><set><value>just some string</value><refbean="myDataSource" /></set></property></bean>
map的健和值的值或者是set的值也可以是以下这些元素中任何一个:
bean | ref | idref | list | set | map | props | value | null

集合的合并

Spring容器也支持集合的合并。任何应用开发者都可以定义一个父类型 <list/>, <map/>, <set/>    <props/>, 并且定义子类型 <list/> , <map/> <set/>   <props/> 来继承和重写父集合的值。子集合的值是父集合元素和子集合元素合并的结果,当用自己和元素重写父集合中指定的元素时。
这节讨论父-子bean机制。读者如果不熟悉父和子bean的定义,可以在阅读之前先阅读relevant section

下面的例子展示了集合的合并:
<beans><beanid="parent"abstract="true"class="example.ComplexObject"><propertyname="adminEmails"><props><propkey="administrator">administrator@example.com</prop><propkey="support">support@example.com</prop></props></property></bean><beanid="child"parent="parent"><propertyname="adminEmails"><!-- the merge is specified on the child collection definition --><propsmerge="true"><propkey="sales">sales@example.com</prop><propkey="support">support@example.co.uk</prop></props></property></bean><beans>
注意 child bean定义的 adminEmails属性的 <props/>标签的 merge=true 属性。当child bean被用到或者是被容器实例化时,最终的结果有一个adminEmails Properties 集合包含子adminEmails 和父 adminEmails 集合合并的结果。
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
Properties  集合的值通过继承父 <props/> 标签的值,并且子bean的 support  的值重写了父容器的值。

这个合并行为同样适用于 <list/> ,  <map/>   <set/> 集合类型。对于特别的<list/>元素的特殊情况,<list/>集合类型的语义是,一个维护的有序的值得集合。父list的值是在子list值之前的。对于Map,SetProperties 集合类型,不存在顺序。 因此没有排序的集合类型语义实际上构成相关的Map,SetProperties 实现类型的容器在内部使用。

集合合并的限制

你不能合并不同的集合类型(例如一个 Map  和一个 List合并),如果你尝试那么做就会抛出异常。 merge  属性必须在低层次的,继承的,子集合中定义。在父集合中设置 merge 属性是多余的并且不会获得想要的合并结果。

强类型集合

随着JAVA5推出了泛型,你可以使用强类型集合。这就使声明一个只包含String类型的集合成为可能。如果你用Spring给一个bean依赖注入一个强类型集合,你可以借助Spring的类型转换支持,那样你的强类型结合实例的元素就会在加入集合之前被转换为合适的类型。
publicclass Foo {

    private Map<String, Float> accounts;

    publicvoid setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans><beanid="foo"class="x.y.Foo"><propertyname="accounts"><map><entrykey="one"value="9.99"/><entrykey="two"value="2.75"/><entrykey="six"value="3.99"/></map></property></bean></beans>
foo  accounts  属性准备被注入的时候,强类型 Map<String, Float>元素类型的泛型信息就会通过反射生效。这样Spring类型转换结构会把各种值元素认为是 Float类型,把String值 9.99, 2.753.99转换成真正的 Float  类型。

Null和空字符串

Spring把属性或相似空参数市委空字符串。下面基于XML配置文件片段把email属性设置为空的字符串("")。
<beanclass="ExampleBean"><propertyname="email"value=""/></bean>
上面的例子和下面的JAVA代码是相同的:
exampleBean.setEmail("")
<null/>元素代表 null值。例如:
<beanclass="ExampleBean"><propertyname="email"><null/></property></bean>
上面的配置和下面的JAVA代码是相同的:
exampleBean.setEmail(null)

用p-namespace简写XML

p-namespace允许你使用bean元素的属性来代替嵌套的<property/> 元素,来描述你属性的值或者是依赖的bean。

Spring支持命名空间的可扩展的配置格式,此拓展的格式是基于XML模式定义的、这章讨论的bean的配置格式是被定义在XML模式文档中的。但是,p-namespace没有被定义在XSD文件中而是仅仅存在Spring核心模块中。

下面的的例子展示的两个XML片段产生相同的结果:第一个用标准的XML格式,第二个用p-namespace。
<beansxmlns="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"><beanname="classic"class="com.example.ExampleBean"><propertyname="email"value="foo@bar.com"/></bean><beanname="p-namespace"class="com.example.ExampleBean"p:email="foo@bar.com"/></beans>
例子展示了一个在bean定义中用p-namespace定义的email属性。这告诉Spring包含一个属性声明。正如前面提到的,p-namespace不需要有一个模式定义,你可以把标签的名字设置为属性名。

下面展示了两个更多的例子,这两个的bean都有对方的引用:
<beansxmlns="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"><beanname="john-classic"class="com.example.Person"><propertyname="name"value="John Doe"/><propertyname="spouse"ref="jane"/></bean><beanname="john-modern"class="com.example.Person"p:name="John Doe"p:spouse-ref="jane"/><beanname="jane"class="com.example.Person"><propertyname="name"value="Jane Doe"/></bean></beans>

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

[Note]

p-namespace不像标准的XML格式那样灵活。例如,对于结尾是ref的属性就会和证明属性引用的格式冲突,这种情况不会再XML标准格式上发生。我们建议谨慎选择你的方式,并且和你的团队成员沟通好,来避免在配置XML文档时同时使用三种方式。

用c-namespace简写XML

和p-namespace简写相似,c-namespace是spring3.1引进的,允许使用内部属性的方式来配置构造参数而不是嵌套的constructor-arg元素。

让我们来看下面这个用c-namespace通过构造参数的依赖注入:
<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>
c:命名空间和 p:用相同的命名方式(结尾 -ref代表bean的引用)来通过他们的名字设置构造参数。它也不是通过XSD模式定义的(而是存在Spring核心模块中)。

对于上面的例子构造参数名字不可用的时(通常如果二进制代码没有编译时没有Debug信息),可以使用参数索引的方式:
<!-- c-namespace index declaration -->
<bean id="foo"class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

[Note]

由于XML语法的因素,索引符号需要_的存在,因为属性不成不能以数字开头(即使有一些IDE支持)

复合属性名称

当你设置一个bean的属性时你可以使用复合或者嵌套的属性名,只要除了最后的属性名其他的组件路径不为空。考虑下面的bean定义:
<beanid="foo"class="foo.Bar"><propertyname="fred.bob.sammy"value="123" /></bean>
bean  foo  下面有一个 fred  属性, fred  下面有一个 bob  属性, bob  下面有一个 sammy  属性,最终 sammy  被设置为123。为了使这个能工作,必须保证bean被创建以后 foo下的 fred  fred  下的 bob  不为空,要不就会抛出空指针异常 NullPointerException  

使用depends-on

如果一个bean是另一个的依赖,通常意味着一个bean被设置为另一个bean的属性。通常情况下你通过XML配置文件的   <ref/> 标签来完成配置。但是,有些时候bean之间的依赖没有那么直接。例如,一个类中静态初始化器需要被触发,比如数据库驱动注册。 depends-on属性可以明确的强制一个或多个bean在被用到需要初始化之前被初始化。下面的例子展示用 depends-on属性来表达一个单例的依赖。
<beanid="beanOne"class="ExampleBean"depends-on="manager"/><beanid="manager"class="ManagerBean" />
通过提供一个bean名称的列表作为depend-on的值,用逗号,空格或者分号作为一个可用的分隔符,来表达对多个bean的依赖。
<beanid="beanOne"class="ExampleBean"depends-on="manager,accountDao"><propertyname="manager"ref="manager" /></bean><beanid="manager"class="ManagerBean" /><beanid="accountDao"class="x.y.jdbc.JdbcAccountDao" />
[Note]

Bean中的depends-on属性不仅可以指定初始化时间的依赖,并且仅仅对于单例范围的bean的情况,还可以指定一个相应的销毁时间的依赖。depends-on指定的依赖关系会在给定的bean被销毁之前被销毁。因此depends-on属性可以控制依赖关系关闭的顺序。


延迟初始化bean

ApplicationContext 默认情况下会立即创建和注册单例范围的bean做为它初始化的一部分。通常情况下这种提前的初始化是可取的,因为在配合和周边环境中的错误可以被及时发现,如果不这样就会在数小时后甚至数天之后才会被发现。当不需要这样做的时候,你可以通过定义bean的延迟加载来阻止单例bean提前初始化。配置为延迟加载的bean告诉IoC容器当他被请求的时候再实例化而不是容器启动的时候。

在XML配置中,这个行为是被 <bean/>元素的 lazy-init属性控制的,例子如下:
<beanid="lazy"class="com.foo.ExpensiveToCreateBean"lazy-init="true"/><beanname="not.lazy"class="com.foo.AnotherBean"/>
当前面的配置被 ApplicationContext执行,名字是 lazy的bean当 ApplicationContext启动的时候不会立即提前初始化,而名字是 not.lazy的bean会立即执行提前初始化。

但是,当一个延迟初始化的bean是一个单例bean的依赖的时候,就不会延迟初始化了。 ApplicationContext  在启动的时候创建延迟加载的bean,因为它必须满足单例的依赖关系。延迟初始化的bean被注入答单例bean里,这里他就不会延迟加载。

你可以用 default-lazy-init属性控制容器延迟初始化的等级;例如:
<beansdefault-lazy-init="true"><!-- no beans will be pre-instantiated... --></beans>

自动装配

Spring容器可以自动装配各协作bean之间的关系。你可以允许Spring通过检查ApplicationContext的上下文来为你的bean自动处理协作关系。自动装配有如下优势:

  • 自动装配可以大量减少指定属性或者构造参数的需要。(其他机制例如本章将会讨论到的bean模板技术也会避免这些操作)
  • 自动装配可以随着你的对象改变而改变。例如,你需要为类增加一个依赖,在不用更改配置的情况下依赖就可以满足自动装配。因此自动装配在开发期间尤其有用,不需要使显示的写的选项失效可以使代码更稳定。

当用XML配置的时候,你需要用<bean/> 元素的autowire 属性为bean定义指定自动装配模式。自动装配功能有四种模式,你为每个bean指定自动装配,因此你可以选择哪一个进行自动装配。

Table 6.2. 自动装配模式

模式 解释

no

默认不装配。bean引用必须通过ref 元素定义。对于大量的加载更改默认配置是不推荐的,因为显示的指定协作关系可以更好的控制和更清晰。某种程度上,它是系统结构的文档。

byName

通过属性名来自动装配。当属性需要被自动装配时Spring寻找有相同名字的bean来进行装备。例如,如果一个bean的autowiring属性设置为byName,并且它包含一个master变量(也就是他有一个setMaster(...)方法),Spring寻找名字是master的bean,用它为变量赋值。

byType

如果恰好容器中有一个bean的属性类型时,允许变量进行自动装配。如果多于一个时,异常就会抛出,预示着你可能不能把bean的autowiring属性设置为byType。若果没有匹配的类型,什么也不会发生,变量不会被设置。

constructor

类似于byType,但是提供是的构造参数。如果恰好容器中没有bean的构造参数类型,一个错误就会抛出。


你可以通过byType或constructor自动装配模式装配数组和类型集合。在这种情况下,所有的容器中符合预期的自动装配类型都会被提供来满足依赖。如果期望的map的key类型时String你可以自动装配强类型的map。自动装配的map的value会包括所有符合预期类型的实例,并且map的key会包括所有相应bean的名称。

你可以把自动装备和依赖校验合到一起,这样依赖校验会在自动装配完成以后执行。

自动装配的局限和缺点

自动装配当始终用在一个项目上的时候运行时最好的。如果自动装配不这样使用,它可能会使开发者疑惑使用它装配了仅仅一个还是两个bean定义。

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

  • 显示的用property constructor-arg设置依赖通常会覆盖自动装配。你不能自动装配简单属性,像基础类型,Strings,类(包含简单类型的数组)。设计就是如此。
  • 自动装配没有显示装配准确。尽管,正如上面表格提到的,当遇到歧义可能导致预想不到的结果时,Spring很小心的避免猜测。
  • 装配信息可对不能用于从Spring容器产生文档的工具。
  • 容器中的多个bean定义有可能都符合setter方法和构造参数被自动装配。对于数组,集合或者map,这不是问题。但是对于那些希望单个值的依赖,这个歧义就不能武断的解决了。如果没有唯一的bean定义可用,就会抛出异常。

遇到后者情况,你有几个选择:

  • 放弃自动装配选择显示装配
  • 避免自动装配时设置bean定义的autowire-candidate属性为false,正如下面一节描述的
  • 通过设置<bean/>primary属性为true,指定一个唯一的bean定义作为主要的候选
  • 通过声明的配置来实现一些细微粒度的控制,正如在6.9“基于声明的配置”中描述的

从自动装配剔除指定的bean

在每一个bean的基础上,你都可以从自动装配中剔除。在XML配置中,设置<bean/>autowire-candidate属性为false。容器就会从自动装配结构上剔除指定的bean(包括用声明配置的bean,例如@Autowired)。

你也可以通过基于模式匹配候选bean的方式来显示bean的名称。最顶层的<beans/>元素在default-autowire-candidates参数中接受一个或多个模式。例如,来剔除那些名字末尾是repository的候选bean,提供一个*repository的值。可以通过逗号分开列表的方式提供多种模式。通过显示指定bean的autowire-candidate属性为true false 会被优先处理,对于这些bean,模式匹配上的都不会使用。

这项技术对于你不想把一些bean通过自动注入的方式注入到其他bean的时候会很有用。这并不是意味着被剔除的bean自己不能使用自动装配,而是说这个bean不会被自动装配给其他的bean

方法注入

对于大多应用程序场景,大多数容器中的bean都是单例范围的。当一个单例范围的bean需要和另一个单例范围的bean协作的时候,或者一个非单例范围的bean需要和另一个非单例范围的bean协作的,通常你会定义一个bean作为另一个的属性的方式来保持依赖。当bean的声明周期不同时,问题就会产生了。假设一个单例Bean A需要用一个非单例(原型范围)Bean    B,假设A的每一个方法都调用了B。容器只创建单例范围Bean A一次,而且只有一次机会来设置依赖。容器不能每一次在需要B的时候都提供一个Bean B新的实例。

一种方案是放弃一些依赖注入。你可以使Bean A实现 ApplicationContextAware  接口来使A意识到容器的存在,然后通过调用getBean("B")来在每次需要B的时候请求容器产生一个B的实例(通常情况是新的)。下面是这种方法的例子:
// a class that uses a stateful Command-style class to perform some processingpackage fiona.apple;

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

publicclass 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!returnthis.applicationContext.getBean("command", Command.class);
    }

    publicvoid setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
上面的做法实不可取的,应为业务代码意识到了容器的存在并且和Spring容器耦合了。方法注入,一些Spring IoC容器更高级的特征,允许用一种更简洁的方式实现这种情况。

搜索方法注入

搜索方法注入是容器通过重写被容器管理的bean的方法返回一个搜索的容器中另一个命名bean结果的能力。这个搜索通常会涉及到一个原型Bean,正如上一部分的场景中描述的。Spring通过字节码,这些字节码是从CGLIB库动态创建子类用来重写方法,来实现方法的注入。
[Note]
  • 为了让这个动态子类工作,将要被Spring容器实现子类的类不能为final,并且将要被重写的方法也不能为final
  • 当对有抽象方法的类进行单元测试时,需要你自己实现子类,并提供抽象方法的直接实现。
  • 组件扫描所需的具体方法也需要具体的类。
  • 一个更关键的限制是,搜索的方法不能和工厂方法一起工作并且不能和配置类里的@Bean方法一起。因为容器在那种情况下不负责创建实例,因此不能在运行期间创建一个子类。
  • 最后对象中作为注入的目标方法不能是序列化的

看下上面片段中的 CommandManager  类,你会看到Spring容器会动态的重写 createCommand()方法的实现。你的 CommandManager  类将不会有任何Spring的依赖,正如下面修改了的例子中看到的:

package fiona.apple;

// no more Spring imports!publicabstractclass 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?protectedabstract Command createCommand();
}
客户端类包含的方法被注入了(在这里是 CommandManager  类),要被注入的方法需要下面的格式:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);

如果方法是抽象的,动态产生的子类会实现这个方法。否则,动态产生的子类会重写定义在原始类中实体的方法。例如:
<!-- a stateful bean deployed as a prototype (non-singleton) --><beanid="command"class="fiona.apple.AsyncCommand"scope="prototype"><!-- inject dependencies here as required --></bean><!-- commandProcessor uses statefulCommandHelper --><beanid="commandManager"class="fiona.apple.CommandManager"><lookup-methodname="createCommand"bean="command"/></bean>
Bean定义为当任何时候它需要一个 command  Bean的实例的时候都会调用它自己的 createCommand()方法。你必须小心的把 command  Bean加载为原型范围,就像实际需要的那样。如果加载为单例范围,每次就会返回相同的实例。
[Tip]

感兴趣的读者可能也发现ServiceLocatorFactoryBean (在包org.springframework.beans.factory.config中)也可以被使用。这个类的用法和另一个使用的类相似,这个类是ObjectFactoryCreatingFactoryBean。但是他允许你指定你自己的搜索接口的方式而不是用Spring指定的所搜接口。更多的信息请参考这些类的javadoc文档

任意方法替换

一个比搜索方法注入更有用的方法注入的形式是通过另一个方法的实现来替换Bean中的任意方法。用户在不需要这些功能的时候可以跳过剩下的部分。

用XML的方式,你可以用 replaced-method属性来吧存在的方法实现替换为另一个方法实现。考虑下面的类,我们想重写下面类的ComputeValue方法:
publicclass 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
 */publicclass 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 ...;
    }
}
你可以通过使用一个或多个包含在 <replaced-method/>元素里的 <arg-type/>  属性来表明方法的方法签名要被重写。只有当方法是重载的或者类中存在多个变种时参数的签名才是必须的。为了方便,参数的类型字符串可以是参数全名的一个子字符串。例如,下面的都会匹配 java.lang.String
java.lang.String
String
Str
因为参数的个数通常足够来区分每一个可能的选择,这样缩写可以节省很多输入,通过允许你输入一个简单的字符串来匹配参数类型。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐