介绍 Spring IoC 容器和 bean

Posted 倒流二十年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了介绍 Spring IoC 容器和 bean相关的知识,希望对你有一定的参考价值。

简介

本章涵盖了 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.FooBar使字符来分割外部类和内部类的名称。

通过构造函数实例化

当你使用构造方法来创建 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 [email protected]({"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来声明类),或者是用基于[email protected]类的@Bean方法。这些资源然后在内部被转换为BeanDefinition实例,然后被用来加载整个Spring IoC容器实例。

构造方法和setter对比

基于构造函数还是基于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来展示一种更简洁的方式:
上面的XML足够简洁,但是拼写错误是在运行时而不是设计时被检测出来,除非你用了如Intellij IDEA或者是Spring Tool Suite来支持在创建bean定义时自动属性完成功能。这些IDE提示是高度推荐的。

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

你也可以这样配置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属性一致。

 <!-- 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的属性和参数。

<bean id="moreComplexObject"class="example.ComplexObject"><!-- results in a setAdminEmails(java.util.Properties) call --><propertyname="adminEmails"><props><propkey="administrator">[e

以上是关于介绍 Spring IoC 容器和 bean的主要内容,如果未能解决你的问题,请参考以下文章

Spring IOC源代码具体解释之容器依赖注入

Spring框架参考手册翻译——第三部分 核心技术 6.1 Spring IoC容器和bean的介绍

Spring源码剖析1:初探Spring IOC核心流程

Spring IoC源码解析——Bean的创建和初始化

Spring IOC-介绍和使用

Spring IoC容器初始化源码—populateBeaninitializeBean填充Bean字段反射和setter方法依赖注入以及IoC容器初始化总结四万字