20220507 Core - 1. The IoC Container
IoC 容器
Inversion of Control (IoC) container
控制反转
Spring IoC Container 和 Bean 介绍
org.springframework.beans
和 org.springframework.context
包是 Spring Framework 的 IoC 容器的基础
BeanFactory
接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext
是 BeanFactory
的子接口,它补充了:
- 更容易与 Spring 的 AOP 特性集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用层特定的上下文,例如在 Web 应用程序中使用的
WebApplicationContext
简而言之,BeanFactory
提供了配置框架和基本功能,ApplicationContext
添加了更多企业级别特定的功能。ApplicationContext
是对 BeanFactory
的一个完整的超集。
在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean 。bean 是由 Spring IoC 容器实例化、组装和管理的对象。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。
参考源码
org.springframework.beans.factory.BeanFactory
org.springframework.context.ApplicationContext
org.springframework.core.ResolvableType
org.springframework.beans.factory.ObjectProvider
org.springframework.beans.factory.ObjectFactory
容器概述
org.springframework.context.ApplicationContext
接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示。它可以让您表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。
Spring 提供了 ApplicationContext
接口的几个实现。在独立应用程序中,通常创建 ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的实例。虽然 XML 一直是定义配置元数据的传统格式,但是您可以通过提供少量 XML 配置来声明性地支持这些额外的元数据格式,从而指示容器使用 Java 注解或代码作为元数据格式。
在大多数应用程序方案中,不需要显式用户代码来实例化 Spring IoC 容器的一个或多个实例。例如,在 Web 应用程序场景中,应用程序文件 web.xml
中的简单八行样板 Web 描述符 XML :
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
下图显示了 Spring 如何工作的高级视图。您的应用程序类与配置元数据相结合,以便在 ApplicationContext
创建和初始化之后,您就有了一个完全配置且可执行的系统或应用程序。
配置元数据
如上图所示,Spring IoC 容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化、配置和组装应用程序中的对象。Spring IoC 容器本身与实际写入此配置元数据的格式完全分离,配置元数据可以基于 XML 或 Java 配置。
Spring 配置包含至少一个并且通常不止一个容器必须管理的 bean 定义。基于 XML 的配置元数据将这些 bean 配置为顶级元素 <beans/>
中的 <bean/>
元素。而 Java 配置通常在 @Configuration
类中使用 @Bean
注解方法。
实例化一个容器
提供给 ApplicationContext
构造函数的位置路径或路径是资源字符串,它允许容器从各种外部资源(如本地文件系统、 Java CLASSPATH 等)加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "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">
<!-- 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>
构建基于 XML 的配置元数据
让 bean 定义跨越多个 XML 文件会很有用。通常,每个单独的 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>
所有位置路径都相对于执行导入的定义文件,因此 services.xml
必须与执行导入的文件位于同一目录或类路径位置, messageSource.xml
和 themeSource.xml
必须位于 resources
目录下。前导斜杠被忽略。但是,鉴于这些路径是相对的,最好根本不使用斜杠。
使用 ../
相对路径引用父目录中的文件是可能的,但不建议这样做。这样做会在当前应用程序之外的文件上创建一个依赖项。特别不推荐配合 classpath:
使用,运行时解析处理时会选择“最近的”类路径根,然后查看其父目录。类路径配置的更改可能导致选择不同的、不正确的目录。
可以使用完全限定的资源位置而不是相对路径: 例如,file:C:/config/services.xml
或 classpath:/config/services.xml
。但是,请注意,您正在将应用程序的配置耦合到特定的绝对位置。对于这种绝对位置,通常更可取的做法是保持间接性ーー例如,通过在运行时根据 JVM 系统属性解析的 ${ ... }
占位符
使用示例
目录路径:
\resources
|-\ioc
|--my1.xml
|--my2.xml
|--\sub
|---my3.xml
|---my4.xml
ApplicationContext ac = new ClassPathXmlApplicationContext("ioc/my1.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">
<import resource="my2.xml"/>
<import resource="sub/my3.xml"/>
<import resource="classpath:ioc/sub/my4.xml"/>
</beans>
使用容器
通过使用 T getBean(String name, Class<T> requiredType)
可以获取到容器中的 bean 实例
读取 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();
最灵活的变体是 GenericApplicationContext
与 reader 委托相结合,例如,使用 XmlBeanDefinitionReader
来处理 XML 文件
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
可以在相同的 ApplicationContext
上混合和匹配这些读取器委托,从不同的配置源中读取 bean 定义
ApplicationContext
接口还有其他一些检索 bean 的方法,但是理想情况下,您的应用程序代码不应该使用它们。实际上,您的应用程序代码根本不应该调用 getBean()
方法,因此根本不依赖于 Spring api。
Bean 概述
Spring IoC 容器管理一个或多个 bean 。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>
定义的形式)
在容器本身内,bean 定义表示为 BeanDefinition
对象,其中包含以下元数据:
- 包限定的类名:通常是定义的 bean 的实际实现类
- Bean 行为配置元素,它说明 Bean 在容器中的行为方式(scope, lifecycle callbacks 等)
- 对 bean 完成工作所需的其他 bean 的引用。这些引用也称为协作者或 依赖关系
- 在新创建的对象中设置的其他配置设置——例如,池的大小限制或在管理连接池的 bean 中使用的连接数
此元数据转换为组成每个 bean 定义的一组属性。下表描述了这些属性:
Property | 含义 | Explained in… |
---|---|---|
Class | 类名 | Instantiating Beans |
Name | bean 名称 | 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 的信息的 bean 定义之外,ApplicationContext
实现还允许注册容器外(由用户创建)的现有对象。这是通过 getBeanFactory()
方法访问 ApplicationContext
的 BeanFactory
来完成的,该方法返回的 BeanFactory
是 DefaultListableBeanFactory
实现。DefaultListableBeanFactory
通过 registerSingleton(..)
和 registerBeanDefinition(..)
方法支持此注册。但是,典型的应用程序仅使用通过常规 bean 定义元数据定义的 bean。
Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确推理它们。虽然在一定程度上支持覆盖现有元数据和现有单例实例,但官方不支持在运行时(与对工厂的实时访问同时进行)注册新 bean,并可能导致并发访问异常或者 bean 容器中的状态不一致。
参考源码
org.springframework.beans.factory.config.BeanDefinition
org.springframework.beans.factory.support.DefaultListableBeanFactory
命名 Bean
每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。但是,如果它需要多个,则可以将多余的视为别名。
在基于 XML 的配置元数据中,您可以使用 id
或 name
属性来指定 bean 标识符。这些名称可以是字母数字,也可以包含特殊字符。id
属性允许您指定一个 ID。如果要为 bean 引入其他别名,也可以在 name
属性中指定它们,用逗号 ,
、分号 ;
或空格分隔。
如果没有明确提供 id
或 name
属性,则容器会为该 bean 生成一个唯一的名称。但是,如果您想通过名称引用该 bean,通过使用 ref
元素或 Service Locator 样式查找,您必须提供名称。
<bean class="study.hwj.spring.bean.MyBean2"/>
<!-- 自动生成的 bean 名称是 study.hwj.spring.bean.MyBean2#0 ,别名是 study.hwj.spring.bean.MyBean2 -->
Bean 命名约定:约定是在命名 bean 时对实例字段名称使用标准 Java 约定。也就是说,bean 名称以小写字母开头,并以驼峰格式显示。例如 accountManager
、 accountService
、userDao
、loginController
通过类路径中的组件扫描,Spring 为未命名的组件生成 bean 名称,遵循前面描述的规则:本质上,采用简单的类名并将其初始字符转换为小写。但是,在有多个字符且第一个和第二个字符都是大写的特殊情况下,原始大小写被保留。这些规则与 java.beans.Introspector.decapitalize
定义的规则相同,也就是说, FooBah
变为 fooBah
, X
变为 x
,但 URL
保持为 URL
参考源码
java.beans.Introspector.decapitalize
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
// name 长度大于 1 并且 第一个和第二个字符都是大写的,返回原值
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){
return name;
}
// 否则,将字符串首字母转为小写
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
在 Bean 定义之外给 Bean 取别名
在实际定义 bean 的地方指定所有别名并不总是足够的。有时需要为在别处定义的 bean 引入别名。这在大型系统中很常见,其中配置在每个子系统之间拆分,每个子系统都有自己的一组对象定义。在基于 XML 的配置元数据中,可以使用 <alias/>
元素来完成此操作。
<alias name="fromName" alias="toName"/>
如果使用 Java 配置,可以使用 @Bean
注解提供别名。
实例化 Bean
bean 定义本质上是创建一个或多个对象的配方。
如果使用基于 XML 的配置元数据,则要在 <bean/>
元素的 class
属性中指定实例化的对象的类型。这个类属性(在内部是 BeanDefinition
实例上的 Class
属性)通常是强制的。可以通过以下两种方式之一使用 Class
属性:
- 通常,在容器本身通过反射地调用其构造函数直接创建 bean 的情况下,指定要构造的 bean 类,这在一定程度上等同于 Java 代码的
new
操作符 - 指定包含被调用来创建对象的静态工厂方法的实际类,在不太常见的情况下,容器调用类上的静态工厂方法来创建 bean。从静态工厂方法调用返回的对象类型可能是同一个类,也可能完全是另一个类
嵌套类名:如果您在 com.example
包中调用了一个 SomeThing
类,并且 SomeThing
类有一个名为 OtherThing
的 static
嵌套类,则它们之间可以用美元符号 $
或点 .
分隔。因此 bean 定义中的 class
属性值将是 com.example.SomeThing$OtherThing
或 com.example.SomeThing.OtherThing
。
使用构造函数实例化
根据对该特定 bean 使用的 IoC 类型,可能需要一个默认(空)构造函数。
Spring IoC 容器实际上可以管理您希望它管理的任何类。它不仅限于管理真正的 JavaBeans 。大多数 Spring 用户更喜欢只有默认(无参数)构造函数和适当的 setters 和 getter 的实际 JavaBeans ,它们是根据容器中的属性建模的。您的容器中还可以有更多奇特的非 bean 风格类。
用基于 xml 的配置元数据,指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
使用静态工厂方法实例化
在定义用静态工厂方法创建的 bean 时,使用 class
属性指定包含静态工厂方法的类和名为 factory-method
的属性指定工厂方法的名称。您应该能够调用这个方法,并返回一个对象,随后将其视为通过构造函数创建的对象。这种 bean 定义的一个用途是在遗留代码中调用静态工厂。
<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;
}
}
使用实例工厂方法实例化
使用实例工厂方法进行的实例化从容器中调用现有 bean 的非静态方法来创建新 bean。要使用这种机制,保留 class
属性为空,并在 factory-bean
属性中,在当前(或父或祖先)容器中指定 bean 的名称,该容器包含要调用来创建对象的实例方法。使用 factory-method
属性指定工厂方法本身的名称
一个工厂类也可以包含多个工厂方法
<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;
}
}
这种方法表明,工厂 bean 本身可以通过依赖注入(DI)来管理和配置
在 Spring 文档中,“工厂 bean” 指的是在 Spring 容器中配置并通过实例或静态工厂方法创建对象的 bean 。相比之下,FactoryBean
引用了一个 Spring 特定的 FactoryBean
实现类。
参考源码
org.springframework.beans.factory.FactoryBean
确定 Bean 的运行时类型
要确定特定 bean 的运行时类型并非易事。Bean 元数据定义中的指定类仅仅是一个初始类引用,它可能与已声明的工厂方法或 FactoryBean 类结合在一起,后者可能导致 bean 的不同运行时类型,或者在实例级工厂方法(可以通过指定的工厂 bean 名称解析)的情况下根本不设置类。此外,AOP 代理可以使用基于接口的代理来包装 bean 实例,并限制目标 bean 的实际类型(仅限于实现的接口)
找出特定 bean 的实际运行时类型的推荐方法是BeanFactory.getType
调用指定的 bean 名称。这考虑了所有情况,并返回 BeanFactory.getBean
调用将返回的相同 bean 名称的对象类型。
依赖关系
依赖注入(Dependency Injection , DI)
DI 有两个主要的变体: 基于构造函数的依赖注入基于 setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有许多参数的构造函数来实现的,每个参数表示一个依赖项。与调用具有特定参数的静态工厂方法来构造 bean 几乎是等效的。
一个只能通过依赖注入的构造函数注入的类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final 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...
}
构造函数参数解析
使用参数的类型进行构造函数参数解析匹配。如果 bean 定义的构造函数参数中不存在潜在的歧义,那么在 bean 定义中定义构造函数参数的顺序就是在实例化 bean 时将这些参数提供给适当的构造函数的顺序。
以下示例,假设 ThingTwo
和 ThingThree
类不通过继承关联,则不存在潜在的歧义。
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
<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 无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。考虑下面的类:
package examples;
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;
}
}
构造函数参数类型匹配
通过 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
注解显式命名构造函数参数。示例类如下所示:
package examples;
public class ExampleBean {
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
java.beans.ConstructorProperties
基于 Setter 的依赖注入
在调用无参数构造函数或无参数静态工厂方法实例化 bean 之后,bean 上的容器调用 setter 方法可以实现基于 setter 的 DI。
一个只能通过使用纯 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
为它管理的 bean 支持基于构造函数和基于 setter 的 DI。在通过构造函数方法注入一些依赖项之后,它还支持基于 setter 的 DI。您可以以 BeanDefinition
的形式配置依赖关系,将其与 PropertyEditor
实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户并不直接使用这些类(通过编程) ,而是使用 XML bean 定义、带注解的组件(用@Component
、@Controller
等注解的类)或基于 java 的 @Configuration
类中的 @Bean
方法。然后,这些源在内部转换为 BeanDefinition
实例,并用于加载整个 Spring IoC 容器实例。
基于构造函数还是基于 setter 的 DI?
由于您可以混合使用基于构造函数和基于 setter 的 DI,因此 最好将构造函数用于强制依赖项,将 setter 方法或配置方法用于可选依赖项。请注意, 在 setter 方法上使用 @Required
注解可用于使属性成为必需的依赖项;但是,最好使用带有参数编程验证的构造函数注入。另外,大量的构造函数参数意味着类可能有太多的责任,应该重构以更好地解决适当的关注点分离/代码。
Spring 团队通常提倡构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保所需的依赖项不是 null
。构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。
Setter 注入应该主要仅用于可以在类中分配合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。setter 注入的一个好处是 setter 方法使该类的对象可以在以后重新配置或重新注入。因此,通过 JMX mbean 进行管理是 setter 注入的一个引人注目的用例。
使用对特定类最有意义的 DI 样式。例如,如果第三方类没有公开任何 setter 方法,那么构造函数注入可能是 DI 唯一可用的形式。
依赖解析过程
容器执行 bean 依赖解析如下:
- 创建
ApplicationContext
并使用描述所有 bean 的配置元数据进行初始化。 。配置元数据可以由 XML、Java 代码或注解指定。 - 对于每个 bean,它的依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示。在实际创建 bean 时,将这些依赖关系提供给 bean。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个 bean 的引用。
- 作为值的每个属性或构造函数参数都从其指定格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如
int
、long
、String
、boolean
等。
Spring 容器在创建容器时验证每个 bean 的配置。但是,在 bean 实际创建之前,不会设置 bean 的属性。在创建容器时,将创建单例作用域并设置为预实例化(默认情况)的 bean。作用域在 Bean 作用域中定义。此外,只有在请求 bean 时才会创建它。创建 bean 可能会导致创建 bean 图,因为创建和分配 bean 的依赖项及其依赖项的依赖项等等。请注意,这些依赖项之间的解析不匹配可能会延迟出现,即在首次创建受影响的 bean 时出现。
通常可以相信 Spring 会做正确的事情。它在容器加载时检测配置问题,例如对不存在的 bean 的引用和循环依赖项。Spring 在实际创建 bean 时设置属性并尽可能晚地解析依赖项。这意味着,如果创建对象或其某个依赖项时出现问题,那么正确加载的 Spring 容器随后可以在请求对象时生成异常ーー例如,由于缺少或无效的属性,bean 抛出异常。一些配置问题的可见性可能会延迟,这就是为什么默认情况下 ApplicationContext
实现会预先实例化单例 bean。在实际需要这些 bean 之前,需要花费一些前期时间和内存来创建它们,因此在创建 ApplicationContext
时(而不是以后)会发现配置问题。您仍然可以覆盖这个默认行为,以便单例 bean 以惰性方式初始化,而不是急切地预先实例化。
如果不存在循环依赖关系,当一个或多个合作 bean 被注入到依赖 bean 中时,每个合作 bean 在被注入到依赖 bean 之前都会被完全配置。这意味着,如果 bean A 对 bean B 有依赖关系,那么在调用 bean A 上的 setter 方法之前,Spring IoC 容器将完全配置 bean B 。换句话说,bean 被实例化(如果它不是预实例化的单例) ,它的依赖关系被设置,相关的生命周期方法(如配置的 init
方法或 InitializingBean
回调方法)被调用。
循环依赖
如果您主要使用构造函数注入,则可能会创建无法解决的循环依赖场景。例如:A 类通过构造函数注入需要 B 类的实例,B 类通过构造函数注入需要 A 类的实例。如果您将类 A 和 B 的 bean 配置为相互注入,则 Spring IoC 容器在运行时检测到此循环引用,并抛出一个 BeanCurrentlyInCreationException
。
一种可能的解决方案是编辑一些类的源代码,以便由 setter 而不是构造函数来配置。或者,避免构造函数注入并仅使用 setter 注入。也就是说,虽然不推荐,但是可以通过 setter 注入来配置循环依赖项。与典型情况(没有循环依赖)不同,bean A 和 bean B 之间的循环依赖迫使其中一个 bean 在完全初始化之前被注入另一个 bean(经典的鸡和蛋场景)。
依赖注入的例子
调用一个静态工厂方法来返回一个对象的实例,而不是使用一个构造函数:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
相应的 ExampleBean
类:
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;
}
}
静态工厂方法的参数由 <constructor-arg/>
元素提供,与实际使用的构造函数完全相同。工厂方法返回的类的类型不必与包含静态工厂方法的类的类型相同。实例(非静态)工厂方法可以以本质上相同的方式使用(除了使用 factory-bean
属性而不是 class
属性之外)
依赖和配置详情
可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用或作为内联定义的值。为此,Spring 的基于 XML 的配置元数据支持 <property/>
和 <constructor-arg/>
标签中的子标签。
直接值(原语、字符串等)
<property/>
元素的 value
指定属性或构造器参数的字符串表示。Spring 的 转换服务 用于将这些值从 String
转换为属性或参数的实际类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
使用 p 名称空间 进行更简洁的 XML 配置
<beans xmlns="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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="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="misterkaoli"/>
</beans>
还可以配置 java.util.Properties
实例
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring 容器使用 JavaBeans PropertyEditor 机制将 <value/>
元素内的文本转换为 java.util.Properties
idref
标签
idref
标签用来将容器内其它bean的 id
传给 <constructor-arg/>
或 <property/>
标签,同时提供错误验证功能。
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
等价于
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
使用 idref
标记可以让容器在部署时验证引用的命名 bean 是否实际存在。在第二个变体中,不对传递给客户端 bean 的 targetName
属性的值执行验证。只有在实际实例化客户端 bean 时才会发现输入错误(很可能导致致命的结果)。如果客户端 bean 是一个原型 bean,那么只有在部署容器之后很长时间才能发现这个错误。
<idref/>
标签一个常见位置是 ProxyFactoryBean
定义中 AOP 拦截器的配置。在指定拦截器名称时使用 <idref/>
元素可以防止错误拼写拦截器 ID。
对其他 Bean 的引用(协作者)( ref
)
ref
可用在 <constructor-arg/>
或 <property/>
标签内,包含 bean
或 parent
属性。bean
属性引用同一容器或父容器中的任何 bean ,parent
属性指定目标 bean 会创建对当前容器的父容器中的 bean 的引用。bean
属性的值可能与目标 bean 的 id
属性相同,或者与目标 bean 的 name
属性中的一个值相同。
<ref bean="someBean"/>
<!-- 引用父容器中的 bean -->
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
内部 Bean
<constructor-arg/>
或 <property/>
标签内使用 <bean/>
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部 bean 定义不需要定义的 ID 或名称。如果指定,容器不会使用这样的值作为标识符。容器在创建时也会忽略 scope
标志,因为内部 bean 始终是匿名的,并且始终与外部 bean 一起创建。不可能独立访问内部 bean 或将它们注入除外部 bean 之外的其他协作 bean 中。
作为特殊情况,可以从自定义作用域接收 destruction 回调,例如,对于包含在单例 bean 中的请求作用域的内部 bean。内部 bean 实例的创建与其包含的 bean 绑定在一起,但是销毁回调允许它参与请求范围的生命周期。这种情况并不常见。内部 bean 通常只是简单地共享外部 bean 的作用域。
集合( <list/>
、<set/>
、<map/>
、 <props/>
)
<list/>
、<set/>
、<map/>
、 <props/>
标签分别对应 Java 集合类型 List
、Set
、Map
、和 Properties
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map 键或值或 set 的值也可以是以下任何一个元素:
bean | ref | idref | list | set | map | props | value | null
集合合并( merge
)
可以定义父 <list/>
、<set/>
、<map/>
、 <props/>
元素,并让子 <list/>
、<set/>
、<map/>
、 <props/>
从父集合继承和覆盖值。也就是说,子集合的值是合并父集合和子集合的元素的结果,其中子集合元素重写父集合中指定的值。
集合合并适用于 <list/>
、<set/>
、<map/>
、 <props/>
,但是不能合并不同的集合类型
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
合并后 child
的值是:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
<list/>
、<set/>
、<map/>
集合类型的合并行为类似。在 <list/>
标签的特定情况下,维护与 List
集合类型相关的语义。父列表的值位于所有子列表的值之前。对于 Map
、 Set
和 Properties
集合类型,不存在排序。因此,对于构成容器内部使用的关联映射、集合和属性实现类型基础的集合类型,没有有效的排序语义。
不能合并不同的集合类型(如 Map
和 List
)。merge
属性必须在较低的继承子定义上指定。在父集合定义上指定 merge
属性是多余的,不会导致所需的合并。
强类型集合
Spring 的类型转换提供支持
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
Null 和空字符串值( <null/>
)
Spring 将属性和类似属性的空参数视为空字符串。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
等价于:
exampleBean.setEmail("");
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
等价于:
exampleBean.setEmail(null);
使用 p 命名空间的 XML 快捷配置
p 命名空间允许您使用 bean 元素的属性(而不是嵌套的 <property/>
元素)来描述协作 bean 的属性值,或者两者都使用。
Spring 支持基于 XML Schema 定义的名称空间的可扩展配置格式。p 命名空间并未在 XSD 文件中定义,它只存在于 Spring 的 core 中。
以下示例显示了两个解析为相同结果的 XML 片段(第一个使用标准 XML 格式,第二个使用 p 命名空间):
<beans xmlns="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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用 property 元素 -->
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<!-- 使用 p 命名空间 -->
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
使用 c 命名空间的 XML 快捷配置
c 命名空间并未在 XSD 文件中定义
对应 constructor-arg
标签
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- 指定参数名称,使用 constructor-arg -->
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- 指定参数名称,使用 c 名称空间 -->
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
<!-- 指定参数索引,使用 c 名称空间 -->
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
</beans>
复合属性名称
使用复合或嵌套属性名
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something
bean 有 fred
属性,fred
有 bob
属性,bob
有 sammy
属性,并且最终 sammy
属性被设置为值 123
。中间的属性不能为 null
,否则会抛出 NullPointerException
。
使用 depends-on
如果一个 bean 是另一个 bean 的依赖项,这通常意味着一个 bean 被设置为另一个 bean 的属性。通常,您使用基于 XML 的配置元数据中的 ref
元素来完成此操作。但是,有时 bean 之间的依赖关系不那么直接。例如,当需要触发类中的静态初始化程序时,例如数据库驱动程序注册。在初始化使用此元素的 bean 之前,depends-on
属性可以显式地强制初始化一个或多个 bean。
depends-on
属性可以指定初始化时的依赖项,并且在单例 bean 的情况下,还可以指定相应的销毁时的依赖项。在给定的 bean 本身被销毁之前,首先销毁与给定 bean 定义的 depends-on
指定的依赖 bean 。这样,depends-on
也可以控制销毁顺序。
逗号、空格和分号是有效的分隔符
<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" />
延迟初始化的 Bean ( lazy-init
)
默认情况下,ApplicationContext
实现会在初始化过程中急切地创建和配置所有单例 bean 。
可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。一个延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时创建一个 bean 实例,而不是在 Spring 容器启动时。
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当延迟初始化 bean 是非延迟初始化的单例 bean 的依赖项时,ApplicationContext
会在启动时创建延迟初始化 bean,因为它必须满足单例 bean 的依赖项。
可以通过使用 <beans/>
标签上的 default-lazy-init
属性在 容器级别 控制延迟初始化
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
自动装配协作者
Spring 容器可以自动装配协作 bean 之间的关系。您可以通过检查 ApplicationContext
的内容,让 Spring 自动为您的 bean 解析协作者。自动装配有以下优点:
- 自动装配可以显著减少指定属性或构造函数参数的需要
- 自动装配可以根据对象的发展更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。
使用基于 XML 的配置元数据时,您可以使用 <bean/>
元素的 autowire
属性为 bean 定义指定自动装配模式。自动装配功能有四种模式。您可以为每个 bean 指定自动装配模式。下表描述了四种自动装配模式:
模式 | 解释 |
---|---|
no |
(默认)不使用自动装配。Bean 引用必须由 ref 元素定义。对于较大的部署,不建议更改默认设置,因为明确指定依赖可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName |
按属性名称自动装配。Spring 查找与需要自动装配的属性同名的 bean 。例如,如果一个 bean 定义被设置为按名称自动装配并且它包含一个 master 属性(即它有一个 setMaster 方法),Spring 会查找一个名为 master 的 bean 定义并使用它来设置属性。 |
byType |
如果容器中恰好存在一个与属性相同类型的 bean,则让属性自动装配。如果存在多个,则会引发异常,这表明您不能为该 bean 使用 byType 自动装配。如果没有匹配的 bean,则不会发生任何事情(不会设置该属性)。 |
constructor |
类似于 byType ,但适用于构造函数参数。如果容器中没有一个构造函数参数相同类型的 bean,则会引发致命错误。 |
使用 byType
或 constructor
自动装配模式,您可以装配数组和集合。在这种情况下,提供容器内与预期类型匹配的所有自动装配候选者以满足依赖关系。如果预期的键类型是 String
,可以自动装配强类型 Map
实例。自动装配 Map
实例的值由与预期类型匹配的所有 bean 实例组成,并且 Map
实例的键包含相应的 bean 名称。
自动装配的局限性和缺点
自动装配在整个项目中一致使用时效果最佳。
自动装配的局限性和缺点:
property
和constructor-arg
设置中的显式依赖项始终覆盖自动装配。不能自动装配简单属性,例如 基本数据类型、Strings
、Classes
- 自动装配不如显式装配精确
- 对于可能从 Spring 容器生成文档的工具,装配信息可能不可用
- 可能存在多个相同类型的 bean 定义,解决方法:
- 放弃自动装配,转而使用显式指定
- 通过将
autowire-candidate
属性设置为false
来避免对 bean 定义进行自动装配 - 通过将
primary
属性设置为true
,将单个 bean 定义指定为主要候选者 - 使用基于注解的配置实现更细粒度的控制
从自动装配中排除 Bean
在 XML 配置中,将 <bean/>
标签的 autowire-candidate
属性设置为 false
,容器使该特定 bean 定义对自动装配不可用。
autowire-candidate
属性旨在仅影响基于类型的自动装配。它不会影响按名称的显式引用,即使指定的 bean 未标记为自动装配候选者,也会解析。因此,如果名称匹配,按名称自动装配仍然会注入一个 bean。
还可以根据对 bean 名称的模式匹配来限制自动装配候选者。顶级 <beans/>
标签在 default-autowire-candidates
属性中接受一个或多个模式 。例如,要将自动装配候选状态限制为名称以 Repository
结尾的任何 bean ,设置值为 *Repository
。要提供多个模式,请在 逗号分隔 的列表中定义它们。bean 定义的 autowire-candidate
属性的显式值 true
或 false
始终优先。对于此类 bean,模式匹配规则不适用。
这些技术对于您永远不想通过自动装配注入其他 bean 的 bean 很有用。这并不意味着不能使用自动装配来配置被排除的 bean 本身。相反,bean 本身不是自动装配其他 bean 的候选者。
使用说明
byType
依赖注入的过程:
- 根据属性类型查找容器内所有相同类型
autowire-candidate="true"
的候选 bean ,autowire-candidate
属性默认为true
- 如果候选 bean 超过一个,选择
primary="ture"
的 bean ,primary
属性默认为false
方法注入
在大多数应用场景中,容器中的大部分 bean 都是 单例的。当单例 bean 需要与另一个单例 bean 协作,或者,非单例 bean 需要与另一个非单例 bean 协作时,您通常通过将一个 bean 定义为另一个 bean 的属性来处理依赖关系。但是当 bean 生命周期不同时就会出现问题。
假设在每次 A 的方法调用上,单例 bean A 需要使用非单例(原型)bean B。容器只创建单例 bean A 一次,因此只有一次设置属性的机会。容器无法在每次需要时为 bean A 提供 bean B 的新实例。
一个解决方案是放弃一些控制反转,通过实现 ApplicationContextAware
接口,并在每次 bean A 需要时对容器进行 getBean("b")
调用,请求 bean B 实例
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
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 Framework。方法注入是 Spring IoC 容器的一个有点高级的特性,可以让你干净地处理这个用例。
查找方法注入( lookup-method
)
查找方法注入是容器重写容器管理 bean 上的方法并返回容器中另一个命名 bean 的查找结果的能力。查找通常涉及一个原型 bean,如前面部分所描述的场景。Spring 框架通过使用从 CGLIB 库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。
- 为了使这个动态子类化生效,Spring bean 容器子类的类不能是
final
类,而要重写的方法也不能是final
类 - 单元测试具有抽象方法的类需要您自己对该类进行子类化,并提供抽象方法的存根实现
- 具体的方法对于组件扫描也是必要的,这需要具体的类来获取
- 另一个关键限制是查找方法不能与工厂方法一起工作,特别是不能与配置类中的
@Bean
方法一起工作,因为在这种情况下,容器不负责创建实例,因此不能动态创建运行时生成的子类
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
要注入的方法需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果是 abstract
方法,则动态生成的子类实现该方法。否则,动态生成的子类会覆盖原始类中定义的具体方法。考虑以下示例:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
commandManager
bean 在需要 myCommand
bean 的新实例时调用它自己的 createCommand()
方法。如果确实需要每次都是新的 myCommand
实例,您必须小心地将 bean 的作用域设置为原型。如果是单例,则每次都返回相同的 myCommand
bean 实例。
@Lookup
注解形式:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
可以依靠目标 bean 根据查找方法的声明返回类型进行解析:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
请注意,通常应该使用具体的存根实现来声明这种带注解的查找方法,以便它们与 Spring 的组件扫描规则兼容。
访问不同作用域的目标 bean 的其他方法:
ObjectFactory
/Provider
注入点org.springframework.beans.factory.config.ServiceLocatorFactoryBean
任意方法替换( replaced-method
)
与查找方法注入相比,一种不太有用的方法注入形式是能够用另一种方法实现替换托管 bean 中的任意方法。
举例:需要被覆盖的方法 computeValue
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现 org.springframework.beans.factory.support.MethodReplacer
接口的类提供了新的方法定义
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
用于部署原始类和指定方法覆盖的 bean 定义
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
您可以在 <replaced-method/>
标签内使用一个或多个 <arg-type/>
元素来指示被覆盖的方法的方法签名。仅当方法重载并且类中存在多个变体时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下所有匹配 java.lang.String
:
java.lang.String
String
Str
Bean 作用域( scope
)
在创建 bean 定义时,您创建了一个配方,用于创建由该 bean 定义定义的类的实际实例。Bean 定义是一个配方的想法很重要,因为它意味着,与类一样,您可以从单个配方创建许多对象实例。
您不仅可以控制各种依赖项和配置值,这些依赖项和配置值将被插入从特定 bean 定义创建的对象中,还可以控制从特定 bean 定义创建的对象的作用域。这种方法是强大而灵活的,因为您可以选择通过配置创建的对象的作用域,而不必在 Java 类级别的对象作用域内进行操作。可以将 bean 定义为部署在许多作用域中的一个。Spring 框架支持六个作用域,其中四个作用域只有在使用感知 web ( web-aware) 的 ApplicationContext
时才可用。您还可以创建自定义作用域。
Bean 作用域 | 描述 |
---|---|
singleton |
(默认)将单个 bean 定义作用域限定为每个 Spring IoC 容器的单个对象实例 |
prototype |
将单个 bean 定义作用域限定为任意数量的对象实例 |
request |
将单个 bean 定义作用域限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 bean 实例。仅在感知 web 的 Spring ApplicationContext 上下文中有效 |
session |
将单个 bean 定义作用域限定为 HTTP Session 的生命周期。仅在感知 web 的 Spring ApplicationContext 上下文中有效 |
application |
将单个 bean 定义作用域限定为 ServletContext 的生命周期。仅在感知 web 的 Spring ApplicationContext 上下文中有效 |
websocket |
将单个 bean 定义作用域限定为 WebSocket 的生命周期。在感知 web 的 Spring ApplicationContext 上下文中有效 |
从 Spring 3.0 开始,线程作用域可用,但默认情况下未注册。有关更多信息,请参阅 SimpleThreadScope
的文档 。有关如何注册此或任何其他自定义范围的说明,请参阅 使用自定义范围。
单例作用域( singleton
)
只管理单一 bean 的一个共享实例,对 ID 或 ID 与 bean 定义相匹配的 bean 的所有请求都将导致 Spring 容器返回一个特定 bean 实例。
Spring 的单例 bean 概念不同于四人组 (GoF) 模式书中定义的单例模式。GoF 单例对对象的范围进行了硬编码,以便每个 ClassLoader 只创建一个特定类的一个实例。Spring 单例作用域最好描述为每个容器和每个 bean。这意味着,如果您在单个 Spring 容器中为特定类定义一个 bean,则 Spring 容器会为该 bean 定义创建类的仅一个实例。单例作用域是 Spring 中的默认作用域。
<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"/>
原型作用域( prototype
)
bean 部署的非单例原型作用域导致每次对特定 bean 发出请求时都会创建一个新 bean 实例。
通常,您应该对所有有状态 bean 使用原型作用域,对无状态 bean 使用单例作用域。
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
Spring 不管理原型 bean 的整个生命周期。 Spring 会调用原型 bean 的初始化生命周期回调方法,但不会调用销毁生命周期回调。客户端代码必须清理原型作用域的对象并释放原型 bean 持有的昂贵资源。要让 Spring 容器释放原型作用域 bean 持有的资源,请尝试使用自定义 bean 后置处理器 post-processor,它保存对需要清理的 bean 的引用。
具有 Prototype bean 依赖关系的 Singleton Bean
当您使用单实例作用域的 bean 与原型 bean 的依赖关系时,请注意依赖关系在实例化时解析。
单例 bean 属性中注入原型 bean ,每次获取得到的是相同的 bean,不会每次都生成新的 bean 实例,因为单例 bean 的生命周期只有一次。
假设您希望单实例范围的 bean 在运行时重复获取原型范围的 bean 的新实例。不能依赖性地将原型范围的 bean 注入到单例 bean 中,因为注入只发生一次,即当 Spring 容器实例化单例 bean 并解析和注入它的依赖项时。如果在运行时多次需要原型 bean 的新实例,请参见 方法注入( lookup
)
Request, Session, Application, WebSocket 作用域
request
,session
,application
,websocket
作用域只有当你使用感知 web 的 Spring ApplicationContext
实现(例如 XmlWebApplicationContext
)时可用。如果将这些作用域与常规 Spring IoC 容器(例如 ClassPathXmlApplicationContext
)一起使用,会抛出异常
参考源码
@RequestScope
、@SessionScope
、@ApplicationScope
初始 web 配置
为了支持 request
,session
,application
,websocket
作用域对 bean 进行作用域限定,在定义 bean 之前需要进行一些小的初始配置。(标准作用域:singleton
和 prototype
不需要这种初始设置)
如果您在 Spring Web MVC 中访问作用域 bean,在 Spring DispatcherServlet
处理的请求中,不需要特殊设置。
如果您使用 Servlet 2.5 web 容器,并在 Spring 的 DispatcherServlet
之外处理请求(例如,在使用 JSF 或 Struts 时),则需要注册 org.springframework.web.context.request.RequestContextListener
实现了 javax.servlet.ServletRequestListener
。对于 Servlet 3.0 + ,可以通过使用 WebApplicationInitializer
接口以编程方式完成。或者,对于较旧的容器,将以下声明添加到 web 应用程序的 web.xml 文件中:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果监听器设置有问题,可以考虑使用 Spring 的 RequestContextFilter
。过滤器映射取决于周围的 web 应用程序配置,因此您必须根据需要更改它。下面的清单显示了 web 应用程序的过滤器部分:
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet
、 RequestContextListener
和 RequestContextFilter
都做完全相同的事情,即将 HTTP 请求对象绑定到服务该请求的 Thread
。这使得请求范围和会话范围的 bean 在调用链的下一级可用。
request 作用域( request
)
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器通过为每个 HTTP 请求使用 LoginAction
bean 定义来创建新实例。当请求完成处理时,作用域为 request
的 bean 被丢弃。
可以使用 @RequestScope
注解
session 作用域( session
)
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器通过在单个 HTTP Session 的生存期内使用 UserPreferences
bean 定义创建 UserPreferences
bean 的新实例。当 HTTP 会话最终被丢弃时,作用域为特定 HTTP 会话的 bean 也被丢弃。
可以使用 @SessionScope
注解
application 作用域( application
)
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器为整个 web 应用程序使用一次 AppPreferences
bean 定义,从而创建 AppPreferences
bean 的一个新实例。也就是说,appPreferences
bean 的作用域在 ServletContext
级别,并作为常规 ServletContext
属性存储。这有点类似于 Spring 单例 bean,但是在两个重要方面有所不同:它是对应 ServletContext
的一个单例,而不是对应 Spring 的 ApplicationContext
(在任何给定的 web 应用程序中都可能有多个),它实际上是公开的,因此可以作为一个 ServletContext
属性看到。
可以使用 @ApplicationScope
注解
作用域 Bean 作为依赖项
Spring IoC 容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想将一个 HTTP request
作用域的 bean 注入到另一个生命周期更长的 bean 中,您可以选择注入一个 AOP 代理来代替该作用域的 bean。也就是说,您需要注入一个代理对象,该对象公开与作用域对象相同的公共接口,但也可以从相关作用域(例如 HTTP 请求)中检索真实目标对象,并将方法调用委托给真实对象。
可以在作用域为单例的 bean 之间使用 <aop:scoped-proxy/>
,然后通过一个可序列化的中间代理,从而能够在反序列化中重新获得目标单例 bean 。
当针对一个原型作用域 bean 声明 <aop:scoped-proxy/>
时,共享代理上的每个方法调用都会创建一个新的目标实例,然后将调用转发到该实例。
而且,作用域代理并不是以生命周期安全的方式从较短的作用域访问 bean 的唯一方法。您还可以将注入点(即构造函数或 setter 参数或自动装配字段)声明为 ObjectFactory<MyTargetBean>
,从而允许 getObject()
调用在每次需要时根据需要检索当前实例,而不必保留实例或分别存储它。
作为一个扩展的变体,您可以声明 ObjectProvider<MyTargetBean>
,它提供了几个附加的访问变体,包括 getIfAvailable
和 getIfUnique
。
这种方法的 JSR-330 变体被称为 Provider
,与 Provider<MyTargetBean>
声明以及每次检索尝试的相应 get()
调用一起使用。
将短命的作用域 bean 注入长命的作用域 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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
参考源码
org.springframework.beans.factory.ObjectFactory
org.springframework.beans.factory.ObjectProvider
选择要创建的代理类型
默认情况下,当 Spring 容器为标记有 <aop:scoped-proxy/>
元素的 bean 创建代理时,会创建一个基于 CGLIB 的类代理。
CGLIB 代理只拦截公共方法调用!不要在这样的代理上调用非公共方法。它们不会委托给实际作用域的目标对象。
或者,您可以配置 Spring 容器,通过指定 <aop:scoped-proxy/>
元素的 proxy-target-class
属性值 false
,为此类作用域 bean 创建标准的基于 JDK 接口的代理。使用基于 JDK 接口的代理意味着您不需要应用程序类路径中的其他库来影响此类代理。但是,这也意味着作用域 bean 的类必须至少实现一个接口,并且注入作用域 bean 的所有协作者都必须通过其接口之一引用 bean。以下示例显示了基于接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="singletonBean" class="study.hwj.spring.scope.SingletonBean">
<property name="prototypeBean" ref="prototypeBean"></property>
</bean>
<bean id="prototypeBean" class="study.hwj.spring.scope.PrototypeBean" scope="thread">
<aop:scoped-proxy/>
</bean>
</beans>
如上配置,每个线程里 singletonBean
能够获取到一个新的 PrototypeBean
实例
自定义作用域
bean 作用域机制是可扩展的。您可以定义自己的范围,甚至可以重新定义现有的范围,尽管后者被认为是不好的实践,而且您不能覆盖内置的单例和原型范围。
要将自定义作用域集成到 Spring 容器中,需要实现 org.springframework.beans.factory.config.Scope
接口
Scope 方法名称 |
方法声明 | 描述 |
---|---|---|
get |
public Object get(String name, ObjectFactory<?> objectFactory) |
从作用域返回对象 |
remove |
public Object remove(String name) |
将对象从作用域中移除 |
registerDestructionCallback |
public void registerDestructionCallback(String name, Runnable callback) |
注册了一个回调,当作用域被销毁或作用域中指定的对象被销毁时,调用这个回调 |
getConversationId |
public String getConversationId() |
获取作用域的会话标识符,此标识符对于每个作用域是不同的 |
使用自定义作用域:org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope
参考实现:org.springframework.context.support.SimpleThreadScope
Java 方式注册自定义作用域:
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
<bean id="..." class="..." scope="thread">
XML 配置方式注册自定义作用域:
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
当您在 FactoryBean
实现的 <bean>
声明中时设置 <aop:scoped-proxy/>
,作用域作用的是工厂 bean 本身,而不是 getObject()
返回的对象。
自定义 Bean 的生命周期
Spring 框架提供了许多接口,您可以使用它们来自定义 bean 的生命周期:
- 生命周期回调
ApplicationContextAware
和BeanNameAware
- 其他
Aware
接口
生命周期回调
在内部,Spring 框架使用 BeanPostProcessor
实现来处理它能够找到并调用适当方法的任何回调接口。如果您需要自定义特性或其他 Spring 默认不提供的生命周期行为,您可以自己实现 BeanPostProcessor
。
除了初始化和销毁回调之外,Spring 管理的对象还可以实现 Lifecycle
接口,以便这些对象能够参与启动和关闭过程,这是由容器自身的生命周期驱动的。
初始化、销毁回调
-
JSR-250 的
@PostConstruct
和@PreDestroy
注解(不与 Spring 耦合) -
Spring 的
InitializingBean
和DisposableBean
接口afterPropertiesSet
和destroy
方法
-
Bean 定义时配置:
<bean>
标签的init-method
和destroy-method
属性@Bean
注解的initMethod
和destroyMethod
您可以为 <bean>
元素的 destroy-method
属性设置一个特殊值 (inferred)
,它指示 Spring 自动检测 bean 类上的 public 修饰的 close
或 shutdown
方法。(可以匹配任何实现 java.lang.AutoCloseable
或 java.io.Closeable
的类)您还可以在 <beans>
元素的 default-destroy-method
属性上设置此特殊值 (inferred)
,以将此行为应用于整个 bean 集。
默认初始化和销毁方法
在编写不使用 Spring 特定的 InitializingBean
和 DisposableBean
回调接口的初始化和销毁方法回调时,通常使用 init
、 initialize
、 dispose
等名称编写方法。理想情况下,这种生命周期回调方法的名称在项目中是标准化的,这样所有开发人员都使用相同的方法名称,并确保一致性。
顶级 <beans/>
元素的 default-init-method
和 default-destroy-method
属性配置默认的初始化和销毁方法
如果现有的 bean 类已经有按照约定命名的回调方法,那么可以通过使用 <bean/>
本身的 init-method
和 destroy-method
属性指定方法名称来覆盖缺省值。
Spring 容器保证在提供所有依赖项的 bean 之后立即调用已配置的初始化回调。因此,在原始 bean 引用上调用初始化回调,这意味着还没有将 AOP 拦截器等应用到 bean 上。首先完全创建一个目标 bean,然后应用一个带有拦截器链的 AOP 代理。如果分别定义了目标 bean 和代理,您的代码甚至可以绕过代理与原始目标 bean 交互。因此,将拦截器应用到 init
方法是不一致的,因为这样做会将目标 bean 的生命周期耦合到它的代理或拦截器上,并在代码直接与原始目标 bean 进行交互时留下奇怪的语义。
结合生命周期机制
为同一个 bean 配置的多个生命周期机制,具有不同的初始化方法,调用顺序(注解,接口,自定义)如下:
- 带有
@PostConstruct
注解的方法 - 由
InitializingBean
回调接口定义afterPropertiesSet()
- 自定义配置方法
init()
销毁方法以相同的顺序调用:
- 带有
@PreDestroy
注解的方法 - 由
DisposableBean
回调接口定义destroy()
- 自定义配置方法
destroy()
如果为一个以上的生命周期机制配置了相同的 init
方法,那么该方法将运行一次
启动和关闭回调
org.springframework.context.Lifecycle
接口为任何具有自己生命周期要求的对象定义了基本方法
任何 Spring 管理的对象都可以实现 Lifecycle
接口。然后,当 ApplicationContext
接收到启动和停止信号时(例如,对于运行时的停止/重启场景),它将这些调用传递给在该上下文中定义的所有 Lifecycle
实现。它通过委托给 LifecycleProcessor
来做到这一点,LifecycleProcessor
本身是 Lifecycle
接口的扩展。它还添加了另外两种方法来对正在刷新和关闭的上下文做出反应。
注意,常规 org.springframework.context.Lifecycle
接口是用于显式启动和停止通知的简单约定,并不意味着在上下文刷新时自动启动。要对特定 bean 的自动启动(包括启动阶段)进行细粒度控制,请考虑实现 org.springframework.context.SmartLifecycle
另外,请注意,不能保证在销毁之前发出 stop 通知。在常规关闭时,所有 Lifecycle
bean 在传播常规销毁回调之前首先收到 stop 通知。但是,在上下文生命周期内的热刷新或停止刷新尝试时,只会调用销毁方法。
启动和关闭调用的顺序可能很重要。如果任意两个对象之间存在“依赖”关系,依赖端在其依赖关系之后开始,在其依赖关系之前停止。但是,有时直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象开始。SmartLifecycle
接口定义了另一个方法,即在其超级接口 Phased
上定义的方法 getPhase()
启动时,phase
最低的对象首先启动。停止时,遵循相反的顺序。SmartLifecycle
的 phase
默认为 Integer.MAX_VALUE
。没有实现 SmartLifecycle
的任何 Lifecycle
对象的默认 phase
为 0
也很重要。因此,任何负相位值都表明一个对象应该在这些标准组件之前开始(并在它们之后停止)。对于任何正的相位值,反之亦然。
SmartLifecycle
定义的 stop
方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的 run
方法。在必要的情况下,启用异步关机,因为 LifecycleProcessor
接口的默认实现 DefaultLifecycleProcessor
等待每个阶段中的对象组的超时值来调用该回调。每个阶段的默认超时时间为 30
秒。您可以通过在上下文中定义一个名为 lifecycleProcessor
的 bean 来覆盖默认的生命周期处理器实例。如果你只想修改超时,定义下面的代码就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor
接口定义了用于刷新和关闭上下文的回调方法。后者驱动关闭进程,就好像已经显式调用了 stop
一样,但是它发生在上下文关闭时。另一方面,“刷新”回调启用了 SmartLifecycle
bean 的另一个特性。当上下文刷新(在所有对象实例化和初始化之后)时,将调用该回调。此时,默认的生命周期处理器检查每个 SmartLifecycle
对象的 isAutoStartup
方法返回的布尔值。如果为 true
,那么该对象将在该点启动,而不是等待对上下文的显式调用或其自己的 start
方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。阶段值( phase
)和任何“依赖”关系决定前面描述的启动顺序。
使用说明
- 容器启动过程不会自动触发
Lifecycle#start
,会自动触发SmartLifecycle#start
- 调用
ConfigurableApplicationContext#start
,会触发Lifecycle#start
参考源码
org.springframework.context.Lifecycle
org.springframework.context.SmartLifecycle
org.springframework.context.LifecycleProcessor
org.springframework.context.support.DefaultLifecycleProcessor
在非 Web 应用程序中优雅地关闭 Spring IoC 容器
本节仅适用于非 web 应用程序。Spring 的基于 web 的 ApplicationContext
实现已经准备好了代码,可以在相关 web 应用程序关闭时优雅地关闭 Spring IoC 容器。
如果在非 web 应用程序环境中使用 Spring 的 IoC 容器,请向 JVM 注册一个 shutdown hook 。这样做可以确保优雅地关闭并调用单例 bean 上的相关 destroy 方法,从而释放所有资源。
向 JVM 注册关闭挂钩:
org.springframework.context.ConfigurableApplicationContext#registerShutdownHook
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(Config1.class);
// ctx.registerShutdownHook(); // 加上这句后,会触发销毁回调,否则不触发
System.exit(0);
ApplicationContextAware
和 BeanNameAware
实现 ApplicationContextAware
,容器会提供 ApplicationContext
实例给 bean
自动装配是获取 ApplicationContext
引用的另一种选择。传统的 constructor
和 byType
自动装配模式可以分别为构造函数参数或 setter 方法参数提供 ApplicationContext
类型的依赖项。为了获得更大的灵活性,包括自动装配字段和多参数方法的能力,请使用基于注解的自动装配特性。如果这个字段、构造函数或者方法带有 @Autowired
注解,ApplicationContext
就会被自动装配到一个字段、构造函数参数或者方法参数中
实现 BeanNameAware
接口,容器会提供 bean 实例的名称给 bean
触发的时机在属性赋值后,初始化方法执行前
参考源码
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeAwareMethods
org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces
ApplicationContextAware
是通过 ApplicationContextAwareProcessor
( BeanPostProcessor
)后置处理注入的,BeanNameAware
不是
其他 Aware
接口
除了 ApplicationContextAware
和 BeanNameAware
之外,Spring 还提供了大量的 Aware
回调接口,允许 bean 向容器指示它们需要某种基础结构依赖项。作为一般规则,名称表示依赖类型。
名称 | 注入依赖项 | 链接 |
---|---|---|
ApplicationContextAware |
声明 ApplicationContext |
ApplicationContextAware 和 BeanNameAware |
ApplicationEventPublisherAware |
封闭 ApplicationContext 的事件发布者 |
ApplicationContext 的附加功能 |
BeanClassLoaderAware |
用于加载 bean 类的类加载器 | 实例化 Bean |
BeanFactoryAware |
声明 BeanFactory |
ApplicationContextAware 和 BeanNameAware |
BeanNameAware |
声明 bean 的名称 | ApplicationContextAware 和 BeanNameAware |
LoadTimeWeaverAware |
用于在加载时处理类定义的编织器 | 在 Spring 框架中使用 AspectJ 进行加载时编织 |
MessageSourceAware |
用于解析消息的配置策略(支持参数化和国际化) | ApplicationContext 的附加功能 |
NotificationPublisherAware |
Spring JMX 通知发布者 | 通知 |
ResourceLoaderAware |
配置加载程序,用于低级别访问资源 | 资源 |
ServletConfigAware |
当前运行容器的 ServletConfig 。仅在基于 web 的 Spring ApplicationContext 中有效 |
Spring MVC |
ServletContextAware |
当前运行容器的 ServletContext 。仅在基于 web 的Spring ApplicationContext 中有效 |
Spring MVC |
Bean 定义继承
bean 定义可以包含很多配置信息,包括构造函数参数、属性值和特定于容器的信息,例如初始化方法、静态工厂方法名称等。子 bean 定义从父定义继承配置数据。子定义可以根据需要覆盖某些值或添加其他值。使用父和子 bean 定义可以节省大量输入。实际上,这是一种模板形式。
如果您以编程方式使用 ApplicationContext
接口,则子 bean 定义由 ChildBeanDefinition
类表示。
使用基于 xml 的配置元数据时,可以通过使用父属性指定父 bean 作为该属性的值来指定子 bean 定义:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
当 <bean>
定义为 abstract
时,它只能用作纯模板 bean 定义,用作子定义的父定义。
父 bean 不能自己实例化,因为它是不完整的,并且显式地标记为 abstract
。当定义是 abstract
的时候,它只能作为纯模板 bean 定义使用,纯模板 bean 定义作为子定义的父定义。尝试单独使用这样的 abstract
父 bean,通过将其引用为另一个 bean 的 ref
属性或使用父 bean ID 执行显式的 getBean
调用会返回一个错误。类似地,容器的内部 preInstantiateSingletons
方法忽略定义为 abstract
的 bean 定义。
容器扩展点
通常,应用程序开发人员不需要对 ApplicationContext
实现类进行子类化。相反,可以通过插入 特殊集成接口 的实现来扩展 Spring IoC 容器
使用 BeanPostProcessor
自定义 Bean
BeanPostProcessor
接口定义了回调方法,您可以实现这些方法来提供您自己的(或者覆盖容器的默认)实例化逻辑、依赖解析逻辑等等。如果希望在 Spring 容器完成实例化、配置和初始化 bean 之后实现一些自定义逻辑,可以插入一个或多个自定义 BeanPostProcessor
实现。
BeanPostProcessor
被调用的时机是在 bean 初始化方法前后
可以配置多个 BeanPostProcessor
实例,并且可以通过设置 order
属性来控制这些 BeanPostProcessor
实例的运行顺序。仅当 BeanPostProcessor
实现Ordered
接口时才能设置 order
属性。如果您自己编写BeanPostProcessor
,也应该考虑实现 Ordered
接口。
BeanPostProcessor
实例对 bean 实例进行操作。也就是说,Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor
实例执行工作。
每个容器都有 BeanPostProcessor
实例的作用域。只有在使用容器层次结构时,这才是相关的。如果您在一个容器中定义一个 BeanPostProcessor
,那么它只后置处理该容器中的 bean。换句话说,在一个容器中定义的 bean 不会由在另一个容器中定义的 BeanPostProcessor
进行后置处理,即使两个容器都属于相同的层次结构。
要更改实际的 bean 定义( BeanDefinition
),需要使用 BeanFactoryPostProcessor
org.springframework.beans.factory.config.BeanPostProcessor
接口恰好由两个回调方法( postProcessBeforeInitialization
、 postProcessAfterInitialization
)组成。当这样的类注册为容器的后置处理器时,对于容器创建的每个 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
实例:org.springframework.beans.factory.config.ConfigurableBeanFactory#addBeanPostProcessor
,
注意,以 BeanPostProcessor
编程方式注册的实例总是在通过自动检测注册的实例之前处理,而不管任何显式排序( Ordered
接口 )。
实现 BeanPostProcessor
接口的类是特殊的,容器对它们的处理不同。所有 BeanPostProcessor
实例和被直接引用的 bean 都在启动时实例化,作为 ApplicationContext
特殊启动阶段的一部分。接下来,以排序的方式注册所有 BeanPostProcessor
实例,并将其应用于容器中的所有其他 bean。
因为 AOP 自动代理是通过 BeanPostProcessor
实现的,所以 BeanPostProcessor
实例和它们直接引用的Bean 都不符合自动代理的条件。对于任何这样的 Bean,您应该看到一条信息性的日志消息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)
使用说明
BeanPostProcessor
和它依赖的 bean 会更早的实例化,并且无法使用 AOP 自动代理BeanPostProcessor
bean 需要比普通 bean 更早的实例化,在registerBeanPostProcessors(beanFactory);
这一步中实例化,普通 bean 在finishBeanFactoryInitialization(beanFactory);
方法里实例化- 容器启动时,默认添加两个
BeanPostProcessor
org.springframework.context.support.ApplicationContextAwareProcessor
- 检测实现
xxxAware
接口的 bean 并注入xxx
- 检测实现
org.springframework.context.support.ApplicationListenerDetector
- 检测
org.springframework.context.ApplicationListener
接口的 bean ,并将其中的单例 bean 加入ApplicationContext
- 检测
参考源码
org.springframework.beans.factory.config.BeanPostProcessor
org.springframework.beans.factory.config.BeanFactoryPostProcessor
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
org.springframework.beans.factory.config.PropertyOverrideConfigurer
使用 BeanFactoryPostProcessor
自定义配置元数据
org.springframework.beans.factory.config.BeanFactoryPostProcessor
接口的语义类似于 BeanPostProcessor
,但有一个主要区别: BeanFactoryPostProcessor
对 bean 配置元数据进行操作。也就是说,Spring IoC 容器允许 BeanFactoryPostProcessor
在容器实例化除 BeanFactoryPostProcessor
实例以外的任何 bean 之前读取配置元数据并更改它。
BeanFactoryPostProcessor
可以在任何非 BeanFactoryPostProcessor
类型的 bean 实例化前更改配置元信息
可以配置多个 BeanFactoryPostProcessor
实例,并通过设置 order
属性来控制 BeanFactoryPostProcessor
实例的运行顺序。但是,只有在 BeanFactoryPostProcessor
实现 Ordered
接口时,才能设置此属性。
如果您想要更改实际的 bean 实例,那么您需要使用 BeanPostProcessor
。虽然在技术上可以在 BeanFactoryPostProcessor
中使用 bean 实例(例如,使用 BeanFactory.getBean
) ,但这样做会导致 bean 提前实例化,从而违反标准容器生命周期。这可能会导致负面的副作用,例如绕过 bean 的后期处理。
此外,BeanFactoryPostProcessor
实例的作用域为每个容器。只有在使用容器层次结构时,这才是相关的。如果在一个容器中定义 BeanFactoryPostProcessor
,则它只应用于该容器中的 bean 定义。一个容器中的 Bean 定义不会由另一个容器中的 BeanFactoryPostProcessor
实例进行后处理,即使两个容器都属于同一层次结构。
当在 ApplicationContext 中声明 BeanFactory 后置处理器时会自动运行 ,以便将更改应用于定义容器的配置元数据。Spring 包含许多预定义的 BeanFactory 后置处理器,例如 PropertyOverrideConfigurer
和 PropertySourcesPlaceholderConfigurer
。您还可以使用自定义 BeanFactoryPostProcessor
,例如,注册自定义属性编辑器。
与 BeanPostProcessors
一样,您通常不希望配置 BeanFactoryPostProcessors
为延迟加载。如果没有其他 Bean 引用 Bean(Factory) 后处理器,那么该后置处理器根本不会被实例化。因此,将其标记为惰性初始模式将被忽略,即使您在 <beans/>
元素的声明中将 default-lazy-init
属性设置为 true
,Bean(Factory) PostProcessor 也会被急切地实例化。
示例:类名替换 PropertySourcesPlaceholderConfigurer
可以使用 PropertySourcesPlaceholderConfigurer
以标准 Java Properties
格式将 bean 的属性值定义在一个单独的外部化文件中,例如数据库 URL 和密码。
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
在运行时,PropertySourcesPlaceholderConfigurer
应用于替换数据源的某些属性的元数据。要替换的值被指定为表单的占位符 ${property-name}
,它遵循 Ant 和 log4j 以及 JSP EL 样式,可以自定义占位符前缀和后缀
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
使用 Spring 2.5 中引入的 context
命名空间,您可以使用专用配置元素配置属性占位符。您可以在 location
属性中以逗号分隔列表的形式提供一个或多个位置
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不仅将查找指定的 Properties
文件。默认情况下,如果在指定的属性文件中找不到属性,它会检查 Spring Environment
属性和常规 Java System
属性
可以使用 PropertySourcesPlaceholderConfigurer
来替换类名,如果该类在运行时无法解析为有效类,则在创建 bean 时解析 bean 失败,这是在 ApplicationContext
非延迟初始化 bean 的 preInstantiateSingletons()
阶段。
<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/something/strategy.properties</value>
</property>
<property name="properties">
<value>custom.strategy.class=com.something.DefaultStrategy</value>
</property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
使用说明
<?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 class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:postprocess/beanfactory/bean.properties"/>
<property name="properties" >
<value>id=11</value>
</property>
</bean>
<bean id="propertiesBean" class="${cls.name}">
<property name="id" value="${id}"/>
<property name="name" value="${name}"/>
</bean>
</beans>
#id=9
name=我是中国人
cls.name=study.hwj.spring.postprocess.beanfactory.PropertiesBean
package study.hwj.spring.postprocess.beanfactory;
import lombok.Data;
@Data
public class PropertiesBean {
private Integer id;
private String name;
}
PropertySourcesPlaceholderConfigurer#properties
既可以从文件中读取,也可以定义在 bean 属性上,会进行合并,文件中的属性优先级高于定义在 bean 上的属性
参考源码
org.springframework.beans.factory.config.BeanFactoryPostProcessor
org.springframework.context.support.PropertySourcesPlaceholderConfigurer
示例:PropertyOverrideConfigurer
另一个 BeanFactory 后处理器 PropertyOverrideConfigurer
类似于 PropertySourcesPlaceholderConfigurer
,但与后者不同,原始定义可以有 bean 属性的默认值或根本没有值。如果重写的 Properties
文件没有某个 bean 属性的条目,则使用默认上下文定义。
注意,bean 定义不知道正在被重写,因此从 XML 定义文件中不能立即看出正在使用重写配置。如果多个 PropertyOverrideConfigurer
实例为同一个 bean 属性定义不同的值,由于覆盖机制,最后一个实例获胜。
指定的重写值总是文字值。它们不会被翻译成 bean 引用。当 XML bean 定义中的原始值指定 bean 引用时,该约定也适用。
作用是属性值覆盖
beanName.property=value
context
命名空间,可以使用专用的配置元素配置重写属性
<context:property-override location="classpath:override.properties"/>
使用说明
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="locations" value="classpath:postprocess/beanfactory/override.properties"/>
<property name="properties" >
<value>propertiesBean.id=111</value>
</property>
</bean>
<bean id="propertiesBean" class="study.hwj.spring.postprocess.beanfactory.PropertiesBean">
<property name="id" value="1"/>
<property name="name" value="n1"/>
</bean>
</beans>
propertiesBean.id=110
使用 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 提供了 FactoryBean
接口的 50 多个实现
当你需要获取 FactoryBean
实例本身,而不是它创建的 bean,调用 ApplicationContext
的 getBean()
方法时,bean的 id
加上前缀 &
。因此,对于具有 id
为 myBean
的给定 FactoryBean
,调用 getBean("myBean")
时,容器返回 FactoryBean
工厂产生的对象,而调用 getBean("&myBean")
时,返回 FactoryBean
实例本身
参考源码
org.springframework.beans.factory.FactoryBean
org.springframework.beans.factory.SmartFactoryBean
基于注解的容器配置
注解配置是否优于 XML 配置?答案是 视情况而定
每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注解在其声明中提供了大量上下文,从而使配置更简洁。然而,XML 擅长将组件连接起来而不触及它们的源代码或重新编译它们。一些开发人员倾向于让装配接近源代码,而其他人则认为带注解的类不再是 POJO ,而且配置变得分散且难以控制。
无论选择什么,Spring 都可以容纳这两种风格,甚至可以将它们混合在一起。值得指出的是,通过 JavaConfig 选项,Spring 允许以非侵入性的方式使用注解,而不涉及目标组件的源代码
结合使用 BeanPostProcessor
和注解是扩展 Spring IoC 容器的常用方法,例如 AutowiredAnnotationBeanPostProcessor
。Spring 引入了 @Required
注解强制必需属性的可能性。支持 @Autowired
,JSR-250 注解 ( @PostConstruct
、@PreDestroy
), JSR-330 注解 ( @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>
<context:annotation-config/>
元素隐式注册以下后置处理器:
ConfigurationClassPostProcessor
BeanFactoryPostProcessor
@Configuration
AutowiredAnnotationBeanPostProcessor
BeanPostProcessor
@Autowired
、@Value
、@Inject
CommonAnnotationBeanPostProcessor
BeanPostProcessor
PostConstruct
、PreDestroy
PersistenceAnnotationBeanPostProcessor
- 需要 jpa 环境
EventListenerMethodProcessor
BeanFactoryPostProcessor
@EventListener
<context:annotation-config/>
只在定义 bean 的应用程序上下文中查找 bean 上的注解。这意味着,如果将 <context:annotation-config/>
放在对于 DispatcherServlet
的 WebApplicationContext
中,它只检查控制器中的 @Autowired
bean 。
参考源码
org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors
@Required
@Required
注解适用于 bean 属性 setter 方法
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
必须注册 RequiredAnnotationBeanPostProcessor
bean 来启用支持 @Required
注解
从 Spring Framework 5.1 开始,@Required
注解和 RequiredAnnotationBeanPostProcessor
正式弃用,推荐使用构造函数注入进行所需的设置(或自定义实现 InitializingBean.afterPropertiesSet()
或自定义 @PostConstruct
方法以及 bean 属性设置方法)。
@Autowired
可以使用 JSR 330 的 @Inject
注解来代替 Spring 的 @Autowired
注解
可以将 @Autowired
注解应用于构造函数、setter 方法
从 Spring Framework 4.3 开始,如果目标 bean 只定义了一个构造函数,则不再需要对此类构造函数进行 @Autowired
注解。但是,如果有多个构造函数可用并且没有默认构造函数,则必须至少对其中一个构造函数进行 @Autowired
注解,以指示容器使用哪一个。
可以将 @Autowired
注解应用于具有任意名称和多个参数的方法
也可以应用于字段,甚至可以将其与构造函数混合使用
确保您的目标组件一致地由 @Autowired
注解声明。否则,注入可能会失败,原因是在运行时出现“未找到类型匹配”错误。
对于通过类路径扫描找到的 XML 定义的 bean 或组件类,容器通常预先知道具体类型。但是,对于 @Bean
工厂方法,您需要确保声明的返回类型足够具体。对于实现多个接口的组件或由其实现类型可能引用的组件,请考虑在您的工厂方法中声明最具体的返回类型(至少与引用您的 bean 的注入点要求的一样具体)。
将 @Autowired
注解添加到需要该类型数组或集合的字段或方法来指示 Spring 提供特定类型的所有 bean ,如果您希望数组或列表中的项目按特定顺序排序,您的目标 bean 可以实现 org.springframework.core.Ordered
接口或使用 @Order
或 标准 @Priority
注解。否则,它们的顺序遵循容器中相应目标 bean 定义的注册顺序。
@Autowired
private MovieCatalog[] movieCatalogs;
// -----------------------------------------
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// -----------------------------------------
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
@Order
值可能会影响注入点的优先级,但请注意,它们不会影响单例启动顺序,这是由依赖关系和 @DependsOn
声明决定的。
标准 javax.annotation.Priority
注解在 @Bean
级别不可用 ,因为它不能在方法上声明。它的语义可以通过 @Order
值结合 @Primary
每个类型的单个 bean 进行建模。
如果自动注入的类型是 Map
,只要 key 是 String
类型,也可以注入进去。key 对应的是 bean 的名称。
默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配失败。对于声明的数组、集合或映射,至少需要一个匹配元素。
默认行为是将带注解的方法和字段视为指示所需的依赖项。通过将 @Autowired
的 required
属性设置为 false
,可以跳过不能满足的注入点。
@Autowired
的 required
可以替代过期的 @Required
可以通过 Java 8 的 java.util.Optional
来表达特定依赖项的非必需性质
从 Spring Framework 5.0 开始,您还可以使用 JSR-305 的 @Nullable
、@NonNull
注解
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
还可以使用 @Autowired
对于那些众所周知的解析依赖接口:BeanFactory
,ApplicationContext
,Environment
,ResourceLoader
, ApplicationEventPublisher
,和MessageSource
。这些接口及其扩展接口(例如 ConfigurableApplicationContext
或 ResourcePatternResolver
)会自动解析,无需特殊设置。
@Autowired
,@Inject
,@Value
,@Resource
注解由 Spring 的 BeanPostProcessor
实现处理。这意味着您不能在自己的 BeanPostProcessor
或 BeanFactoryPostProcessor
类型中应用这些注解。这些类型必须使用 XML 或 Spring @Bean
方法显式注入
使用 @Primary
微调基于注解的自动装配
由于按类型自动装配可能会导致多个候选对象,因此通常需要对选择过程进行更多控制。实现此目的的一种方法是使用 Spring 的 @Primary
注解。@Primary
表示当多个 bean 是自动装配到单值依赖项的候选者时,应优先考虑特定 bean。如果候选中恰好存在一个 primary bean,则它成为自动装配的值。
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
@Primary
对应 <bean>
的 primary="true"
使用限定符微调基于注解的自动装配
当可以确定一个主要候选对象时,@Primary
是在多个实例中按类型使用自动装配的有效方法。当您需要对选择过程进行更多控制时,可以使用 Spring 的 @Qualifier
注解。您可以将限定符值与特定参数相关联,缩小类型匹配的范围,以便为每个参数选择一个特定的 bean。
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier("main")
对应 <qualifier value="main"/>
如果没有指定 @Qualifier
的 value
,默认为 bean 名称。限定符值在类型匹配集合中始终具有收缩语义,@Qualifier
在语义表达上不是指 bean 的唯一 id。推荐的限定值是 main
、persistent
等,表达的是独立于 id 的特定部件的特性。
限定符也适用于类型化集合,例如,Set<MovieCatalog>
。在这种情况下,根据声明的限定符,所有匹配的 bean 作为集合注入。这意味着限定符不必是唯一的。相反,它们构成过滤标准。例如,您可以定义多个具有相同限定符值 action
的 MovieCatalog
bean,所有这些 @Qualifier("action")
bean 都被注入到 Set<MovieCatalog>
让限定符值在类型匹配候选中针对目标 bean 名称进行选择,不需要在注入点进行 @Qualifier
注解。如果没有其他解析指示符(例如 @Qualifier
或 @Primary
),对于非唯一依赖的情况,Spring 将注入点名称(即字段名称或参数名称)与目标 bean 名称进行匹配并选择相同名称的 bean。
如果您打算按名称表达注解驱动的注入,请不要使用 @Autowired
,即使它能够在类型匹配候选者中按 bean 名称进行选择。相反,应该使用 JSR-250 @Resource
注解,该注解在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。
@Autowired
具有相当不同的语义:按类型选择候选 bean 后,指定的 String
限定符值仅在那些类型选择的候选中考虑(例如,将 account
限定符与标记有相同限定符标签的 bean 进行匹配)。
对于被定义为集合、 Map 或数组类型的 bean ,@Resource
是一个很好的解决方案,通过唯一名称引用特定的集合或数组 bean。也就是说,在4.3集合中,你可以通过 Spring 的 @Autowired
类型匹配算法匹配 Map 和数组类型,只要元素类型信息保留在 @Bean
返回类型签名或集合继承层次结构中。在这种情况下,可以使用限定符值在相同类型的集合中进行选择。
@Autowired
还考虑注入的自引用。注意,自我注入是一种回退。对其他组件的常规依赖关系始终具有优先权。从这个意义上说,自我推荐并不参与正常的候选选择,因此自引用从来不是 primary 的。相反,它们总是以最低优先级结束。实际上,您应该将自引用作为最后的手段(例如,通过 bean 的事务代理调用同一实例上的其他方法)。在这种情况下,可以考虑将受影响的方法分解为一个单独的委托 bean。或者,您可以使用 @Resource
,它可以通过当前 bean 的唯一名称获得返回当前 bean 的代理。
试图将来自@Bean 方法的结果注入到同一个配置类中也是一种有效的自引用场景。要么在方法签名中实际需要的地方惰性地解析这些引用(相对于配置类中的 autowired 字段) ,要么将受影响的@Bean 方法声明为静态,将它们与包含的配置类实例及其生命周期解耦。否则,这样的 bean 只能在回退阶段考虑,在其他配置类上选择 bean 作为主要候选类。
@Autowired
适用于字段、构造函数和多参数方法,允许在参数级通过 qualifier 注解进行收缩。相比之下,@Resource
仅支持字段和 bean 属性 setter 方法,并且只有一个参数。因此,如果注入目标是构造函数或多参数方法,则应该坚持使用限定符。
可以创建自己的自定义限定符注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
可以添加 <qualifier/>
标签作为 <bean/>
标签的子元素,然后指定 type
和 value
以匹配您的自定义限定符注解。该类型与注解的完全限定类名匹配。或者,如果不存在名称冲突的风险,为了方便起见,您可以使用短类名称。以下示例演示了这两种方法:
<?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">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在某些情况下,使用没有值的注解可能就足够了:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
除了简单 value
属性之外,您还可以定义接受命名属性的自定义限定符注解。如果随后在要自动装配的字段或参数上指定了多个属性值,则 bean 定义必须匹配所有此类属性值才能被视为自动装配候选者。
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
public enum Format {
VHS, DVD, BLURAY
}
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
<?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">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>
使用泛型作为自动装配限定符
除了 @Qualifier
注解之外,您还可以使用 Java 泛型类型作为限定的隐式形式
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
通用限定符也适用于自动装配列表、Map
实例和数组。
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
使用 CustomAutowireConfigurer
CustomAutowireConfigurer
是一个 BeanFactoryPostProcessor
,允许您注册自己的自定义限定符注解类型,即使它们没有使用 Spring 的 @Qualifier
注解。以下示例显示了如何使用 CustomAutowireConfigurer
:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver
通过以下方式确定自动装配候选者:
- 每个 bean 定义的
autowire-candidate
<beans/>
元素上default-autowire-candidates
@Qualifier
注解的和任何CustomAutowireConfigurer
注册的自定义注解
当多个 bean 有资格作为自动装配候选时,primary
的确定如下:如果候选中的一个 bean 定义的 primary
属性设置为 true
,则选择它。
参考源码
org.springframework.beans.factory.annotation.CustomAutowireConfigurer
org.springframework.beans.factory.annotation.CustomAutowireConfigurer#postProcessBeanFactory
@Resource
注入
JSR-250 注解,支持字段注入或 setter 方法注入
@Resource
采用 name
属性。默认情况下,Spring 将该值解释为要注入的 bean 名称。
如果未明确指定名称,则默认名称源自字段名称或 setter 方法。如果是字段,则采用字段名称。在 setter 方法的情况下,它采用 bean 属性名称。
CommonAnnotationBeanPostProcessor
对 @Resource
提供支持。如果您显式配置 Spring 的 SimpleJndiBeanFactory
,则可以通过 JNDI 解析这些名称。但是,我们建议您依赖默认行为,并使用 Spring 的 JNDI 查找功能来保持间接级别。
在 @Resource
没有指定显式名称的情况下,与 @Autowired
类似,@Resource
会找到一个 primary 类型匹配,而不是一个特定的命名 bean,并解析依赖项:BeanFactory
、ApplicationContext
、ResourceLoader
、ApplicationEventPublisher
和 MessageSource
接口。
在下面的示例中,customerPreferenceDao
字段注入时会首先查找名为 customerPreferenceDao
的 bean,然后返回到 customerPreferenceDao
类型的 primary 类型匹配
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
使用 @Value
@Value
通常用于注入外部属性
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
以及 application.properties 文件:
catalog.name=MovieCatalog
可以将值 MovieCatalog
注入 catalog
字段
Spring 提供了一个默认的宽松嵌入值解析器。它将尝试解析属性值,如果无法解析,则属性名称(例如 ${catalog.name}
)将作为值注入。如果你想对不存在的值保持严格的控制,应该声明一个 PropertySourcesPlaceholderConfigurer
bean。
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
当使用 JavaConfig 配置 PropertySourcesPlaceholderConfigurer
时, @Bean
方法必须是 static
。
如果无法解析 ${}
占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用方法setPlaceholderPrefix
,setPlaceholderSuffix
或 setValueSeparator
自定义占位符。
Spring Boot 默认情况下,配置了 PropertySourcesPlaceholderConfigurer
,从 application.properties
和 application.yml
文件中获取属性
Spring 提供的内置转换器支持允许自动处理简单的类型转换(例如,将字符串转为 Integer
或 int
)。多个逗号分隔的值可以自动转换为 String 数组。
@Value("${catalog.name:defaultCatalog}")
提供默认值 defaultCatalog
Spring BeanPostProcessor
底层使用 ConversionService
来处理将 String 值转换为 @Value
目标类型的过程。如果您想为您自己的自定义类型提供转换支持,您可以提供您自己的 ConversionService
bean 实例:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当 @Value
包含 SpEL
表达式时 ,该值将在运行时动态计算,例如 @Value("#{systemProperties['user.catalog'] + 'Catalog' }")
、@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog
参考源码
org.springframework.context.support.PropertySourcesPlaceholderConfigurer
org.springframework.core.convert.ConversionService
org.springframework.core.convert.support.DefaultConversionService
org.springframework.format.support.DefaultFormattingConversionService
使用 @PostConstruct
和 @PreDestroy
CommonAnnotationBeanPostProcessor
不仅支持 @Resource
注解,也支持 JSR-250 的生命周期注解: javax.annotation.PostConstruct
和 javax.annotation.PreDestroy
如果 CommonAnnotationBeanPostProcessor
在 Spring ApplicationContext
中注册,那么将调用带有这些注解的方法,作为对应的 Spring 生命周期接口方法或显式声明的回调方法。
@Resource
、@PostConstruct
和 @PreDestroy
注解是标准 Java 库的一部分。然而,整个 javax.annotation
包在 JDK 9 里从 Java 核心模块分离,并最终在 JDK 11 里被删除。如果需要的话,可以通过 Maven Central 获得 javax.annotation-api
组件。
类路径扫描和托管组件
从 Spring 3.0开始,Spring JavaConfig 项目提供的许多特性都是核心 Spring 框架的一部分。这允许您使用 Java 而不是传统的 XML 文件来定义 bean。例如 @Configuration
,@Bean
, @Import
,和 @DependsOn
注解
@Component
和更多的模板注解
@Repository
注解是针对满足的存储库角色(也被称为数据访问对象或 DAO )的任何类的标记。此标记的用途之一是异常的自动转换
Spring 提供模板注解:@Component
,@Service
, @Controller
。@Component
是任何 Spring 管理的组件的通用模板。 @Repository
、@Service
和 @Controller
是 @Component
针对更具体用例(分别在持久层、服务层和表示层)的特化。因此,通过 @Repository
,@Service
, @Controller
注解,你的类能更好地适合于通过工具处理,或与切面进行关联。
@Repository
, @Service
, 和 @Controller
还可以在 Spring Framework 的未来版本中携带额外的语义。@Repository
已经支持作为持久层中自动异常转换的标记。
使用元注解和组合注解
Spring 提供的许多注解都可以在您自己的代码中用作 元注解 。元注解是可以应用于另一个注解的注解。例如 @Component
、@Service
等。
还可以组合元注解来创建 组合注解 ,例如 Spring MVC 的 @RestController
注解是由 @Controller
和 @ResponseBody
组成的
组合注解可以选择性地从元注解重新声明属性以允许自定义。当您只想公开元注解属性的子集时,这尤其有用。例如,Spring 的 @SessionScope
注解将 scope
名称硬编码为 session
,但仍允许自定义 proxyMode
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
自动检测类并注册 Bean 定义( @ComponentScan
)
Spring 可以自动检测模板注解类(例如 @Service
、@Repository
)并使用 ApplicationContext
注册 BeanDefinition
要自动检测这些类并注册相应的bean,您需要添加 @ComponentScan
到 @Configuration
类中,其中 basePackages
属性指定要扫描的包。(可以指定包含 逗号或分号或空格 分隔的列表)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@ComponentScan(basePackages = "org.example")
等同于 @ComponentScan("org.example")
使用 XML 配置:
<context:component-scan base-package="org.example"/>
使用 <context:component-scan>
隐式启用 <context:annotation-config>
。使用 <context:annotation-config>
时通常不需要包含 <context:component-scan>
元素 。
当您使用 component-scan
标签时,AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
都隐式包含在内。
可以通过指定 <context:component-scan>
的 annotation-config
属性值为 false
,可以禁用注册AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
使用过滤器自定义扫描( includeFilters
、excludeFilters
)
默认情况下,用 @Component
, @Repository
, @Service
, @Controller
, @Configuration
注解的类或本身被 @Component
注解的自定义注解是唯一被检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。添加它们作为 @ComponentScan
注解的 includeFilters
或 excludeFilters
属性(或作为 XML 配置中 <context:component-scan>
标签的 <context:include-filter />
或 <context:exclude-filter />
子标签)。每个过滤器元素都需要 type
和 expression
属性。
下表描述了过滤选项( org.springframework.context.annotation.FilterType
):
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
annotation (默认) |
org.example.SomeAnnotation |
在目标组件中的类定义上存在模板注解或自定义注解上有模板元注解 |
assignable |
org.example.SomeClass |
指定(扩展或实现)的类(或接口)的目标组件 |
aspectj |
org.example..*Service+ |
与目标组件匹配的 AspectJ 类型表达式 |
regex |
org\.example\.Default.* |
与目标组件的类名匹配的正则表达式 |
custom |
org.example.MyTypeFilter |
org.springframework.core.type.TypeFilter 接口的自定义实现 |
以下示例表示忽略所有 @Repository
注解而使用 Stub
存储库的配置:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
等效的 XML 配置:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex" expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
还可以通过在注解上设置 useDefaultFilters=false
或设置 <component-scan/>
标签的 use-default-filters="false"
属性来禁用默认过滤器。这样有效地禁止自动检测 @Component
,@Repository
,@Service
,@Controller
, @RestController
,@Configuration
注解或元注解的类。
在组件中定义 Bean 元数据
Spring 组件还可以将 bean 定义元数据提供给容器。您可以使用与在 @Configuration
注解类中定义 bean 元数据相同的 @Bean
注解来实现这一点。
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
注意:bean 定义工厂方法 publicInstance()
,@Bean
通过注解标识工厂方法和其他 bean 定义属性,例如限定符值 @Qualifier
。可以指定其他方法级别的注解是 @Scope
,@Lazy
和自定义限定符注解。
除了用于组件初始化的作用外,您还可以将 @Lazy
注解放置在标有 @Autowired
或 @Inject
的注入点上。在这种情况下,它导致注入一个延迟解析代理。
从 Spring Framework 4.3 开始,您还可以声明类型 InjectionPoint
(或其更具体的子类:DependencyDescriptor
)的工厂方法参数来访问触发当前 bean 创建的请求注入点。请注意,这仅适用于 bean 实例的实际创建,不适用于现有实例的注入。因此,此功能对于原型作用域的 bean 最有意义。对于其他作用域,工厂方法只会看到在给定作用域内触发创建新 bean 实例的注入点(例如,触发创建惰性单例 bean 的依赖项)。在这种情况下,可以使用提供的注入点元数据进行语义关注。
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常规 Spring 组件中的 @Bean
方法的处理方式与 Spring @Configuration
类中的对应方法不同。不同之处在于,@Component
类没有使用 CGLIB 来增强以拦截方法和字段的调用。CGLIB 代理是调用 @Configuration
类中 @Bean
方法中的方法或字段来创建对协作对象的 Bean 元数据引用的方法。这些方法不是用普通的 Java 语义调用的,而是通过容器来提供通常的生命周期管理和 Spring bean 的代理,甚至在通过编程调用 @Bean
方法引用其他 bean 时也是如此。相比之下,在普通的 @Component
类中调用 @Bean
方法中的方法或字段具有标准的 Java 语义,不应用特殊的 CGLIB 处理或其他约束。
@Configuration
// @Component
public class Config2 {
@Bean
public MyBean1 myBean1() {
MyBean1 myBean1 = new MyBean1();
myBean1.setMyBean2(myBean2());
return myBean1;
}
@Bean
public MyBean2 myBean2() {
MyBean2 myBean2 = new MyBean2();
return myBean2;
}
}
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(Config2.class);
MyBean1 myBean1 = ctx.getBean(MyBean1.class);
MyBean2 myBean2 = ctx.getBean(MyBean2.class);
System.out.println("if same :: " + (myBean1.getMyBean2() == myBean2)); // 如果Config2上的注解是@Configuration, 返回 true ;如果是 @Component ,返回 false ;两个注解都存在,true ;都不存在,false
可以将 @Bean
方法声明为 static
,从而允许在不将其包含的配置类创建为实例的情况下调用它们。这在定义后置处理器 bean(例如,BeanFactoryPostProcessor
或 BeanPostProcessor
)时特别有意义,因为此类 bean 在容器生命周期的早期被初始化,并且应避免在此时触发配置的其他部分。
由于技术限制,对静态 @Bean
方法的调用永远不会被容器拦截,即使在 @Configuration
类中也不会 :CGLIB 子类化只能重写非静态方法。因此,对另一个 @Bean
方法的直接调用具有标准的 Java 语义,导致直接从工厂方法本身返回一个独立的实例,而不是返回容器内的单例对象。
@Bean
方法的 Java 语言可见性不会对 Spring 容器中生成的 bean 定义产生直接影响。您可以自由地在非 @Configuration
类中以及任何地方的静态方法中自由地声明您认为合适的工厂方法。然而, @Configuration
类中的常规 @Bean
方法需要是可覆盖的——也就是说,它们不能被声明为 private
或 final
。
@Bean
方法也可以在给定组件或配置类的基类上发现,以及在组件或配置类实现的接口中声明的 Java 8 默认方法( default
)上。这为组合复杂的配置安排提供了很大的灵活性,甚至可以通过 Java 8 默认方法(从 Spring 4.2 开始)进行多重继承。
最后,单个类可能包含同一个 bean 的多个 @Bean
方法。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时选择具有最多可满足依赖项的变体,类似于容器如何在多个 @Autowired
构造函数之间进行选择。
命名自动检测的组件( nameGenerator
)
当一个组件作为扫描过程的一部分被自动检测时,它的 bean 名称由该扫描器已知的 BeanNameGenerator
策略生成。默认情况下,任何包含 value
的 Spring 构造型注解( @Component
、@Repository
、@Service
和 @Controller
)通过 value
将该名称提供给相应的 bean 定义。
如果不包含 value
值,则默认的 bean 名称生成器返回未大写的非限定类名。
@Service("myMovieLister") // bean 名称是 myMovieLister
public class SimpleMovieLister {
// ...
}
@Repository // bean 名称是 movieFinderImpl
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果不想依赖默认的 bean 命名策略,可以提供自定义 bean 命名策略。首先,实现 BeanNameGenerator
接口,并确保包含一个默认的无参数构造函数。
如果由于多个自动检测到的组件具有相同的非限定类名(即具有相同名称但驻留在不同包中的类)而遇到命名冲突,您可能需要配置一个 BeanNameGenerator
默认为生成的完全限定类名。从 Spring Framework 5.2.3 开始, 位于包 org.springframework.context.annotation
中的 FullyQualifiedAnnotationBeanNameGenerator
可用于此类目的。
在配置自动扫描时指定名称生成器:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" />
</beans>
作为一般规则,每当其他组件可能对注解进行显式引用时,请考虑使用注解指定名称。另一方面,只要容器负责连接,自动生成的名称就足够了。
参考源码
org.springframework.beans.factory.support.BeanNameGenerator
为自动检测组件提供作用域( scopeResolver
)
由 @Scope
注解指定的不同作用域。可以在注释中提供作用域的名称,例如 @Scope("prototype")
或 @Scope(org.springframework.beans.factory.config.ConfigurableBeanFactory#SCOPE_PROTOTYPE)
@Scope
注解仅在具体 bean 类(用于带注解的组件)或工厂方法(用于 @Bean
方法)上进行自省。与 XML bean 定义相反,没有 bean 定义继承的概念,类级别的继承层次结构与元数据目的无关。
也可以使用 Spring 的元注解方法来构建自己的作用域注解:例如,使用 @Scope("prototype")
进行自定义注解的元注解,还可能声明一个自定义作用域代理模式。
要为作用域解析提供自定义策略而不是依赖基于注解的方法,您可以实现 ScopeMetadataResolver
接口。确保包含默认的无参数构造函数。然后您可以在配置扫描器时提供完全限定的类名:
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
当使用某些非单例作用域时,可能需要为作用域对象生成代理。为此,可以在 component-scan
元素上使用 scoped-proxy
属性。值域是:no
,interfaces
, targetClass
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
参考源码
org.springframework.context.annotation.ScopeMetadataResolver
提供带有注解的限定符元数据( @Qualifier
)
当依靠类路径扫描来自动检测组件时,可以在候选类上为 qualifier 元数据提供类型级别的注释。
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
与大多数基于注解的备选方案一样,请记住,注解元数据绑定到类定义本身,而 XML 的使用允许同一类型的多个 bean 提供其限定符元数据的变体,因为元数据是按实例而不是按类提供的。
生成候选组件的索引
虽然类路径扫描非常快,但可以通过在 编译时 创建一个静态候选列表来提高大型应用程序的启动性能。在此模式下,所有作为组件扫描目标的模块都必须使用此机制。
现有的 @ComponentScan
或 <context:component-scan/>
指令必须保持不变才能请求上下文扫描某些包中的候选。当 ApplicationContext
检测到这样的索引时,它会自动使用它而不是扫描类路径。
要生成索引,请向包含作为组件扫描指令目标的组件的每个模块添加额外的依赖项。
对于 Maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.8</version>
<optional>true</optional>
</dependency>
</dependencies>
对于 Gradle :
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
}
spring-context-indexer
工件生成一个包含在 jar 文件中的 META-INF/spring.components
文件
在 IDE 中使用此模式时,必须将 spring-context-indexer
注册为注解处理器,以确保在更新候选组件时索引是最新的。
当在类路径上找到 META-INF/spring.components
文件时,索引会自动启用。如果某个索引对某些库部分可用,但无法为整个应用程序构建,则可以通过设置 spring.index.ignore
为 true
,将其设置为 JVM 系统属性或通过 SpringProperties
机制,回退到常规类路径排列(就好像根本不存在索引)。
使用 JSR 330 标准注解
JSR-330 标准注解(依赖注入)
需要增加依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
使用 @Inject
和 @Named
进行依赖注入
@Inject
类比 @Autowired
与 @Autowired
一样,您可以在字段级别、方法级别和构造函数-参数级别使用 @Inject
。
可以将注入点声明为 javax.inject.Provider
,从而允许按需访问范围较短的 bean 或通过 Provider.get()
调用延迟访问其他 bean 。
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
想对应该注入的依赖项使用限定名称,则应使用 @Named
注解
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
与 @Autowired
一样,@Inject
也可以与 java.util.Optional
或 @Nullable
一起使用。这在这里更适用,因为 @Inject
没有 required
属性
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
@Named
和 @ManagedBean
:与 @Component
注解相同
可以使用 @javax.inject.Named
或 javax.annotation.ManagedBean
代替 @Component
:
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
使用 @Component
时不为组件指定名称是很常见的。@Named
可以以类似的方式使用
使用 @Named
或 @ManagedBean
时,您可以以与使用 Spring 注解时完全相同的方式使用组件扫描
与 @Component
不同,JSR-330 的 @Named
和 JSR-250 的 ManagedBean
注解不可组合。您应该使用 Spring 的模板来构建自定义组件注解。
JSR-330 标准注解的限制
Spring 注解与 JSR-330 标准注解的比较
Spring | javax.inject.* | javax.inject 限制/描述 |
---|---|---|
@Autowired |
@Inject |
@Inject 没有 required 属性。但是可以与 Optional 一起使用 |
@Component |
@Named / @ManagedBean |
JSR-330 不提供可组合模型,仅提供一种识别命名组件的方法 |
@Scope("singleton") |
@Singleton |
JSR-330 默认作用域类似于 Spring 的 prototype 。但是,为了与 Spring 的一般默认值保持一致,Spring 容器中声明的 JSR-330 bean 默认是 singleton 。为了使用除 singleton 之外的作用域,应该使用 Spring 的 @Scope 注解。javax.inject 还提供了一个 @Scope 注解。然而,这仅用于创建自定义注解 |
@Qualifier |
@Qualifier / @Named |
javax.inject.Qualifier 只是用于构建自定义限定符的元注解。具体的 String 限定符(如带有值的 Spring @Qualifier )可以通过使用 javax.inject.Named |
@Value |
- | 没有等价物 |
@Required |
- | 没有等价物 |
@Lazy |
- | 没有等价物 |
ObjectFactory |
Provider |
javax.inject.Provider 是 Spring 的 ObjectFactory 的直接替代品,get() 方法名称更短。它也可以与 Spring 的 @Autowired 或没有注解的构造函数和 setter 方法结合使用 |
基于 Java 的容器配置
介绍如何在 Java 代码中使用注解来配置 Spring 容器
基本概念:@Bean
和 @Configuration
@Bean
和 @Configuration
是 Spring 基于 Java 配置的核心构件
@Bean
注解用于指示方法实例化、配置和初始化将由 Spring IoC 容器管理的新对象
@Bean
注解扮演着与<bean/>
标签相同的角色
@Configuration
注解一个类表明它的主要目的是作为 bean 定义的来源。@Configuration
类允许通过调用同一类中的其他 @Bean
方法来定义 bean 间的依赖关系。
最简单的 @Configuration
类如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
完全( full )的 @Configuration 配置 VS 精简( lite )的 @Bean 模式?
当在没有 @Configuration
注解的类中声明 @Bean
方法时 ,它们被称为在“精简”模式下处理。在精简模式下,@Bean
方法是一种通用的工厂方法机制,不应调用其他 @Bean
方法,精简模式不能声明 bean 间的依赖关系。
在常见的场景中,@Bean
方法在 @Configuration
类中声明,确保始终使用 “full” 模式,因此跨方法引用被重定向到容器的生命周期管理。这可以防止同一个 @Bean
方法意外地通过一个普通的 Java 调用被调用,这有助于减少在 “lite” 模式下运行时难以跟踪的细微错误。
使用说明
@Bean
方法内调用另一个 @Bean
方法时,配置类必须是代理类,否则拿到的不是容器内的 bean 。可以使用方法参数注入的方式避免这个问题。
// @Configuration(proxyBeanMethods = true)
public class ConfigurationConfig {
@Bean
public MyBean2 myBean22() {
return new MyBean2(2, "a2");
}
@Bean
public MyBean3 myBean3() {
MyBean3 myBean3 = new MyBean3();
myBean3.setMyBean2(myBean22());
return myBean3;
}
}
使用 AnnotationConfigApplicationContext
实例化 Spring 容器
AnnotationConfigApplicationContext
不仅能够接受 @Configuration
类作为输入,还能够接受普通 @Component
类和用 JSR-330 注解的类。
简单构造
// @Configuration 类
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// 普通 @Component 类和用 JSR-330 注解的类
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
以编程方式构建容器 register(Class…)
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
启用组件扫描 scan(String…)
@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig {
...
}
等价于
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
等价于
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
@Component
是 @Configuration
的元注解
支持 Web 应用程序 AnnotationConfigWebApplicationContext
可以通过 AnnotationConfigWebApplicationContext
获得 AnnotationConfigApplicationContext
的 WebApplicationContext
变量
web.xml
里的模板代码:
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
参考源码:
org.springframework.web.context.WebApplicationContext
使用 @Bean
注解
@Bean
是方法级别的注解,可以对比 XML 配置里的 <bean/>
标签
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
注意 @Bean
方法的返回类型 ,可以使用接口(或基类)返回类型声明 @Bean
方法,但是推荐返回最具体的类型
Bean 依赖
@Bean
注解的方法可以具有任意数量的参数,这些参数描述构建 bean 所需的依赖项。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
解析机制与基于构造函数的依赖注入几乎相同。
支持生命周期回调
使用 @Bean
注解定义的任何类都支持常规的生命周期回调,并且可以使用 JSR-250 中的 @PostConstruct
和 @PreDestroy
注解
完全支持常规的 Spring 生命周期回调。如果 bean 实现 InitializingBean
、 DisposableBean
或 Lifecycle
,则容器将调用它们各自的方法
完全支持 *Aware
接口
默认情况下,使用 Java 配置定义的具有公共 close
或 shutdown
方法的 bean 会自动配置为销毁回调。如果您不希望在容器关闭时调用它,您可以添加 @Bean(destroyMethod="")
到您的 bean 定义中以禁用默认推断 (inferred)
模式。
下面的示例演示如何防止数据源的自动销毁回调:
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
@Bean
注解支持指定任意初始化和销毁回调方法
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
也可以不依赖容器的生命周期回调,直接调用初始化方法:
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
指定 Bean 作用域
@Scope
ConfigurableBeanFactory.SCOPE_PROTOTYPE
ConfigurableBeanFactory.SCOPE_SINGLETON
默认作用域是 singleton
,可以使用 @Scope
注解覆盖它
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
@Scope
和 scoped-proxy
Spring 提供了一种通过限定作用域的代理处理限定作用域的依赖项的方便方法。使用 XML 配置时创建这样一个代理的最简单方法是 <aop: scoped-proxy/>
元素。在 Java 中使用 @Scope
注解配置 bean 可以提供与 proxyMode
属性相当的支持。缺省值是 ScopedProxyMode.DEFAULT
,它通常表示除非在组件扫描指令级别配置了不同的缺省值,否则不应创建作用域代理。你可以指定 ScopedProxyMode.TARGET_CLASS
,ScopedProxyMode.INTERFACES
或者 ScopedProxyMode.NO
自定义 Bean 命名
默认情况下,配置类使用 @Bean
方法的名称作为结果 bean 的名称。可以使用 name
属性指定名称
@Configuration
public class AppConfig {
@Bean(name = "myThing")
public Thing thing() {
return new Thing();
}
}
有时需要给单个 bean 多个名称:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
使用注解 @Description
提供 bean 的更详细的文本描述。当 bean 被公开(可能通过 JMX )用于监视目的时,这可能特别有用。
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
使用 @Configuration
注解
@Configuration
是一个类级别的注释,指示对象是 bean 定义的源。 @Configuration
类通过@Bean 注释的方法声明 bean。对 @Configuration
类上的 @Bean
方法的调用也可用于定义 bean 之间的依赖关系。
注入 Bean 间依赖
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
这种声明 bean 间依赖关系的 @Bean
方法仅在该方法在 @Configuration
类中声明时才有效。不能使用普通 @Component
类来声明 bean 间的依赖关系。
查找方法注入( Lookup )
在单例作用域的 bean 依赖于原型作用域的 bean 的情况下,查找方法注入很有用
可以实现单例每次获取原型作用域的 bean 时,都是新的原型 bean
@Configuration
public class Config2Bean {
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
};
}
}
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
public class AsyncCommand extends Command {
public AsyncCommand() {
System.out.println("AsyncCommand Constructor..." + hashCode());
}
@Override
public Object execute() {
System.out.println("AsyncCommand execute ..");
return null;
}
}
@Test
public void test4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config2Bean.class);
for (int i = 0; i < 5; i++) {
System.out.println("================");
CommandManager commandManager = context.getBean(CommandManager.class);
System.out.println(commandManager);
System.out.println(commandManager.process("abc"));
}
}
关于基于 Java 的配置如何在内部工作的更多信息
所有 @Configuration
类在启动时都使用 CGLIB
代理类,子方法在调用父方法和创建新实例之前,首先检查容器中是否有任何缓存的(作用域)bean
从 Spring 3.2 开始,不再需要将 CGLIB 添加到类路径中,因为 CGLIB 类已经被重新打包到 org.springframework.cglib
下,并直接包含在 spring-core
JAR 中。
由于 CGLIB 在启动时动态添加功能,因此存在一些限制。特别是,配置类不能是 final
的。但是,从 4.3 开始,配置类上允许使用任何构造函数,包括使用 @Autowired
或单个非默认构造函数声明进行默认注入。
如果您希望避免任何 CGLIB 强加的限制,请考虑在非 @Configuration
类上声明您的 @Bean
方法(例如,改为在普通 @Component
类上)。这样的话,@Bean
方法之间的跨方法调用不会被拦截,因此您必须完全依赖构造函数或方法级别的依赖注入。
对基于 Java 的配置进行组合
使用 @Import
注解
对标 XML 里的 <import/>
@Import
注解允许从另一个配置类加载 @Bean
定义
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
这样在容器启动时,只需要指定一个配置类
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
从 Spring Framework 4.2 开始,@Import
也支持对常规组件类的引用,类似于 AnnotationConfigApplicationContext.register
方法。通过使用一些配置类作为入口点来显式定义所有组件。
注入被导入依赖项的 @Bean
定义
在大多数实际场景中,bean 跨配置类彼此依赖。在使用 XML 时,这不是问题,因为不涉及编译器,您可以声明 ref="someBean"
,并相信 Spring 会在容器初始化期间解决这个问题。当使用 @Configuration
类时,Java 编译器在配置模型上存在约束,对其他 bean 的引用必须是有效的 Java 语法。
注意: @Configuration
类最终只是容器中的另一个 bean :这意味着它们可以利用 @Autowired
和 @Value
注入以及与任何其他 bean 相同的特性。
bean 跨配置类相互依赖的场景
- 通过
@Autowired
注入 - 通过
@Bean
方法参数注入
@Configuration
类在上下文的初始化过程中很早就被处理,使用 @Autowired
方法注入会导致过早的初始化,因此尽可能使用基于 @Bean
方法参数注入
通过 @Bean
对 BeanPostProcessor
和 BeanPactoryPostProcessor
定义要特别小心。这些方法通常应该声明为静态 static @Bean
方法,这样不会触发其包含的配置类的实例化。否则,@Autowired
和 @Value
可能无法在配置类本身上工作,因为可能将其创建为早于 AutowiredAnnotationBeanPostProcessor
的 bean 实例。
从 Spring Framework 4.3 开始,支持 @Configuration
类中的构造函数注入。另请注意,如果目标 bean 仅定义一个构造函数,则无需指定 @Autowired
。
在不同 @Configuration
类中导航 bean
@Autowired
效果很好并提供了所需的模块化,但是对于确定自动装配 bean 定义的确切位置仍然有些模糊。
如果这种歧义是不可接受的,并且您希望在 IDE 中从一个 @Configuration
类直接导航到另一个 @Configuration
类,请考虑注入配置类本身。
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
在前面的情况中,AccountRepository
的定义是完全明确的。 ServiceConfig
和 RepositoryConfig
是强关联的,可以通过接口缓解耦合:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // 导入具体类
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
如果您想影响某些 bean 的启动创建顺序,请考虑将其中一些声明为 @Lazy
(用于在首次访问时创建而不是在启动时创建)或 @DependsOn
(确保在当前 bean 之前创建特定的其他 bean ,而不是后者的直接依赖项)。
@DependsOn
注解主要用于指定当前 bean 所依赖的 beans 。任何被指定依赖的 bean 都由 Spring 保证在当前 bean 之前创建。在少数情况下,bean 不是通过属性或构造函数参数显式依赖于另一个 bean ,但却需要要求另一个 bean 优先完成初始化,则可以使用@DependsOn
这个注解。
@DependsOn
既可以指定初始化依赖顺序,也可以指定bean相应的销毁执行顺序(仅在单例bean的情况下)。
条件判断( Condition )包含 @Configuration
类或 @Bean
方法
@Profile
:只有在 Spring Environment
中启用了 profile
时才使用 @Profile
注解激活 bean
@Profile
注解是通过使用更灵活的 @Conditional
注解实际执行。@Conditional
注解指示注册 @Bean
前应咨询特定 org.springframework.context.annotation.Condition
的实现。
Condition
接口的实现提供了一个返回 true
或 false
的 matches(…)
方法。参考 org.springframework.context.annotation.ProfileCondition
组合 Java 配置和 XML 配置
Spring 的 @Configuration
类支持并不是要 100% 完全替代 Spring XML。某些工具,例如 Spring XML 命名空间,仍然是配置容器的理想方式。在 XML 更方便或必要的情况下,你可以选择:通过 ClassPathXmlApplicationContext
实例化容器中的 “XML 配置为中心” 的方式,或者通过使用 AnnotationConfigApplicationContext
实例化一个 “Java 配置为中心” 的方式,然后使用 @ImportResource
注解根据需要导入 XML。
以 XML 为中心使用 @Configuration
类
从 XML 引导 Spring 容器并以特定的方式包含 @Configuration
类可能更可取。例如,在使用 Spring XML 的大型现有代码库中,根据需要创建 @Configuration
类并从现有 XML 文件中包含它们会更容易。
将 @Configuration
类声明为普通的 Spring <bean/>
元素
@Configuration
类最终是容器中的 bean 定义。在本系列示例中,我们创建了一个名为 AppConfig
的 @Configuration
类,并将其作为 <bean/>
定义包含在 system-test-config.xml
中。因为 <context:annotation-config/>
,所以容器识别 @Configuration
注解并正确处理 AppConfig
中声明的 @Bean
方法。
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
使用 <context:component-scan/>
获取 @Configuration
类
@Component
是 @Configuration
的元注解,@Configuration
注解的类是组件扫描的候选对象
不需要再显式声明 <context:annotation-config/>
,因为 <context:component-scan/>
支持相同的功能。
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
以 @Configuration
类为中心使用 @ImportResource
引入 XML
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
Environment 抽象
Environment
接口是集成在容器中的抽象,它为应用程序环境的两个关键方面建模:profiles 和 properties
profile 是一组命名的、逻辑 bean 定义,仅当给定的 profile
处于活动状态时才向容器注册。Bean 可以分配给 profile
,无论是在 XML 中定义还是使用注解。Environment
对象与 profile
的关系是确定哪些 profile
当前是活动状态的,以及默认情况下哪些 profile
应该是活动的。
属性( properties )在几乎所有应用程序中都扮演着重要的角色,并且可能来自各种来源:属性文件、JVM 系统属性、系统环境变量、JNDI、servlet 上下文参数、ad-hoc Properties
对象、Map
对象等。Environment
对象与属性相关的作用是为用户提供方便的服务接口,用于配置属性源并从中解析属性。
Bean 定义 Profiles
Bean 定义 profile 在核心容器中提供了一种机制,允许在不同环境中注册不同的 Bean。此功能的作用场景,包括:
- 在开发中使用内存数据源,而不是在 QA 或生产中从 JNDI 查找相同的数据源。
- 仅在将应用程序部署到生产环境中时才注册监控基础设施。
- 为客户 A 和客户 B 部署注册 bean 的自定义实现。
使用 @Profile
@Profile
可以用在类或者方法上。
@Profile
注解指示组件在一个或多个指定的 profile
中是活动的。
@Profile("production")
profile
字符串可以包含一个简单的名称(例如 production
)或表达式。profile
表达式允许表达更复杂的 profile
逻辑(例如 production & us-east
)。profile
表达式支持以下运算符:
!
:逻辑“非”&
:逻辑“与”|
:逻辑“或”
不能在不使用括号的情况下混合使用 &
和 |
运算符。例如, production & us-east | eu-central
不是有效的表达式。它必须表示为 production & (us-east | eu-central)
。
@Profile
可以用作元注解来创建自定义组合注解。以下示例定义了一个自定义 @Production
注解,您可以将其用作 @Profile("production")
的替代品:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
如果一个 @Configuration
类被标记为 @Profile
,除非 profile
处于活动状态,否则与该类关联的所有 @Bean
方法和 @Import
注解都将被绕过。如果 @Component
或 @Configuration
类标有 @Profile({"p1", "p2"})
,则除非已激活 p1
或 p2
,否则不会注册该类。如果给定的 profile
以 NOT 运算符 !
为前缀,则仅当 profile
未处于活动状态时才注册带注解的元素。例如,@Profile({"p1", "!p2"})
如果 p1
处于活动状态或 p2
未处于活动状态,则会发生注册。
对于 @Profile
注解的 @Bean
方法,对于重载的相同 Java 方法名称的 @Bean
方法(类似于构造函数重载) ,需要在所有重载方法上一致地声明 @Profile
条件。如果条件不一致,则重载方法中只有第一个声明的条件有关系。因此,不能使用 @Profile
选择具有特定参数签名的重载方法。同一个 bean 的所有工厂方法之间的解析在创建时遵循 Spring 的构造函数解析算法。
如果希望定义具有不同配置文件条件的替代 bean,可以使用不同的 Java 方法名称,通过使用 @Bean
的 name
属性指向相同的 bean 名称
XML Bean 定义 Profile
@Profile
注解对标 <beans>
元素的 profile
属性
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
使用嵌套 <beans/>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
XML 不支持前面描述的配置文件表达式。但是,可以使用 !
运算符来否定配置文件。也可以通过嵌套配置文件来应用逻辑“与”
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
</beans>
激活 profile
可以通过多种方式激活 profile
,最直接的是针对 Environment
API 以编程方式进行,通过 ApplicationContext
调用
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
还可以通过 spring.profiles.active
属性激活 profile
,这可以通过系统环境变量、JVM 系统属性、web.xml
中的 servlet 上下文参数指定,甚至作为 JNDI 中的条目(请参阅 PropertySource
抽象 )来指定。在集成测试中,可以使用 spring-test
模块中的@ActiveProfiles
注解来声明活动 profile
profile
不是非此即彼,可以一次激活多个 profile
可以混合使用 Java API 和属性配置激活 profile
默认 profile
@Profile("default")
如果没有激活任何 profile ,那么默认激活的 profile 是 default
,如果存在激活的 profile ,不会激活默认的 default
可以通过 Environment
的 setDefaultProfiles()
更改默认的 profile ,或者通过配置 spring.profiles.default
属性
PropertySource
抽象
Spring 的 Environment
抽象提供了对属性源的 可配置 层次结构 的搜索操作
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
Environment
对象对一组 PropertySource
对象执行搜索。PropertySource
是对任何键值对源的简单抽象,Spring 的 StandardEnvironment
配置了两个 PropertySource
对象, 一个表示 JVM 系统属性集 ( System.getProperties()
) 和一个表示系统环境变量集 ( System.getenv()
)
这些默认属性源用于 StandardEnvironment
,用于独立应用程序。StandardServletEnvironment
还使用额外的默认属性源进行填充,包括 servlet 配置和 servlet 上下文参数。它可以选择性的启用一个 JndiPropertySource
执行搜索是分层的。默认情况下,系统属性优先于环境变量。因此,如果在调用期间碰巧在两个地方都设置了 my-property
属性,则系统属性值“获胜”并返回。请注意,属性值不会合并,而是被前面的条目完全覆盖。
对于通用的 StandardServletEnvironment
,完整的层次结构如下,最高优先级的条目位于顶部:
ServletConfig
参数(在DispatcherServlet
上下文的情况下)ServletContext
参数(web.xml
里的context-param
)- JNDI 环境变量(
java:comp/env/
) - JVM 系统属性(
-D
命令行参数) - JVM 系统环境(操作系统环境变量)
整个机制是可配置的。也许您有一个自定义的属性源,希望将其集成到此搜索中。为此,实现并实例化您自己的 PropertySource
,并将其添加到当前 Environment
的 PropertySources
集合中
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
在前面的代码中,MyPropertySource
以最高优先级添加到了搜索中。MutablePropertySources
API 公开了许多允许精确操作属性源集的方法。
参考源码
org.springframework.core.env.MutablePropertySources
使用 @PropertySource
@PropertySource
注解提供了一种方便的机制来将 PropertySource
添加到 Spring 的 Environment
@PropertySource("classpath:/com/myco/app.properties")
@PropertySource
中存在的任何 ${…}
占位符都针对已在环境中注册的属性源进行解析
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
假设 my.placeholder
存在于已注册的属性源(例如,系统属性或环境变量)中,占位符将解析为相应的值。如果没有,则将 default/path
用作默认值。如果未指定默认值且无法解析属性,则抛出 IllegalArgumentException
@PropertySource
注解是可重复的。但是,所有此类 @PropertySource
注解都需要在同一级别声明,要么直接在配置类上声明,要么作为同一自定义注解中的元注解。不推荐混合直接注解和元注解,因为直接注解有效地覆盖了元注解。
也可以直接使用 @PropertySources
语句中的占位符解析
Environment
抽象是集成在整个容器中的,很容易通过它来对占位符进行解析。这意味着可以按照自己喜欢的任何方式配置解析过程。可以更改搜索系统属性和环境变量的优先级或完全删除它们,还可以根据需要将自己的属性源添加到组合中。
具体来说,以下语句无论在何处定义属性都有效,只要 customer
在 Environment
中可用:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
注册 LoadTimeWeaver
Spring 使用 LoadTimeWeaver
动态转换加载到 Java 虚拟机( JVM )中的类。
要启用加载时编织( load-time weaving ),可以将 @EnableLoadTimeWeaving
添加到 @Configuration
类上
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
等价于
<beans>
<context:load-time-weaver/>
</beans>
配置后,ApplicationContext
中的任何 bean 都可以实现 LoadTimeWeaverAware
,从而接收对加载时编织器实例的引用。这在结合 Spring 的 JPA 支持时 特别有用, 因为 JPA 类转换可能需要加载时编织。
ApplicationContext
的附加功能
为了以更面向框架的风格增强 BeanFactory
功能,ApplicationContext
还提供了以下功能:
- 通过
MessageSource
接口访问 i18n 风格的消息。 - 通过
ResourceLoader
接口访问资源,例如 URL 和文件。 - 事件发布,即通过使用
ApplicationEventPublisher
接口发布事件到实现ApplicationListener
接口的 bean 。 - 通过
HierarchicalBeanFactory
接口加载多个(分层)上下文,让每个上下文都专注于一个特定的层,例如应用程序的 Web 层 。
使用 MessageSource
国际化
MessageSource
提供了国际化功能,HierarchicalMessageSource
提供了分层解析消息的功能。
当加载 ApplicationContext
时,它自动搜索在上下文中定义的 MessageSource
bean。bean 名称必须是 messageSource
。如果找到这样的 bean,则对 getMessage
方法的所有调用都将委托给消息源。如果未找到 MessageSource
,则 ApplicationContext
尝试查找包含同名 bean 的父级。如果找到,它将使用该 bean 作为 MessageSource
。如果 ApplicationContext
找不到任何消息来源,则会实例化一个空 DelegatingMessageSource
对象,以便能够接受对 getMessage
方法的调用。
Spring 提供了三种 MessageSource
实现。ResourceBundleMessageSource
, ReloadableResourceBundleMessageSource
和 StaticMessageSource
。为了进行嵌套消息传递,它们都实现了 HierarchicalMessageSource
。StaticMessageSource
很少使用,但它提供了向消息源添加消息的编程方式。下面的例子展示了 ResourceBundleMessageSource
:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
该示例假定在类路径中定义了三个资源包,分别称为 format
、 exceptions
和 windows
。任何解析消息的请求都以 JDK 标准的方式处理,即通过 ResourceBundle
对象解析消息。
注意:所有 ApplicationContext
实现也是 MessageSource
实现,因此可以强制转换为 MessageSource
接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
messageSource
bean 定义通过其基名属性引用了许多资源包。在列表中传递给 basenames
属性的三个文件分别称为 format.properties
、 exceptions.properties
和 windows.properties
,它们作为文件存在于类路径的根目录中。
关于国际化(“i18n”),Spring 的各种 MessageSource
实现遵循与标准 JDK ResourceBundle
相同的区域设置解析和回退规则 。如果你想对英国(en-GB
)的语言环境,你需要分别创建的文件名为format_en_GB.properties
,exceptions_en_GB.properties
和 windows_en_GB.properties
。
可以实现 MessageSourceAware
接口以获取 MessageSource
作为 ResourceBundleMessageSource
的替代,Spring 提供了 ReloadableResourceBundleMessageSource
类。此变体支持相同的包文件格式,但比基于标准 JDK 的 ResourceBundleMessageSource
实现更灵活 。特别是,它允许从任何 Spring 资源位置(不仅从类路径)读取文件,并支持属性文件的热重载。
ResourceBundleMessageSource
简单使用
<?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="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>message.format</value>
<value>message.exceptions</value>
<value>message.windows</value>
</list>
</property>
</bean>
</beans>
该示例假设您有三个名为 format
、exceptions
、 windows
的资源包,在您的类路径中定义,任何解析消息的请求都以 JDK 标准的通过 ResourceBundle
对象解析消息的方式处理。
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
传递给消息查找的参数
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
参考源码
org.springframework.context.MessageSource
org.springframework.context.HierarchicalMessageSource
org.springframework.context.support.ReloadableResourceBundleMessageSource
标准事件和自定义事件
ApplicationContext
中的事件处理是通过 ApplicationEvent
类和 ApplicationListener
接口提供的。如果将实现 ApplicationListener
接口的 bean 部署到上下文中,则每次将 ApplicationEvent
发布到 ApplicationContext
时,都会通知该 bean。本质上,这是标准的观察者设计模式。
从 Spring 4.2 开始,提供了 基于注解的模型 以及发布任意事件(也就是不一定从 ApplicationEvent
扩展的对象)的能力。当此类对象发布时,我们会为您将其包装在一个事件中。
Spring 提供的标准事件:
事件 | 描述 |
---|---|
ContextRefreshedEvent |
在 ApplicationContext 初始化( initialized )或刷新时发布(例如,通过使用 ConfigurableApplicationContext 接口上的 refresh() 方法)。在这里,initialized 意味着所有 bean 都被加载,后置处理器 bean 被检测并激活,预先实例化单例对象,并且准备好使用 ApplicationContext 对象。只要上下文尚未关闭,就可以多次触发刷新,前提是所选 ApplicationContext 支持这种“热”刷新。例如,XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持 。 |
ContextStartedEvent |
使用 ConfigurableApplicationContext 接口上的 start() 方法启动 ApplicationContext 时发布。在这里,started 意味着所有Lifecycle bean 都收到一个明确的启动信号。通常,此信号用于在显式停止后重新启动 bean,但它也可用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。 |
ContextStoppedEvent |
使用 ConfigurableApplicationContext 接口上的 stop() 方法停止 ApplicationContext 时发布。这里,stopped 意味着所有 Lifecycle bean 都收到一个明确的停止信号。停止的上下文可以通过调用 start() 重新启动 。 |
ContextClosedEvent |
在使用 ConfigurableApplicationContext 接口上的 close() 方法或关闭 ApplicationContext 时通过 JVM 关闭挂钩发布。在这里,closed 意味着所有的单例 bean 都将被销毁。一旦上下文关闭,它就会到达生命的尽头,无法刷新或重新启动。 |
RequestHandledEvent |
特定于 Web 的事件,告诉所有 bean HTTP 请求已经得到服务。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 DispatcherServlet 的 web 应用程序 |
ServletRequestHandledEvent |
RequestHandledEvent 的一个子类,添加了特定于 Servlet 的上下文信息 |
还可以创建和发布自己的自定义事件。下面的示例显示了一个简单的类,它扩展了 Spring 的 ApplicationEvent
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
要发布自定义 ApplicationEvent
,调用 ApplicationEventPublisher
上的 publishEvent()
方法 。通常,这是通过创建一个实现 ApplicationEventPublisherAware
的类并将其注册为 Spring bean 来完成的
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
在配置 EmailService
时,Spring 容器会检测到实现 ApplicationEventPublisherAware
并自动调用 setApplicationEventPublisher()
。实际上,传入的参数是 Spring 容器本身。您正在通过 ApplicationEventPublisher
接口与应用程序上下文进行交互。
要接收自定义 ApplicationEvent
,您可以创建一个实现 ApplicationListener
的类并将其注册为 Spring bean。
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
请注意,ApplicationListener
通常使用自定义事件的类型进行参数化(例如示例中的 BlockedListEvent
)。这意味着 onApplicationEvent()
方法可以保持类型安全,避免任何向下转型的需要。您可以根据需要注册任意数量的事件监听器,但请注意,默认情况下,事件监听器会同步接收事件。这意味着 publishEvent()
方法会阻塞,直到所有监听器都完成对事件的处理。这种同步和单线程方法的一个优点是,当监听器接收到事件时,如果事务上下文可用,它就会在发布者的事务上下文中运行。如果需要另一种事件发布策略,请参阅 Spring ApplicationEventMulticaster
接口和 SimpleApplicationEventMulticaster
实现。
以下示例显示了用于注册和配置上述每个类的 bean 定义:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
当调用 emailService
bean 的 sendEmail()
方法时,如果有任何应该被阻止的电子邮件消息,会发布一个自定义事件 BlockedListEvent
。blockedListNotifier
bean 被注册为接收 BlockedListEvent
的 ApplicationListener
。
Spring 的事件机制是为同一应用程序上下文中 Spring bean 之间的简单通信而设计的。然而,对于更复杂的企业集成需求,单独维护的 Spring Integration 项目为构建基于 Spring 编程模型的轻量级、面向模式、事件驱动架构提供了完整的支持 。
基于注解的事件监听器
可以使用 @EventListener
注解在托管 bean 的任何方法上注册事件监听器
BlockedListNotifier
可改写如下:
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明它监听的事件类型,但这次使用了灵活的名称,并且没有实现特定的监听器接口。只要实际事件类型在其实现层次结构中解析您的泛型参数,也可以通过泛型缩小事件类型。
如果您的方法应该监听多个事件,或者您想定义它时根本不带参数,则还可以在注解本身上指定事件类型。以下示例显示了如何执行此操作:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
还可以通过使用注解的 condition
属性添加额外的运行时过滤,该属性定义一个 SpEL
表达式 ,表达式应该匹配以实际调用特定事件的方法。
以下示例显示了如何重写我们的通知程序,使其仅在事件的 content
属性等于 my-event
时才被调用 :
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个 SpEL
表达式都针对专用上下文进行评估。下表列出了可用于上下文的项目,可以将它们用于条件事件处理:
Name | Location | 描述 | 例子 |
---|---|---|---|
事件 | 根对象 | 实际的 ApplicationEvent |
#root.event 或 event |
参数数组 | 根对象 | 调用方法的参数(作为对象数组) | #root.args 或 args ; args[0] 访问第一个参数 |
参数名称 | 评估上下文 | 任何方法参数的名称。如果由于某种原因,名称不可用(例如,因为已编译的字节码中没有调试信息),也可以使用 #a<#arg> 语法使用单个参数,其中 <#arg> 表示参数索引(从 0 开始) |
#blEvent 或 #a0 (也可以使用 #p0 或 #p<#arg> 参数表示法作为别名) |
请注意 ,即使您的方法签名实际上是指已发布的任意对象,您也可以通过 #root.event
访问基础事件。
如果您需要发布一个事件作为处理另一个事件的结果,您可以更改方法签名以返回应该发布的事件,如以下示例所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步监听器 不支持此功能
handleBlockedListEvent()
方法为它处理的每一个 BlockedListEvent
发布一个新的 ListUpdateEvent
。如果您需要发布多个事件,则可以返回一个 Collection
或一个事件数组。
异步监听器
如果您希望特定监听器异步处理事件,则可以重用 常规 @Async
支持 。以下示例显示了如何执行此操作:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时请注意以下限制:
- 如果异步事件监听器抛出
Exception
,则不会将其传播给调用者。参阅AsyncUncaughtExceptionHandler
- 异步事件监听器方法无法通过返回值来发布后续事件。如果您需要发布另一个事件作为处理结果,请注入
ApplicationEventPublisher
以手动发布事件
监听器排序
可以在方法声明上添加 @Order
注解
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
泛型事件
您还可以使用泛型进一步定义事件的结构。考虑使用 EntityCreatedEvent<T>
,这里 T
是创建的实际实体的类型。例如,您可以创建以下监听器定义只接收泛型类型是 Person
的 EntityCreatedEvent
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
由于类型擦除,这仅在触发的事件解析事件监听器过滤的泛型参数(即类似 class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
)时才有效 。
可以实现 ResolvableTypeProvider
来指导框架超越运行时环境提供的功能。以下事件显示了如何执行此操作:
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
这不仅适用于 ApplicationEvent
,也适用于作为事件发送的任何任意对象。
便捷的访问底层资源
应该熟悉 Spring 的 Resource
抽象
应用程序上下文是 ResourceLoader
,可用于加载 Resource
对象。Resource
本质上是 JDK java.net.URL
类的功能更丰富的版本。事实上,在适当的情况下,Resource
实现包装 java.net.URL
的实例。Resource
可以以透明的方式从几乎任何位置获取低级资源,包括从类路径、文件系统位置、用标准 URL 描述的任何位置以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合实际的应用程序上下文类型。
您可以将应用程序上下文中的 bean 实现特殊的回调接口 ResourceLoaderAware
,以在初始化时自动回调应用程序上下文本身作为 ResourceLoader
。您还可以公开 Resource
类型的属性,用于访问静态资源。它们像任何其他属性一样被注入其中。您可以将这些 Resource
属性指定为简单 String
路径,在部署 bean 时这些文本字符串自动转换到实际 Resource
对象。
提供给 ApplicationContext
构造函数的位置路径实际上是资源字符串,根据特定的上下文实现进行适当处理。例如,ClassPathXmlApplicationContext
将简单的位置路径视为类路径位置。还可以使用带有特殊前缀的位置路径(资源字符串)来强制从类路径或 URL 加载定义,而不管实际的上下文类型。
应用程序启动跟踪
ApplicationContext
管理 Spring 应用程序生命周期,并围绕组件提供丰富的编程模型。因此,复杂的应用程序可能具有同样复杂的组件图和启动阶段。
使用特定指标跟踪应用程序启动步骤有助于了解启动阶段的时间花在何处,也可以用作更好地了解整个上下文生命周期的方法。
AbstractApplicationContext
(及其子类)使用 ApplicationStartup
进行检测 ,它收集各个启动阶段的 StartupStep
数据:
- 应用程序上下文生命周期(基本包扫描、配置类管理)
- bean 生命周期(实例化、智能初始化、后置处理处理)
- 应用事件处理
下面是 AnnotationConfigApplicationContext
中的一个插装示例:
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
应用程序上下文已经通过多个步骤进行了检测。记录后,可以使用特定工具收集、显示和分析这些启动步骤。有关现有启动步骤的完整列表,您可以查看 专用附录部分
默认 ApplicationStartup
实现是无操作变体,以实现最小开销。这意味着默认情况下不会在应用程序启动期间收集任何指标。Spring Framework 附带了一个使用 Java Flight Recorder 跟踪启动步骤的实现: FlightRecorderApplicationStartup
。要使用此变体,您必须在创建 ApplicationContext
后立即将其配置。
如果开发人员提供他们自己的 AbstractApplicationContext
子类,或者如果他们希望收集更精确的数据,他们也可以使用 ApplicationStartup
基础设施 。
ApplicationStartup
仅在应用程序启动期间和核心容器中使用;这绝不是 Java 分析器或 Micrometer 等度量库的替代品。
要开始收集自定义 StartupStep
,组件可以直接从应用程序上下文中获取 ApplicationStartup
实例,使它们的组件实现 ApplicationStartupAware
,或者在任何注入点上注入 ApplicationStartup
类型。
开发人员在创建自定义启动步骤时不应使用 spring.*
命名空间。这个命名空间是为内部 Spring 使用保留的,可能会发生变化。
Web 应用的 ApplicationContext 方便实例化
可以通过使用 ContextLoader
以声明方式创建 ApplicationContext
实例 。当然,您也可以通过使用 ApplicationContext
实现以编程方式创建 ApplicationContext
实例。
可以使用 ContextLoaderListener
注册 ApplicationContext
:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
监听器检查 contextConfigLocation
参数。如果该参数不存在,则监听器将 /WEB-INF/applicationContext.xml
用作默认值。当参数确实存在时,监听器通过使用预定义的分隔符(逗号、分号和空格)来分隔 String
参数,并将这些值用作搜索应用程序上下文的位置。支持 Ant 风格的路径模式。示例是 /WEB-INF/*Context.xml
(对于名称以 Context.xml
结尾并驻留在 WEB-INF
目录中的所有文件)和 /WEB-INF/**/*Context.xml
(对于 WEB-INF
的任何子目录中的所有此类文件)。
将 Spring ApplicationContext
部署为 Java EE RAR 文件
可以将 Spring ApplicationContext
部署为 RAR 文件,将上下文及其所有必需的 bean 类和库 JAR 封装在 Java EE RAR 部署单元中。这相当于引导独立的 ApplicationContext
(仅托管在 Java EE 环境中)能够访问 Java EE 服务器设施。RAR 部署是部署无头(headless ) WAR 文件场景的更自然的替代方案——实际上,这是一个没有任何 HTTP 入口点的 WAR 文件,仅用于在 Java EE 环境中引导 Spring ApplicationContext
。
BeanFactory
BeanFactory
API 为 Spring 的 IoC 功能提供了基础。它多用于与 Spring 的其他部分以及相关的第三方框架的集成
DefaultListableBeanFactory
实现是高级 GenericApplicationContext
容器内的关键委托
BeanFactory
和相关接口(如 BeanFactoryAware
、 InitializingBean
、 DisposableBean
)是其他框架组件的重要集成点。通过不需要任何注解甚至反射,它们允许容器及其组件之间进行非常有效的交互。应用程序级 bean 可以使用相同的回调接口,但通常更喜欢声明性的依赖注入,或者通过注解或者通过编程配置。
核心 BeanFactory
API 以及 DefaultListableBeanFactory
实现不对要使用的配置格式或任何组件注解做出假设。所有这些都通过扩展(例如 XmlBeanDefinitionReader
和 AutowiredAnnotationBeanPostProcessor
)引入,并作为核心元数据表示对共享 BeanDefinition
对象进行操作。这就是使 Spring 的容器如此灵活和可扩展的本质。
BeanFactory
或者 ApplicationContext
?
除非有充分的理由,否则应该使用 ApplicationContext
,而 GenericApplicationContext
及其子类 AnnotationConfigApplicationContext
是自定义引导的通用实现。这些是 Spring 核心容器的主要入口点,用于所有常见目的: 加载配置文件、触发类路径扫描、以编程方式注册 bean 定义和带注解的类,以及(从5.0开始)注册函数 bean 定义。
由于 ApplicationContext
包含 BeanFactory
的所有功能,因此通常建议不要使用普通 BeanFactory
,除非需要完全控制 bean 处理。在 ApplicationContext
中(例如 GenericaApplicationContext
实现),通过约定(即通过 bean 名称或 bean 类型)检测几种 bean —— 特别是后置处理器),而普通的 DefaultListableBeanFactory
对任何特殊 bean 都是不可知的。
对于很多扩展的容器特性,比如注解处理和 AOP 代理,BeanPostProcessor
扩展点 是必不可少的。如果您只使用一个普通的 DefaultListableBeanFactory
,默认情况下不会检测到并激活此类后处理器。
功能 | BeanFactory |
ApplicationContext |
---|---|---|
Bean 实例化/装配 | Y | Y |
集成的生命周期管理 | N | Y |
自动 BeanPostProcessor 注册 |
N | Y |
自动 BeanFactoryPostProcessor 注册 |
N | Y |
方便的 MessageSource 访问(用于国际化) |
N | Y |
内置 ApplicationEvent 发布机制 |
N | Y |
要使用 DefaultListableBeanFactory
显式注册 bean 后处理器,您需要以编程方式调用 addBeanPostProcessor
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
要将 BeanFactoryPostProcessor
应用于普通 DefaultListableBeanFactory
,您需要调用 postProcessBeanFactory
方法
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
在这两种情况下,显式注册都不方便,这就是为什么在 Spring 支持的应用程序中,各种 ApplicationContext
比普通的 DefaultListableBeanFactory
更受欢迎的原因,特别是在典型的企业设置中,需要依赖 BeanFactoryPostProcessor
和 BeanPostProcessor
实例实现扩展容器功能时。
AnnotationConfigApplicationContext
注册了所有常见的注解后置处理器,并且可以通过配置注解(如@EnableTransactionManagement
)引入额外的处理器。在 Spring 基于注解的配置模型的抽象层上,bean 后置处理器的概念仅仅成为一个内部容器细节。