Spring —— IoC 容器详解

引言

本篇博客总结自官网的《The IoC Container》,其中会结合王富强老师的《Spring揭秘》融入自己的语言和理解,争取通过这一篇文章彻底扫除spring IOC的盲区。

本文介绍什么是 IoC 容器,什么是 Bean,依赖,Bean Definition,Bean Factory 等概念知识。

文中会大量出现 xml 的容器配置文件,虽然由于目前 Spring Boot 项目的流行,人们已经很少使用 xml 配置,而且更喜好 Java Config 的配置,但配置只是形式的不同,其内部逻辑都是共通的!

一、什么是 Spring IoC 容器?什么是 Bean?

IoC 也可以理解为依赖注入(dependency injection)(参考《控制反转 IOC 与依赖注入 DI》),它是一个只通过构造函数参数、工厂方法参数、或通过属性赋值等方式去定义它们的依赖的一个过程,简言之,就是属性对象赋值。

【《Spring揭秘》:在Spring中,存在三种依赖注入的方式接口注入、构造器注入、setter注入

1、接口注入:要求实现某个接口才能完成依赖注入,本身带有很强的侵入性,目前已经“退役”

2、构造方法注入:优点是对象在构造完成之后,即已进入就绪状态,可以马上使用。缺点是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。而且在Java中,构造方法无法被继承,无法设置默认值。

3、setter方法注入:setter方法描述性更强,可以被继承,允许设置默认值。缺点是对象无法在构造完成后马上进入就绪状态。

用一句话概括IOC可以给我们带来什么?IOC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。——《Spring解密》

IoC容器会在创建对象的时候注入这些依赖,这基本上是 bean 自己去控制实例化的一个反向,因此叫 Inversion of control 控制反转。

org.springframework.beans 和 org.springframework.context 包是 Spring IoC 容器的基础。BeanFactory 接口提供了一种高级的配置机制,使得 Spring 有能力去管理对象的类型。

ApplicationContext 是 BeanFactory 的一个子接口,它添加了一些额外的特性:

1、更简单的 Spring AOP 特性的集成

2、信息资源处理(用于国际化)

3、事件发布

4、应用程序层面的特定上下文,例如 WebApplicationContext 之于 Web 项目。

简而言之,BeanFactory 提供了配置框架和基本功能,ApplicationContext 加入更适合企业级特性的功能。

【《Spring揭秘》:

两种容器类型的基本区别:

1、BeanFactory:基础类型IOC容器。默认采用延迟初始化策略(lazy-load)(受管对象直到客户端请求时才进行初始化及依赖注入)。容器启动速度较快,所需资源有限。

2、ApplicationContext:继承自BeanFactory,拥有BeanFactory的所有支持,并扩展了高级特性。该类型容器启动后,默认对受管对象全部初始化并绑定完成容器启动速度较慢,所需资源更多。

对容器的比喻(如何看待bean工厂的存在?):

BeanFactory就像一个汽车生产厂。你将汽车零件送入这个汽车生产厂,最后,只需要从生产线的终点取得成品汽车就可以了。】

在 Spring 中,组成应用主干并且由 Spring IoC 容器管理的对象被称为 Bean。bean 是一个由 spring IoC 容器实例化、装配和管理的对象。否则,bean 就是一个应用程序中许许多多对象中的一个普通一员。Bean 和它的依赖都通过IoC容器使用的配置文件来描述。

org.springframework.context.ApplicationContext 接口就代表 Spring 的 IoC 容器,它的职责就是 实例化、配置、装配各种 bean。容器通过读取配置文件获得实例化、配置、装配什么对象的指令。配置信息可以使用 xml、Java 注解、或者 Java 代码来描述。

Spring 已经提供了一些 ApplicationContext 接口的实现。在单体应用中,通常会创建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的实例对象。

在更多数的应用场景中,不需要显式的用户代码来实例化一个或多个 Spring IoC 容器。下面的图解展示了 Spring 是如何工作的:

二、用于描述 IoC 容器的配置信息

如前面图解所示,spring IoC 容器需要使用一些配置信息。这些配置信息表示你将要告诉 IoC 容器如何去实例化、配置、装配应用程序中的对象。

配置信息通常以简单直观的XML格式提供,本文也会以这种配置形式来讲解关键概念和 IoC 容器的特性。

提示:xml 并不是唯一的配置形式。不同的配置形式与 Spring 框架本身完全解耦,目前,很多开发者更偏好使用基于Java  Config 的配置形式。 

1、基于注解(如@Service、@Component等)的配置,是 Spring 2.5 引入的支持以注解形式描述配置信息的一种方式。

2、基于Java 的配置,从 Spring 3.0 开始,JavaConfig 提供的许多特性已经变成 Spring Framework 的核心。这样,你可以通过 Java 而不是 xml 文件来定义应用程序中的各种类。例如 @Configuration、@Bean、@Import 和 @DependsOn 等。

Spring 容器的配置由至少一个 bean 组成。xml 形式的配置以<bean/> 标签来描述这些 bean 定义,并以顶级标签<beans/> 来包裹他们。JavaConfig 形式的配置则需要在一个标记了@Configuration 的类中的 bean定义的方法上标记@Bean。

这些 bean 定义直接对应着组成你的应用程序的真正的对象。最典型的,就是定义 Service 层对象、DAO对象(data access objects),基础设施对象如Hibernate 的 SessionFactories,JMS 的 Queues 等等。通常,并不需要配置细粒度的域对象(domain objects 即实体类对象),因为创建和加载它们通常是 DAO 和业务逻辑的职责。

下面的例子展示了 XML 结构的配置(id 属性是 bean 的唯一标识,class 属性以全类名定义 bean的类型。):

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

    <bean id="..." class="...">  
        <!-- 内部配置协作者和配置信息 -->
    </bean>

    <bean id="..." class="...">
        <!-- 内部配置协作者和配置信息 -->
    </bean>

    <!-- 更多的bean配置 -->
</beans>

三、容器的应用

3.1 实例化一个容器

ApplicationContext 构造函数允许传入配置信息的位置路径,可以是系统路径,也可以是项目的 classpath 下。

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

提示:Spring Resource 抽象提供了一种更便捷的机制,允许从一个 URI 定位信息中以 InputStream 来读取资源。更多描述参考:Application Contexts and Resource Paths

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
        https://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
        https://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>

bean 的定义可以跨越多个配置文件。如上所示,services.xml 中定义的 “petStore” 依赖了 daos.xml 中定义的 “accountDao ”和 “itemDao”。通常,每个单独的 XML  配置文件都代表一个逻辑层或者应用架构中的一个模块

可以使用应用程序上下文构造器来加载所有这些 xml 片段中的 bean 定义。这些构造器可接收多个 Resource 位置,如:

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

或者,使用 <import/> 来加载其他文件中的 bean 定义。例如:

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

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

上例中,外部的 bean 定义会从三个文件中加载过来:services.xml、messageSource.xml、themeSource.xml。所有的位置路径都是执行导入操作文件(以下简称“执行 import 文件”)的相对路径,所以 services.xml 必须和执行 import 文件在相同的文件夹,messageSource.xml 和 themeSource.xml 必须在执行 import 文件所在路径下的 resources 文件夹中。 如你所见,开头的斜杠可以忽略。但是给定的路径一定是相对路径,所以最好的形式就是使用不带斜杠的形式。包括顶层<beans>标签一起都会导入进来,要求被导入的一定要是有效的 xml bean 定义文件。

