org.springframework.context.ApplicationContext 接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。 容器通过读取配置元数据来获取要实例化、配置和组装哪些对象的指令。 配置元数据以 XML、Java 注释或 Java 代码表示。 它可以让您表达组成应用程序的对象以及这些对象之间丰富的相互依赖性。
Spring 提供了 ApplicationContext 接口的多个实现。 在独立应用程序中,通常创建 ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext 的实例。 虽然 XML 是定义配置元数据的传统格式,但您可以通过提供少量 XML 配置来指示容器使用 Java 注释或代码作为元数据格式,以声明方式启用对这些附加元数据格式的支持。
在大多数应用场景中,不需要显式的用户代码来实例化一个或多个 Spring IoC 容器实例。 例如,在 Web 应用程序场景中,应用程序的 web.xml 文件中的简单八行(左右)样板 Web 描述符 XML 通常就足够了(请参阅 Web 应用程序的便捷 ApplicationContext 实例化)。
下图显示了 Spring 工作原理的高级视图。 您的应用程序类与配置元数据相结合,以便在创建并初始化 ApplicationContext 后,您拥有一个完全配置且可执行的系统或应用程序。
Spring IoC 容器管理一个或多个 bean。 这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML 定义的形式)。
在容器本身内,这些 bean 定义表示为 BeanDefinition 对象,其中包含(以及其他信息)以下元数据:
包限定的类名:通常是所定义的 bean 的实际实现类。
Bean 行为配置元素,说明 Bean 在容器中的行为方式(范围、生命周期回调等)。
对 Bean 完成其工作所需的其他 Bean 的引用。 这些引用也称为协作者或依赖项。
在新创建的对象中设置的其他配置设置——例如,池的大小限制或管理连接池的 bean 中使用的连接数。
此元数据转换为构成每个 bean 定义的一组属性。 下表描述了这些属性:
属性 | 说明 |
---|---|
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 |
NOTE:
ApplicationContext 实现还允许注册在容器外部(由用户)创建的现有对象。 这是通过 getBeanFactory() 方法访问 ApplicationContext 的 BeanFactory 来完成的,该方法返回 BeanFactory DefaultListableBeanFactory 实现。 DefaultListableBeanFactory 通过 registerSingleton(…) 和 registerBeanDefinition(…) 方法支持这种注册。 然而,典型的应用程序仅使用通过常规 bean 定义元数据定义的 bean。
Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他自省步骤期间正确推理它们。 虽然在某种程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时注册新 bean(与对工厂的实时访问同时进行),并且可能会导致并发访问异常、bean 容器中的状态不一致。
依赖注入 (DI) 是一个过程,对象仅通过构造函数参数、工厂方法的参数或对象实例构造后设置的属性来定义其依赖项(即与它们一起工作的其他对象)。 从工厂方法返回。 然后,容器在创建 bean 时注入这些依赖项。 这个过程从根本上来说是 bean 本身的逆过程(因此得名“控制反转”),通过使用类的直接构造或服务定位器模式自行控制其依赖项的实例化或位置。
采用 DI 原则,代码更加清晰,并且当对象提供其依赖项时,解耦更加有效。 该对象不会查找其依赖项,也不知道依赖项的位置或类。 因此,您的类变得更容易测试,特别是当依赖项位于接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI 存在两种主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用带有多个参数的构造函数来完成的,每个参数代表一个依赖项。 使用特定参数调用静态工厂方法来构造 bean 几乎是等效的,并且本讨论以类似方式对待构造函数和静态工厂方法的参数。 以下示例显示了一个只能通过构造函数注入进行依赖注入的类:
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型进行。 如果 bean 定义的构造函数参数中不存在潜在的歧义,则在 bean 定义中定义构造函数参数的顺序就是在实例化 bean 时将这些参数提供给适当的构造函数的顺序。 考虑下面的类:
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
依赖解析过程
容器执行bean依赖解析如下:
ApplicationContext 使用描述所有 bean 的配置元数据创建和初始化。 配置元数据可以通过 XML、Java 代码或注释来指定。
对于每个 bean,其依赖项以属性、构造函数参数或静态工厂方法的参数(如果您使用它而不是普通构造函数)的形式表示。 这些依赖关系是在实际创建 bean 时提供给 bean 的。
每个属性或构造函数参数都是要设置的值的实际定义,或对容器中另一个 bean 的引用。
作为值的每个属性或构造函数参数都会从其指定格式转换为该属性或构造函数参数的实际类型。 默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如 int、long、String、boolean 等。
Spring 容器在创建容器时验证每个 bean 的配置。 但是,直到实际创建 bean 后,才会设置 bean 属性本身。 单例范围并设置为预实例化(默认)的 Bean 是在创建容器时创建的。 范围在 Bean 范围中定义。 否则,仅当请求时才创建 bean。 创建 Bean 可能会导致创建 Bean 图表,因为创建并分配了 Bean 的依赖项及其依赖项的依赖项(等等)。 请注意,解析
循环依赖
如果主要使用构造函数注入,则可能会创建无法解析的循环依赖场景。
例如:A类通过构造函数注入需要B类的实例,B类通过构造函数注入需要A类的实例。 如果您为类 A 和 B 配置 Bean 以相互注入,Spring IoC 容器会在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException。
一种可能的解决方案是编辑某些类的源代码,使其由 setter 而不是构造函数进行配置。 或者,避免构造函数注入并仅使用 setter 注入。 换句话说,虽然不推荐,但是可以通过setter注入来配置循环依赖。
与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖会强制其中一个 Bean 在完全初始化之前注入另一个 Bean(典型的先有鸡还是先有蛋的场景)。
创建 bean 定义时,您将创建一个配方来创建由该 bean 定义定义的类的实际实例。 bean 定义是一个配方的想法很重要,因为这意味着,与类一样,您可以从单个配方创建许多对象实例。
您不仅可以控制要插入从特定 bean 定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean 定义创建的对象的范围。 这种方法功能强大且灵活,因为您可以通过配置选择创建的对象的范围,而不必在 Java 类级别烘焙对象的范围。 Bean 可以定义为部署在多个范围之一中。 Spring 框架支持六个作用域,其中四个作用域仅在您使用 Web 感知的 ApplicationContext 时才可用。 您还可以创建自定义范围。
下表描述了支持的范围:
单例
(默认)将单个 bean 定义范围限定为每个 Spring IoC 容器的单个对象实例。
原型
将单个 bean 定义的范围限定为任意数量的对象实例。
请求(request)
将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。 也就是说,每个 HTTP 请求都有自己的 Bean 实例,该实例是根据单个 Bean 定义创建的。 仅在 Web 感知的 Spring ApplicationContext 上下文中有效。
会话 (session)
将单个 bean 定义的范围限定为 HTTP 会话的生命周期。 仅在 Web 感知的 Spring ApplicationContext 上下文中有效。
应用 (application)
将单个 bean 定义的范围限定为 ServletContext 的生命周期。 仅在 Web 感知的 Spring ApplicationContext 上下文中有效。
网络套接字
将单个 bean 定义的范围限定为 WebSocket 的生命周期。 仅在 Web 感知的 Spring ApplicationContext 上下文中有效。
生命周期回调 Callbacks
ApplicationContextAware 和 BeanNameAware
要与容器对 bean 生命周期的管理进行交互,您可以实现 Spring 的 InitializingBean 和 DisposableBean 接口。 容器为前者调用 afterPropertiesSet() ,为后者调用 destroy() ,让 Bean 在初始化和销毁 Bean 时执行某些操作。
JSR-250 @PostConstruct 和 @PreDestroy 注释通常被认为是在现代 Spring 应用程序中接收生命周期回调的最佳实践。 使用这些注释意味着您的 bean 不会耦合到 Spring 特定的接口。 有关详细信息,请参阅使用@PostConstruct 和@PreDestroy。
如果您不想使用 JSR-250 注释,但仍想消除耦合,请考虑 init-method 和 destroy-method bean 定义元数据。
在内部,Spring 框架使用 BeanPostProcessor 实现来处理它可以找到的任何回调接口并调用适当的方法。 如果您需要 Spring 默认情况下未提供的自定义功能或其他生命周期行为,您可以自己实现 BeanPostProcessor。 有关更多信息,请参阅容器扩展点。
除了初始化和销毁回调之外,Spring 管理的对象还可以实现 Lifecycle 接口,以便这些对象可以参与容器自身生命周期驱动的启动和关闭过程。
初始化回调
org.springframework.beans.factory.InitializingBean 接口允许 bean 在容器设置 bean 的所有必要属性后执行初始化工作。 InitializingBean 接口指定了一个方法:
void afterPropertiesSet() throws Exception;
销毁回调
实现 org.springframework.beans.factory.DisposableBean 接口可以让 bean 在包含它的容器被销毁时获得回调。 DisposableBean 接口指定了一个方法:
void destroy() throws Exception;