Spring 框架最重要的是控制反转 (IoC) 容器
1.容器概述
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 后,您拥有一个完全配置且可执行的系统或应用程序。
2. bean 概述
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 容器中的状态不一致。
3.依赖注入 (DI)
依赖注入 (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(典型的先有鸡还是先有蛋的场景)。
4.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 上下文中有效。
5.定制一个bean
-
生命周期回调 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;
默认初始化和销毁方法
当您编写不使用 Spring 特定的 InitializingBean 和 DisposableBean 回调接口的初始化和销毁方法回调时,您通常会编写名称为 init()、initialize()、dispose() 等的方法。 理想情况下,此类生命周期回调方法的名称在整个项目中是标准化的,以便所有开发人员都使用相同的方法名称并确保一致性。
您可以将 Spring 容器配置为“查找”每个 bean 上的命名初始化并销毁回调方法名称。 这意味着,作为应用程序开发人员,您可以编写应用程序类并使用名为 init() 的初始化回调,而无需为每个 bean 定义配置 init-method=“init” 属性。 Spring IoC 容器在创建 bean 时调用该方法(并且根据前面描述的标准生命周期回调协定)。 此功能还为初始化和销毁方法回调强制执行一致的命名约定。
假设您的初始化回调方法名为 init(),销毁回调方法名为 destroy()。 您的类类似于以下示例中的类:
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.");
}
}
}
ApplicationContextAware 和 BeanNameAware
当 ApplicationContext 创建一个实现 org.springframework.context.ApplicationContextAware 接口的对象实例时,该实例将提供对该 ApplicationContext 的引用。 以下清单显示了 ApplicationContextAware 接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean 可以通过 ApplicationContext 接口或通过将引用强制转换为该接口的已知子类(例如 ConfigurableApplicationContext,它公开了附加功能),以编程方式操作创建它们的 ApplicationContext。 一种用途是以编程方式检索其他 bean。 有时此功能很有用。 然而,一般来说,您应该避免它,因为它将代码耦合到 Spring 并且不遵循控制反转风格,在这种风格中,协作者作为属性提供给 bean。 ApplicationContext 的其他方法提供对文件资源的访问、发布应用程序事件以及访问 MessageSource。 这些附加功能在 ApplicationContext 的附加功能中进行了描述。
自动装配是获取 ApplicationContext 引用的另一种替代方法。 传统的构造函数和 byType 自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或 setter 方法参数提供类型为 ApplicationContext 的依赖项。 为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,请使用基于注释的自动装配功能。 如果这样做,如果相关字段、构造函数或方法带有 @Autowired 注释,则 ApplicationContext 会自动装配到需要 ApplicationContext 类型的字段、构造函数参数或方法参数中。 有关更多信息,请参阅使用@Autowired。
当 ApplicationContext 创建一个实现 org.springframework.beans.factory.BeanNameAware 接口的类时,该类将获得对其关联对象定义中定义的名称的引用。 以下清单显示了 BeanNameAware 接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
6 容器的扩展点
通常,应用程序开发人员不需要子类化 ApplicationContext 实现类。 相反,Spring IoC 容器可以通过插入特殊集成接口的实现来扩展。 接下来的几节将描述这些集成接口。
使用 BeanPostProcessor 自定义 Bean
BeanPostProcessor 接口定义了回调方法,您可以实现这些方法来提供您自己的(或覆盖容器的默认值)实例化逻辑、依赖项解析逻辑等。 如果您想在 Spring 容器完成实例化、配置和初始化 Bean 后实现一些自定义逻辑,您可以插入一个或多个自定义 BeanPostProcessor 实现。
您可以配置多个 BeanPostProcessor 实例,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的运行顺序。 仅当 BeanPostProcessor 实现 Ordered 接口时才可以设置此属性。 如果您编写自己的 BeanPostProcessor,您也应该考虑实现 Ordered 接口。 有关更多详细信息,请参阅 BeanPostProcessor 和 Ordered 接口的 javadoc。 另请参阅有关 BeanPostProcessor 实例的编程注册的注释。
BeanPostProcessor 实例对 bean(或对象)实例进行操作。 也就是说,Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 实例完成它们的工作。
BeanPostProcessor 实例的作用域为每个容器。 仅当您使用容器层次结构时这才相关。 如果您在一个容器中定义 BeanPostProcessor,则它仅对该容器中的 Bean 进行后处理。 换句话说,在一个容器中定义的 Bean 不会由在另一容器中定义的 BeanPostProcessor 进行后处理,即使两个容器属于同一层次结构也是如此。
要更改实际的 bean 定义(即定义 bean 的蓝图),您需要使用 BeanFactoryPostProcessor,如使用 BeanFactoryPostProcessor 自定义配置元数据中所述。
org.springframework.beans.factory.config.BeanPostProcessor 接口由两个回调方法组成。 当这样的类在容器中注册为后处理器时,对于容器创建的每个 bean 实例,后处理器在容器初始化方法(例如 InitializingBean.afterPropertiesSet() 或 任何声明的 init 方法)都会被调用,并且在任何 bean 初始化回调之后。 后处理器可以对 bean 实例执行任何操作,包括完全忽略回调。 bean 后处理器通常会检查回调接口,或者它可能会用代理包装 bean。 一些 Spring AOP 基础结构类被实现为 bean 后处理器,以提供代理包装逻辑。
ApplicationContext 自动检测配置元数据中定义的实现 BeanPostProcessor 接口的任何 bean。 ApplicationContext 将这些 bean 注册为后处理器,以便稍后在创建 bean 时调用它们。 Bean 后处理器可以像任何其他 Bean 一样部署在容器中。
注意,在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,明确 指示该 bean 的后处理器性质。 否则,ApplicationContext 在完全创建之前无法按类型自动检测它。 由于 BeanPostProcessor 需要尽早实例化,以便应用于上下文中其他 bean 的初始化,因此这种早期类型检测至关重要。
以编程方式注册 BeanPostProcessor 实例
虽然推荐的 BeanPostProcessor 注册方法是通过 ApplicationContext 自动检测(如前所述),但您可以使用 addBeanPostProcessor 方法以编程方式针对 ConfigurableBeanFactory 注册它们。 当您需要在注册之前评估条件逻辑,甚至需要在层次结构中的上下文之间复制 Bean 后处理器时,这会很有用。 但请注意,以编程方式添加的 BeanPostProcessor 实例不遵循 Ordered 接口。 在这里,注册的顺序决定了执行的顺序。 另请注意,无论任何显式顺序如何,以编程方式注册的 BeanPostProcessor 实例始终先于通过自动检测注册的实例进行处理。
BeanPostProcessor 实例和 AOP 自动代理
实现 BeanPostProcessor 接口的类很特殊,容器会以不同的方式对待它们。 所有 BeanPostProcessor 实例和它们直接引用的 bean 都会在启动时实例化,作为 ApplicationContext 特殊启动阶段的一部分。 接下来,所有 BeanPostProcessor 实例都以排序的方式注册,并应用于容器中的所有其他 bean。 因为 AOP 自动代理是作为 BeanPostProcessor 本身实现的,所以 BeanPostProcessor 实例和它们直接引用的 bean 都没有资格进行自动代理,因此没有将方面编织到其中。
对于任何此类 Bean,您应该看到一条信息性日志消息:Bean someBean 不符合所有 BeanPostProcessor 接口的处理条件(例如:不符合自动代理的条件)。
如果您通过使用自动装配或@Resource(可能会回退到自动装配)将bean连接到BeanPostProcessor,则Spring在搜索类型匹配依赖项候选时可能会访问意外的bean,因此使它们不符合自动代理或其他类型的条件 豆后处理。 例如,如果您有一个用 @Resource 注释的依赖项,其中字段或 setter 名称不直接对应于 bean 的声明名称,并且未使用 name 属性,则 Spring 会访问其他 bean 以按类型匹配它们。
以下示例演示如何在 ApplicationContext 中编写、注册和使用 BeanPostProcessor 实例。
- 示例:Hello World,BeanPostProcessor 风格
第一个示例说明了基本用法。 该示例显示了一个自定义 BeanPostProcessor 实现,它在容器创建每个 bean 时调用 toString() 方法,并将结果字符串打印到系统控制台。
以下清单显示了自定义 BeanPostProcessor 实现类定义:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
- 示例:AutowiredAnnotationBeanPostProcessor
将回调接口或注释与自定义 BeanPostProcessor 实现结合使用是扩展 Spring IoC 容器的常用方法。 一个例子是 Spring 的 AutowiredAnnotationBeanPostProcessor - 一个 BeanPostProcessor 实现,它随 Spring 发行版一起提供,并自动装配带注释的字段、setter 方法和任意配置方法。
使用 FactoryBean 自定义实例化逻辑
您可以为本身就是工厂的对象实现 org.springframework.beans.factory.FactoryBean 接口。
FactoryBean 接口是 Spring IoC 容器实例化逻辑的可插入点。 如果您有复杂的初始化代码,最好用 Java 来表达,而不是(可能)冗长的 XML,那么您可以创建自己的 FactoryBean,在该类中编写复杂的初始化,然后将自定义 FactoryBean 插入容器中。
FactoryBean 接口提供了三种方法:
T getObject():返回该工厂创建的对象的实例。 该实例可能会被共享,具体取决于该工厂是否返回单例或原型。
boolean isSingleton():如果此 FactoryBean 返回单例则返回 true,否则返回 false。 该方法的默认实现返回 true。
Class<?> getObjectType():返回 getObject() 方法返回的对象类型,如果事先未知类型,则返回 null。
FactoryBean 概念和接口在 Spring 框架内的许多地方使用。 Spring 本身附带了超过 50 个 FactoryBean 接口的实现。
当您需要向容器请求实际的 FactoryBean 实例本身而不是它生成的 bean 时,请在调用 ApplicationContext 的 getBean() 方法时在 bean 的 id 前加上与号 (&) 前缀。 因此,对于 id 为 myBean 的给定 FactoryBean,在容器上调用 getBean(“myBean”) 将返回 FactoryBean 的乘积,而调用 getBean(“&myBean”) 将返回 FactoryBean 实例本身。