提示:如果想引用一个父文件夹中的文件,使用 “../” 的形式是可以的,但并不推荐。因为这么做会创建一个相对于当前应用程序之外的文件依赖。尤其不推荐引用路径相对于 classpath: URL(例如:classpath:../services.xml),这样会让运行时解析程序既要选择“最近的” classpath 根路径,还要去它的父路径中检查。类路径配置更改可能导致选择不同的、不正确的目录。

你也可以使用绝对路径,例如,file:C:/config/services.xml 或者 classpath:/config/services.xml。但是,你要知道这么做会让你的应用程序配置和特殊的绝对路径耦合

3.2 使用 IoC 容器(updated on 2020-10-14)

【《Spring揭秘》BeanFactory的对象注册与依赖绑定方式:

相关接口:

BeanFactory接口定义了最基本的获取bean的方法,包括各种方式的getBean,以及对bean的一些判断,包括isSingletoncontainsBean

BeanDefinitionRegistry接口定义了基本的管理bean的方法,包括注册、移除等,从名字可以看出,这个接口针对的都是BeanDefinition类型,这也是bean在容器中的信息模板对象。

DefaultListableBeanFactory类是默认的通用bean工厂,(间接)实现了前面两个接口,具有了获取bean和注册bean的能力。

如何理解BeanFactory和BeanDefinitionRegistry的关系?

打个比方,BeanDefinitionRegistry就像图书馆的书架,所有的书都放在书架上,借书还书都是跟图书馆(BeanFactory)打交道,但书架才是图书馆存放书籍的地方,即:BeanDefinitionRegistry就是BeanFactory的书架

BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的bean管理,DefaultListableBeanFactory就是这样一个比较通用的BeanFactory实现类。它除了间接实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当bean注册管理的角色。

基本上,BeanFactory接口只定义如何访问容器内管理的bean的方法,各个BeanFactory的具体实现类负责具体bean的注册以及管理工作。BeanDefinitionRegistry接口定义抽象了bean的注册逻辑。通常情况下,具体的BeanFactory实现类会实现这个接口来管理bean的注册)】

ApplicationContext 是一个接口,是一个更高级的对象工厂(advanced factory),可以维护各种 bean 和它们的依赖。使用 getBean(String name, Class<T> requiredType)方法,你可以拿到你已经定义好的 bean。ApplicationContext 允许你读取 bean 定义和访问他们。例如:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("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 集成了 web 框架,提供了各种 web 框架组件的依赖注入,例如 controller 和 JSF-managed bean,允许你通过元数据(如自动注入注解 @Autowired)来声明一个特殊的bean。

四、Bean 概述

4.1 什么是 Bean Definition?(updated on 2020-10-14)

一个 IoC 容器管理一个或多个 bean。这些 bean 由你提供给容器的配置信息创建(例如,在 xml 中由 <bean/> 定义)。

在容器内部,这些 bean 的定义被描述为 BeanDefinition 实例(RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类,包含以下这些元数据(部分):

1、一个全类名。通常是定义的 bean 的实际实现类

2、Bean 的行为配置元素,声明bean在容器中的状态行为(作用域、生命周期回调,等等)。

3、对bean执行其工作所需的其他bean的引用。这些引用也称为协作者或依赖项

4、其他配置,例如,池的大小限制或在管理连接池的bean中使用的连接数。

这些元数据被转换为一系列的组成 BeanDefinition 的属性。下表描述了这些属性信息:

属性名详细描述链接

Class

Instantiating Beans

Name

Naming Beans

Scope

Bean Scopes

Constructor arguments

Dependency Injection

Properties

Dependency Injection

Autowiring mode

Autowiring Collaborators

Lazy initialization mode

Lazy-initialized Beans

Initialization method

Initialization Callbacks

Destruction method

Destruction Callbacks

除了 bean definition 这种包含了如何创建一个具体对象的 bean 定义之外,ApplicationContext 的实现也允许容器之外的已存在的对象注册进来。只需要使用 ApplicationContext 的 BeanFactory——getBeanFactory() 方法。这个方法会返回 DefaultListableBeanFactory 对象,它是 BeanFactory的实现类。DefaultListableBeanFactory 中的 registerSingleton(..) 和 registerBeanDefinition(..) 方法支持这种容器外注册操作。然而,典型的应用程序仅使用通过常规bean定义元数据定义的bean。

提示:bean 的元数据和手动提供的单例实例需要尽早注册,这是为了容器能够在自动装配和其他自省步骤中正确地对它们进行推理。虽然在某种程度上支持覆盖存在的元数据和存在的单例对象,但在运行时(并发地实时访问工厂)注册新的 bean不受官方支持,并可能导致并发访问异常,或容器中 bean 的状态不一致,或者两者都有可能。

4.2 Bean 的命名(updated on 2020-10-14)

每个 bean 都有一个或多个标识(identifiers)。这些标识在 bean 定义的所属容器中必须是唯一的。一个 bean 通常只有一个 标识,但是,如果它需要更多的标识,其他的可视为别名。

在 xml 配置中,你可以使用 id 属性 和 name 属性,或者同时使用。id 属性代表唯一 id 。按照惯例,这些名称是字母数字('myBean'、'someService'等),但它们也可以包含特殊字符。如果你想为这些 bean 引入其他别名,也可以指定 name 属性,以逗号、分号 或空白字符间隔即可。但不建议指定过多的别名,这会造成一定的兼容问题。

其实你并不需要为 bean 指定 name 或 id ,容器会自动为这些 bean 生成一个唯一名称。但是如果你想通过名称来引用另一个 bean,可以使用 ref 或 Service Locator 风格查找,就必须为 bean 指定一个名称。不提供名称往往和使用内部 bean(inner bean) 和自动装配协作者(autowiring collaborators)有关。

Bean 的命名约定

bean 命名约定遵循标准的 Java 对实例属性名称的命名惯例。也就是,bean 的名称以小写字母开头配合驼峰命名法。例如:accountManager、accountService、userDao、loginController 等等。

一致的 bean 命名风格可以让你的配置更容易阅读和理解。同样,如果你使用 Spring AOP,这样的命名在将通知应用到一组名称相关的 bean 时很有帮助。

提示:通过类路径(classpath)扫描,Spring 会为未命名的组件生成一个名字。遵从的规则就是前面的规则 :取得类名,然后把首字母变成小写。但是如果一些特殊情况,比如,有一个以上的字符,且第一个和第二个字符都是大写,那么会保留原始大小写。这个规则和 java.beans.Introspector.decapitalize 的规则一致。

Java Config 配置 Bean 的名称

 如果使用 Java Config 配置 bean,可以使用 @Bean 注解的 name 属性,支持传入多个别名:

public @interface Bean {
	@AliasFor("name")
	String[] value() default {};

	@AliasFor("value")
	String[] name() default {};

    // initMethod()、destroyMethod() ...
}

如果只有一个名称,可以像这样配置:@Bean("accountService")。

4.3 Bean 的实例化(updated on 2020-10-14)

一个 bean 定义本质上就是一个创建一个或多个对象的“配方”(recipe,或食谱)。当被请求时,容器查看已命名bean的“配方”,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。

<bean>标签的 class 属性表示该 bean 的类型,这个属性是必须的。在 BeanDefinition 内部,就是 Class 属性。你可以使用下面两种方式其一来使用 Class 属性。

1、大多数情况,容器通过反射调用对象的构造器来创建 bean ,class 用于指定一个构造类型,有点类似于 new 操作。

2、指定一个包含静态工厂方法的类,这个静态工厂方法会创建对象,这中情况不多见。从静态工厂方法返回的对象类型,可以是本类,也可以完全是另一个类。

内部类名称

如果你想为一个静态内部类(static nested class)配置一个 bean,那必须使用该内部类的二进制名称(the binary name of the nested class) 。

例如,如果你有一个类叫做 SomeThing ,包名是 com.example,而且这个 SomeThing 类有一个静态内部类叫做 OtherThing,那么 bean 定义的 class 属性就应该是:com.example.SomeThing$OtherThing。

注意 $ 符号的使用,它将内部类的名称与外部类的名称分隔开。

4.3.1 使用构造器实例化 bean

Spring 极大的兼容了这种创建 bean 的方式,也就是说,被实例化的类不需要实现任何特定的接口或以特殊的方式进行编码。简单地指定声明 bean 的类型就足够。但是,视创建指定bean 的 IOC 容器的类型而定,你可能会需要一个默认 bean 构造器。

Spring IoC容器实际上可以管理你希望它管理的任何类。而不限于 JavaBean。绝大多数 Spring 用户更喜欢实际的 JavaBean,它只有一个默认的(无参数的)构造器和适当的setter和getter方法,这些方法是根据容器中的属性建模的。你还可以在容器中使用更多奇异的非bean样式的类。例如,你需要使用绝对不符合JavaBean规范的遗留的连接池,Spring也可以管理它。

以 xml 的形式,你可以像下面这样:

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

更多关于如何向构造器提供参数(如果需要的话)和对象构造结束后为对象赋值的机制,参考:Injecting Dependencies。 

4.3.2 使用静态工厂方法实例化 bean

当定义一个使用静态工厂方法创建的 bean 时,使用 class 属性指定包含了静态工厂方法的类,然后再使用 factory-method 属性来指定工厂方法的名字。如下所示:

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

而这个静态方法应该是真实可被调用的:

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

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

更多有关向工厂方法提供参数和返回对象后如何赋值的机制,参考: Dependencies and Configuration in Detail。 

4.3.3 使用实例工厂方法实例化 bean

还有一种方法是通过实例工厂方法创建 bean。和静态工厂方法类似,使用实例工厂方法进行实例化会调用一个“非静态”的已存在容器内的bean的方法来创建对象。

使用这种机制时,class 属性为空即可,factory-bean 属性指定一个在当前(或父或祖先)容器内的 bean 名称,这个 bean 需要包含一个可以被用于创建对象的工厂方法。剩下的,就和静态工厂的配置差不多了:

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

 在 Spring 文档中,“factory bean” 指的是在 Spring 容器中配置的可以通过实例工厂或静态工厂方法创建对象的一种 bean。相对的,FactoryBean(注意大小写)指的是 Spring 框架特有的 FactoryBean 接口及实现类。

 【《Spring揭秘》关于FactoryBean:

当某些对象的实例化过程过于繁琐,通过xml配置过于复杂,就可以实现FactoryBean接口,给出对象实例化逻辑代码。FactoryBean是Spring提供的对付这种情况的“制式装备”(正规军)。

FactoryBean的三个抽象方法

1、getObject()方法会返回该FactoryBean“生产”的对象实例,我们需要实现这个方法以给出自己的对象实例化逻辑。

2、getObjectType()方法返回对象的类型,如果预先无法确定,则返回null。

3、isSingleton()方法用于表明getObject()返回的对象是否以singleton形式存在。

案例:

public class NextDayDateFactoryBean implements FactoryBean{
public Object getObject() throws Exception{
 		return new DateTime().plusDays(1);
}
public Class getObjectType(){ return DateTime.class; }
 	public boolean isSingleton(){ return false; }
}
<bean id=”displayer” class=”...Displayer”>
 	<property name=”nextDay”>
 		<ref bean=”nextDayDate”>
 	</property>
</bean>
<bean id=”nextDayDate” class=”...NextDayDateFactoryBean”>
</bean>
public class Displayer {
    private DateTime nextDay;
    // ...
}

提示:FactoryBean类型的bean定义,通过正常的 id 引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean类型本身】

4.4 判断 Bean 的运行时类型(updated on 2020-10-14)

具体 bean 的运行时类型并不容易确定。bean 定义中指定的 class 只不过是一个初始类型参考,可能潜在绑定了一个声明的工厂方法,或者作为一个可能会导致一个不同运行时 bean 类型的 FactoryBean,或在实例工厂方法中根本不设置(如前所述)。此外,AOP的代理可能会使用基于接口的代理对象包裹 bean 实例,这也会限制目标 bean 的真实类型的暴露。

找出指定 bean 的真实运行时类型的方法,推荐使用 BeanFactory.getType 方法,使用 bean 的名称作为参数。上述所有情况都包括在内,这个方法可以返回相同 bean 名称的对象的类型。

五、依赖

5.1 Dependency Injection 依赖注入

依赖注入是一个只通过构造函数参数、工厂方法参数、或通过属性赋值等方式去定义它们的依赖的一个过程,简言之,就是属性赋值。

容器会在创建 bean 的时候注入那些它需要的依赖。这基本是由对象自己控制实例化的反向操作,因此得名控制反转。

使用 DI 原则会让代码更加整洁,而且,当对象被它们的依赖所提供时,也可以更好的解耦。对象不再需要去查找它的依赖,也不需要知道这些依赖的位置和类型信息。因此,你的类就会更容易的去做测试。

DI 存在两种主要的变体:构造器依赖注入Setter 依赖注入

构造器依赖注入通过容器调用一定数量参数的构造来完成,每一个参数都代表一个依赖项。调用静态工厂方法,传入指定参数来构造 Bean 的形式和这差不多。下面代码展示了只能用构造器注入的依赖注入模式:

public class SimpleMovieLister {

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

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

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

注意,上述代码并没有任何特别之处。这就是一个 POJO ,没有依赖容器的任何特定接口、基础类或注解。

使用参数类型就会触发构造器参数类型解析匹配。如果bean定义的构造函数参数中不存在潜在的歧义,那么构造函数参数在bean定义中定义的顺序就是实例化bean时将这些参数提供给适当的构造函数的顺序。考虑以下类:

public class ThingOne {

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

假设 ThingTwo 和 ThingThree 类没有继承关系,没有潜在的歧义存在。这样,下面的配置就可以很好的工作,你不需要在<constructor-args/>标签中显式地指定构造器参数下标或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

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

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

当另一个 bean 被引入,且类型已知,那么就会匹配到。当用到一个简单类型,如<value>true</value>,Spring 无法判断值的类型,也因此无法自主匹配。如下所示:

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 属性来显式指定构造器参数的下标,注意下标从 0 开始。如下所示:

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

下标有时还可解决构造器具有相同简单类型时出现的歧义问题。

也可以使用参数名称来消除歧义:

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

注意,这种方式需要配合 debug 标志,以便 Spring 可以找到构造器的参数名,你可以使用 @ConstructorProperties JDK注解来显式地命名你的构造器参数,如下所示:

public class ExampleBean {

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

Setter 依赖注入,通过容器调用无参构造或无参静态工厂方法实例化 bean 之后调用 setter 方法来完成。

下面的 demo 展示只能通过纯 setter 方法实现依赖注入的方式:

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 支持基于构造器和基于 Setter 方法来对它所管理的 bean 进行依赖注入,同时也支持两种混合使用。

你可以BeanDefinition的形式配置依赖,结合 PropertyEditor 实例,将属性从一种形式转化为另一种形式。然而,绝大多数 Spring 用户并不会以编程的方式直接使用这些类,而是使用 XML 的bean definition、注解式组件(即 @Component、@Controller等等)、或在@Configuration 标记的类中使用 @Bean 的形式(即 Java Config)。这些元信息依然会在内部转化为 BeanDefinition 实例,并且被用于加载整个 Spring IOC 容器实例。

选择构造器 DI 还是 Setter DI?

由于可以混合使用基于构造器和基于setter的DI,对于强制依赖项使用构造器,而对于可选依赖项使用setter方法或配置方法,这是一个很好的经验法则。请注意,在setter方法上使用@Required注释可以使属性成为必需的依赖项,但是,更好的方式是使用构造器然后对参数进行编程验证。

Spring 团队更提倡使用构造器注入,因为这样可以将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造器注入的组件总是会返回完全初始化的状态给调用代码。另外提一句,根据马丁福勒的重构原理,携带大量参数的构造器是一种“坏味道”,这意味着类可能有太多的职责,应当适当进行重构,分离一部分依赖。

Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象易于稍后重新配置或重新注入。

5.2 依赖解析处理

容器会以下面的方式执行 bean 依赖解析操作:

1、ApplicationContext 通过使用 bean 配置信息(可以是 XML、注解、Java Config)进行创建和初始化。

2、对于每一个 bean ,它的依赖描述为属性、构造器函数参数、或者静态工厂方法中的其中一种形式。这些依赖会当 bean 真正创建的时候提供给 bean。

3、每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。

4、作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring可以将字符串格式提供的值转换为所有内置类型,比如int、long、string、boolean等等。

Spring 容器会在创建时验证每个 bean 的配置。但只有在真正实例化 bean 的时候才会为这些 bean 属性赋值。容器在创建之初,会将 bean 创建为 单例(singleton-scoped)且设置为“预实例化”(默认)。Scope 被定义在 Bean Scopes。否则,bean 只有在被请求时才会创建。创建 bean 可能会导致一系列的 bean 都被创建,因为bean 本身需要依赖,而依赖也需要依赖。这些依赖项的解析工作可能会在其后发生,即第一次创建受影响的 bean 时。(Creation of a bean potentially causes a graph of beans to be created, as the bean’s dependencies and its dependencies' dependencies (and so on) are created and assigned. Note that resolution mismatches among those dependencies may show up late — that is, on first creation of the affected bean.)

循环依赖

如果你使用显式构造器注入,可能会出现无法解析的循环依赖问题。

例如,A 对象需要通过构造器注入一个 B 对象,并且 B 也需要通过构造器注入一个 A 对象。如果使用 spring 容器配置 A 和 B 注入彼此,那么 IOC 容器会在运行时检测到这样的循环引用,并抛出 BeanCurrentlyInCreationException。

一个可能的解决方案是编辑源码,将构造器注入改为 setter。要么,避免使用构造器注入,只允许使用 setter 注入。换句话说,尽管并不推荐 setter 注入,但 setter 注入却可以解决循环依赖问题。

和典型的应用场景不同,循环依赖问题描述的是 bean A 和 bean B 之间,强制其中一个要在完全初始化之前注入到另一个对象中(这是典型的先有鸡还是先有蛋的问题)。

Spring 容器可以在加载时检测配置问题,例如引用了不存在的 bean 和循环依赖问题。当 bean 被真正创建之时,Spring 会尽可能晚的为属性赋值和解析依赖。也就是说,当你请求一个对象时,如果创建该对象或其依赖发生了问题,Spring 容器会晚一点产生一个异常,例如一个找不到或无效的属性,就会抛出一个异常。这些都是一些潜在的延迟问题,这也是为什么 ApplicationContext 实现要默认采用预实例化单例 bean 的原因。在实际需要之前创建这些bean需要花费一些前期时间和内存,但可以在创建 ApplicationContext 之时发现配置问题,而不是稍后发现。但也可以覆盖这个默认行为,以便单例 bean 可以惰性地初始化,而不是预先实例化。

如果不存在循环依赖关系,那么当一个或多个协作bean被注入到依赖bean中时,每个协作bean在被注入到依赖bean之前都已被完全配置。也就是说,如果bean A依赖于bean B,那么容器会在调用bean A上的setter方法之前完全配置bean B。换句话说,这个 bean B 会被实例化(如果它不是预实例化的),它的依赖会设置完毕,它的相关生命周期回调方法(例如配置的init 方法或 InitializingBean 回调方法)都会被调用执行。

以下代码是使用 xml 描述的 setter 注入:

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

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

5.3 depends-on

如果一个 bean 是另一个 bean 的依赖,通常意味着这个 bean 需要作为一个属性设置到 另一个 bean 中。一般你可以使用 <ref> 标签配置这样的信息。但是,有时候 bean 之间的依赖关系并不那么直接。例如,当一个静态初始化器需要注册时,例如 数据库驱动注册。那么 depends-on 属性就可以显式地强制在初始化使用此元素的bean之前初始化一个或多个bean。如下所示:

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

当需要描述多个依赖时,可以这样写:

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

提示:depends-on属性既可以指定初始化阶段依赖项,也可以指定对应的销毁阶段依赖项(仅在单例bean中)。在销毁给定bean本身之前,首先销毁与给定bean定义依赖关系的依赖bean。因此,依赖还可以控制关机顺序。

5.4 懒加载 Bean

默认情况,作为初始化过程的一部分,ApplicationContext 的实现会饿汉式创建和配置所有单例 bean。通常,这种预实例化的操作是好的,因为这样可以立刻发现配置中或者环境中的问题和错误,而不是几小时甚至几天后。当不适合用这种方式时,你可以将 bean definition 设置为懒加载来避免预实例化。一个懒加载 bean 会告诉 IOC 容器,只在第一次请求对象的时候创建对象,而不是一启动的时候。如下设置:

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

但是注意,如果懒加载 bean 恰好是非懒加载 bean 的依赖时,那么IOC 容器会忽略懒加载属性,而直接在启动时创建这个懒加载 bean,因为它必须满足作为一个依赖的身份。

也就是说,延迟初始化设置会由于被其他非延迟初始化bean依赖而失效!

另外还可以通过使用<beans/>元素的 default-lazy-init 属性来控制容器级别的延迟初始化,如下面的示例所示:

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

5.5 自动装配依赖

Spring 容器可以自动装配协作者之间的关系。自动装配有以下优点:

1、自动装配可以显著减少指定属性或构造函数参数的需要。

2、自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用。

5.6 方法注入(Method Injection)

大多数应用场景,容器中的 bean 都是单例的。当一个单例 bean 需要和另一个单例 bean 协同工作,或一个非单例 bean 需要和另一个非单例bean 协同工作,你一般会将依赖定义为另一个 bean 的属性。

但当 bean 的生命周期不同时,就会出现问题。

假设单例对象 A 需要使用非单例(原型)对象 B,可能在 A 的每个方法调用上。容器只会创建 A 一次,这样也就只会有一次机会为属性赋值。那么容器无法每次在 A 需要使用 B 的时候都实例化一个 B 提供给 A。

一种解决方法是,放弃一定的控制反转。你可以令 A 实现ApplicationContextAware 接口来让 A 知晓容器的存在,并且在每次 A 需要使用 B 的时候,通过 getBean("B") 来请求一个新的 B 对象。如下所示:

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

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

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

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

这种方式并不可取,因为业务代码知道了 Spring 框架,并与之耦合。方法注入是一个IOC 容器的高级特性,可以让你干净利落的处理这样的情况。

【《Spring揭秘》bean的scope使用陷阱:

依赖于“prototype”对象的客户端对象第一次从容器中取得该prototype对象后,会一直持有该对象引用。因此需要注意,后续从客户端对象中使用的该“prototype”对象总是同一个,即prototype只针对每次从容器中取得才可返回新对象

方法注入(Method Injection)的含义是,容器通过指定的方法将bean注入

普通的getBean方法会使容器带有侵入性,因此,当我们获取prototype对象时,往往会结合方法注入来使用,指定某个方法代替getBean从容器中取得prototype对象。

其底层逻辑是Spring会通过Cglib动态代理该类并重写该注入方法,因此,Spring要求声明的注入方法需要符合固定格式:

<public|protected> [abstract] <return-type> injectMethodName(no-args);

对于一般的getter方法,就符合这样的格式。因此,可以通过<lookup-method>标签告知Spring容器,使用getXxx()方法完成指定bean对象的注入:

<bean id=”b” class=”...B” singleton=”false”></bean>
<bean id=”a” class=”...A”>
 	<lookup-method name=”getB” bean=”b”/>
</bean>

lookup-method标签的name属性指定需要注入的方法名,bean属性指定需要注入的对象。这样,在每次调用a.getB()的时候,程序都会向容器请求一个新的B对象并返回。

六、Bean Scope 作用域

scope用来描述容器中的对象的限定场景或存活时间

打个比方:我们都处于社会(容器)中,如果把中学教师作为一个类定义,那么当容器初始化这些类之后,中学教师只能局限在中学这样的场景中。中学,就可以看做中学教师的scope。——《Spring揭秘》

当你创建一个 bean definition ,你实际上是创建了一个用于实例化的食谱(或处方)。这个“食谱”的思想很重要,因为这意味着,就像类一样,你可以从一个单独的食谱中创建许多对象

Spring 中支持 6 种scope,singleton和prototype是Spring最开始最迟的两种scope类型,在Spring2.0 之后又加入了另外四种只在支持 web 的ApplicationContext中使用的scope类型。你也可以自定义 scope:

Scope描述

singleton

(默认) Scopes a single bean definition to a single object instance for each Spring IoC container.

prototype

Scopes a single bean definition to any number of object instances.

request

Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.

session

Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.

application

Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.

websocket

Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

【《Spring揭秘》以下给出部分关键scope类型的精简说明:

1、singleton:首先,同一个IOC容器只存在一个singleton的bean。其次,容器启动时创建,第一次请求时初始化,容器不退出,对象就就一直存在。

2、prototype:每次重新生成一个新的对象给请求方。实例化、属性设置等工作由容器负责,返回后,容器不再拥有该对象的引用,请求方需自行负责后续(如销毁)生命周期活动。通常,声明为prototype的bean都含有一些可变状态。

3、request:WebApplicationContext会为每个http请求创建一个scope为request的对象,请求结束,对象的生命周期也将结束。从不严格的意义上说,request可以看做是prototype的一种特例,除了场景更加具体,语义上差不多。

4、session:容器会为每个会话创建scope为session的实例。与request相比,除了更长的存活时间,其他方面没什么差别。】 

6.1 单例作用域

单例很好理解,即只有一个实例对象,spring 容器在每次请求该 bean 的时候都只返回同一个对象。

换句话说,当你定义了一个 bean ,且它的 scope 属性是 singleton,那么 IOC 容器只会为其创建一个实例。这个实例被存储在一个用于存储这种单例 bean 的缓存区,后续所有的请求或引用,都会返回已缓存的单例对象。下图展示了单例作用域的工作模式:

Spring 中的单例 bean 的概念与GoF的单例模式有些不同。GoF 单例对对象的作用域进行硬编码,这样每个类加载器都会创建一个且只有一个特定类的实例。Spring单例的作用域最好描述为每个容器和每个bean。这意味着,如果你在单个Spring容器中为特定类定义了一个bean,那么Spring容器将创建由该 bean 定义的类的一个且仅一个实例。

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

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

6.2 原型作用域

原型作用域的 bean 会在每次请求后返回一个新的 bean 实例。你应该对所有有状态的 bean 使用原型作用域,对无状态的 bean 使用单例作用域。下图展示了原型作用域:

数据访问对象(DAO)通常不配置为原型,因为典型的DAO不持有任何会话状态。

与其他作用域相比,Spring不管理原型bean的完整生命周期。容器实例化、配置和组装原型对象并将其交给客户端,而不进一步记录该原型实例。因此,尽管初始化的回调方法在所有对象上都被调用,但在原型的情况下,配置的销毁回调不会被调用。为了让 Spring 容器释放被原型 bean 所持有的资源,请使用自定义bean的后置处理器(bean post-processor),它持有一个需要被清理的 bean的引用。

在一些方面,Spring 容器的原型bean 被视为 Java new 操作符的替代品。

6.3 单例bean依赖原型bean

当使用原型 bean 作为单例 bean 的依赖项时,请注意,依赖关系是在实例化时解析的。因此,如果你将一个原型 bean 依赖式地注入到一个 单例 bean 中,那么就会创建一个新的原型 bean 然后注入到单例 bean中。原型实例是提供给单例 bean的唯一实例。

但是,假设你想让单例 bean 在运行时反复依赖原型 bean ,你不能依赖式地注入原型 bean,因为注入只会发生一次,即只会在IOC 容器实例化该单例 bean 并解析、注入它的依赖时。如果想在运行时多次请求原型 bean ,请使用方法注入(前面有介绍)。

6.4 Request、Session、Application  和 WebSocket 作用域

参考:Request, Session, Application, and WebSocket Scopes

6.5 自定义作用域

bean 的 Scope 机制是可扩展的。你可以定义你自己的作用域,或重新定义现有的作用域,但是后者并不推荐。而且,你无法重写内建的 singleton 和 prototype 作用域。

为了能够将你自定义的 scope 整合到 IOC 容器,你必须实现 org.springframework.beans.factory.config.Scope 接口。

Scope 接口包含 4 个方法,可以从作用域中获取对象,从作用域中移除对象,也可以销毁他们。

以 Session 作用域的实现为例,会返回一个 session 作用域的 bean(如果它不存在,方法就会返回一个新的 bean 实例,然后将它绑定到 session 上,以便后续访问)。

Object get(String name, ObjectFactory<?> objectFactory)

更多内容参考:Custom Scopes

七、Bean 的生命周期与 Aware

Spring 提供了一些接口用来帮助开发者自定义 bean 的一些性质。这一节主要讲解:

生命周期回调;ApplicationContextAware 和 BeanNameAware;以及其他的 Aware 接口。

7.1 生命周期回调

要与容器对 bean 的生命周期管理交互,你需要实现 InitializingBean 接口和 DisposableBean 接口。容器对前者调用afterPropertiesSet(),对后者调用destroy(),让bean在初始化和销毁bean时执行某些操作。

在内部,Spring框架使用BeanPostProcessor处理任何回调接口的实现,它可以找到并调用适当的方法。如果你需要定制特性或Spring默认不提供的其他生命周期行为,可以自己实现BeanPostProcessor。除了初始化和销毁回调之外,spring管理的对象还可以实现生命周期接口,以便这些对象可以参与启动和关闭过程,这是由容器自己的生命周期驱动的。

初始化回调

org.springframework.beans.factory.InitializingBean 接口在容器设置了bean的所有必要属性之后,让bean执行初始化工作。该接口只有一个方法:

void afterPropertiesSet() throws Exception;

我们不建议你使用 InitializingBean 接口,因为它令业务代码与 Spring 框架建立了不必要的耦合。还有另一种方式,我们更建议使用 @PostConstruct 注解或指定一个POJO 初始化方法。在 xml 形式的配置中,你可以使用 init-method 属性来指定该方法的名称,该方法需要具备无返回值(void)、无参数(no-arguments)的特点。如果使用 JavaConfig ,你可以使用 @Bean 中的 initMethod 属性。

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

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

上面的例子和下面的例子效果完全相同,以下是实现 InitializingBean 接口的版本:

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

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

但是,更推荐第一个例子,因为更解耦。

销毁回调

和初始化回调类似,Spring 同样提供了两种实现方式,既可以实现 DisposableBean 接口,也可以直接在 配置中指定 destroy-method 属性。以下片段分别是实现 DisposableBean 接口和 destroy-method 属性配置两种不同方式,请任选其一:

---XML配置:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
---Java 代码:
public class AnotherExampleBean implements DisposableBean {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
---XML配置:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
---Java 代码:
public class ExampleBean {

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

默认的初始化和销毁方法

不需要实现 InitializingBean 接口,也不需要指定 init-method 属性,Spring 提供了以约定命名为基础的初始化和销毁回调方法。

只需要在 bean 中加入 init()、destroy() 方法,Spring 就会自动查找并执行对应生命周期的回调。例如 init() 方法:

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

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

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

那么在配置中就不需要显式声明任何初始化回调方法:

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

Spring 容器保证在 bean 配置完全部依赖后,会立即执行配置好的初始化回调方法。因此,初始化回调是在原始 bean 上调用的,这意味着 AOP 拦截器等还没有使用到 bean 。

混合使用生命周期机制

Spring 2.5 之后,你有三种选择来控制 bean 生命周期行为:

1、InitializingBean 和 DisposableBean 回调接口

2、自定义 init() 和 destroy() 方法

3、@PostConstruct 和 @PreDestroy 注解。你可以混合这些机制来控制一个给定的 bean 。

如果 bean 配置了使用不同的初始化方法的多个生命周期机制,它们会以下面的顺序调用:

1、@PostConstruct 注解标记的方法

2、InitializingBean回调接口定义的afterPropertiesSet()

3、自定义的 init() 方法

销毁方法以下面的顺序执行:

1、@PreDestroy 注解标记的方法

2、DisposableBean 回调接口定义的 destroy()

3、自定义的 destroy() 方法

启动和关闭回调

Lifecycle 接口为任何具有它们自己的生命周期需求的对象定义了一些基本方法(例如启动或停止一些后台处理):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何由Spring 管理的对象都可以实现 Lifecycle 接口。当 ApplicationContext 接收到开始或停止信息时(例如,在运行过程中的 stop/restart 场景),就会将这些请求串联到对应上下文中定义的 Lifecycle 实现上。这是通过探测 LifecycleProcessor 来实现的,如下所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

启动的调用顺序和关闭的调用顺序可能很重要。如果两个对象之间存在依赖关系,依赖端要在依赖项之后开始,在依赖项之前停止。然而,有时候这种直接的依赖关系并不清晰。你可能只知道某个对象应该在另一个对象之前启动。这种情况下, SmartLifecycle 接口就派上用场了,它是 Phased 接口的子接口:

public interface Phased {

    int getPhase();
}

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

当启动时,具有最低相位的对象先开始。当停止时,顺序则反过来。因此,如果一个对象实现了 SmartLifecycle 并且它的 getPhase() 方法返回一个 Integer.MIN_VALUE ,那么它就会第一个启动,最后一个停止。相反的,如果 getPhase() 的值返回Integer.MAX_VALUE ,则表明这个对象应该最后一个启动,并且第一个停止。当考虑phase值时,同样重要的是要知道对于任何没有实现SmartLifecycle的“正常”生命周期对象,其默认的phase是0。因此,任何负相位值都表示一个对象应该在那些标准组件之前开始(并在它们之后停止)。对于任何正相位值,情况正好相反。

7.2 ApplicationContextAware 和 BeanNameAware

当 ApplicationContext 创建了一个实现了 ApplicationContextAware 接口的 bean 实例,那么该实例同样也会拿到一个 ApplicationContext 的引用。以下是 ApplicationContextAware 接口:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

这样, bean 就可以通过 ApplicationContext 接口或将引用转化为它的已知子类(如ConfigurableApplicationContext)以编程的方式来操作创建它们的 ApplicationContext 对象。

当 ApplicationContext 创建了一个实现了 BeanNameAware 接口的 bean 实例,那么该实例也可以拿到一个由关联的 bean definition 定义的 bean name。下面代码片段是 BeanNameAware 接口:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

这个回调方法会在填充普通bean属性之后,在初始化回调(如InitializingBean、afterPropertiesSet或自定义初始化方法)之前调用执行。

7.3 其他的 Aware 接口

除了 ApplicationContextAware 和 BeanNameAware,Spring 提供了广泛的感知回调接口,好让 bean 能够指示容器,它们需要一个基础设施依赖。作为一般规则,名称表示依赖类型。下表总结了最有用的 Aware 接口:

名称注入的依赖详细描述

ApplicationContextAware

Declaring ApplicationContext.

ApplicationContextAware and BeanNameAware

ApplicationEventPublisherAware

Event publisher of the enclosing ApplicationContext.

Additional Capabilities of the ApplicationContext

BeanClassLoaderAware

Class loader used to load the bean classes.

Instantiating Beans

BeanFactoryAware

Declaring BeanFactory.

ApplicationContextAware and BeanNameAware

BeanNameAware

Name of the declaring bean. bean 的名称

ApplicationContextAware and BeanNameAware

BootstrapContextAware

Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContext instances.

JCA CCI

LoadTimeWeaverAware

Defined weaver for processing class definition at load time.

Load-time Weaving with AspectJ in the Spring Framework

MessageSourceAware

Configured strategy for resolving messages (with support for parametrization and internationalization).

Additional Capabilities of the ApplicationContext

NotificationPublisherAware

Spring JMX notification publisher.

Notifications

ResourceLoaderAware

Configured loader for low-level access to resources.

Resources

ServletConfigAware

Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext.

Spring MVC

ServletContextAware

Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext.

Spring MVC

注意,使用这些接口会将你的代码与 spring api 进行耦合,且不符合控制反转的风格。因此,我们建议将它们用于需要对容器进行编程访问的基础设施bean。

八、容器扩展性指导

一般,应用开发者不需要继承 ApplicationContext 的实现类。而是通过插入特殊的继承接口来扩展IOC 容器。

8.1 使用 BeanPostProcessor 来自定义 Bean

BeanPostProcessor,Bean 的后置处理器接口定义了回调方法,你可以实现该接口并提供你的初始化 bean 的逻辑(也可以重写容器的默认实现)、依赖解析逻辑等等。如果你想在 容器完成实例化、装配、初始化 bean 之后实现一些自定义逻辑,你可以插入一个或多个自定义的 BeanPostProcessor 实现。

你可以配置多个 BeanPostProcessor 实例,并且通过设置 order 属性来控制这些实例的执行顺序。只有当 BeanPostProcessor 实现了 Ordered 接口才可以设置该属性。如果你需要自定义 BeanPostProcessor,你就也需要考虑实现 Ordered 接口。

BeanPostProcessor 实例基于 bean 实例执行操作。也就是说,IOC 容器实例化一个 bean ,然后 BeanPostProcessor 才能工作。

如果在一个容器中定义BeanPostProcessor,那么它只对该容器中的bean进行后处理,也就是说,BeanPostProcessor 的作用域是单独的容器。

BeanPostProcessor 接口由两个回调方法构成。当一个类在容器中注册为一个 post-processor ,那么它的每一个实例,在容器初始化方法之前和 bean 初始化回调之后,都会从容器获得一个回调。后置处理器可以对bean实例采取任何操作,包括完全忽略回调。bean 后置处理器通常检查回调接口,或者用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean 后置处理器。

ApplicationContext 会自动检测到 bean 中实现了 BeanPostProcessor 接口。ApplicationContext 会将这些 bean 注册为 post-processor,以便后面在 bean 创建的时候进行调用。bean 的后置处理器可以像其他 bean 一样部署在容器中。

注意,当使用 @Bean 声明了一个 BeanPostProcessor,返回值类型应该是后置处理器的实现类,至少也应该是 BeanPostProcessor 接口类型,清楚地表明该 bean 的后置处理器特性。否则, ApplicationContext 无法自动检测到它。由于BeanPostProcessor需要尽早实例化,以便应用于上下文中其他bean的初始化,因此这种早期类型检测非常关键

编程式注册 BeanPostProcessor 

虽然推荐的 BeanPostProcessor 注册方式是通过 ApplicationContext 自动检测,但你依然可以通过一个 ConfigurableBeanFactory的 addBeanPostProcessor() 方法以编程的方式注册后置处理器。当你需要在注册前计算条件逻辑,或在跨层次结构的上下文复制后置处理器时,这可能非常有用。但是注意,以编程方式注册后置处理器不遵从 Ordered 接口。那么注册顺序就决定了执行顺序。而且也要注意,编程式注册的后置处理器永远在自动检测注册的后置处理器之前执行,会忽略任何显式的顺序声明。

BeanPostProcessor 实例和 AOP 自动代理

实现了 BeanPostProcessor 接口的类会被容器特殊对待。所有的 BeanPostProcessor 实例和直接引用的 bean,都会在容器启动时实例化,并作为 ApplicationContext 启动阶段的一个特殊部分。

Next, all BeanPostProcessor instances are registered in a sorted fashion and applied to all further beans in the container. Because AOP auto-proxying is implemented as a BeanPostProcessor itself, neither BeanPostProcessor instances nor the beans they directly reference are eligible for auto-proxying and, thus, do not have aspects woven into them.

For any such bean, you should see an informational log message: Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

If you have beans wired into your BeanPostProcessor by using autowiring or @Resource (which may fall back to autowiring), Spring might access unexpected beans when searching for type-matching dependency candidates and, therefore, make them ineligible for auto-proxying or other kinds of bean post-processing. For example, if you have a dependency annotated with @Resource where the field or setter name does not directly correspond to the declared name of a bean and no name attribute is used, Spring accesses other beans for matching them by type.

8.2 使用 BeanFactoryPostProcessor 来自定义配置数据

在语义上,BeanFactoryPostProcessor 和 BeanPostProcessor 相似,只有一个不同点,那就是 BeanFactoryPostProcessor 在 bean 的配置数据上进行操作。也就是说,Spring IOC 容器允许 BeanFactoryPostProcessor 读取配置数据,并在容器实例化任何 bean (除了BeanFactoryPostProcessor实例)之前改变配置。

你可以配置多个 BeanFactoryPostProcessor 实例,也设置 order 属性来可以控制运行顺序,但也必须要实现 Ordered 接口才可以。

如果你想改变实际的 bean 实例,那么你需要使用 BeanPostProcessor(如前所述)。虽然在BeanFactoryPostProcessor中使用bean实例在技术上是可行的(例如,通过使用BeanFactory.getBean()),但是这样做会导致过早的bean实例化,违反标准的容器生命周期。这可能会导致负面的副作用,比如绕过bean的后处理。

另外,BeanFactoryPostProcessor实例的作用域为每个容器。这只有在使用容器层次结构时才有用。如果您在一个容器中定义了BeanFactoryPostProcessor,那么它只应用于该容器中的bean定义。一个容器中的Bean定义不会被另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使这两个容器属于同一层次结构。

当Bean 工厂后置处理器在 ApplicationContext 中声明,它将自动执行,以便对定义容器的配置数据执行更改。Spring包括许多预定义的bean工厂后处理器,如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。当然也可以自定义。

ApplicationContext自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。在适当的时候,它将这些bean用作bean工厂的后置处理器。可以像部署任何其他bean一样部署这些后处理bean。

九、注解式容器配置

注解要比XML更适合配置Spring吗?

简单的回答是:看情况。详细的回答是,每种方式都有其利弊,通常,这取决于开发者觉得那种方式更适合。

由于其定义方式,注解在其声明中提供了大量上下文,从而使配置更短、更简洁。但是,XML擅长在不改动源代码或重新编译组件的情况下连接组件。有些开发者喜欢将配置贴近源码,但也有一些人认为这样带注解的类已不再是 POJO ,而且配置也会变得更分散,难以控制。

但不论那种方式,Spring 都可以适应,甚至是混用。值得说明的是,通过 Java Config 的引入,Spring 可以让注解以一种非侵入式的方式进行配置,不需要接触目标组件。

注解式配置为 xml 配置提供了另一种选择。与 xml 不同,开发者可以将配置以注解的方式放到组件类上、方法上、域上。例如 Spring 2.0 引入了 @Required 注解,这让配置必须属性成为可能。Spring 2.5 使遵循相同的通用方法来驱动依赖注入成为可能。从本质上,@Autowired注解提供了与Autowiring协作器中描述的相同的功能,但是拥有更细粒度的控制和更广泛的适用性。Spring 2.5 也增加了对 JSR-250 注解的支持,如 @PostConstruct 和 @PreDestroy。Spring 3.0 增加了对 JSR-330(Java 依赖注入)的支持,包含了 javax.inject 包,如 @Inject 和 @Named。

提示:注释注入在XML注入之前执行。这样,xml 配置就可以对同时使用这两种方式的属性覆盖掉注解配置。

和往常一样,你可以单独地注册 bean 定义,但也可以通过通过下面的标签隐式地注册到基于 xml 配置的Spring 容器中(注意包含的 context 命名空间)。

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

    <context:annotation-config/>

</beans>

(隐式注册的后置处理器包括 AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor、和前面提到的 RequiredAnnotationBeanPostProcessor)

提示:<context:annotation-config/> 只查找定义在同一个应用程序 context 上下文的 bean 上的注解。也就是说,如果你为DispatcherServlet 在 WebApplicationContext 中配置了<context:annotation-config>标签,那么它只检查你的 controller 中 @Autowired 的 bean,而不会检查 service。

9.1 @Required

@Required 注解可以放在 setter 方法上,例如:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

这个注解表示必须在配置时填充受影响的bean属性,要么通过显式地 bean definition 要么通过 自动注入。如果 bean 属性没有成功注入,那么容器就会抛出一个异常。这种饿汉式明确的失败,避免了空指针的发生。我们仍然建议将断言放到bean类本身中(例如,放到init方法中)。这样做会强制执行那些必需的引用和值,即使你在容器之外使用该类。

提示: @Required 注解在 Spring 5.1 被正式废弃,我们更支持使用构造器注入必需配置(或者自定义实现 InitializingBean.afterPropertiesSet() 和 setter 方法)

9.2 使用 @Autowired

你可以在构造器上使用 @Autowired,例如:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

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

提示:从Spring 4.3 开始,如果目标 bean 只定义了一个构造器,就没必要将@Autowired 注解标记在这样的构造器上。然而,如果有两个或多个构造器,且没有默认构造器,那么至少要在一个构造器上标记该注解,以便告诉容器要使用哪个。

你也可以将@Autowired 注解使用在 setter 方法上:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

也可以将@Autowired 注解放在任意方法名的多参方法上:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

你可以可以将@Autowired 放在域上,甚至是混用在构造器上:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

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

你可以通过向需要该类型数组的字段或方法添加@Autowired注解来指示Spring从ApplicationContext中提供所有特定类型的bean:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;
}

类型集合也可以:

public class MovieRecommender {

    private Set<MovieCatalog> movieCatalogs;

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

}

提示:如果想让数组或集合中的bean 有序,你可以让目标 bean 实现 Ordered 接口或使用 @Order 注解或使用 标准的@Priority 注解。否则,只能以注册时的顺序来排序这些 bean。

你可以在目标类型上指定 @Order 注解,或者 @Bean 方法上。 @Order 注解的 value 可能影响注入点的优先级,但不能影响单例的启动顺序,单例的启动顺序是由依赖关系和@DependsOn 决定的。

注意,标准 @Priority 注解不能使用在 @Bean 级别上,因为它无法声明在方法上。

Map 的实例也可以自动注入,只要key 类型是 String。Map 的key 应该保存对应 bean 的名字,如下所示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

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

默认情况,如果在注入点没有匹配的候选 bean 可用,那么自动注入就会失败。声明的如果是数组、集合或 Map,至少需要有一个匹配的 bean 元素。

默认标记 @Autowired 注入的依赖是必需项。你可以改变这种默认设置,使框架跳过非必需依赖注入,如下所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

如果依赖项不可用,或其中一个依赖项的 setter 方法有多个参数,那么非必需方法将根本不会被调用。在这种情况下,将根本不填充非必需字段,保留其默认值。

从 5.0 开始,你也可以使用 @Nullable 注解:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}

你也可以使用 @Autowired 注解注入这些已知的框架相关依赖:BeanFactory、ApplicationContext、Environment、ResourceLoader、ApplicationEventPublisher,和 MessageSource。这些接口及其子接口,例如ConfigurableApplicationContext 或 ResourcePatternResolver,已经自动解析完毕,不需要特殊的设置:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }
}

提示:@Autowired、@Inject、@Value  和 @Resource 注解,都会被 Spring 自己的 BeanPostProcessor 实现来处理。也就是说,你无法在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。这些类型必须通过使用XML或Spring @Bean方法显式地“连接起来”。

9.3 使用 @Primary 来微调基于注解的自动注入(updated on 2020-10-11)

因为通过类型来完成自动注入可能会有多个“候选项”(注:如接口类型可能会有多个实现子类),因此也就有必要有更多的控制手段。一种方法是通过 @Primary 注解来完成此项操作。@Primary 表示当单值依赖(single-valued dependency)注入时有多个候选项,那么应该只提供一个特定的 bean 引用如果这些候选项中有一个确定的“主要的”(primary)bean ,那么它就会是那个被注入的值。

考虑下面的用例:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

}

基于上面的配置用例,下面的 MovieRecommender 电影推荐类就会注入 firstMovieCatalog 类型的 bean:

public class MovieRecommender {
    @Autowired
    private MovieCatalog movieCatalog;
}

对应的 bean 定义如下:

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

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

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

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

</beans>

9.4 使用 @Qualifier 微调基于注解的自动注入(updated on 2020-10-11)

@Primary 是一种可以处理通过类型判断自动注入时多个候选项只注入主要候选项的有效方式(或者可以描述为默认候选项注入)。当你需要更有控制力的方式来影响注入 bean 的匹配过程时,可以使用 Spring 提供的 @Qualifier 注解。你可以为 @Qualifier 的 value 属性指定特殊的值,从而缩小类型匹配的范围,以便为每个参数选择特定的bean。例如下面用例:

public class MovieRecommender {

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

}

或者用在构造器上,指定注入的参数类型:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

对应的 bean 定义如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans ... >

    <context:annotation-config/>

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

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

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

</beans>

对于降级匹配(fallback match),bean 的名称被视为一个默认的限定值(qualifier value)。这样,你可以将 bean 的 id 属性定义为 “main” 而不是使用内嵌的<qualifier> 标签,来达到同样的匹配效果。

然而,虽然你可以使用这种约定,通过名称来引入一个特定的 bean,但 @Autowired 基本上还是一个以类型驱动(type-driven)的注入方式,只是带有可选的语义限定符(optional semantic qualifier)。这也就是说,限定值即使带有名称降级匹配,但也总是在类型匹配集中具有收缩语义(narrowing semantics)。它们在语义上并不表达唯一的 bean 的 id。

好的限定值是 main、EMEA、或 persistent,表示独立于bean id 的特定组件的特征,在匿名bean定义(如前面示例中的bean定义)的情况下,可以自动生成。

@Qualifier 同样可以用于类型化集合的注入情况。例如, Set<MovieCatalog>。在这种情况下,所有匹配的 bean,根据声明的限定值,都会被注入到集合中。这也就是说,限定值不必唯一,它们组成了某个过滤条件。例如,你可以定义多个 MovieCatalog 类的 bean,使用相同的限定值“action”,那么所有这些 bean 都会被注入到声明了 @Qualifier("action") 的 Set<MoiveCatalog> 集合中。

提示:在类型匹配候选中选择指定名称的 bean,不需要在注入点使用 @Qualifier 注解。如果没有其他解析指示器(例如 @Qualifier 或 @Primary),对于非唯一依赖情况,Spring将注入点名称(即字段名称或参数名称)与目标 bean 名称匹配,并选择同名的候选对象,也就是说字段名称本身就默认是一种限定值

也就是说,如果你打算通过名称来表示注解注入,不要都用 @Autowired,虽然它的确可以在类型匹配的候选中选择指定名称的 bean。相反地,应该使用 JSR-250 标准的 @Resource 注解,这个注解在语义上定义为通过惟一名称标识特定的目标组件,声明的类型与匹配过程无关。@Autowired 注解有着相当不同的语义:在类型匹配结束后,指定的 String 类型限定符值只会在类型匹配的候选项中选择。

对于定义为集合、Map 或 数组类型的 bean,@Resource 是一个很好的解决方式,通过唯一的名称引入集合或数组 bean。也就是说,从 Spring 4.3 开始,你依然可以使用 @Autowired 的类型匹配算法来匹配集合、Map 和 数组类型,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中。这种情况下,你可以使用限定符在相同类型的集合中进行选择,如前一段所述。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页