核心
1. IoC 容器
本章介绍 Spring 的控制反转 (IoC) 容器。
1.1. Spring IoC 容器和 Bean 简介
本章介绍了控制反转的 Spring Framework 实现 (IoC)原则。IoC 也称为依赖注入 (DI)。这是一个过程,通过这个过程 对象仅通过以下方式定义其依赖关系(即它们使用的其他对象) 构造函数参数、工厂方法的参数或在 object 实例。容器 然后在创建 bean 时注入这些依赖项。这个过程从根本上说是 bean 本身的逆(因此得名 Inversion of Control) 使用 direct 控制其依赖项的实例化或位置 类或机制(如服务定位器模式)的构造。
这org.springframework.beans
和org.springframework.context
套餐是基础
用于 Spring Framework 的 IoC 容器。这BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的
对象。ApplicationContext
是BeanFactory
.它补充说:
-
更轻松地与 Spring 的 AOP 功能集成
-
消息资源处理(用于国际化)
-
活动发布
-
特定于应用程序层的上下文,例如
WebApplicationContext
用于 Web 应用程序。
简而言之,BeanFactory
提供配置框架和基本功能,
和ApplicationContext
添加了更多特定于企业的功能。这ApplicationContext
是BeanFactory
并且专门使用
在本章中对 Spring 的 IoC 容器的描述中。有关使用
这BeanFactory
而不是ApplicationContext,
请参阅涵盖BeanFactory
应用程序接口.
在 Spring 中,构成应用程序主干并被管理的对象 by Spring IoC 容器称为 bean。bean 是一个对象,它是 由 Spring IoC 容器实例化、组装和管理。否则,一个 bean 只是应用程序中众多对象之一。Bean 和依赖项 其中,反映在容器使用的配置元数据中。
1.2. 容器概述
这org.springframework.context.ApplicationContext
接口表示 Spring IoC
容器,并负责实例化、配置和组装
豆。容器获取有关哪些对象的指令
通过读取配置元数据来实例化、配置和组装。这
配置元数据以 XML、Java 注释或 Java 代码表示。它让
您可以表达构成应用程序的对象和丰富的相互依赖关系
在这些对象之间。
的几个实现ApplicationContext
提供接口
与Spring。在独立应用程序中,通常会创建一个
实例ClassPathXmlApplicationContext
或FileSystemXmlApplicationContext
.
虽然 XML 一直是定义配置元数据的传统格式,但您可以
通过以下方式指示容器使用 Java 注释或代码作为元数据格式
提供少量的 XML 配置以声明方式启用对这些
其他元数据格式。
在大多数应用程序场景中,不需要显式用户代码来实例化一个或
更多 Spring IoC 容器的实例。例如,在 Web 应用程序场景中,一个
简单的八行(或大约)行样板 Web 描述符 XMLweb.xml
文件
通常就足够了(请参阅 Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用 Spring Tools for Eclipse(一种由 Eclipse 提供支持的开发
环境),您可以通过单击几下鼠标或
击 键。
下图显示了 Spring 工作原理的高级视图。您的应用程序类
与配置元数据相结合,以便在ApplicationContext
是
创建并初始化,则您拥有完全配置和可执行的系统,或者
应用。

1.2.1. 配置元数据
如上图所示,Spring IoC 容器使用 配置元数据。此配置元数据表示您作为 应用程序开发人员,告诉 Spring 容器实例化、配置和汇编 应用程序中的对象。
配置元数据传统上以简单直观的 XML 格式提供, 这是本章的大部分内容,用于传达 Spring IoC 容器。
基于 XML 的元数据并不是唯一允许的配置元数据形式。 Spring IoC 容器本身与 配置元数据实际上是写入的。如今,许多开发人员为他们的 Spring 应用程序选择基于 Java 的配置。 |
有关将其他形式的元数据与 Spring 容器一起使用的信息,请参阅:
-
基于注释的配置:引入 Spring 2.5 支持基于注释的配置元数据。
-
基于 Java 的配置:从 Spring 3.0 开始,许多功能 由 Spring JavaConfig 项目提供,成为核心 Spring Framework 的一部分。 因此,您可以使用 Java 定义应用程序类外部的 Bean,而不是 比 XML 文件。要使用这些新功能,请参阅
@Configuration
,@Bean
,@Import
, 和@DependsOn
附注。
Spring 配置至少由一个 Bean 组成,通常不止一个 Bean
容器必须管理的定义。基于 XML 的配置元数据配置了这些
beans 作为<bean/>
顶级元素<beans/>
元素。Java
配置通常使用@Bean
-annotated 方法中的@Configuration
类。
这些 Bean 定义对应于构成应用程序的实际对象。
通常,定义服务层对象、数据访问对象 (DAO)、表示
支柱等对象Action
实例、基础设施对象,例如 HibernateSessionFactories
, JMSQueues
,依此类推。通常,不配置
细粒度域对象,因为通常是负责
的 DAO 和业务逻辑来创建和加载域对象。但是,您可以使用
Spring 与 AspectJ 的集成以配置已在外部创建的对象
IoC 容器的控制。请参阅使用 AspectJ
dependency-inject 域对象。
以下示例显示了基于 XML 的配置元数据的基本结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> (1) (2)
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
1 | 这id 属性是标识单个 Bean 定义的字符串。 |
2 | 这class 属性定义 bean 的类型并使用完全限定的
类名。 |
的值id
属性是指协作对象。的 XML
此示例中未显示引用协作对象。有关详细信息,请参阅依赖项。
1.2.2. 实例化容器
一个或多个位置路径
供应给ApplicationContext
构造函数是资源字符串,允许
容器加载来自各种外部资源的配置元数据,例如
作为本地文件系统,JavaCLASSPATH
,依此类推。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
了解了 Spring 的 IoC 容器后,您可能想了解更多有关 Spring 的 |
以下示例显示了服务层对象(services.xml)
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
以下示例显示了数据访问对象daos.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的示例中,服务层由PetStoreServiceImpl
类
以及两个类型的数据访问对象JpaAccountDao
和JpaItemDao
(基于
在 JPA 对象关系映射标准上)。这property name
元素引用
javaBean 属性的名称,以及ref
元素引用另一个 bean 的名称
定义。这种联系id
和ref
元素表示
协作对象。有关配置对象依赖项的详细信息,请参阅依赖项。
编写基于 XML 的配置元数据
让 Bean 定义跨越多个 XML 文件会很有用。通常,每个人 XML 配置文件表示架构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些构造函数加载 Bean 定义
XML 片段。此构造函数采用多个Resource
位置,如上一节所示。或者,使用一个或多个
出现<import/>
元素从另一个文件加载 Bean 定义,或者
文件。以下示例显示了如何执行此作:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的示例中,外部 Bean 定义是从三个文件加载的:services.xml
,messageSource.xml
和themeSource.xml
.所有位置路径都是
相对于执行导入的定义文件,因此services.xml
必须位于
与导入的文件相同的目录或类路径位置,而messageSource.xml
和themeSource.xml
必须位于resources
位置低于
导入文件的位置。如您所见,前导斜杠被忽略。但是,鉴于
这些路径是相对的,最好不要使用斜杠。这
要导入的文件的内容,包括顶层<beans/>
元素,必须
是有效的 XML bean 定义,根据 Spring Schema。
可以使用
相对“../“ 路径。这样做会创建对当前文件之外的文件的依赖关系
应用。特别是,不建议将此引用用于 您始终可以使用完全限定的资源位置而不是相对路径:对于
例 |
命名空间本身提供了导入指令功能。进一步
除了普通 bean 定义之外的配置功能,还可以在选择中使用
Spring 提供的 XML 命名空间——例如,context
和util
命名空间。
Groovy Bean 定义 DSL
作为外部化配置元数据的进一步示例,bean 定义还可以 在 Spring 的 Groovy Bean Definition DSL 中表示,如 Grails 框架中所知。 通常,此类配置位于“.groovy”文件中,其结构如 以下示例:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这种配置样式在很大程度上等同于 XML bean 定义,甚至支持 Spring 的 XML 配置命名空间。它还允许导入 XMLbean 定义文件importBeans
命令。
1.2.3. 使用容器
这ApplicationContext
是能够维护的高级工厂的接口不同 bean 及其依赖项的注册表。通过使用T getBean(String name, Class<T> requiredType)
,您可以检索 Bean 的实例。
这ApplicationContext
允许您读取 Bean 定义并访问它们,如下所示示例所示:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
import org.springframework.beans.factory.getBean
// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// use configured instance
var userList = service.getUsernameList()
使用 Groovy 配置,引导看起来非常相似。它有一个不同的上下文实现类,它是 Groovy 感知的(但也理解 XML bean 定义)。以下示例显示了 Groovy 配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")
最灵活的变体是GenericApplicationContext
与阅读器结合使用
委托 — 例如,使用XmlBeanDefinitionReader
对于 XML 文件,如下所示
示例显示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
val context = GenericApplicationContext()
XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml")
context.refresh()
您还可以使用GroovyBeanDefinitionReader
对于 Groovy 文件,如下所示
示例显示:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
val context = GenericApplicationContext()
GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy")
context.refresh()
您可以在同一个ApplicationContext
,
从不同的配置源读取 Bean 定义。
然后,您可以使用getBean
检索 bean 的实例。这ApplicationContext
interface 有一些其他方法用于检索 bean,但理想情况下,您的应用程序
代码永远不应该使用它们。事实上,您的应用程序代码不应调用getBean()
方法,因此根本不依赖于 Spring API。例如
Spring 与 Web 框架的集成为各种 Web 提供了依赖注入
框架组件,例如控制器和 JSF 管理的 bean,允许您声明
通过元数据(例如自动配线注释)对特定 Bean 的依赖关系。
1.3. Bean 概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用
提供给容器的配置元数据(例如,以 XML 的形式<bean/>
定义)。
在容器本身中,这些 bean 定义表示为BeanDefinition
对象,其中包含(除其他信息外)以下元数据:
-
包限定的类名:通常是 bean 正在定义。
-
Bean 行为配置元素,这些元素说明 Bean 在 容器(范围、生命周期回调等)。
-
对 Bean 执行其工作所需的其他 Bean 的引用。这些 引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置设置,例如大小 池的限制或要在管理 连接池。
此元数据转换为构成每个 Bean 定义的一组属性。 下表描述了这些属性:
属性 | 解释在... |
---|---|
类 |
|
名称 |
|
范围 |
|
构造函数参数 |
|
性能 |
|
自动接线模式 |
|
延迟初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了包含有关如何创建特定bean 的信息的 bean 定义之外,ApplicationContext
实现还允许注册现有的在容器外部(由用户)创建的对象。这是通过访问ApplicationContext 的BeanFactory
通过getBeanFactory()
方法,返回 这DefaultListableBeanFactory
实现。DefaultListableBeanFactory
支持 通过registerSingleton(..)
和registerBeanDefinition(..)
方法。 但是,典型的应用程序仅使用通过常规bean 定义元数据定义的 bean。
Bean 元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配期间正确推理它们和其他自省步骤。虽然在某种程度上支持覆盖现有元数据和现有单例实例,但在运行时(与对工厂的实时访问同时)不受官方支持,并且可能导致并发访问异常、Bean 容器中的状态不一致或两者兼而有之。 |
1.3.1. 命名 Bean
每个 bean 都有一个或多个标识符。这些标识符在 托管 Bean 的容器。一个 bean 通常只有一个标识符。但是,如果它 需要多个,多余的可以被视为别名。
在基于 XML 的配置元数据中,您可以使用id
属性,则name
属性,或
两者都指定 bean 标识符。这id
属性允许您指定
恰好一个 ID。通常,这些名称是字母数字(“myBean”,
'someService' 等),但它们也可以包含特殊字符。如果你想
为 bean 引入其他别名,您也可以在name
属性,用逗号 (,
)、分号 () 或空格。作为
历史注释,在 Spring 3.1 之前的版本中,;
id
属性是
定义为xsd:ID
type,它限制了可能的字符。从 3.1 开始,
它被定义为xsd:string
类型。请注意,beanid
独特性依然
由容器强制执行,但不再由 XML 解析器强制执行。
您无需提供name
或id
对于一颗豆子。如果您不提供name
或id
显式地,容器为该 Bean 生成一个唯一的名称。然而
如果您想按名称引用该 bean,请使用ref
元素或
服务定位器样式查找,必须提供名称。
不提供名称的动机与使用 inner 有关
豆和自动布线合作者。
通过在类路径中进行组件扫描,Spring 会为未命名的组件生成 bean 名称,遵循前面描述的规则:本质上,采用简单的类名并将其初始字符转换为小写。然而,在(不寻常的)特殊当有多个字符并且第一个和第二个字符都是大写时,原始大小写将被保留。这些规则与定义为java.beans.Introspector.decapitalize (Spring 在这里使用)。 |
在 Bean 定义之外对 Bean 进行别名化
在 Bean 定义本身中,您可以使用
最多一个名称的组合,由id
属性和任意数量的其他
名称中的name
属性。这些名称可以是同一 Bean 的等效别名
并且在某些情况下很有用,例如让应用程序中的每个组件
使用特定于该组件的 Bean 名称来引用公共依赖关系
本身。
指定实际定义 bean 的所有别名并不总是足够的,
然而。有时需要为已定义的 bean 引入别名
别处。这在配置拆分的大型系统中很常见
在每个子系统中,每个子系统都有自己的一组对象定义。
在基于 XML 的配置元数据中,您可以使用<alias/>
要完成的元素
这。以下示例显示了如何执行此作:
<alias name="fromName" alias="toName"/>
在这种情况下,名为fromName
也可能,
使用此别名定义后,称为toName
.
例如,子系统 A 的配置元数据可以通过
名称subsystemA-dataSource
.子系统 B 的配置元数据可以引用
一个 DataSource,名称为subsystemB-dataSource
.在编写主应用程序时
使用这两个子系统时,主应用程序通过
名称myApp-dataSource
.要使所有三个名称都引用同一个对象,您可以
将以下别名定义添加到配置元数据:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在,每个组件和主应用程序都可以通过名称引用 dataSource 这是唯一的,并保证不会与任何其他定义发生冲突(实际上 创建命名空间),但它们引用的是同一个 bean。
1.3.2. 实例化 Bean
Bean 定义本质上是创建一个或多个对象的配方。这 容器在询问时查看命名 bean 的配方并使用配置 由该 bean 定义封装的元数据,以创建(或获取)实际对象。
如果使用基于 XML 的配置元数据,请指定对象的类型(或类)
即在class
属性的<bean/>
元素。这class
属性(在内部是一个Class
属性BeanDefinition
instance)通常是强制性的。(有关异常,请参阅使用实例工厂方法实例化和 Bean 定义继承。
您可以使用Class
属性,采用以下两种方式之一:
-
通常,要指定在容器 本身通过反射性地调用其构造函数来直接创建 bean,在某种程度上 等效于 Java 代码,其中
new
算子。 -
要指定包含
static
工厂方法,即 调用以创建对象,在不太常见的情况下,容器调用static
factory 方法来创建 bean。返回的对象类型 从static
factory 方法可以是同一类或另一个类 完全上课。
使用构造函数实例化
当您通过构造函数方法创建 bean 时,所有普通类都可以通过 和 与 Spring 兼容。也就是说,正在开发的类不需要实现 任何特定接口或以特定方式编码。只需指定 bean 班级应该足够了。但是,根据您为该特定 IoC 使用的类型 bean,您可能需要一个默认(空)构造函数。
Spring IoC 容器几乎可以管理您希望它管理的任何类。是的 不仅限于管理真正的 JavaBeans。大多数 Spring 用户更喜欢实际的 JavaBeans 仅建模默认(无参数)构造函数和适当的 setter 和 getter 在容器中的属性之后。还可以有更异国情调的非豆式 类。例如,如果您需要使用旧连接池 绝对不符合 JavaBean 规范的,Spring 可以将其管理为 井。
使用基于 XML 的配置元数据,您可以按如下方式指定 bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数的机制的详细信息(如果需要) 以及在构建对象后设置对象实例属性,请参阅注入依赖项。
使用静态工厂方法实例化
在定义使用静态工厂方法创建的 bean 时,请使用class
属性来指定包含static
factory 方法和属性
叫factory-method
以指定工厂方法本身的名称。你应该是
能够调用此方法(使用可选参数,如下所述)并返回
对象,随后将其视为通过构造函数创建的。
这种 bean 定义的一个用法是调用static
工厂。
以下 bean 定义指定将通过调用
工厂方法。定义未指定返回对象的类型(类),
而是包含工厂方法的类。在此示例中,createInstance()
方法必须是static
方法。以下示例演示如何
指定工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
以下示例显示了将与前面的 bean 定义一起使用的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
class ClientService private constructor() {
companion object {
private val clientService = ClientService()
@JvmStatic
fun createInstance() = clientService
}
}
有关向工厂方法提供(可选)参数的机制的详细信息 以及设置对象从工厂返回后的对象实例属性, 请参阅依赖项和配置的详细信息。
使用实例工厂方法实例化
与通过静态factory 方法实例化类似,使用实例工厂方法实例化会调用非静态容器中现有 bean 的方法来创建新 bean。要使用此机制,请将class
属性为空,并且在factory-bean
属性 指定当前(或父级或祖先)容器中包含要调用以创建对象的实例方法。设置factory 方法本身使用factory-method
属性。 以下示例显示了如何配置这样的 bean:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
}
一个工厂类还可以保存多个工厂方法,如以下示例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
private val accountService = AccountServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
fun createAccountServiceInstance(): AccountService {
return accountService
}
}
这种方法表明工厂 Bean 本身可以通过依赖注入 (DI) 进行管理和配置。请参阅依赖关系和配置 详细。
在 Spring 文档中,“工厂 bean”是指在Spring 容器中配置的 bean,它通过实例或静态工厂方法创建对象。相比之下,FactoryBean (注意大写)指的是特定于 Spring 的FactoryBean 实现类。 |
确定 Bean 的运行时类型
确定特定 Bean 的运行时类型并非易事。中的指定类Bean 元数据定义只是一个初始类引用,可能会将与声明的工厂方法组合在一起,或者作为FactoryBean
类,这可能会导致不同的运行时类型,或者在实例级factory 方法(通过指定的factory-bean
name 代替)。此外,AOP 代理可能会使用目标 bean 的实际类型(仅其实现的接口)的有限公开。
了解特定 Bean 的实际运行时类型的推荐方法是 一个BeanFactory.getType
调用指定的 bean 名称。这会考虑上述所有cases 并返回对象类型BeanFactory.getBean
call 是将返回相同的 bean 名称。
1.4. 依赖项
典型的企业应用程序不由单个对象(或Spring 术语中的 bean)组成。即使是最简单的应用程序也有几个对象,这些对象协同工作以呈现最终用户认为的连贯应用程序。下一节将介绍如何您从定义许多独立的 bean 定义到完全实现的对象协作以实现目标的应用程序。
1.4.1. 依赖注入
依赖注入 (DI) 是一个过程,对象仅通过构造函数参数定义其依赖关系(即它们使用的其他对象),工厂方法的参数,或在对象实例上设置的属性它是从工厂方法构造或返回的。然后,容器在创建 bean 时注入这些依赖关系。这个过程从根本上说是 bean 本身的反向(因此名称,控制反转)控制实例化或通过使用类的直接构造自行定位其依赖项或服务定位器模式。
使用 DI 原理,代码更简洁,当对象被提供它们的依赖项。该对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易进行测试,特别是当依赖项依赖于接口或抽象基类时,这允许在单元测试中使用存根或模拟实现。
DI 存在两个主要变体:基于构造函数 依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有多个参数来完成的,每个参数代表一个依赖项。调用static
工厂方法使用特定的参数来构造 bean 几乎是等价的,并且这个讨论将参数视为构造函数和static
工厂方法类似。 这 以下示例显示了一个只能使用构造函数注入依赖项的类 注射:
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...
}
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
// business logic that actually uses the injected MovieFinder is omitted...
}
请注意,这个类没有什么特别之处。它是一个 POJO,它不依赖于容器特定的接口、基类或注解。
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型进行。如果没有Bean 定义的构造函数参数中存在潜在的歧义,则在 Bean 定义中定义构造函数参数的顺序是当 Bean 是被实例化时,这些参数将提供给适当的构造函数。考虑以下类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
package x.y
class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
假设ThingTwo
和ThingThree
类不通过继承相关,没有
存在潜在的歧义。因此,以下配置工作正常,而您不会
需要在<constructor-arg/>
元素。
<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 时,类型是已知的,并且可以发生匹配(就像
case 与前面的示例)。当使用简单类型时,例如<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;
}
}
package examples
class ExampleBean(
private val years: Int, // Number of years to calculate the Ultimate Answer
private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)
在前面的方案中,容器可以将类型匹配与简单类型一起使用,如果
您可以使用type
属性
如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
您可以使用index
属性显式指定构造函数参数的索引,
如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的歧义外,还指定一个索引 解决构造函数具有两个相同类型的参数的歧义。
该索引从 0 开始。 |
还可以使用构造函数参数名称来消除值歧义,如下所示 示例显示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,要使此功能开箱即用,您的代码必须使用 debug 标志,以便 Spring 可以从构造函数中查找参数名称。 如果您不能或不想使用调试标志编译代码,则可以使用@ConstructorProperties JDK 注释来显式命名构造函数参数。示例类将 然后必须看如下:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
package examples
class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
基于 Setter 的依赖注入
基于 setter 的 DI 是通过容器调用 setter 方法来完成的
调用无参数构造函数或无参数构造函数后的 beanstatic
factory 方法设置为
实例化你的 bean。
以下示例显示了一个只能使用 pur 塞特注射。此类是传统的 Java。它是一个没有依赖关系的 POJO 在容器特定接口、基类或注释上。
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...
}
class SimpleMovieLister {
// a late-initialized property so that the Spring container can inject a MovieFinder
lateinit var movieFinder: MovieFinder
// business logic that actually uses the injected MovieFinder is omitted...
}
这ApplicationContext
支持基于构造函数和基于 setter 的 DI 用于 Bean
管理。在某些依赖项已经存在后,它还支持基于 setter 的 DI
通过构造函数方法注入。您可以以
一个BeanDefinition
,您可以将其与PropertyEditor
instances 设置为
将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户无法工作
直接(即以编程方式)而不是 XML 使用这些类bean
定义、带注释的组件(即用@Component
,@Controller
,依此类推),或@Bean
基于 Java 的方法@Configuration
类。
然后,这些源在内部转换为BeanDefinition
并且习惯于
加载整个 Spring IoC 容器实例。
依赖关系解析过程
容器执行 bean 依赖关系解析,如下所示:
-
这
ApplicationContext
使用配置元数据创建和初始化 描述了所有的豆子。配置元数据可以通过 XML、Java 代码或 附注。 -
对于每个 bean,其依赖关系以属性、构造函数的形式表示 参数,或静态工厂方法的参数(如果您使用它而不是 法式构造函数)。当 bean 是 实际创建。
-
每个属性或构造函数参数都是要设置的值的实际定义,或者 对容器中另一个 bean 的引用。
-
作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,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,Spring IoC 容器在调用 bean A 上的 setter 方法。换句话说,bean 被实例化(如果它不是 预实例化的单例),其依赖项被设置,相关生命周期 方法(例如配置的 init 方法或 InitializingBean 回调方法) 被调用。
依赖注入示例
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。一个小 Spring XML 配置文件的一部分指定了一些 bean 定义,如下所示:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例显示了相应的ExampleBean
类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
class ExampleBean {
lateinit var beanOne: AnotherBean
lateinit var beanTwo: YetAnotherBean
var i: Int = 0
}
在前面的示例中,声明 setter 与指定的属性匹配 在 XML 文件中。以下示例使用基于构造函数的 DI:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例显示了相应的ExampleBean
类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
class ExampleBean(
private val beanOne: AnotherBean,
private val beanTwo: YetAnotherBean,
private val i: Int)
bean 定义中指定的构造函数参数用作
的构造函数ExampleBean
.
现在考虑这个例子的一个变体,其中 Spring 不是使用构造函数,而是
被告知要调用static
factory 方法返回对象的实例:
<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;
}
}
class ExampleBean private constructor() {
companion object {
// 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.
@JvmStatic
fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
val eb = ExampleBean (...)
// some other operations...
return eb
}
}
}
参数static
工厂方法由<constructor-arg/>
元素
与实际使用构造函数完全相同。类的类型是
由工厂方法返回的类不必与
包含static
factory 方法(尽管在本例中是)。实例
(非静态)工厂方法可以以基本相同的方式使用(旁白
从使用factory-bean
属性而不是class
属性),所以我们
这里不要讨论这些细节。
1.4.2. 依赖关系和配置的详细信息
如上一节所述,您可以定义 bean
属性和构造函数参数作为对其他托管 Bean(协作者)的引用
或作为内联定义的值。Spring 基于 XML 的配置元数据支持
subelement 类型<property/>
和<constructor-arg/>
元素
目的。
直值(基元、字符串等)
这value
属性的<property/>
元素指定属性或构造函数
参数作为人类可读的字符串表示形式。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>
前面的 XML 更为简洁。但是,拼写错误是在运行时发现的,而不是 设计时,除非您使用 IDE(例如 IntelliJ IDEA 或 Spring Tools for Eclipse) 支持在创建 Bean 定义时自动完成属性。这样的IDE 强烈建议提供帮助。
您还可以配置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 容器将<value/>
元素转换为java.util.Properties
使用 JavaBeansPropertyEditor
机制。这
是一个不错的捷径,也是 Spring 团队确实喜欢使用
嵌套的<value/>
元素在value
属性样式。
这idref
元素
这idref
元素只是一种传递id
(字符串值 - 不是
a 引用) 到<constructor-arg/>
或<property/>
元素。以下示例演示如何使用它:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面的 bean 定义片段完全等同于(在运行时)与 以下代码段:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式比第二种形式更可取,因为使用idref
标记让
容器在部署时验证引用的命名 bean 实际上
存在。在第二个变体中,不对传递的值执行验证
到targetName
属性的client
豆。拼写错误仅被发现(大多数
可能致命的结果)当client
bean 实际上是实例化的。如果client
bean 是一个原型 bean,这个拼写错误和由此产生的异常
可能只有在部署容器很久之后才会被发现。
这local 属性idref 4.0 Bean 中不再支持
XSD,因为它不提供比常规bean 更多参考。改变
您现有的idref local 引用idref bean 升级到 4.0 架构时。 |
一个常见的地方(至少在 Spring 2.0 之前的版本中),其中<idref/>
元素
带来的价值在于 AOP 拦截器的配置ProxyFactoryBean
bean 定义。用<idref/>
元素,当您指定
拦截器名称可防止您拼写错误拦截器 ID。
对其他 Bean(协作者)的引用
这ref
元素是<constructor-arg/>
或<property/>
definition 元素。在这里,您将 bean 的指定属性的值设置为
引用由容器管理的另一个 Bean(协作者)。引用的 bean
是要设置其属性的 bean 的依赖项,并按需初始化
在设置属性之前根据需要。(如果协作者是单例 bean,则可能
已经被容器初始化。所有引用最终都是对
另一个对象。范围和验证取决于您是指定 ID 还是名称
其他对象通过bean
或parent
属性。
通过bean
属性的<ref/>
标签是最
通用形式,并允许在同一容器中创建对任何 bean 的引用,或者
父容器,无论它是否在同一个 XML 文件中。的值bean
属性可能与id
属性或相同
作为name
属性。以下示例
展示了如何使用ref
元素:
<ref bean="someBean"/>
通过parent
属性创建对 bean 的引用
即在当前容器的父容器中。的值parent
属性可以与id
属性或
值name
属性。目标 Bean 必须位于
当前容器的父容器。您应该主要使用此 bean 引用变体
当您有一个容器层次结构并且想要将现有 Bean 包装在父 Bean 中时
容器,其代理与父 Bean 具有相同的名称。以下一对
listings 展示了如何使用parent
属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required here -->
</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>
这local 属性ref 4.0 Bean 中不再支持
XSD,因为它不提供比常规bean 更多参考。改变
您现有的ref local 引用ref bean 升级到 4.0 架构时。 |
内豆
一个<bean/>
元素<property/>
或<constructor-arg/>
元素定义了一个
inner 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
标记 on
creation,因为内部 bean 始终是匿名的,并且始终与外部 bean 一起创建
豆。无法独立访问内部 bean 或将它们注入
协作 Bean 以外的 Bean。
作为一种极端情况,可以从自定义作用域接收销毁回调——例如,对于单例 Bean 中包含的请求范围的内部 Bean。创作 的内部 bean 实例绑定到其包含的 bean,但销毁回调允许它 参与请求范围的生命周期。这不是一种常见的情况。内豆 通常只是共享其包含的 bean 的作用域。
收集
这<list/>
,<set/>
,<map/>
和<props/>
元素设置属性
和 Java 的论点Collection
类型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">[email protected]</prop>
<prop key="support">[email protected]</prop>
<prop key="development">[email protected]</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>
映射键或值的值,或设置值,也可以是 以下元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring 容器还支持合并集合。应用程序
developer 可以定义父级<list/>
,<map/>
,<set/>
或<props/>
元素
并生孩子<list/>
,<map/>
,<set/>
或<props/>
元素继承和
覆盖父集合中的值。也就是说,子集合的值为
将父集合和子集合的元素与子集合的
集合元素覆盖父集合中指定的值。
关于合并的这一节讨论父子 Bean 机制。读者不熟悉 对于父 Bean 和子 Bean 定义,在继续之前,可能希望阅读相关部分。
以下示例演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</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">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>
请注意merge=true
属性<props/>
元素的adminEmails
属性的child
bean 定义。当child
bean 已解析
并由容器实例化,则生成的实例具有adminEmails
Properties
包含合并子项的adminEmails
集合与父级的adminEmails
收集。以下列表
显示结果:
孩子Properties
集合的值集从
父母<props/>
,而孩子的值support
value 覆盖
父集合。
此合并行为类似于<list/>
,<map/>
和<set/>
集合类型。在具体情况下<list/>
元素,语义
与List
集合类型(即ordered
值集合)被维护。父级的值位于所有子列表的
值。在Map
,Set
和Properties
集合类型,无排序
存在。因此,对于基础的集合类型,没有排序语义有效
关联的Map
,Set
和Properties
容器的实现类型
内部使用。
集合合并的局限性
您不能合并不同的集合类型(例如Map
和List
).如果你
尝试这样做,适当的Exception
被抛出。这merge
属性必须是
在较低的继承子定义上指定。指定merge
属性
父集合定义是冗余的,不会导致所需的合并。
强类型集合
由于 Java 对泛型类型的支持,您可以使用强类型集合。
也就是说,可以声明一个Collection
类型,使其只能包含
(例如)String
元素。如果您使用 Spring 依赖注入一个
强类型Collection
变成 bean,你可以利用 Spring 的
类型转换支持,以便强类型的元素Collection
实例在添加到Collection
.
以下 Java 类和 bean 定义显示了如何执行此作:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
class SomeClass {
lateinit var accounts: Map<String, Float>
}
<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>
当accounts
属性的something
豆子是注射用的,仿制药
有关强类型的元素类型的信息Map<String, Float>
是
可通过反思获得。因此,Spring 的类型转换基础设施识别
各种值元素作为类型Float
,字符串值 (9.99
,2.75
和3.99
) 转换为实际的Float
类型。
空字符串值和空字符串值
Spring 将属性等的空参数视为空Strings
.这
以下基于 XML 的配置元数据代码段将email
属性到空的String
值(“”)。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
exampleBean.email = ""
这<null/>
元素句柄null
值。以下列表显示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等效于以下 Java 代码:
exampleBean.setEmail(null);
exampleBean.email = null
带有 p 命名空间的 XML 快捷方式
p-namespace 允许您使用bean
元素的属性(而不是嵌套的<property/>
元素)来描述您的属性值、协作 bean 或两者。
Spring 支持带有命名空间的可扩展配置格式,
这些基于 XML 模式定义。这beans
配置格式在
本章在 XML Schema 文档中定义。但是,没有定义 p 命名空间
在 XSD 文件中,并且仅存在于 Spring 的核心中。
以下示例显示了两个 XML 片段(第一个使用 标准 XML 格式,第二个使用 p-namespace),解析为相同的结果:
<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 name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</beans>
该示例显示了 p 命名空间中名为email
在 bean 定义中。
这告诉 Spring 包含一个属性声明。如前所述,
p-namespace 没有模式定义,因此您可以设置属性的名称
到属性名称。
下一个示例包括另外两个 bean 定义,它们都引用了 另一个 bean:
<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 name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<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>
此示例不仅包括使用 p-namespace的属性值,还使用特殊格式来声明属性引用。而第一个 bean定义使用<property name="spouse" ref="jane"/>
从 bean 创建引用john
到豆jane
,第二个 bean 定义使用p:spouse-ref="jane"
作为
属性来做完全相同的事情。在这种情况下,spouse
是属性名称,
而-ref
part 表示这不是一个直接值,而是一个
引用另一个 bean。
p 命名空间不如标准 XML 格式灵活。例如,格式
用于声明属性引用与结尾为Ref ,而
标准 XML 格式则不然。我们建议您谨慎选择方法,并
将此信息传达给您的团队成员,以避免生成使用
同时进行三种方法。 |
带有 c 命名空间的 XML 快捷方式
类似于带有 p 命名空间的 XML 快捷方式,即 Spring 中引入的 c 命名空间
3.1,允许内联属性来配置构造函数参数,而不是
然后嵌套constructor-arg
元素。
以下示例使用c:
命名空间来执行与基于构造函数的依赖注入相同的作:
<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"/>
<!-- 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="[email protected]"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
</beans>
这c:
命名空间使用与p:
一(尾随-ref
为
bean 引用)用于按名称设置构造函数参数。同样地
它需要在 XML 文件中声明,即使它未在 XSD 模式中定义
(它存在于 Spring 核心中)。
对于构造函数参数名称不可用的极少数情况(通常如果 字节码是在没有调试信息的情况下编译的),您可以使用回退到 参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
由于 XML 语法,索引表示法需要存在前导 ,
因为 XML 属性名称不能以数字开头(即使某些 IDE 允许)。
相应的索引表示法也可用于_ <constructor-arg> 元素,但
不常用,因为简单的声明顺序通常就足够了。 |
在实践中,构造函数解析机制在匹配方面非常高效 参数,因此除非您真的需要,否则我们建议使用名称表示法 在整个配置中。
复合属性名称
设置 bean 属性时,可以使用复合或嵌套属性名称,只要
除最终属性名称外,路径的所有组件都不是null
.考虑一下
以下 bean 定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
这something
bean 有一个fred
属性,其中具有bob
属性,其中具有sammy
属性,并且最终sammy
属性设置为123
.为了
这要工作,fred
属性something
和bob
属性fred
莫
是null
在 bean 构建之后。否则,一个NullPointerException
被抛出。
1.4.3. 使用depends-on
如果一个 Bean 是另一个 Bean 的依赖项,这通常意味着一个 Bean 被设置为
另一个人的财产。通常,您可以使用<ref/>
元素在基于 XML 的配置元数据中。但是,有时
豆子不太直接。例如,当类中的静态初始值设定项需要
触发,例如用于数据库驱动程序注册。这depends-on
属性可以
使用此元素显式强制在 bean 之前初始化一个或多个 bean
已初始化。以下示例使用depends-on
属性来表达
对单个 bean 的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表达对多个 Bean 的依赖关系,请提供 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" />
这depends-on 属性可以指定初始化时依赖项和,
在仅单例 Bean 的情况下,对应的
销毁时间依赖。定义depends-on 关系 在给定的 bean 本身被销毁之前,首先销毁给定的 bean。 因此depends-on 还可以控制关机顺序。 |
1.4.4. 延迟初始化的 Bean
默认情况下,ApplicationContext
实现在初始化过程中急切地创建和配置所有单例 Bean 过程。 通常,这种预实例化是可取的,因为配置或周围环境会立即发现错误,而不是数小时甚至几天后。当这种行为不可取时,您可以阻止通过将 Bean 定义标记为延迟初始化来预实例化单例 Bean。延迟初始化的 Bean 告诉 IoC 容器创建一个 Bean实例,而不是在启动时。
在 XML 中,此行为由lazy-init
属性<bean/>
元素,如以下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当上述配置被ApplicationContext
这lazy
豆
当ApplicationContext
开始
而not.lazy
bean 被急切地预实例化。
但是,当延迟初始化的 Bean 是单例 Bean 的依赖项时,即
not lazy-initialized,则ApplicationContext
在
startup,因为它必须满足单例的依赖关系。延迟初始化的 bean
被注入到其他未延迟初始化的单例 Bean 中。
您还可以使用default-lazy-init
属性<beans/>
元素,如以下示例所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 自动布线协作者
Spring 容器可以自动连接协作 Bean 之间的关系。您可以
让 Spring 通过以下方式自动解析您的 bean 的协作者(其他 bean)。
检查ApplicationContext
.自动布线具有以下内容
优势:
-
自动布线可以显着减少指定属性或构造函数的需要 参数。(其他机制,例如本章其他地方讨论的 bean 模板也很有价值 在这方面。
-
自动布线可以随着对象的发展而更新配置。例如,如果您需要 要向类添加依赖项,可以自动满足该依赖项,而无需 您需要修改配置。因此,自动接线可能特别有用 在开发过程中,不会否定切换到显式布线的选项 代码库变得更加稳定。
使用基于 XML 的配置元数据(请参阅依赖项注入)时,您可以
可以使用autowire
属性的<bean/>
元素。自动布线功能有四种模式。指定自动布线
每个 bean,因此可以选择要自动连接哪些。下表描述了
四种自动接线模式:
模式 | 解释 |
---|---|
|
(默认)没有自动接线。Bean 引用必须由 |
|
按属性名称自动布线。Spring 寻找与
需要自动连接的属性。例如,如果 Bean 定义设置为
autowire by name,它包含一个 |
|
如果该属性类型的 bean 恰好存在于
容器。如果存在多个异常,则会抛出致命异常,这表示
您不得使用 |
|
类似于 |
跟byType
或constructor
autowiring 模式下,您可以连接阵列和
类型化集合。在这种情况下,容器内的所有自动连接候选者
匹配预期类型以满足依赖关系。您可以自动布线
强类型Map
如果预期的键类型为String
.自动接线Map
实例的值由与预期类型匹配的所有 Bean 实例组成,并且Map
实例的键包含相应的 bean 名称。
自动接线的局限性和缺点
当自动布线在整个项目中一致使用时,效果最佳。如果自动接线是 通常不使用,仅使用它来连接一个或 两个 bean 定义。
考虑自动布线的局限性和缺点:
-
显式依赖项
property
和constructor-arg
设置始终覆盖 自动接线。您不能自动连接简单的属性,例如基元、Strings
和Classes
(以及此类简单属性的数组)。此限制是 设计。 -
自动布线不如显式布线精确。虽然,如上表所述, Spring 小心翼翼地避免猜测,以防出现可能出现意外的歧义 结果。Spring 托管对象之间的关系不再 明确记录。
-
可能无法从以下位置生成文档的工具提供接线信息 弹簧容器。
-
容器中的多个 bean 定义可能与 setter 方法或构造函数参数。对于数组、集合或
Map
实例,这不一定是问题。但是,对于依赖项 期望单个值,则不会任意解决这种歧义。如果没有唯一的 bean definition 可用,则引发异常。
在后一种情况下,您有多种选择:
从自动布线中排除 Bean
在每个 Bean 的基础上,您可以从自动布线中排除 Bean。在 Spring 的 XML 格式中,将
这autowire-candidate
属性的<bean/>
元素设置为false
.容器
使该特定 Bean 定义对自动布线基础结构不可用
(包括注释样式配置,例如@Autowired
).
这autowire-candidate 属性设计为仅影响基于类型的自动布线。
它不会影响按名称的显式引用,即使
指定的 bean 未标记为自动配线候选。因此,自动接线
但是,如果名称匹配,则按名称会注入 bean。 |
您还可以根据与 Bean 名称的模式匹配来限制自动连线候选者。这
顶层<beans/>
元素接受其default-autowire-candidates
属性。例如,要限制自动配线候选状态
到名称以Repository
,提供*Repository
.自
提供多个模式,在逗号分隔的列表中定义它们。显式值true
或false
对于 bean 定义的autowire-candidate
属性始终采用
优先。对于此类 bean,模式匹配规则不适用。
这些技术对于您永远不想注入到其他 Bean 中的 Bean 很有用 通过自动布线的豆子。这并不意味着排除的 Bean 本身不能由 使用自动布线。相反,Bean 本身不是自动连接其他 Bean 的候选者。
1.4.6. 方法注入
在大多数应用场景中,容器中的大多数 bean 都是单例。当单例 Bean 需要 与另一个单例 Bean 协作,或者需要协作的非单例 Bean 对于另一个非单例 Bean,您通常通过定义一个依赖关系来处理依赖关系 bean 作为另一个的属性。当 Bean 生命周期 不同。假设单例 Bean A 需要使用非单例(原型)Bean B, 也许在 A 上的每个方法调用时。容器仅创建单例 Bean A 一次,因此只有一次设置属性的机会。容器不能 每次需要 bean B 时,都会为 bean A 提供一个新的 bean B 实例。
一个解决方案是放弃一些控制倒置。你可以制作
bean A 通过实现ApplicationContextAware
接口
和制作一个getBean("B")
调用容器要求 (a
通常为 new) 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;
}
}
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple
// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
class CommandManager : ApplicationContextAware {
private lateinit var applicationContext: ApplicationContext
fun process(commandState: Map<*, *>): Any {
// grab a new instance of the appropriate Command
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.state = commandState
return command.execute()
}
// notice the Spring API dependency!
protected fun createCommand() =
applicationContext.getBean("command", Command::class.java)
override fun setApplicationContext(applicationContext: ApplicationContext) {
this.applicationContext = applicationContext
}
}
前面的命令是不可取的,因为业务代码知道并耦合到 Spring 框架。方法注入,Spring IoC 的一个高级功能 容器,可让您干净地处理此用例。
查找方法注入
查找方法注入是容器覆盖容器管理的 bean 上的方法的能力,并在 容器。 查找通常涉及原型 bean,如上一节中描述的场景中。Spring 框架通过使用 CGLIB 库中的字节码生成来实现此方法注入,以动态生成覆盖该方法的子类。
|
在CommandManager
类,则
Spring 容器动态覆盖createCommand()
方法。这CommandManager
class 没有任何 Spring 依赖项,因为
重新设计的示例显示:
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();
}
package fiona.apple
// no more Spring imports!
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.state = commandState
return command.execute()
}
// okay... but where is the implementation of this method?
protected abstract fun createCommand(): Command
}
在包含要注入的方法的客户端类中(CommandManager
在这个
case),要注入的方法需要以下形式的签名:
<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
调用自己的createCommand()
方法
每当它需要一个新的实例时myCommand
豆。部署时必须小心
这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();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup("myCommand")
protected abstract fun createCommand(): Command
}
或者,更惯用地,您可以依赖目标 bean 针对 查找方法的声明返回类型:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup
protected abstract fun createCommand(): Command
}
请注意,您通常应该使用具体的 存根实现,以便它们与 Spring 的组件兼容 扫描默认忽略抽象类的规则。此限制不会 适用于显式注册或显式导入的 Bean 类。
访问不同作用域的目标 Bean 的另一种方法是 您可能还会发现 |
任意方法替换
与查找方法注入相比,方法注入的一种不太有用的形式是能够 将托管 Bean 中的任意方法替换为另一个方法实现。你 可以安全地跳过本节的其余部分,直到您真正需要此功能。
对于基于 XML 的配置元数据,您可以使用replaced-method
元素设置为
将现有方法实现替换为另一个已部署 bean 的方法实现。考虑
以下类,它有一个名为computeValue
我们想要覆盖的:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
class MyValueCalculator {
fun computeValue(input: String): String {
// some real code...
}
// some other methods...
}
实现org.springframework.beans.factory.support.MethodReplacer
interface 提供了新的方法定义,如以下示例所示:
/**
* 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 ...;
}
}
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
class ReplacementComputeValue : MethodReplacer {
override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
// get the input value, work with it, and return a computed result
val input = args[0] as String;
...
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"/>
您可以使用一个或多个<arg-type/>
元素中的<replaced-method/>
元素来指示被覆盖的方法的方法签名。签名
因为参数只有在方法重载且多个变体时才需要
存在于类中。为方便起见,参数的类型字符串可以是
子字符串。例如,以下全部匹配java.lang.String
:
java.lang.String
String
Str
因为参数的数量通常足以区分每个可能的参数 选择,这个快捷方式可以节省大量输入,让你只输入 与参数类型匹配的最短字符串。
1.5. Bean 作用域
创建 Bean 定义时,将创建一个用于创建实际实例的配方 由该 bean 定义定义的类。bean 定义是 recipe 很重要,因为它意味着,与类一样,您可以创建许多对象 实例。
您不仅可以控制各种依赖关系和配置值,这些依赖关系和配置值
插入到从特定 bean 定义创建的对象中,但同时也要控制
从特定 Bean 定义创建的对象的范围。这种方法是
强大而灵活,因为您可以选择创建对象的范围
通过配置,而不必在 Java 的对象范围内烘焙
班级级别。可以将 Bean 定义为部署在多个作用域之一中。
Spring Framework 支持六个作用域,其中四个仅在
您使用 Web 感知ApplicationContext
.您还可以创建自定义范围。
支持的范围如下表所示:
范围 | 描述 |
---|---|
(默认)将单个 bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。 |
|
将单个 Bean 定义的范围限定为任意数量的对象实例。 |
|
将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是
每个 HTTP 请求都有自己的 Bean 实例,这些实例是在单个 Bean 的后面创建的
定义。仅在 Web 感知 Spring 的上下文中有效 |
|
将单个 Bean 定义的范围限定为 HTTP 的生命周期 |
|
将单个 Bean 定义的范围限定为 |
|
将单个 Bean 定义的范围限定为 |
从 Spring 3.0 开始,线程作用域可用,但默认情况下未注册。为
更多信息,请参阅文档SimpleThreadScope .
有关如何注册此或任何其他自定义范围的说明,请参阅使用自定义范围。 |
1.5.1. 单例作用域
仅管理单例 Bean 的一个共享实例,并且对 Bean 的所有请求 与该 Bean 定义匹配的一个或多个 ID 会导致该特定 Bean 实例。
换句话说,当您定义一个 Bean 定义并且它的作用域为 singleton,则 Spring IoC 容器只创建对象的一个实例 由该 bean 定义定义。这个单个实例存储在这样的缓存中 单例 Bean,以及该命名 Bean 的所有后续请求和引用 返回缓存对象。下图显示了单例作用域的工作原理:

Spring 的单例 Bean 概念与 四人帮 (GoF) 模式书。GoF 单例对 对象,以便每个类只创建一个特定类的实例 ClassLoader 的 ClassLoader 中。Spring 单例的范围最好描述为每个容器 和每颗豆子。这意味着,如果您在 单个 Spring 容器,Spring 容器创建一个且仅一个实例 由该 bean 定义定义的类。单例作用域是默认作用域 在Spring。要在 XML 中将 Bean 定义为单例,您可以定义一个 Bean,如 以下示例:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2. 原型作用域
Bean 部署的非单例原型范围会导致创建新的
bean 实例。也就是说,豆子
被注入到另一个 Bean 中,或者您通过getBean()
方法调用
容器。通常,您应该对所有有状态 bean 使用原型作用域,并且
无状态 bean 的单例作用域。
下图说明了 Spring 原型作用域:

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不包含 任何对话状态。我们更容易重用 单例图。
以下示例将 bean 定义为 XML 中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring 不管理 原型豆。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,没有该原型的进一步记录 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 不会调用生命周期回调。客户端代码必须清理原型范围的 对象并释放原型 bean 持有的昂贵资源。获取 Spring 容器来释放原型范围的 bean 所持有的资源,请尝试使用 自定义 bean 后处理器,它包含对 需要清理的豆子。
在某些方面,Spring 容器在原型范围的 bean 中的作用是
替换 Javanew
算子。超过该点的所有生命周期管理都必须
由客户处理。(有关 Spring 中 Bean 生命周期的详细信息
容器,请参阅生命周期回调。
1.5.3. 具有原型 bean 依赖项的单例 Bean
当您使用依赖于原型 Bean 的单例范围 Bean 时,请注意 依赖关系在实例化时解析。因此,如果您依赖注入一个 原型作用域的 Bean 转换为单例作用域的 Bean,则实例化新的原型 Bean 然后依赖注入到单例 Bean 中。原型实例是唯一的 实例,该实例曾经提供给单例作用域的 bean。
但是,假设您希望单例作用域的 bean 获取 原型作用域的 bean 在运行时重复。您不能依赖注入 原型作用域的 bean 到您的单例 bean 中,因为该注入仅发生 一次,当 Spring 容器实例化单例 Bean 并解析 并注入其依赖项。如果您需要一个新的原型 Bean 实例,请位于 运行时,请参阅方法注入。
1.5.4. 请求、会话、应用程序和 WebSocket 作用域
这request
,session
,application
和websocket
范围仅可用
如果您使用 Web 感知的 SpringApplicationContext
实现(例如XmlWebApplicationContext
).如果将这些作用域与常规 Spring IoC 容器一起使用,
例如ClassPathXmlApplicationContext
一IllegalStateException
抱怨
抛出一个未知的 bean 作用域。
初始 Web 配置
支持在request
,session
,application
和websocket
级别(Web 范围的 bean),一些次要的初始配置是
required 在定义 bean 之前。(不需要此初始设置
对于标准示波器:singleton
和prototype
.)
如何完成此初始设置取决于您的特定 Servlet 环境。
如果您在 Spring Web MVC 中访问作用域 bean,则实际上,在请求中
由弹簧加工DispatcherServlet
,无需特殊设置。DispatcherServlet
已经暴露了所有相关状态。
如果您使用 Servlet 2.5 Web 容器,则在 Spring 的DispatcherServlet
(例如,当使用 JSF 或 Struts 时),您需要注册org.springframework.web.context.request.RequestContextListener
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-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 进一步可用
呼叫链的下游。
请求范围
考虑 Bean 定义的以下 XML 配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器创建了一个LoginAction
bean 通过使用loginAction
每个 HTTP 请求的 bean 定义。也就是说,loginAction
bean 的范围为 HTTP 请求级别。您可以更改内部
根据需要创建的实例的状态,因为其他实例
从相同的loginAction
bean 定义在状态中看不到这些变化。
它们特定于个人要求。当请求完成处理时,
范围限定为请求的 bean 将被丢弃。
当使用注释驱动的组件或 Java 配置时,@RequestScope
注解
可用于将组件分配给request
范围。以下示例演示了如何
为此:
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
会话范围
考虑 Bean 定义的以下 XML 配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器创建了一个UserPreferences
bean 通过使用userPreferences
单个 HTTP 生命周期的 bean 定义Session
.在其他
单词,userPreferences
bean 的有效作用域为 HTTPSession
水平。如
使用请求范围的 Bean,您可以更改实例的内部状态,即
随心所欲地创建,知道其他 HTTPSession
实例也是
使用从同一userPreferences
bean 定义看不到这些
状态更改,因为它们特定于单个 HTTPSession
.当
HTTPSession
最终被丢弃,则作用域限定为该特定 HTTP 的 BeanSession
也被丢弃了。
使用注释驱动的组件或 Java 配置时,可以使用@SessionScope
注释以将组件分配给session
范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
应用范围
考虑 Bean 定义的以下 XML 配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器创建了一个AppPreferences
bean 通过使用appPreferences
为整个 Web 应用程序定义一次。也就是说,appPreferences
bean 的作用域为ServletContext
级别并存储为常规ServletContext
属性。这有点类似于 Spring 单例 bean,但是
在两个重要方面有所不同: 它是单例 perServletContext
,而不是每个弹簧ApplicationContext
(在任何给定的 Web 应用程序中可能有多个),
它实际上是公开的,因此可见为ServletContext
属性。
使用注释驱动的组件或 Java 配置时,可以使用@ApplicationScope
注释以将组件分配给application
范围。这
以下示例显示了如何执行此作:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
WebSocket 作用域
WebSocket 作用域与 WebSocket 会话的生命周期相关联,并适用于 STOMP over WebSocket 应用程序,请参阅 WebSocket 范围了解更多详细信息。
作用域 Bean 作为依赖项
Spring IoC 容器不仅管理对象(bean)的实例化, 还有合作者(或依赖关系)的连接。如果要注射(对于 例如)将一个 HTTP 请求范围的 Bean 转换为另一个寿命较长的 Bean,您可以 选择注入 AOP 代理来代替作用域 bean。也就是说,你需要注射 一个代理对象,它公开与作用域对象相同的公共接口,但可以 还可以从相关作用域(例如 HTTP 请求)中检索真实目标对象 并将方法调用委托到真实对象上。
您还可以使用 声明时 此外,作用域代理并不是从较短作用域访问 Bean 的唯一方法
生命周期安全的时尚。您还可以声明您的注入点(即
构造函数或 setter 参数或自动连接字段)作为 作为扩展变体,您可以声明 其 JSR-330 变体称为 |
以下示例中的配置只有一行,但重要的是 了解背后的“为什么”和“如何”:
<?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/> (1)
</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>
1 | 定义代理的行。 |
要创建此类代理,请插入一个子代理<aop:scoped-proxy/>
元素转换为作用域
bean 定义(请参阅选择要创建的代理类型和基于 XML 模式的配置)。
为什么 bean 的定义作用域为request
,session
和自定义范围
级别需要<aop:scoped-proxy/>
元素?
考虑以下单例 bean 定义,并将其与
您需要为上述范围定义什么(请注意,以下内容userPreferences
bean 定义目前是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例 Bean (userManager
) 注入引用
到 HTTPSession
-作用域 bean (userPreferences
).这里的突出点是userManager
bean 是一个单例:它每
容器及其依赖项(在本例中只有一个,即userPreferences
bean)是
也只注射过一次。这意味着userManager
bean 仅在
一模一样userPreferences
对象(即最初注入它的对象)。
这不是将寿命较短的作用域 bean 注入
较长寿命的作用域 Bean(例如,注入 HTTPSession
-范围协作
bean 作为单例 bean 的依赖项)。相反,您需要一个userManager
对象,并且,在 HTTP 的生命周期内Session
,您需要一个userPreferences
对象
特定于 HTTPSession
.因此,容器创建一个对象,该对象
公开与UserPreferences
类(理想情况下是
对象,即UserPreferences
instance),它可以获取实数UserPreferences
对象(HTTP 请求、Session
,等等
第四)。容器将此代理对象注入到userManager
bean,即
不知道这个UserPreferences
reference 是代理。在此示例中,当UserManager
实例调用依赖注入的方法UserPreferences
对象,它实际上是在调用代理上的方法。然后代理获取真实的UserPreferences
对象来自(在本例中)HTTPSession
并委托
方法调用到检索到的实数UserPreferences
对象。
因此,在注入时需要以下(正确且完整的)配置request-
和session-scoped
bean 转换为协作对象,如以下示例所示
显示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型
默认情况下,当 Spring 容器为标记为 这<aop:scoped-proxy/>
元素,则创建基于 CGLIB 的类代理。
CGLIB 代理不会拦截私有方法。尝试调用私有方法 在这样的代理上,不会委托给实际作用域的目标对象。 |
或者,您可以配置 Spring 容器以创建标准 JDK
此类作用域 Bean 的基于接口的代理,通过指定false
对于
这proxy-target-class
属性的<aop:scoped-proxy/>
元素。使用 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>
有关选择基于类或基于接口的代理的更多详细信息, 请参阅代理机制。
1.5.5. 自定义作用域
bean 作用域机制是可扩展的。您可以定义自己的
范围,甚至重新定义现有范围,尽管后者被认为是不好的做法
并且您不能覆盖内置的singleton
和prototype
范围。
创建自定义作用域
要将自定义范围集成到 Spring 容器中,您需要实现org.springframework.beans.factory.config.Scope
接口,在此
部分。有关如何实现您自己的范围的想法,请参阅Scope
Spring Framework 本身提供的实现和Scope
javadoc,
它更详细地解释了您需要实现的方法。
这Scope
接口有四种方法从作用域中获取对象,从
范围,让它们被摧毁。
例如,会话作用域实现返回会话作用域的 bean(如果它 不存在,则该方法在将 bean 绑定到 会议以备将来参考)。以下方法从 基础范围:
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
例如,会话范围实现从
基础会话。应该返回对象,但你可以返回null
如果
找不到具有指定名称的对象。以下方法从
底层范围:
Object remove(String name)
fun remove(name: String): Any
以下方法注册一个回调,当它 destroyed 或当作用域中的指定对象被销毁时:
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
有关销毁回调的更多信息,请参阅 javadoc 或 Spring 作用域实现。
以下方法获取基础作用域的对话标识符:
String getConversationId()
fun getConversationId(): String
每个作用域的此标识符都不同。对于会话范围的实现,此 identifier 可以是会话标识符。
使用自定义作用域
编写并测试一个或多个自定义Scope
实现,您需要使
Spring 容器知道你的新作用域。以下方法是中心
注册新Scope
使用 Spring 容器:
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
此方法在ConfigurableBeanFactory
接口,该接口可用
通过BeanFactory
大部分混凝土的属性ApplicationContext
Spring 附带的实现。
第一个参数registerScope(..)
method 是与
一个范围。Spring 容器本身中此类名称的示例是singleton
和prototype
.第二个参数registerScope(..)
method 是一个实际实例
的Scope
您希望注册和使用的实现。
假设你编写了自定义Scope
实现,然后如图所示注册
在下一个示例中。
下一个示例使用SimpleThreadScope ,它包含在 Spring 中,但不是
默认注册。对于您自己的自定义,说明将相同Scope 实现。 |
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
然后,您可以创建符合自定义范围规则的 Bean 定义Scope
如下:
<bean id="..." class="..." scope="thread">
使用自定义Scope
实施,您不仅限于程序化注册
范围。您还可以执行Scope
注册,通过使用CustomScopeConfigurer
类,如以下示例所示:
<?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">
<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>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
当您放置<aop:scoped-proxy/> 在<bean> 声明FactoryBean 实现时,作用域的是工厂 bean 本身,而不是对象
返回自getObject() . |
1.6. 自定义 Bean 的性质
Spring Framework 提供了许多接口,您可以使用它来自定义性质 豆子的。本节按如下方式对它们进行分组:
1.6.1. 生命周期回调
要与容器对 Bean 生命周期的管理进行交互,您可以实现
SpringInitializingBean
和DisposableBean
接口。容器调用afterPropertiesSet()
对于前者和destroy()
让后者让豆子
在初始化和销毁 Bean 时执行某些作。
The JSR-250 如果您不想使用 JSR-250 注释,但仍想删除
耦合,考虑 |
在内部,Spring 框架使用BeanPostProcessor
实现来处理任何
回调接口,它可以找到并调用适当的方法。如果您需要定制
功能或其他生命周期行为 Spring 默认不提供,您可以
实现一个BeanPostProcessor
你自己。有关详细信息,请参阅容器扩展点。
除了初始化和销毁回调之外,Spring 管理的对象还可以
还实现了Lifecycle
接口,以便这些对象可以参与
启动和关闭过程,由容器自身的生命周期驱动。
本节介绍了生命周期回调接口。
初始化回调
这org.springframework.beans.factory.InitializingBean
接口允许 bean
在容器在
豆。这InitializingBean
interface 指定了单个方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用InitializingBean
接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
这@PostConstruct
注释或
指定 POJO 初始化方法。对于基于 XML 的配置元数据,
您可以使用init-method
属性来指定具有 void 的方法的名称
无参数签名。在 Java 配置中,您可以使用initMethod
属性@Bean
.请参阅接收生命周期回调。请考虑以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
class ExampleBean {
fun init() {
// do some initialization work
}
}
前面的示例与以下示例具有几乎完全相同的效果 (由两个列表组成):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// do some initialization work
}
}
但是,前面两个示例中的第一个示例并未将代码耦合到 Spring。
请注意 对于要触发昂贵的初始化后活动的方案,
例如,异步数据库准备步骤,您的 bean 应该实现 或者,您可以实现 |
销毁回调
实现org.springframework.beans.factory.DisposableBean
接口允许
bean 在包含它的容器被销毁时获得回调。这DisposableBean
interface 指定了单个方法:
void destroy() throws Exception;
我们建议您不要使用DisposableBean
callback 接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
这@PreDestroy
注释或
指定 Bean 定义支持的通用方法。使用基于 XML 的
配置元数据,您可以使用destroy-method
属性<bean/>
.
在 Java 配置中,您可以使用destroyMethod
属性@Bean
.请参阅接收生命周期回调。考虑以下定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
class ExampleBean {
fun cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
前面的定义与以下定义具有几乎完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
class AnotherExampleBean : DisposableBean {
override fun destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面两个定义中的第一个不会将代码耦合到 Spring。
您可以分配destroy-method 属性<bean> 元素 a 特殊(inferred) value,它指示 Spring 自动检测公共close 或shutdown 方法。(任何实现java.lang.AutoCloseable 或java.io.Closeable 因此会匹配。您还可以
设置这个特别的(inferred) 值default-destroy-method 属性<beans> 元素将此行为应用于整组 bean(参见默认初始化和销毁方法)。请注意,这是
默认行为@Bean Java 配置类中的方法。 |
对于延长的关机阶段,您可以实现 |
默认初始化和销毁方法
当您编写初始化和销毁方法回调时,不使用
弹簧专用InitializingBean
和DisposableBean
回调接口,您
通常使用以下名称编写方法init()
,initialize()
,dispose()
,
等等。理想情况下,此类生命周期回调方法的名称在各个领域都是标准化的
一个项目,以便所有开发人员使用相同的方法名称并确保一致性。
您可以将 Spring 容器配置为“查找”命名初始化并销毁
每个 bean 上的回调方法名称。这意味着,作为应用程序开发人员,您,
可以编写应用程序类并使用名为init()
,
无需配置init-method="init"
属性。
Spring IoC 容器在创建 bean 时调用该方法(并且在
根据前面描述的标准生命周期回调合约)。此功能还强制执行一致的命名约定
初始化和销毁方法回调。
假设初始化回调方法名为init()
和你的毁灭
回调方法命名为destroy()
.然后,您的类类似于
以下示例:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// this is (unsurprisingly) the initialization callback method
fun init() {
if (blogDao == null) {
throw IllegalStateException("The [blogDao] property must be set.")
}
}
}
然后,您可以在类似于以下内容的 bean 中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
的存在default-init-method
属性<beans/>
元素
属性导致 Spring IoC 容器识别一个名为init
在豆子上
class 作为初始化方法回调。创建和组装 Bean 时,如果
bean 类有这样的方法,它在适当的时候被调用。
您可以使用default-destroy-method
属性<beans/>
元素。
现有 bean 类已经具有以差异命名的回调方法
使用约定时,您可以通过指定(即在 XML 中)的
方法 name 的init-method
和destroy-method
属性<bean/>
本身。
Spring 容器保证调用配置的初始化回调
立即提供所有依赖项的 bean。因此,初始化
callback 在原始 bean 引用上调用,这意味着 AOP 拦截器等
第四个尚未应用于 bean。首先完全创建目标 Bean,并且
然后应用 AOP 代理(例如)及其拦截器链。如果目标
bean 和代理是单独定义的,你的代码甚至可以与原始
target bean,绕过代理。因此,应用
拦截器到init
方法,因为这样做会耦合
将 bean 定位到其代理或拦截器,并在代码时留下奇怪的语义
直接与原始目标 Bean 交互。
组合生命周期机制
从 Spring 2.5 开始,您有三个选项来控制 Bean 生命周期行为:
-
习惯
init()
和destroy()
方法 -
这
@PostConstruct
和@PreDestroy
附注.您可以组合这些机制来控制给定的 Bean。
如果为 Bean 配置了多个生命周期机制,并且每个机制都是
配置了不同的方法名称,则每个配置的方法都在
在此注释之后列出的顺序。但是,如果配置了相同的方法名称,例如init() 对于初始化方法 — 对于多个生命周期机制,
该方法运行一次,如上一节所述。 |
为同一 Bean 配置的多个生命周期机制,具有不同的 初始化方法,调用如下:
-
用
@PostConstruct
-
afterPropertiesSet()
如InitializingBean
回调接口 -
自定义配置的
init()
方法
Destroy 方法的调用顺序相同:
-
用
@PreDestroy
-
destroy()
如DisposableBean
回调接口 -
自定义配置的
destroy()
方法
启动和关闭回调
这Lifecycle
接口为任何具有自己的对象定义基本方法
生命周期要求(例如启动和停止某些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring 托管的对象都可以实现Lifecycle
接口。然后,当ApplicationContext
本身接收启动和停止信号(例如,对于停止/重新启动
运行时的场景),它会将这些调用级联到Lifecycle
实现
在这种背景下定义。它通过委托给LifecycleProcessor
显示
在以下列表中:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor
本身就是Lifecycle
接口。它还添加了另外两种方法来对正在刷新的上下文做出反应
并关闭。
请注意,常规 另外,请注意,不能保证在销毁之前会收到停止通知。
在定期关闭时,所有 |
启动和关闭调用的顺序可能很重要。如果“依赖”
关系存在于任意两个对象之间,则依赖端在其之后开始
依赖关系,并且它在依赖关系之前停止。然而,有时,直接
依赖关系未知。您可能只知道某种类型的对象应该启动
在另一种类型的对象之前。在这些情况下,SmartLifecycle
接口定义
另一个选项,即getPhase()
在其超级接口上定义的方法,Phased
.以下列表显示了Phased
接口:
public interface Phased {
int getPhase();
}
以下列表显示了SmartLifecycle
接口:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,相位最低的对象首先启动。停止时,
遵循相反的顺序。因此,实现SmartLifecycle
和
谁的getPhase()
方法返回Integer.MIN_VALUE
将是最早开始的
最后一个停下来的。在频谱的另一端,相位值Integer.MAX_VALUE
将指示对象应最后启动并停止
首先(可能是因为它依赖于其他进程的运行)。在考虑
相位值,同样重要的是要知道任何“正常”的默认相位Lifecycle
未实现的对象SmartLifecycle
是0
.因此,任何
负相位值表示对象应在这些标准之前启动
组件(并在它们之后停止)。对于任何正相位值,情况正相关。
由SmartLifecycle
接受回调。任何
实现必须调用该回调的run()
方法,然后是该实现的
关闭过程完成。这在必要时启用异步关闭,因为
默认实现的LifecycleProcessor
接口DefaultLifecycleProcessor
,等待对象组的超时值
在每个阶段中调用该回调。默认的每阶段超时为 30 秒。
您可以通过定义名为lifecycleProcessor
在上下文中。如果只想修改超时,
定义以下内容就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor
接口定义了
刷新和关闭上下文。后者推动关机
处理好像stop()
已被显式调用,但当上下文为
关闭。另一方面,“refresh”回调启用了另一个功能SmartLifecycle
豆。刷新上下文时(在所有对象都已刷新后)
实例化并初始化),调用该回调。此时,
默认生命周期处理器检查每个SmartLifecycle
对象的isAutoStartup()
方法。如果true
,则该对象是
从那时开始,而不是等待显式调用上下文的 or
自己start()
方法(与上下文刷新不同,上下文启动不会发生
自动用于标准上下文实现)。这phase
value 和任何
“依赖”关系确定启动顺序,如前所述。
在非 Web 应用程序中正常关闭 Spring IoC 容器
本节仅适用于非 Web 应用程序。Spring 的基于 Web 的 |
如果您在非 Web 应用程序环境中使用 Spring 的 IoC 容器(对于 例如,在富客户端桌面环境中),向 JVM.这样做可以确保正常关闭,并在 singleton bean 的 bean,以便释放所有资源。您仍必须配置 并正确实现这些 destroy 回调。
要注册关机挂钩,请调用registerShutdownHook()
方法是
在ConfigurableApplicationContext
接口,如以下示例所示:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
// add a shutdown hook for the above context...
ctx.registerShutdownHook()
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
1.6.2.ApplicationContextAware
和BeanNameAware
当ApplicationContext
创建一个对象实例,该实例实现了org.springframework.context.ApplicationContextAware
接口,则提供实例
并参考了这一点ApplicationContext
.以下列表显示了定义
的ApplicationContextAware
接口:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean 可以以编程方式作ApplicationContext
创造了他们,
通过ApplicationContext
接口或通过将引用转换为已知的
该接口的子类(例如ConfigurableApplicationContext
,这会暴露
附加功能)。一种用途是以编程方式检索其他 bean。
有时此功能很有用。但是,一般来说,您应该避免它,因为
它将代码耦合到 Spring,并且不遵循控制反转样式,
其中协作者作为属性提供给 bean。的其他方法ApplicationContext
提供对文件资源的访问、发布应用程序事件、
并访问MessageSource
.这些附加功能在的附加功能ApplicationContext
.
自动布线是获取对ApplicationContext
.传统的 constructor
和byType
自动接线模式
(如自动布线协作器中所述)可以提供类型ApplicationContext
对于构造函数参数或 setter 方法参数,
分别。为了获得更大的灵活性,包括自动布线字段和
多种参数方法,使用基于注释的自动布线功能。如果你这样做,
这ApplicationContext
自动连接到字段、构造函数参数或方法
参数,该参数需要ApplicationContext
如果字段、构造函数或
有问题的方法携带@Autowired
注解。有关更多信息,请参阅用@Autowired
.
当ApplicationContext
创建一个类,实现org.springframework.beans.factory.BeanNameAware
接口,该类提供了
对其关联对象定义中定义的名称的引用。以下列表
显示了 BeanNameAware 接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调在普通 bean 属性的填充之后调用,但在
初始化回调,例如InitializingBean.afterPropertiesSet()
或自定义
init-方法。
1.6.3. 其他Aware
接口
此外ApplicationContextAware
和BeanNameAware
(前面讨论过),
Spring 提供广泛的Aware
让 bean 向容器指示的回调接口
它们需要一定的基础设施依赖性。作为一般规则,名称表示
依赖类型。下表总结了最重要的Aware
接口:
名称 | 注入的依赖项 | 解释在... |
---|---|---|
|
声明 |
|
|
封闭的事件发布者 |
|
|
用于加载 bean 类的类加载器。 |
|
|
声明 |
|
|
声明 Bean 的名称。 |
|
|
定义了用于在加载时处理类定义的织布器。 |
|
|
配置的消息解析策略(支持参数化和 国际化)。 |
|
|
Spring JMX 通知发布者。 |
|
|
配置了用于对资源的低级访问的加载程序。 |
|
|
当前 |
|
|
当前 |
再次请注意,使用这些接口会将您的代码绑定到 Spring API,而不是 遵循控制反转样式。因此,我们建议将它们用于基础设施 需要以编程方式访问容器的 Bean。
1.7. Bean 定义继承
Bean 定义可以包含大量配置信息,包括构造函数 参数、属性值和特定于容器的信息,例如初始化 方法、静态工厂方法名称等。子 Bean 定义继承 父定义中的配置数据。子定义可以覆盖某些 值或根据需要添加其他值。使用父子 Bean 定义可以节省很多 打字。实际上,这是一种模板形式。
如果您使用ApplicationContext
接口,子 bean
定义由ChildBeanDefinition
类。大多数用户不工作
在这个层面上和他们在一起。相反,他们在类中以声明方式配置 Bean 定义
例如ClassPathXmlApplicationContext
.使用基于 XML 的配置时
metadata,您可以使用parent
属性
将父 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"> (1)
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
1 | 请注意parent 属性。 |
如果没有 指定,但也可以覆盖它。在后一种情况下,子 bean 类必须是 与父级兼容(即,它必须接受父级的属性值)。
子 Bean 定义继承作用域、构造函数参数值、属性值和
方法覆盖父级,并可选择添加新值。任何作用域,初始化
method、destroy 方法或static
您指定的出厂方法设置
覆盖相应的父设置。
其余设置始终取自子定义:取决于, autowire 模式、依赖检查、单例和延迟初始化。
前面的示例通过使用
这abstract
属性。如果父定义未指定类,则显式
将父 Bean 定义标记为abstract
是必需的,如以下示例所示
显示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父 Bean 不能自行实例化,因为它是不完整的,而且它是
也显式标记为abstract
.当定义是abstract
是的
只能用作纯模板 bean 定义,用作
子定义。尝试使用这样的abstract
父 bean 本身,通过引用
将其作为另一个 bean 的 ref 属性或执行显式getBean()
使用
父 Bean ID 返回错误。同样,容器的内部preInstantiateSingletons()
方法忽略定义为
抽象。
ApplicationContext 默认情况下预实例化所有单例。因此,它是
重要的是(至少对于单例 Bean),如果你有一个(父)Bean 定义
您打算仅用作模板,并且此定义指定一个类,则您
必须确保将 abstract 属性设置为 true,否则应用程序
context 实际上会(尝试)预实例化abstract 豆。 |
1.8. 容器扩展点
通常,应用程序开发人员不需要将ApplicationContext
实现类。相反,可以通过插入
特殊集成接口的实现。接下来的几节将介绍这些
集成接口。
1.8.1. 使用BeanPostProcessor
这BeanPostProcessor
接口定义了可以实现的回调方法
提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项
解析逻辑,依此类推。如果你想在
Spring 容器完成实例化、配置和初始化 bean,您可以
插入一个或多个自定义BeanPostProcessor
实现。
您可以配置多个BeanPostProcessor
实例,您可以控制顺序
其中这些BeanPostProcessor
实例通过设置order
财产。
仅当BeanPostProcessor
实现Ordered
接口。如果您编写自己的BeanPostProcessor
,您应该考虑实现
这Ordered
界面也是如此。有关更多详细信息,请参阅BeanPostProcessor
和Ordered
接口。另请参阅注释
上编程
注册BeanPostProcessor
实例.
要更改实际的 Bean 定义(即定义 Bean 的蓝图),
您需要改用 |
这org.springframework.beans.factory.config.BeanPostProcessor
接口由以下内容组成
恰好有两个回调方法。当此类类注册为后处理器时,具有
容器,对于容器创建的每个 Bean 实例,
后处理器在容器之前从容器获取回调
初始化方法(例如InitializingBean.afterPropertiesSet()
或任何
宣布init
方法),并在任何 bean 初始化回调之后调用。
后处理器可以对 bean 实例执行任何作,包括忽略
回调。bean 后处理器通常检查回调接口,
或者它可以用代理包装 bean。一些 Spring AOP 基础设施类是
作为 bean 后处理器实现,以提供代理包装逻辑。
一ApplicationContext
自动检测在
实现BeanPostProcessor
接口。这ApplicationContext
将这些 bean 注册为后处理器,以便可以调用它们
后来,在 bean 创建时。Bean 后处理器可以部署在容器中的
与任何其他豆子一样。
请注意,当声明BeanPostProcessor
通过使用@Bean
工厂方法
配置类,工厂方法的返回类型应该是实现
类本身或至少org.springframework.beans.factory.config.BeanPostProcessor
接口,清楚地指示该 bean 的后处理器性质。否则,ApplicationContext
在完全创建之前无法按类型自动检测它。
由于BeanPostProcessor
需要尽早实例化才能应用于上下文中其他 bean 的初始化,这种早期类型检测至关重要。
以编程方式注册 虽然推荐的方法BeanPostProcessor 实例BeanPostProcessor 注册是通过ApplicationContext 自动检测(如前所述),您可以注册它们以编程方式针对ConfigurableBeanFactory 通过使用addBeanPostProcessor 方法。 当您需要在注册之前评估条件逻辑,甚至用于跨层次结构中的上下文复制 Bean 后处理器时,这会很有用。但是请注意,请注意BeanPostProcessor 以编程方式添加的实例不尊重 这Ordered 接口。 在这里,是注册的顺序决定了顺序的执行。另请注意BeanPostProcessor 以编程方式注册的实例始终在通过自动检测注册的实例之前处理,无论任何显式排序。 |
BeanPostProcessor 实例和 AOP 自动代理实现 对于任何此类 bean,您应该会看到一条信息日志消息: 如果你有 bean 连接到你的 |
以下示例演示如何编写、注册和使用BeanPostProcessor
实例
在ApplicationContext
.
示例:Hello World,BeanPostProcessor
-风格
第一个示例说明了基本用法。该示例显示了自定义BeanPostProcessor
调用toString()
每个 bean 的方法
它由容器创建,并将生成的字符串打印到系统控制台。
以下列表显示了自定义BeanPostProcessor
实现类定义:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
import org.springframework.beans.factory.config.BeanPostProcessor
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
// simply return the instantiated bean as-is
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
return bean // we could potentially return any object reference here...
}
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
println("Bean '$beanName' created : $bean")
return bean
}
}
以下内容beans
元素使用InstantiationTracingBeanPostProcessor
:
<?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:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean (messenger) is instantiated, this custom
BeanPostProcessor implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
请注意InstantiationTracingBeanPostProcessor
只是定义的。它不会
甚至有一个名字,而且,因为它是一个 bean,所以它可以像你一样注入依赖
其他豆子。(前面的配置还定义了一个由 Groovy 支持的 bean
脚本。Spring 动态语言支持在标题为 动态语言支持 的章节中进行了详细介绍。
以下 Java 应用程序运行上述代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
val messenger = ctx.getBean<Messenger>("messenger")
println(messenger)
}
上述应用程序的输出类似于以下内容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
1.8.2. 使用BeanFactoryPostProcessor
我们看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor
.的语义
此接口类似于BeanPostProcessor
,有一个专业
差异:BeanFactoryPostProcessor
对 Bean 配置元数据进行作。
也就是说,Spring IoC 容器允许BeanFactoryPostProcessor
阅读
配置元数据,并可能在容器实例化之前对其进行更改
除BeanFactoryPostProcessor
实例。
您可以配置多个BeanFactoryPostProcessor
实例,您可以控制
这些BeanFactoryPostProcessor
实例通过设置order
财产。
但是,只有在BeanFactoryPostProcessor
实现Ordered
接口。如果您编写自己的BeanFactoryPostProcessor
,你应该
考虑实现Ordered
界面也是如此。请参阅BeanFactoryPostProcessor
和Ordered
接口了解更多详细信息。
如果要更改实际的 bean 实例(即创建的对象
)中,则需要改为 也 |
当 bean 工厂后处理器在ApplicationContext
,以便将更改应用于
定义容器。Spring 包括一些预定义的 bean 工厂
后处理器,例如PropertyOverrideConfigurer
和PropertySourcesPlaceholderConfigurer
.您还可以使用自定义BeanFactoryPostProcessor
- 例如,注册自定义属性编辑器。
一ApplicationContext
自动检测部署到其中的任何 bean
实现BeanFactoryPostProcessor
接口。它使用这些豆子作为豆子工厂
后处理器,在适当的时间。您可以将这些后处理器 Bean 部署为
你会喜欢任何其他豆子。
与BeanPostProcessor s 时,您通常不想配置BeanFactoryPostProcessor s 用于延迟初始化。如果没有其他 bean 引用Bean(Factory)PostProcessor ,则该后处理器根本不会被实例化。因此,将其标记为延迟初始化将被忽略,并且Bean(Factory)PostProcessor 即使您将default-lazy-init 属性设置为true 在声明您的<beans /> 元素。 |
示例:类名替换PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer
外部化属性值使用标准 Java 从单独文件中的 Bean 定义Properties
格式。 这样做使部署应用程序的人员能够自定义特定于环境的属性,例如数据库 URL 和密码,而不会有修改容器的一个或多个主 XML 定义文件。
考虑以下基于 XML 的配置元数据片段,其中DataSource
with 占位符值被定义:
<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>
该示例显示从外部Properties
文件。在运行时,
一个PropertySourcesPlaceholderConfigurer
应用于替换某些
DataSource 的属性。要替换的值被指定为
形式${property-name}
,它遵循 Ant 和 log4j 以及 JSP EL 风格。
实际值来自标准 Java 中的另一个文件Properties
格式:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
因此,${jdbc.username}
string 在运行时替换为值 'sa',并且
这同样适用于与属性文件中的键匹配的其他占位符值。
这PropertySourcesPlaceholderConfigurer
检查大多数属性中的占位符,以及
bean 定义的属性。此外,您可以自定义占位符前缀和后缀。
使用context
命名空间,您可以配置属性占位符
具有专用配置元素。您可以将一个或多个位置作为
逗号分隔的列表location
属性,如以下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
这PropertySourcesPlaceholderConfigurer
不仅在Properties
您指定的文件。默认情况下,如果在指定的属性文件中找不到属性,
它针对 Spring 进行检查Environment
属性和常规 JavaSystem
性能。
您可以使用
如果该类无法在运行时解析为有效类,则 bean 的解析
在即将创建时失败,即在 |
示例:该PropertyOverrideConfigurer
这PropertyOverrideConfigurer
,另一个 bean 工厂后处理器,类似于PropertySourcesPlaceholderConfigurer
,但与后者不同的是,最初的定义
可以有 bean 属性的默认值或根本没有值。如果覆盖Properties
file 没有某个 bean 属性的条目,默认
上下文定义。
请注意,bean 定义不知道被覆盖,因此它没有
从 XML 定义文件中可以立即看出覆盖配置器正在被
使用。如果出现多个PropertyOverrideConfigurer
定义不同
值,由于覆盖机制,最后一个优先。
属性文件配置文件行采用以下格式:
beanName.property=value
以下列表显示了格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
此示例文件可以与包含名为dataSource
那有driverClassName
和url
性能。
还支持复合属性名称,只要路径的每个组件
除了被覆盖的最终属性已经是非 null(大概是初始化的
由构造函数)。在以下示例中,sammy
属性的bob
属性的fred
属性的tom
豆
设置为标量值123
:
tom.fred.bob.sammy=123
指定的替代值始终是文本值。它们不会被翻译成 bean 引用。当 XML Bean 中的原始值 definition 指定 bean 引用。 |
使用context
命名空间在 Spring 2.5 中引入,可以配置
属性覆盖为专用配置元素,如以下示例所示:
<context:property-override location="classpath:override.properties"/>
1.8.3. 使用FactoryBean
您可以实现org.springframework.beans.factory.FactoryBean
接口
本身就是工厂。
这FactoryBean
接口是 Spring IoC 容器的
实例化逻辑。如果您有复杂的初始化代码,最好用
Java 而不是(可能)冗长的 XML,您可以创建自己的 XMLFactoryBean
,在该类中编写复杂的初始化,然后将
习惯FactoryBean
放入容器中。
这FactoryBean<T>
interface 提供了三种方法:
-
T getObject()
:返回此工厂创建的对象的实例。这 实例可以共享,具体取决于此工厂是否返回单例 或原型。 -
boolean isSingleton()
:返回true
如果这FactoryBean
返回单例或false
否则。此方法的默认实现返回true
. -
Class<?> getObjectType()
:返回getObject()
方法 或null
如果事先不知道类型。
这FactoryBean
概念和接口在 Spring 中的许多地方都使用了
框架。超过 50 个实现FactoryBean
与 Spring 一起发布的接口
本身。
当您需要向容器索取实际的FactoryBean
实例本身,而不是
它产生的 bean,前缀 bean 的id
当 & 符号 () 时
调用&
getBean()
方法ApplicationContext
.所以,就给定而言FactoryBean
使用id
之myBean
调用getBean("myBean")
在容器上返回
的乘积FactoryBean
,而调用getBean("&myBean")
返回FactoryBean
实例本身。
1.9. 基于注释的容器配置
基于注释的配置提供了 XML 设置的替代方案,它依赖于
用于连接组件的字节码元数据,而不是尖括号声明。
开发人员不是使用 XML 来描述 Bean 布线,而是移动配置
通过使用相关类、方法或
字段声明。如中所述示例:该AutowiredAnnotationBeanPostProcessor
用
一个BeanPostProcessor
与注释结合使用是扩展
Spring IoC 容器。例如,Spring 2.0 引入了强制执行的可能性
required 属性与@Required
注解。Spring
2.5 使得遵循相同的通用方法来驱动 Spring 的依赖成为可能
注射。从本质上讲,@Autowired
注释提供与
在自动布线协作者中进行了描述,但具有更细粒度和更广泛的控制
适用性。Spring 2.5 还添加了对 JSR-250 注释的支持,例如@PostConstruct
和@PreDestroy
.Spring 3.0 添加了对 JSR-330 的支持(依赖
Injection for Java)注解包含在javax.inject
包,例如@Inject
和@Named
.有关这些注释的详细信息,请参阅相关部分。
注释注入在 XML 注入之前执行。因此,XML 配置 覆盖通过两种方法连接的属性的注释。 |
与往常一样,您可以将后处理器注册为单独的 bean 定义,但它们
也可以通过在基于 XML 的 Spring 中包含以下标签来隐式注册
配置(请注意,包含context
命名空间):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
这<context:annotation-config/>
元素隐式注册以下后处理器:
|
1.9.1. @Required
这@Required
Commentation 适用于 Bean 属性 setter 方法,如下所示
例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@set:Required
lateinit var movieFinder: MovieFinder
// ...
}
此注释指示受影响的 bean 属性必须填充在
配置时间,通过 Bean 定义中的显式属性值或通过
自动接线。如果受影响的 bean 属性尚未
填充。这允许急切和显式失败,避免NullPointerException
实例或类似的东西。我们仍然建议您将断言放入
bean 类本身(例如,进入 init 方法)。这样做会强制执行所需的
引用和值,即使您在容器外部使用类也是如此。
这 |
这 |
1.9.2. 使用@Autowired
JSR 330 的 |
您可以应用@Autowired
注释到构造函数,如以下示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao)
从 Spring Framework 4.3 开始,一个 |
您还可以应用@Autowired
对传统setter方法进行注解,
如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@set:Autowired
lateinit var movieFinder: MovieFinder
// ...
}
您还可以将注释应用于具有任意名称和多个 参数,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
您可以申请@Autowired
添加到字段中,甚至可以将其与构造函数混合,如
以下示例显示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao) {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
确保您的目标组件(例如 对于通过类路径扫描找到的 XML 定义的 bean 或组件类,容器通常预先知道具体类型。但是,对于 |
您还可以指示 Spring 从ApplicationContext
通过添加@Autowired
注释到字段或方法需要该类型的数组,如以下示例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalogs: Array<MovieCatalog>
// ...
}
这同样适用于类型化集合,如以下示例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Set<MovieCatalog>
// ...
}
您的目标 bean 可以实现 您可以声明 请注意,标准 |
甚至打字Map
只要预期的键类型为String
.
映射值包含预期类型的所有 bean,键包含
相应的 bean 名称,如以下示例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Map<String, MovieCatalog>
// ...
}
默认情况下,当给定的候选 Bean 没有匹配的可用时,自动装配将失败 注射点。对于声明的数组、集合或映射,至少一个 匹配元素。
默认行为是将带注释的方法和字段视为指示必需的
依赖。可以更改此行为,如以下示例所示,
使框架能够通过将不满足的注入点标记为
非必需的(即,通过将required
属性@Autowired
自false
):
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Autowired(required = false)
var movieFinder: MovieFinder? = null
// ...
}
如果非必需方法的依赖项(或其 dependencies,如果有多个参数)不可用。非必填字段将 在这种情况下根本不会填充,而是将其默认值保留在原处。
注入的构造函数和工厂方法参数是一种特例,因为required
属性@Autowired
由于 Spring 的构造函数,其含义有些不同
可能处理多个构造函数的解析算法。构造 函数
和工厂方法参数默认情况下是必需的,但有一些特殊的
单构造函数场景中的规则,例如多元素注入点(数组、
collections, maps)如果没有匹配的 bean 可用,则解析为空实例。这
允许一种通用的实现模式,其中所有依赖项都可以在
唯一的多参数构造函数 — 例如,声明为单个公共构造函数
没有@Autowired
注解。
任何给定 bean 类的只有一个构造函数可以声明 这 |
或者,您可以表达特定依赖项的非必需性质
通过 Java 8java.util.Optional
,如以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从 Spring Framework 5.0 开始,您还可以使用@Nullable
注释(任何类型的
在任何包中 — 例如,javax.annotation.Nullable
来自 JSR-305)或只是利用
Kotlin 内置 null-safety 支持:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
class SimpleMovieLister {
@Autowired
var movieFinder: MovieFinder? = null
// ...
}
您还可以使用@Autowired
对于众所周知的可解析接口
依赖:BeanFactory
,ApplicationContext
,Environment
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
.这些接口及其扩展
接口,例如ConfigurableApplicationContext
或ResourcePatternResolver
是
自动解析,无需特殊设置。以下示例自动布线
一ApplicationContext
对象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var context: ApplicationContext
// ...
}
这 |
1.9.3. 微调基于注释的自动布线@Primary
由于按类型自动布线可能会导致多个候选者,因此通常需要
对选择过程有更多的控制权。实现此目的的一种方法是使用 Spring 的@Primary
注解。@Primary
表示应该给出一个特定的 bean
当多个 bean 是自动连接到单值的候选对象时的首选项
Dependency。如果候选者中恰好存在一个主 Bean,则它将成为
autowired 值。
请考虑以下定义firstMovieCatalog
作为
主要MovieCatalog
:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
@Configuration
class MovieConfiguration {
@Bean
@Primary
fun firstMovieCatalog(): MovieCatalog { ... }
@Bean
fun secondMovieCatalog(): MovieCatalog { ... }
// ...
}
使用上述配置,以下内容MovieRecommender
使用firstMovieCatalog
:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
相应的 bean 定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 使用限定符微调基于 Commentation 的自动配线
@Primary
是按类型使用自动布线的有效方法,当一个
可以确定主要候选人。当您需要对选择过程进行更多控制时,
您可以使用 Spring 的@Qualifier
注解。您可以关联限定符值
使用特定参数,缩小类型匹配集的范围,以便特定 bean
为每个参数选择。在最简单的情况下,这可以是一个简单的描述值,因为
如以下示例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
您还可以指定@Qualifier
单个构造函数参数的注释或
方法参数,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
以下示例显示了相应的 bean 定义。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/> (2)
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1 | 带有main 限定符值与构造函数参数连接,该参数
以相同的值限定。 |
2 | 带有action 限定符值与构造函数参数连接,该参数
以相同的值限定。 |
对于回退匹配,bean 名称被视为默认限定符值。因此,你
可以使用id
之main
而不是嵌套限定符元素,前导
以相同的匹配结果。但是,虽然您可以使用此约定来引用
按名称划分的特定豆,@Autowired
从根本上讲,是关于类型驱动的注入
可选的语义限定符。这意味着限定符值,即使带有 bean 名称
fallback,在类型匹配集中始终具有缩小的语义。他们没有
在语义上表达对唯一 bean 的引用id
.好的限定符值是main
或EMEA
或persistent
,表示特定组件的特征
独立于 beanid
,如果是匿名 bean,则可以自动生成
定义,例如前面示例中的定义。
限定符也适用于类型化集合,如前所述,例如Set<MovieCatalog>
.在这种情况下,所有匹配的 bean,根据声明的
限定符,作为集合注入。这意味着限定符不必是
独特。相反,它们构成了过滤标准。例如,您可以定义
倍数MovieCatalog
具有相同限定符值“action”的 bean,所有这些都是
注入到Set<MovieCatalog>
注释为@Qualifier("action")
.
让限定符值根据目标 Bean 名称进行选择,在类型匹配中
候选人,不需要 |
也就是说,如果您打算按名称表示注释驱动的注入,请不要
主要用途@Autowired
,即使它能够按 bean 名称进行选择
类型匹配候选者。相反,请使用 JSR-250@Resource
注释,即
语义定义为通过特定目标组件的唯一名称标识特定目标组件,并使用
声明的类型与匹配过程无关。@Autowired
宁愿
不同的语义:按类型选择候选 Bean 后,指定的String
限定符值仅在这些类型选择的候选项中考虑(例如,
匹配account
限定符与标有相同限定符标签的 bean)。
对于本身被定义为集合的 bean,Map
,或数组类型,@Resource
是一个很好的解决方案,通过唯一名称引用特定的集合或数组 bean。
也就是说,从 4.3 开始,您可以匹配 collectionMap
,以及数组类型通过 Spring 的@Autowired
类型匹配算法,只要元素类型信息
保存在@Bean
返回类型签名或集合继承层次结构。在这种情况下,您可以使用限定符值在相同类型的集合中进行选择,如上一段所述。
从 4.3 开始,@Autowired
还考虑注入的自引用(即引用返回到当前注入的 bean)。请注意,自注入是一种后备。对其他组件的常规依赖始终具有优先级。从这个意义上说,自引用不参与常规候选选择,因此位于特别是从不主要。相反,它们总是以最低优先级结束。在实践中,您应该仅将自引用作为最后的手段(例如,对于通过 bean 的事务代理在同一实例上调用其他方法)。在这种情况下,考虑将受影响的方法分解为单独的委托 bean。或者,您可以使用@Resource
,它可能会获得返回当前 bean 的代理。
尝试注入来自 |
@Autowired
适用于字段、构造函数和多参数方法,允许
在参数级别通过限定符注释缩小范围。相比之下,@Resource
仅支持具有单个参数的字段和 Bean 属性 setter 方法。
因此,如果您的注入目标是
构造函数或多参数方法。
您可以创建自己的自定义限定符注释。为此,请定义注释和
提供@Qualifier
注释,如以下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
然后,您可以在自动连接的字段和参数上提供自定义限定符,作为 以下示例显示:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
class MovieRecommender {
@Autowired
@Genre("Action")
private lateinit var actionCatalog: MovieCatalog
private lateinit var comedyCatalog: MovieCatalog
@Autowired
fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
this.comedyCatalog = comedyCatalog
}
// ...
}
接下来,您可以提供候选 Bean 定义的信息。您可以添加<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>
在 Classpath Scanning 和 Managed Components 中,您可以看到基于注释的替代方法 以 XML 形式提供限定符元数据。具体而言,请参阅提供带有注释的限定符元数据。
在某些情况下,使用不带值的注释可能就足够了。这可以是 当注释具有更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 目录,当没有可用的 Internet 连接时可以搜索。首先,定义 简单注解,如以下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
然后将注释添加到要自动连接的字段或属性中,如 以下示例:
public class MovieRecommender {
@Autowired
@Offline (1)
private MovieCatalog offlineCatalog;
// ...
}
1 | 这一行将@Offline 注解。 |
class MovieRecommender {
@Autowired
@Offline (1)
private lateinit var offlineCatalog: MovieCatalog
// ...
}
1 | 这一行将@Offline 注解。 |
现在 bean 定义只需要一个限定符type
,如以下示例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
1 | 此元素指定限定符。 |
您还可以定义自定义限定符注释,这些注释接受
添加或代替简单的value
属性。如果多个属性值
然后在要自动连接的字段或参数上指定,Bean 定义必须匹配
所有这些属性值都被视为自动配线候选值。例如,
请考虑以下注释定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
在这种情况下Format
是一个枚举,定义如下:
public enum Format {
VHS, DVD, BLURAY
}
enum class Format {
VHS, DVD, BLURAY
}
要自动连接的字段使用自定义限定符进行注释,并包含值
对于这两个属性:genre
和format
,如以下示例所示:
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;
// ...
}
class MovieRecommender {
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Action")
private lateinit var actionVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Comedy")
private lateinit var comedyVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.DVD, genre = "Action")
private lateinit var actionDvdCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.BLURAY, genre = "Comedy")
private lateinit var comedyBluRayCatalog: MovieCatalog
// ...
}
最后,bean 定义应包含匹配的限定符值。这个例子
还演示了您可以使用 bean 元属性而不是<qualifier/>
元素。如果可用,则<qualifier/>
元素及其属性
优先级,但自动布线机制会回退到<meta/>
标记,如果不存在此类限定符,则在
以下示例:
<?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>
1.9.5. 使用泛型作为自动装配限定符
除了@Qualifier
注释,可以使用 Java 泛型类型
作为一种隐含的限定形式。例如,假设您有以下内容
配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Configuration
class MyConfiguration {
@Bean
fun stringStore() = StringStore()
@Bean
fun integerStore() = IntegerStore()
}
假设前面的 bean 实现了一个泛型接口(即,Store<String>
和Store<Integer>
),您可以@Autowire
这Store
接口,泛型是
用作限定符,如以下示例所示:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean
@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean
泛型限定符也适用于自动布线列表,Map
实例和数组。这
以下示例自动连接泛型List
:
// 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;
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>
1.9.6. 使用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
通过以下方式确定自动配线候选者:
-
这
autowire-candidate
每个 bean 定义的值 -
任何
default-autowire-candidates
在<beans/>
元素 -
的存在
@Qualifier
注释和任何已注册的自定义注释 使用CustomAutowireConfigurer
当多个 bean 符合自动连接候选项的条件时,“主”的确定是
如下所示:如果候选者中恰好有一个 bean 定义具有primary
属性设置为true
,则已选择。
1.9.7. 注入@Resource
Spring 还支持使用 JSR-250 进行注入@Resource
注解
(javax.annotation.Resource
) 在字段或 bean 属性 setter 方法上。
这是 Java EE 中的常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 中
端点。Spring 也支持 Spring 管理的对象的这种模式。
@Resource
采用 name 属性。默认情况下,Spring 将该值解释为
要注入的 Bean 名称。换句话说,它遵循按名称语义,
如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") (1)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
1 | 此行注入@Resource . |
class SimpleMovieLister {
@Resource(name="myMovieFinder") (1)
private lateinit var movieFinder:MovieFinder
}
1 | 此行注入@Resource . |
如果未显式指定名称,则默认名称派生自字段名称或
setter 方法。如果是字段,则采用字段名称。在 setter 方法的情况下,
它采用 bean 属性名称。以下示例将有 bean
叫movieFinder
注入到其 setter 方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
class SimpleMovieLister {
@set:Resource
private lateinit var movieFinder: MovieFinder
}
注释提供的名称由ApplicationContext 其中CommonAnnotationBeanPostProcessor 是知道的。
如果将 Spring 的SimpleJndiBeanFactory 明确地。但是,我们建议您依赖默认行为和
使用 Spring 的 JNDI 查找功能来保留间接级别。 |
在排他性情况下@Resource
未指定显式名称的用法,以及类似的用法
自@Autowired
,@Resource
查找主类型匹配项,而不是特定的命名 Bean
并解析众所周知的可解析依赖项:该BeanFactory
,ApplicationContext
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
接口。
因此,在以下示例中,customerPreferenceDao
字段首先查找 bean
命名为“customerPreferenceDao”,然后回退到该类型的主类型匹配CustomerPreferenceDao
:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; (1)
public MovieRecommender() {
}
// ...
}
1 | 这context 字段根据已知的可解析依赖项类型注入:ApplicationContext . |
class MovieRecommender {
@Resource
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Resource
private lateinit var context: ApplicationContext (1)
// ...
}
1 | 这context 字段根据已知的可解析依赖项类型注入:ApplicationContext . |
1.9.8. 使用@Value
@Value
通常用于注入外部化属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig
还有以下内容application.properties
文件:
catalog.name=MovieCatalog
在这种情况下,catalog
参数和字段将等于MovieCatalog
价值。
Spring 提供了默认的宽松嵌入值解析器。它将尝试解决
属性值,如果无法解析,则属性名称(例如${catalog.name}
)
将作为值注入。如果您想对不存在的保持严格控制
values,则应声明PropertySourcesPlaceholderConfigurer
bean,如下所示
示例显示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Configuration
class AppConfig {
@Bean
fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
配置PropertySourcesPlaceholderConfigurer 使用 JavaConfig,@Bean 方法必须是static . |
如果无法解析任何占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用类似${}
setPlaceholderPrefix
,setPlaceholderSuffix
或setValueSeparator
自定义
占位符。
Spring Boot 默认配置一个PropertySourcesPlaceholderConfigurer bean 那
将从application.properties 和application.yml 文件。 |
Spring 提供的内置转换器支持允许简单的类型转换(到Integer
或int
例如)自动处理。多个逗号分隔值可以是
自动转换为String
数组,无需额外努力。
可以提供默认值,如下所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
SpringBeanPostProcessor
使用ConversionService
在幕后处理
转换String
值@Value
到目标类型。如果你想
为自己的自定义类型提供转换支持,您可以提供自己的ConversionService
bean 实例,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): ConversionService {
return DefaultFormattingConversionService().apply {
addConverter(MyCustomConverter())
}
}
}
什么时候@Value
包含一个SpEL
表达该值将是动态的
在运行时计算,如以下示例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(
@Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)
SpEL 还支持使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
@Component
class MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)
1.9.9. 使用@PostConstruct
和@PreDestroy
这CommonAnnotationBeanPostProcessor
不仅识别@Resource
注解
还有 JSR-250 生命周期注释:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
.在 Spring 2.5 中引入,对这些
Commentations 提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方案。前提是CommonAnnotationBeanPostProcessor
在 Spring 中注册ApplicationContext
,
在生命周期的同一点调用带有其中一个注释的方法
作为相应的 Spring 生命周期接口方法或显式声明的回调
方法。在以下示例中,缓存在初始化时预填充,并且
破坏时清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
fun clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制。
喜欢 |
1.10. 类路径扫描和托管组件
本章中的大多数示例都使用 XML 来指定生成
每BeanDefinition
在 Spring 容器中。上一节
(基于注释的容器配置)演示了如何提供大量配置
元数据。然而,即使在这些例子中,“基础”
Bean 定义在 XML 文件中显式定义,而注释仅驱动
依赖注入。本节介绍用于隐式检测
通过扫描类路径来选择候选组件。候选组件是
与过滤器条件匹配,并在
容器。这样就无需使用 XML 来执行 bean 注册。相反,你
可以使用注释(例如,@Component
)、AspectJ 类型表达式或您自己的自定义过滤器条件来选择哪些类注册了 bean 定义容器。
从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能是核心 Spring 框架的一部分。这允许您使用 Java 而不是而不是使用传统的 XML 文件来定义 bean。看看 |
1.10.1.@Component
和进一步的刻板印象注释
这@Repository
注释是任何类的标记,该类履行该角色或存储库的构造型(也称为数据访问对象或 DAO)。其中用途该标记是异常的自动转换,如异常转换中所述。
Spring 提供了进一步的构造型注释:@Component
,@Service
和@Controller
.@Component
是任何 Spring 管理组件的通用构造型。@Repository
,@Service
和@Controller
是@Component
为 更具体的用例(分别在持久性、服务和表示层中)。因此,您可以使用@Component
,但是,通过用@Repository
,@Service
或@Controller
相反,您的类更适合通过工具进行处理或将与方面相关联。例如,这些刻板印象注释是切入点的理想目标。@Repository
,@Service
和@Controller
还可以在 Spring Framework 的未来版本中携带额外的语义。因此,如果你是在使用@Component
或@Service
对于您的服务层,@Service
是 显然是更好的选择。同样,如前所述,@Repository
已经支持作为持久层中自动异常转换的标记。
1.10.2. 使用元注释和组合注释
Spring 提供的许多注解都可以用作自己的代码中的元注解。元注解是可以应用于另一个注解的注解。例如,@Service
前面提到的注释是用元注释的@Component
,如以下示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
1 | 这@Component 原因@Service 以与@Component . |
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
1 | 这@Component 原因@Service 以与@Component . |
您还可以组合元注释以创建“组合注释”。例如
这@RestController
来自 Spring MVC 的注释由@Controller
和@ResponseBody
.
此外,组合的注释可以选择从
meta 注释以允许自定义。当您
只想公开元注释属性的子集。例如,Spring 的@SessionScope
注释将作用域名称硬编码为session
但仍允许
自定义proxyMode
.以下列表显示了SessionScope
注解:
@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;
}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
@get:AliasFor(annotation = Scope::class)
val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
然后,您可以使用@SessionScope
而不声明proxyMode
如下:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope
class SessionScopedService {
// ...
}
您还可以覆盖proxyMode
,如以下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
有关更多详细信息,请参阅 Spring Annotation 编程模型 wiki 页面。
1.10.3. 自动检测类和注册 Bean 定义
Spring 可以自动检测定型类并注册相应的BeanDefinition
实例与ApplicationContext
.例如,以下两个类
有资格进行此类自动检测:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的 bean,您需要将@ComponentScan
给你的@Configuration
类,其中basePackages
属性
是两个类的通用父包。(或者,您可以指定一个
逗号或分号或空格分隔的列表,其中包含每个类的父包。
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
为简洁起见,前面的示例可以使用value 属性的
注释(即,@ComponentScan("org.example") ). |
以下替代方法使用 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"
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:component-scan base-package="org.example"/>
</beans>
使用<context:component-scan> 隐式启用<context:annotation-config> .通常不需要包含<context:annotation-config> 元素时使用<context:component-scan> . |
扫描类路径包需要存在相应的目录 类路径中的条目。使用 Ant 构建 JAR 时,请确保不要 激活 JAR 任务的仅文件开关。此外,类路径目录可能不是 根据某些环境中的安全策略公开,例如,在 JDK 1.7.0_45 及更高版本(需要在清单中设置“Trusted-Library”——参见 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。
但是,请确保您的组件类导出在 |
此外,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
当您使用
component-scan 元素。这意味着这两个组件是自动检测的,并且
连接在一起——所有这些都没有以 XML 形式提供任何 bean 配置元数据。
您可以禁用AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor 通过包含annotation-config 属性
值为false . |
1.10.4. 使用过滤器自定义扫描
默认情况下,用@Component
,@Repository
,@Service
,@Controller
,@Configuration
,或本身使用@Component
是
唯一检测到的候选成分。但是,您可以修改和扩展此行为
通过应用自定义过滤器。将它们添加为includeFilters
或excludeFilters
属性
这@ComponentScan
注释(或作为<context:include-filter />
或<context:exclude-filter />
子元素的<context:component-scan>
元素
XML 配置)。每个滤芯都需要type
和expression
属性。
下表描述了筛选选项:
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
注释(默认) |
|
在目标组件的类型级别存在或元存在的注释。 |
可分配的 |
|
目标组件可分配给(扩展或实现)的类(或接口)。 |
方面J |
|
要由目标组件匹配的 AspectJ 类型表达式。 |
正则表达式 |
|
要与目标组件的类名匹配的正则表达式。 |
习惯 |
|
的自定义实现 |
以下示例显示了忽略所有@Repository
附注
并改用“存根”存储库:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"],
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
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 在
注释或通过提供use-default-filters="false" 作为<component-scan/> 元素。这有效地禁用了对类的自动检测
注释或元注释@Component ,@Repository ,@Service ,@Controller ,@RestController 或@Configuration . |
1.10.5. 在组件中定义 Bean 元数据
Spring 组件还可以向容器提供 bean 定义元数据。你可以做
这与相同的@Bean
用于定义 Bean 元数据的注释@Configuration
带注释的类。以下示例显示了如何执行此作:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@Component
class FactoryMethodComponent {
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
fun doWork() {
// Component method implementation omitted
}
}
前面的类是一个 Spring 组件,其doWork()
方法。但是,它还贡献了一个具有工厂的 bean 定义
方法指的publicInstance()
.这@Bean
注释标识
factory 方法和其他 bean 定义属性,例如通过
这@Qualifier
注解。可以指定的其他方法级注释包括@Scope
,@Lazy
和自定义限定符注释。
除了用于组件初始化的作用外,您还可以将@Lazy 注塑点上的注释标有@Autowired 或@Inject .在这种情况下,
它导致注入延迟分辨率代理。然而,这种代理方法
相当有限。用于复杂的懒惰交互,尤其是组合
对于可选依赖项,我们建议ObjectProvider<MyTargetBean> 相反。 |
如前所述,支持自动连接字段和方法,并具有额外的
支持自动布线@Bean
方法。以下示例显示了如何执行此作:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
该示例自动连接String
方法参数country
设置为age
另一个名为privateInstance
.Spring Expression Language 元素
通过符号定义属性的值#{ <expression> }
.为@Value
注释,表达式解析器被预配置为在以下情况下查找 bean 名称
解析表达式文本。
从 Spring Framework 4.3 开始,您还可以声明类型为InjectionPoint
(或其更具体的子类:DependencyDescriptor
) 更改为
访问触发当前 Bean 创建的请求注入点。
请注意,这仅适用于 Bean 实例的实际创建,不适用于
注入现有实例。因此,此功能对于
原型范围的 bean。对于其他范围,工厂方法只会看到
触发在给定范围内创建新 Bean 实例的注入点
(例如,触发创建惰性单例 Bean 的依赖项)。
在这种情况下,您可以将提供的注入点元数据与语义联系在一起。
以下示例演示如何使用InjectionPoint
:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
这@Bean
常规 Spring 组件中的方法的处理方式与其
弹簧内部的对应物@Configuration
类。不同的是@Component
类不会使用 CGLIB 增强来拦截方法和字段的调用。
CGLIB 代理是调用@Bean
方法
在@Configuration
classes 创建对协作对象的 Bean 元数据引用。
此类方法不是使用正常的 Java 语义调用的,而是通过
container 的生命周期管理与代理,以提供 Spring 的常规生命周期管理和代理
bean,即使通过编程调用@Bean
方法。
相反,在@Bean
plain 中的方法@Component
class 具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他
约束适用。
您可以声明 对静态的调用 Java 语言的可见性
最后,单个类可以容纳多个 |
1.10.6. 命名自动检测的组件
当组件在扫描过程中自动检测时,其 bean 名称为
由BeanNameGenerator
该扫描仪已知的策略。默认情况下,任何
弹簧构造型注释 (@Component
,@Repository
,@Service
和@Controller
) 包含名称value
从而将该名称提供给
相应的 bean 定义。
如果此类注释不包含名称value
或对于任何其他检测到的组件(例如自定义过滤器发现的组件),默认 Bean 名称生成器返回不大写的非限定类名称。例如,如果检测到以下组件类,则名称将为myMovieLister
和movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果您不想依赖默认的 bean-naming 策略,则可以提供自定义的bean-naming 策略。首先,实现BeanNameGenerator
接口,并确保包含默认的无参数构造函数。然后,在配置扫描仪时提供完全限定的类名,如以下示例注释和 bean 定义所示。
如果您由于多个自动检测到的组件具有相同的非限定类名(即具有相同名称但驻留在不同包中的类)而遇到命名冲突,您可能需要配置一个BeanNameGenerator 默认为完全限定的类名。从 Spring Framework 5.2.3 开始,FullyQualifiedAnnotationBeanNameGenerator 位于包中org.springframework.context.annotation 可用于此类目的。 |
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作为一般规则,每当其他组件可能显式引用它时,请考虑使用注释指定名称。另一方面,只要容器负责布线,自动生成的名称就足够了。
1.10.7. 为自动检测的组件提供作用域
与一般的 Spring 托管组件一样,默认和最常见的范围autodetected 组件是singleton
. 但是,有时您需要一个不同的范围这可以通过@Scope
注解。您可以提供
范围,如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 注释仅在具体 bean 类(对于 Commentted
components)或工厂方法(对于@Bean 方法)。与 XML bean 相比
定义,没有 bean 定义继承和继承的概念
类级别的层次结构与元数据目的无关。 |
有关特定于 Web 的范围(例如 Spring 上下文中的“请求”或“会话”)的详细信息,
请参阅请求、会话、应用程序和 WebSocket 范围。与这些作用域的预构建注释一样,
您还可以使用 Spring 的元注释来编写自己的作用域注释
方法:例如,自定义注释 meta-annoted@Scope("prototype")
,
可能还声明自定义作用域代理模式。
要提供用于范围解析的自定义策略,而不是依赖基于注释的方法,您可以实现ScopeMetadataResolver 接口。 请务必包含默认的无参数构造函数。然后,您可以在配置扫描仪时提供完全限定的类名,如以下示例注释和 bean 定义显示: |
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使用某些非单例作用域时,可能需要为作用域对象生成代理。推理在作用域 Bean 作为依赖项中进行了描述。为此,组件扫描上提供了作用域代理属性 元素。 三个可能的值是:no
,interfaces
和targetClass
. 例如 以下配置将产生标准 JDK 动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8. 提供带有注释的限定符元数据
这@Qualifier
注释在使用限定符微调基于注释的自动连接中进行了讨论。该部分中的示例演示了@Qualifier
annotation 和自定义限定符注释,以便在解析自动连线时提供细粒度控制 候选人。 因为这些示例是基于 XML Bean 定义的,所以限定符metadata 是通过使用qualifier
或meta
子元素的bean
元素。当依赖类路径扫描自动检测组件时,您可以为限定符元数据提供类型级候选类上的注释。以下三个示例演示了这一点 技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
与大多数基于注释的替代方案一样,请记住,注释元数据是 绑定到类定义本身,而使用 XML 允许多个 bean 提供其限定符元数据的变体,因为 元数据是按实例而不是按类提供的。 |
1.10.9. 生成候选组件的索引
虽然类路径扫描速度非常快,但可以提高启动性能 通过在编译时创建静态候选列表来处理大型应用程序。在这个 模式下,所有组件扫描目标的模块都必须使用该机制。
您现有的@ComponentScan 或<context:component-scan/> 指令必须保留
unchanged 以请求上下文扫描某些包中的候选者。当ApplicationContext 检测到此类索引时,它会自动使用它而不是扫描
类路径。 |
要生成索引,请向包含 组件扫描指令的目标。以下示例显示 如何使用 Maven 做到这一点:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.39</version>
<optional>true</optional>
</dependency>
</dependencies>
在 Gradle 4.5 及更早版本中,应在compileOnly
配置,如以下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.3.39"
}
在 Gradle 4.6 及更高版本中,依赖项应在annotationProcessor
配置,如以下示例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.3.39"
}
这spring-context-indexer
工件生成一个META-INF/spring.components
文件
包含在 jar 文件中。
在 IDE 中使用此模式时,spring-context-indexer 必须是
注册为注释处理器,以确保索引在以下情况下是最新的
候选组件已更新。 |
当META-INF/spring.components 找到文件
在类路径上。如果索引部分可用于某些库(或用例)
但无法为整个应用程序构建,您可以回退到常规类路径
排列(就好像根本不存在索引一样)通过设置spring.index.ignore 自true ,作为 JVM 系统属性或通过SpringProperties 机制。 |
1.11. 使用 JSR 330 标准注释
从 Spring 3.0 开始,Spring 提供了对 JSR-330 标准注解的支持(依赖注入)。这些注解的扫描方式与 Spring 附注。 要使用它们,您需要在类路径中具有相关的 jar。
如果您使用 Maven,则
|
1.11.1. 使用@Inject
和@Named
而不是@Autowired
,您可以使用@javax.inject.Inject
如下:
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
与@Autowired
,您可以使用@Inject
在字段级别、方法级别和构造函数参数级别。此外,您可以将注入点声明为Provider
,允许按需访问范围较短的 Bean 或延迟访问其他 Bean 通过Provider.get()
叫。 以下示例提供了前面示例的变体:
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(...);
// ...
}
}
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: Provider<MovieFinder>
fun listMovies() {
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;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
@Inject
fun setMovieFinder(@Named("main") movieFinder: MovieFinder) {
this.movieFinder = movieFinder
}
// ...
}
与@Autowired
,@Inject
也可以与java.util.Optional
或@Nullable
. 这在这里更适用,因为@Inject
没有 一个required
属性。 以下一对示例演示如何使用@Inject
和@Nullable
:
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
class SimpleMovieLister {
@Inject
var movieFinder: MovieFinder? = null
}
1.11.2.@Named
和@ManagedBean
:标准等效于@Component
注解
而不是@Component
,您可以使用@javax.inject.Named
或javax.annotation.ManagedBean
, 如以下示例所示:
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;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
使用起来很常见@Component
而不指定组件的名称。@Named
可以以类似的方式使用,如以下示例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
@Named
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
当您使用@Named
或@ManagedBean
,您可以在
与使用 Spring 注释时的方式完全相同,如以下示例所示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
与@Component 、JSR-330@Named 和 JSR-250@ManagedBean 注释不可组合。您应该使用 Spring 的刻板模型来构建
自定义组件注释。 |
1.11.3. JSR-330 标准注释的局限性
当您使用标准注释时,您应该知道一些重要的 功能不可用,如下表所示:
Spring | javax.inject 中。 | javax.inject 限制/注释 |
---|---|---|
@Autowired |
@Inject |
|
@Component |
@Named / @ManagedBean |
JSR-330 不提供可组合模型,仅提供一种识别命名组件的方法。 |
@Scope(“单例”) |
@Singleton |
JSR-330 默认范围类似于 Spring 的 |
@Qualifier |
@Qualifier / @Named |
|
@Value |
- |
没有等效的 |
@Required |
- |
没有等效的 |
@Lazy |
- |
没有等效的 |
对象工厂 |
提供商 |
|
1.12. 基于Java的容器配置
本节介绍如何使用 Java 代码中的注释来配置 Spring 容器。它包括以下主题:
1.12.1. 基本概念:@Bean
和@Configuration
Spring 的新 Java 配置支持中的核心工件是@Configuration
-annotated 类和@Bean
-annotated 方法。
这@Bean
注释用于指示方法实例化、配置和
初始化一个要由 Spring IoC 容器管理的新对象。对于那些熟悉的人
与 Spring 的<beans/>
XML 配置,则@Bean
注释的作用与
这<bean/>
元素。您可以使用@Bean
-annotated 方法与任何 Spring@Component
.但是,它们最常与@Configuration
豆。
使用@Configuration
表示其主要用途是作为
bean 定义的来源。此外@Configuration
类让 bean 间
依赖关系通过调用 other@Bean
方法。
最简单的@Configuration
类内容如下:
@Configuration
public class AppConfig {
@Bean
public MyServiceImpl myService() {
return new MyServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun myService(): MyServiceImpl {
return MyServiceImpl()
}
}
前面的AppConfig
class 相当于下一个 Spring<beans/>
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
这@Bean
和@Configuration
以下部分将深入讨论注释。
然而,首先,我们介绍了通过使用
基于 Java 的配置。
1.12.2. 使用AnnotationConfigApplicationContext
以下部分记录了 Spring 的AnnotationConfigApplicationContext
,在 Spring 中引入
3.0. 这个多功能的ApplicationContext
实施不仅能够接受@Configuration
类作为输入,但也是普通的@Component
类和类
用 JSR-330 元数据进行注释。
什么时候@Configuration
类作为输入提供,则@Configuration
类本身
被注册为 bean 定义,并且所有@Bean
类中的方法
也注册为 bean 定义。
什么时候@Component
和 JSR-330 类,它们被注册为 bean
定义,并且假定 DI 元数据(例如@Autowired
或@Inject
是
必要时在这些类中使用。
结构简单
与实例化ClassPathXmlApplicationContext
,您可以使用@Configuration
类作为输入
实例化AnnotationConfigApplicationContext
.这完全允许
Spring 容器的无 XML 用法,如以下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
如前所述,AnnotationConfigApplicationContext
不仅限于工作
跟@Configuration
类。任何@Component
或可以提供 JSR-330 注释类
作为构造函数的输入,如以下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
前面的示例假设MyServiceImpl
,Dependency1
和Dependency2
使用 Spring
依赖注入注解,例如@Autowired
.
使用register(Class<?>…)
您可以实例化AnnotationConfigApplicationContext
通过使用无参数构造函数
,然后使用register()
方法。这种方法特别有用
以编程方式构建AnnotationConfigApplicationContext
.以下内容
示例显示了如何执行此作:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.register(AppConfig::class.java, OtherConfig::class.java)
ctx.register(AdditionalConfig::class.java)
ctx.refresh()
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
启用元件扫描scan(String…)
要启用组件扫描,您可以注释@Configuration
类如下:
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
有经验的 Spring 用户可能熟悉 XML 声明,等效于
Spring的
|
在前面的示例中,com.acme
扫描包以查找任何@Component
-带注释的类,这些类被注册为 Spring bean
容器内的定义。AnnotationConfigApplicationContext
公开scan(String…)
方法允许相同的组件扫描功能,如
以下示例显示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean<MyService>()
}
记住@Configuration 类的元注释为@Component ,因此它们是组件扫描的候选者。在前面的示例中,
假设AppConfig 在com.acme 包(或任何包
),在调用scan() .后refresh() ,其所有@Bean 方法在容器中被处理并注册为 bean 定义。 |
支持 Web 应用程序AnnotationConfigWebApplicationContext
一个WebApplicationContext
的变体AnnotationConfigApplicationContext
可用
跟AnnotationConfigWebApplicationContext
.您可以在以下情况下使用此实现
配置 SpringContextLoaderListener
servlet 监听器,Spring MVCDispatcherServlet
,依此类推。以下内容web.xml
代码段配置一个典型的
Spring MVC Web 应用程序(请注意使用contextClass
context-param 和
init-param):
<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>
对于编程用例,一个GenericWebApplicationContext 可以用作
替代AnnotationConfigWebApplicationContext .请参阅GenericWebApplicationContext javadoc 了解详情。 |
1.12.3. 使用@Bean
注解
@Bean
是方法级注释,是 XML 的直接模拟<bean/>
元素。
注释支持<bean/>
如:
-
name
.
您可以使用@Bean
注释@Configuration
-注释或在@Component
-annotated 类。
声明 Bean
要声明 bean,您可以使用@Bean
注解。你用这个
在ApplicationContext
的类型
指定为方法的返回值。默认情况下,bean 名称与
方法名称。以下示例显示了@Bean
方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService() = TransferServiceImpl()
}
前面的配置完全等同于下面的 Spring XML:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都使一个名为transferService
在ApplicationContext
,绑定到类型为TransferServiceImpl
,作为
以下文字图片显示:
transferService -> com.acme.TransferServiceImpl
您还可以使用默认方法来定义 bean。这允许豆子的组成 通过在默认方法上实现具有 bean 定义的接口来进行配置。
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
您还可以声明您的@Bean
方法与接口(或基类)
返回类型,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
但是,这会将高级类型预测的可见性限制为指定的
接口类型 (TransferService
).然后,使用完整类型 (TransferServiceImpl
)
只有在实例化受影响的单例 Bean 后,容器才知道。
非惰性单例 Bean 根据其声明顺序进行实例化,
因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间
尝试通过非声明类型(例如@Autowired TransferServiceImpl
,
它仅解析一次transferService
bean 已被实例化)。
如果您始终通过声明的服务接口引用您的类型,则您的@Bean 返回类型可以安全地加入该设计决策。但是,对于组件
实现多个接口或用于其可能引用的组件
实现类型,声明最具体的返回类型会更安全
(至少与引用您的 bean 的注入点要求一样具体)。 |
Bean 依赖项
一个@Bean
-annotated 方法可以有任意数量的参数来描述
构建该 bean 所需的依赖项。例如,如果我们的TransferService
需要一个AccountRepository
,我们可以使用一个方法实现这种依赖关系
参数,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
解析机制与基于构造函数的依赖关系几乎相同 注射。有关更多详细信息,请参阅相关部分。
接收生命周期回传
使用@Bean
注解支持常规生命周期回调
并且可以使用@PostConstruct
和@PreDestroy
JSR-250 的注释。有关进一步的信息,请参阅 JSR-250 注释
详。
常规的 Spring 生命周期回调完全支持
井。如果 bean 实现InitializingBean
,DisposableBean
或Lifecycle
他们
容器调用相应的方法。
标准集*Aware
接口(例如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)也完全受支持。
这@Bean
注解支持指定任意初始化和销毁
回调方法,很像 Spring XML 的init-method
和destroy-method
属性
在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();
}
}
class BeanOne {
fun init() {
// initialization logic
}
}
class BeanTwo {
fun cleanup() {
// destruction logic
}
}
@Configuration
class AppConfig {
@Bean(initMethod = "init")
fun beanOne() = BeanOne()
@Bean(destroyMethod = "cleanup")
fun beanTwo() = BeanTwo()
}
默认情况下,使用 Java 配置定义的 bean 具有 public 默认情况下,您可能希望对使用 JNDI 获取的资源执行此作,因为它的
生命周期在应用程序外部进行管理。特别是,确保始终这样做
对于一个 以下示例显示了如何防止 Java
Kotlin
此外,与 |
在以下情况下BeanOne
从前面注释的示例中,调用init()
方法,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
当您直接在 Java 中工作时,您可以对对象做任何您喜欢的事情,并执行以下作 并不总是需要依赖容器生命周期。 |
指定 Bean 作用域
Spring 包括@Scope
注释,以便您可以指定 bean 的作用域。
使用@Scope
注解
您可以指定使用@Bean
注释应该有一个
具体范围。您可以使用 Bean 作用域部分中指定的任何标准作用域。
默认范围为singleton
,但您可以使用@Scope
注解
如以下示例所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope
和scoped-proxy
Spring 提供了一种通过作用域代理处理作用域依赖关系的便捷方法。最简单的创作方式
使用 XML 配置时,这样的代理是<aop:scoped-proxy/>
元素。
使用@Scope
注释提供等效支持
使用proxyMode
属性。默认值为ScopedProxyMode.DEFAULT
哪
通常表示不应创建任何作用域代理,除非有不同的默认值
已在组件扫描指令级别进行配置。您可以指定ScopedProxyMode.TARGET_CLASS
,ScopedProxyMode.INTERFACES
或ScopedProxyMode.NO
.
如果您将作用域代理示例从 XML 参考文档(请参阅作用域代理)移植到我们的@Bean
使用 Java,
它类似于以下内容:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
return SimpleUserService().apply {
// a reference to the proxied userPreferences bean
setUserPreferences(userPreferences())
}
}
自定义 Bean 命名
默认情况下,配置类使用@Bean
方法的名称作为
生成的 bean。但是,可以使用name
属性
如以下示例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean 混叠
正如命名 Bean 中所讨论的,有时需要给出单个 Bean
多个名称,也称为 bean 别名。这name
属性的@Bean
annotation 为此目的接受 String 数组。以下示例演示如何设置
Bean 的多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Configuration
class AppConfig {
@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
fun dataSource(): DataSource {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时,提供 bean 的更详细的文本描述会很有帮助。这可以 当 bean 被公开(可能通过 JMX)用于监控目的时特别有用。
要将描述添加到@Bean
,您可以使用@Description
注释,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}
1.12.4. 使用@Configuration
注解
@Configuration
是类级注解,指示对象是
bean 定义。@Configuration
类通过@Bean
-注释
方法。调用@Bean
方法@Configuration
类也可用于定义
bean 间依赖关系。看基本概念:@Bean
和@Configuration
进行一般介绍。
注入 Bean 间依赖关系
当 bean 彼此依赖时,表达这种依赖关系就像 让一个 bean 方法调用另一个 bean 方法,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne(beanTwo())
@Bean
fun beanTwo() = BeanTwo()
}
在前面的示例中,beanOne
接收对beanTwo
通过构造函数
注射。
这种声明 Bean 间依赖关系的方法仅在@Bean 方法
在@Configuration 类。您不能声明 Bean 间依赖关系
通过使用 plain@Component 类。 |
查找方法注入
如前所述,查找方法注入是一个 您应该很少使用的高级功能。在以下情况下,它很有用 单例范围的 Bean 依赖于原型范围的 Bean。为此使用 Java 配置类型为实现此模式提供了一种自然的方法。这 以下示例显示如何使用查找方法注入:
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();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val 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 fun createCommand(): Command
}
通过使用 Java 配置,您可以创建CommandManager
哪里
摘要createCommand()
方法被覆盖,以便它查找新的
(prototype) 命令对象。以下示例显示了如何执行此作:
@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();
}
}
}
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
val command = AsyncCommand()
// inject dependencies here as required
return command
}
@Bean
fun commandManager(): CommandManager {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return object : CommandManager() {
override fun createCommand(): Command {
return asyncCommand()
}
}
}
有关基于 Java 的配置如何在内部工作的更多信息
请考虑以下示例,其中显示了@Bean
annotated 方法被调用两次:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun clientService1(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientService2(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientDao(): ClientDao {
return ClientDaoImpl()
}
}
clientDao()
已调用一次clientService1()
一旦进入clientService2()
.
由于此方法创建了一个ClientDaoImpl
并返回它,你会
通常期望有两个实例(每个服务一个)。那肯定是
problemacy:在 Spring 中,实例化的 bean 有一个singleton
默认范围。这是
神奇之处:全部@Configuration
类在启动时被子类化
跟CGLIB
.在子类中,子方法首先检查容器中是否有任何
缓存(作用域)bean,然后调用父方法并创建一个新实例。
根据 Bean 的范围,行为可能会有所不同。我们正在谈论 关于单身人士在这里。 |
从 Spring 3.2 开始,不再需要将 CGLIB 添加到类路径中,因为 CGLIB
类已重新打包在 |
由于 CGLIB 在
启动时间。特别是,配置类不得是最终的。但是,作为
4.3 中,配置类上允许使用任何构造函数,包括使用 如果您希望避免 CGLIB 施加的任何限制,请考虑声明您的 |
1.12.5. 编写基于 Java 的配置
Spring 基于 Java 的配置功能允许您编写注释,这可以减少配置的复杂性。
使用@Import
注解
就像<import/>
元素在 Spring XML 文件中使用,以帮助模块化
配置,则@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();
}
}
@Configuration
class ConfigA {
@Bean
fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
@Bean
fun b() = B()
}
现在,不需要同时指定两者ConfigA.class
和ConfigB.class
什么时候
实例化上下文,仅ConfigB
需要显式提供,因为
以下示例显示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
// now both beans A and B will be available...
val a = ctx.getBean<A>()
val b = ctx.getBean<B>()
}
这种方法简化了容器实例化,因为只需要处理一个类
与,而不是要求您记住潜在的大量@Configuration
施工期间的课程。
从 Spring Framework 4.2 开始,@Import 还支持对常规组件的引用
类,类似于AnnotationConfigApplicationContext.register 方法。
如果您想通过使用一些
配置类作为入口点,以显式定义所有组件。 |
在导入的@Bean
定义
前面的示例有效,但很简单。在大多数实际场景中,bean 具有
跨配置类相互依赖。使用 XML 时,这不是
issue,因为不涉及编译器,你可以声明ref="someBean"
并相信 Spring 会在容器初始化期间解决问题。
使用时@Configuration
类,Java 编译器会对
配置模型,因为对其他 bean 的引用必须是有效的 Java 语法。
幸运的是,解决这个问题很简单。正如我们已经讨论过的,
一个@Bean
方法可以具有任意数量的描述 bean 的参数
依赖。考虑以下更真实的场景,其中包含几个@Configuration
类,每个类都取决于其他 bean 中声明的 bean:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig {
@Bean
fun accountRepository(dataSource: DataSource): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
还有另一种方法可以达到相同的结果。记住@Configuration
类是
最终容器中只有另一个 bean:这意味着他们可以利用@Autowired
和@Value
注射和其他功能与任何其他 Bean 相同。
确保以这种方式注入的依赖项仅属于最简单的类型。 避免访问 另外,要特别小心 |
以下示例显示了如何将一个 Bean 自动连接到另一个 Bean:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
lateinit var accountRepository: AccountRepository
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
构造函数注入@Configuration classes 仅从 Spring 开始受支持
框架 4.3.另请注意,无需指定@Autowired 如果目标
bean 只定义了一个构造函数。 |
在前面的场景中,使用@Autowired
效果很好,提供了所需的
模块化,但确定自动连接 bean 定义的确切声明位置是
还是有些模棱两可。例如,作为开发人员,将ServiceConfig
、如何
您确切地知道@Autowired AccountRepository
bean 被声明了吗?它不是
在代码中显式,这可能没问题。请记住,Spring Tools for Eclipse 提供了以下工具:
可以渲染显示所有内容如何连接的图表,这可能就是您所需要的。也
您的 Java IDE 可以轻松找到AccountRepository
类型
并快速向您展示@Bean
返回该类型的方法。
如果这种歧义是不可接受的,并且您希望直接导航
从您的 IDE 中从一个@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());
}
}
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
在上述情况下,其中AccountRepository
定义是完全明确的。
然而ServiceConfig
现在紧密耦合到RepositoryConfig
.那就是
权衡。这种紧密耦合可以通过使用基于接口或
抽象类@Configuration
类。请考虑以下示例:
@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}) // import the concrete config!
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");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
@Configuration
interface RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository
}
@Configuration
class DefaultRepositoryConfig : RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(...)
}
}
@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class) // import the concrete config!
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
现在ServiceConfig
相对于混凝土松耦合DefaultRepositoryConfig
,并且内置的 IDE 工具仍然有用:您可以轻松地
获取类型层次结构RepositoryConfig
实现。在这个
方式, 导航@Configuration
类及其依赖关系没有什么不同
比导航基于接口的代码的通常过程。
如果要影响某些 bean 的启动创建顺序,请考虑
将其中一些声明为@Lazy (用于首次访问时而不是启动时创建)
或作为@DependsOn 某些其他 bean(确保特定的其他 bean 是
在当前 bean 之前创建,超出了后者的直接依赖关系所暗示的范围)。 |
有条件地包括@Configuration
类或@Bean
方法
有条件地启用或禁用完整的@Configuration
类
甚至个人@Bean
方法,基于一些任意系统状态。一种常见的
例如,使用@Profile
注解仅在特定
profile 已在 Spring 中启用Environment
(有关详细信息,请参阅 Bean 定义配置文件)。
这@Profile
注释实际上是通过使用更灵活的注释来实现的
叫@Conditional
.
这@Conditional
注释表示特定的org.springframework.context.annotation.Condition
应该是
在咨询之前@Bean
已注册。
的实现Condition
接口提供matches(…)
返回true
或false
.例如,以下列表显示了实际的Condition
用于@Profile
:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
// Read the @Profile annotation attributes
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
return true
}
}
return false
}
return true
}
请参阅@Conditional
javadoc 了解更多详情。
结合 Java 和 XML 配置
Spring的@Configuration
类支持的目标不是 100% 完全替代对于 Spring XML。一些工具,例如 Spring XML 命名空间,仍然是配置容器的理想方式。在 XML 方便或必要的情况下,您有一个选择:要么以“以 XML 为中心”的方式实例化容器,例如,使用ClassPathXmlApplicationContext
,或使用AnnotationConfigApplicationContext
和@ImportResource
注解来导入 XML
根据需要。
以 XML 为中心的使用@Configuration
类
最好从 XML 引导 Spring 容器并包含@Configuration
以临时方式进行类。例如,在大型现有代码库中
使用 Spring XML 的,创建起来更容易@Configuration
类
根据需要,并从现有 XML 文件中包含它们。在本节后面,我们将介绍
使用选项@Configuration
类在这种“以 XML 为中心”的情况下。
记住@Configuration
类最终是
容器。在本系列示例中,我们创建了一个@Configuration
名为AppConfig
和
将其包含在system-test-config.xml
作为<bean/>
定义。因为<context:annotation-config/>
打开时,容器会识别@Configuration
注释并处理@Bean
在AppConfig
适当地。
以下示例显示了 Java 中的普通配置类:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
@Configuration
class AppConfig {
@Autowired
private lateinit var dataSource: DataSource
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService() = TransferService(accountRepository())
}
以下示例显示了示例的一部分system-test-config.xml
文件:
<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>
以下示例显示了可能的jdbc.properties
文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
在system-test-config.xml 文件,则AppConfig <bean/> 不声明id 元素。虽然这样做是可以接受的,但考虑到没有其他 bean
ever 引用它,并且不太可能按名称从容器中显式获取它。
同样,DataSource bean 仅按类型自动连接,因此显式 beanid 不是严格要求的。 |
因为@Configuration
元注释为@Component
,@Configuration
-注释
类自动成为组件扫描的候选者。使用与
在前面的例子中描述,我们可以重新定义system-test-config.xml
以利用组件扫描。
请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>
因为<context:component-scan/>
启用相同的
功能性。
以下示例显示了修改后的system-test-config.xml
文件:
<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
以类为中心的 XML 使用@ImportResource
在以下应用中@Configuration
类是配置的主要机制
容器,可能仍然需要至少使用一些 XML。在这些
场景,您可以使用@ImportResource
并仅定义您需要的 XML。行为
因此实现了“以 Java 为中心”的方法来配置容器,并将 XML 保持在
最低 限度。以下示例(包括一个配置类、一个 XML 文件
定义 bean、属性文件和main
类)展示了如何使用
这@ImportResource
注释,以实现使用 XML 的“以 Java 为中心”的配置
根据需要:
@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);
}
}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {
@Value("\${jdbc.url}")
private lateinit var url: String
@Value("\${jdbc.username}")
private lateinit var username: String
@Value("\${jdbc.password}")
private lateinit var password: String
@Bean
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val transferService = ctx.getBean<TransferService>()
// ...
}
1.13. 环境抽象
这Environment
接口
是集成在容器中的抽象,它对两个键进行了建模
应用程序环境的各个方面:配置文件和属性。
配置文件是一组命名的逻辑 Bean 定义,要向
仅当给定的配置文件处于活动状态时才容器。可以将 Bean 分配给配置文件
无论是在 XML 中定义还是使用注释定义。的作用Environment
对象替换为
与配置文件的关系在于确定哪些配置文件(如果有)当前处于活动状态,
以及哪些配置文件(如果有)默认应处于活动状态。
属性在几乎所有应用中都起着重要作用,并且可能源自
多种来源:属性文件、JVM 系统属性、系统环境
变量, JNDI, servlet 上下文参数, 临时Properties
对象Map
对象,等等
上。的作用Environment
object 与属性相关的是提供
用户具有方便的服务界面,用于配置属性源和解析
属性。
1.13.1. Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,允许 在不同环境中注册不同的 Bean。“环境”这个词, 对不同的用户来说可能意味着不同的事情,此功能可以帮助许多用户 用例,包括:
-
在开发中使用内存中数据源与查找相同的数据源 在 QA 或生产中时,来自 JNDI 的数据源。
-
仅在将应用程序部署到 性能环境。
-
为客户 A 与客户注册 Bean 的定制实现 B 部署。
考虑实际应用程序中的第一个用例,它需要DataSource
.在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
现在考虑如何将此应用程序部署到 QA 或生产中
环境,假设已注册应用程序的数据源
替换为生产应用程序服务器的 JNDI 目录。我们dataSource
豆
现在看起来像以下列表:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
问题是如何在使用这两种变体之间切换,基于
当前环境。随着时间的推移,Spring 用户设计了多种方法
完成此作,通常依赖于系统环境变量的组合
和 XML<import/>
包含${placeholder}
解析的Tokens
到正确的配置文件路径,具体取决于环境的值
变量。Bean 定义配置文件是一个核心容器功能,它提供了
解决这个问题。
如果我们概括前面特定于环境的 bean 示例中显示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中则不然。您可以说您想注册一个 情况 A 中 Bean 定义的某些配置文件和 情况 B.我们首先更新我们的配置以反映这种需求。
用@Profile
这@Profile
注释允许您指示组件符合注册条件
当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们
可以重写dataSource
配置如下:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("development")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
如前所述,使用@Bean 方法,您通常选择使用编程JNDI 查找,通过使用 Spring 的JndiTemplate /JndiLocatorDelegate helpers 或直接 JNDIInitialContext 用法,但未显示JndiObjectFactoryBean variant,这将强制您将返回类型声明为FactoryBean 类型。 |
配置文件字符串可以包含一个简单的配置文件名称(例如,production
) 或profile 表达式。profile 表达式允许将更复杂的配置文件逻辑表达式(例如,production & us-east
). 以下运算符在profile 表达式中支持:
-
!
:配置文件的逻辑“不” -
&
:配置文件的逻辑“and” -
|
:配置文件的逻辑“或”
不能将 和& | 运算符而不使用括号。 例如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 {
}
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果@Configuration class 标记为@Profile ,所有@Bean 方法和@Import 与该类关联的注释将被绕过,除非一个或多个
指定的配置文件处于活动状态。如果@Component 或@Configuration class 被标记为
跟@Profile({"p1", "p2"}) ,除非
配置文件“P1”或“P2”已激活。如果给定配置文件前缀为
NOT 运算符 (! ),仅当配置文件未注册时才会注册带注释的元素
积极。例如,给定@Profile({"p1", "!p2"}) ,如果配置文件
“p1”处于活动状态,或者配置文件“p2”未处于活动状态。 |
@Profile
也可以在方法级别声明以仅包含一个特定的 bean
配置类的(例如,对于特定 bean 的替代变体),作为
以下示例显示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | 这standaloneDataSource 方法仅在development 轮廓。 |
2 | 这jndiDataSource 方法仅在production 轮廓。 |
@Configuration
class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
fun standaloneDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
@Bean("dataSource")
@Profile("production") (2)
fun jndiDataSource() =
InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 | 这standaloneDataSource 方法仅在development 轮廓。 |
2 | 这jndiDataSource 方法仅在production 轮廓。 |
跟 如果要定义具有不同配置文件条件的替代 Bean,
使用指向相同 bean 名称的不同 Java 方法名称,方法是使用 |
XML Bean 定义配置文件
XML 对应项是profile
属性的<beans>
元素。我们前面的示例
配置可以在两个 XML 文件中重写,如下所示:
<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 profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</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>
这spring-bean.xsd
已限制为仅允许
文件中的最后一个。这应该有助于提供灵活性,而不会产生
XML 文件中的杂乱。
XML 对应项不支持前面描述的配置文件表达式。有可能,
但是,要使用
在前面的示例中, |
激活配置文件
现在我们已经更新了配置,我们仍然需要指示 Spring 哪个
配置文件处于活动状态。如果我们现在开始我们的示例应用程序,我们会看到
一个NoSuchBeanDefinitionException
抛出,因为容器找不到
名为 Spring beandataSource
.
可以通过多种方式激活配置文件,但最直接的是
它以编程方式针对Environment
API 可通过ApplicationContext
.以下示例显示了如何执行此作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
此外,您还可以通过spring.profiles.active
属性,可以通过系统环境指定
变量、JVM 系统属性、servlet 上下文参数web.xml
,甚至作为
JNDI 中的条目(参见PropertySource
抽象化).在集成测试中,活动
可以使用@ActiveProfiles
注释中的spring-test
模块(请参阅使用环境配置文件进行上下文配置)。
请注意,配置文件不是“非此即彼”的命题。您可以激活多个
配置文件。通过编程方式,您可以向setActiveProfiles()
方法,该方法接受String…
varargs。以下示例
激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
声明式,spring.profiles.active
可以接受以逗号分隔的配置文件名称列表,
如以下示例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件表示默认启用的配置文件。考虑一下 以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
如果没有处于活动状态的配置文件,则dataSource
被创建。你可以看到这个
作为为一个或多个 bean 提供默认定义的一种方式。如果有
配置文件,则默认配置文件不适用。
您可以使用以下命令更改默认配置文件的名称setDefaultProfiles()
上
这Environment
或者,以声明方式,通过使用spring.profiles.default
财产。
1.13.2.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);
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
在前面的代码片段中,我们看到了一种高级方式,用于询问 Spring 是否my-property
属性是
为当前环境定义。要回答这个问题,请Environment
对象执行
搜索一组PropertySource
对象。一个PropertySource
是对任何键值对源的简单抽象,并且
Spring的StandardEnvironment
配置了两个 PropertySource 对象 — 一个表示 JVM 系统属性集
(System.getProperties()
)和一个表示系统环境变量集
(System.getenv()
).
这些默认属性源存在于StandardEnvironment ,用于独立使用
应用。StandardServletEnvironment 填充了其他默认属性源,包括 servlet config、servlet
上下文参数,以及JndiPropertySource 如果 JNDI 可用。 |
具体来说,当您使用StandardEnvironment
,调用env.containsProperty("my-property")
如果my-property
系统属性或my-property
环境变量存在于
运行。
执行的搜索是分层的。默认情况下,系统属性优先于
环境变量。因此,如果 对于一个普通的
|
最重要的是,整个机制是可配置的。也许您有一个自定义源
要集成到此搜索中的属性。为此,请实现
并实例化您自己的PropertySource
并将其添加到PropertySources
对于
当前Environment
.以下示例显示了如何执行此作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
在前面的代码中,MyPropertySource
已以最高优先级添加到
搜索。如果它包含my-property
属性,则该属性被检测并返回,有利于
任何my-property
属性PropertySource
.这MutablePropertySources
API 公开了许多方法,允许精确作
属性来源。
1.13.3. 使用@PropertySource
这@PropertySource
注释提供了一种方便的声明性机制,用于添加PropertySource
到 Spring 的Environment
.
给定一个名为app.properties
包含键值对testbean.name=myTestBean
,
以下@Configuration
类用途@PropertySource
以这样一种方式
对testBean.getName()
返回myTestBean
:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
任何${…}
占位符@PropertySource
资源位置是
针对已针对
环境,如以下示例所示:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
假设my.placeholder
已存在于其中一个属性源中
已注册(例如,系统属性或环境变量),占位符为
解析为相应的值。如果没有,那么default/path
被使用
作为默认设置。如果未指定默认值并且无法解析属性,则IllegalArgumentException
被抛出。
这@PropertySource 根据 Java 8 约定,注释是可重复的。
然而,所有这些@PropertySource 注释需要同时声明
级别,可以直接在配置类上,也可以作为
相同的自定义注释。混合直接注释和元注释不是
推荐,因为直接注释有效地覆盖了元注释。 |
1.14. 注册LoadTimeWeaver
这LoadTimeWeaver
Spring 使用它们来动态转换类
加载到 Java 虚拟机 (JVM) 中。
要启用加载时编织,您可以将@EnableLoadTimeWeaving
给你的一个@Configuration
类,如以下示例所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig
或者,对于 XML 配置,您可以使用context:load-time-weaver
元素:
<beans>
<context:load-time-weaver/>
</beans>
一旦为ApplicationContext
,其中的任何 beanApplicationContext
可以实现LoadTimeWeaverAware
,从而接收对加载时间的引用
Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能是
JPA 类转换所必需的。
请查阅LocalContainerEntityManagerFactoryBean
javadoc 了解更多详情。有关 AspectJ 加载时编织的更多信息,请参阅 在 Spring Framework 中使用 AspectJ 进行加载时编织。
1.15. 的附加功能ApplicationContext
正如本章介绍中所讨论的,该org.springframework.beans.factory
package 提供了管理和作 bean 的基本功能,包括在
程序化方式。这org.springframework.context
package 添加ApplicationContext
接口,它扩展了BeanFactory
接口,除了扩展其他
接口,以便在更多应用程序中提供附加功能
面向框架的风格。许多人使用ApplicationContext
在完全
声明式时尚,甚至不是以编程方式创建它,而是依赖于
支持类,例如ContextLoader
自动实例化ApplicationContext
作为 Java EE Web 应用程序正常启动过程的一部分。
增强BeanFactory
功能以更面向框架的风格,上下文
package 还提供以下功能:
-
通过
MessageSource
接口。 -
通过
ResourceLoader
接口。 -
事件发布,即实现
ApplicationListener
接口 通过使用ApplicationEventPublisher
接口。 -
加载多个(分层)上下文,让每个上下文都专注于一个 特定层,例如应用程序的 Web 层,通过
HierarchicalBeanFactory
接口。
1.15.1. 使用MessageSource
这ApplicationContext
interface 扩展了一个名为MessageSource
和
因此,提供国际化(“i18n”)功能。Spring 还提供了HierarchicalMessageSource
接口,可以分层解析消息。
这些接口共同为 Spring 效果消息提供了基础
分辨率。在这些接口上定义的方法包括:
-
String getMessage(String code, Object[] args, String default, Locale loc)
:基本 用于从MessageSource
.找不到消息时 对于指定的区域设置,将使用默认消息。传入的任何参数都会变成 替换值,使用MessageFormat
标准提供的功能 图书馆。 -
String getMessage(String code, Object[] args, Locale loc)
:本质上与 与前一种方法相同,但有一个区别:不能指定默认消息。如果 找不到消息,则NoSuchMessageException
被抛出。 -
String getMessage(MessageSourceResolvable resolvable, Locale locale)
:所有属性 在前面的方法中使用的也包装在名为MessageSourceResolvable
,您可以将其与此方法一起使用。
当ApplicationContext
加载时,它会自动搜索MessageSource
bean 在上下文中定义。豆子必须有名称messageSource
.如果这样的豆子
找到,则对上述方法的所有调用都委托给消息源。如果没有
消息源,则ApplicationContext
尝试查找包含
同名的豆子。如果是这样,它会使用该 bean 作为MessageSource
.如果ApplicationContext
找不到消息的任何源,则为空DelegatingMessageSource
被实例化,以便能够接受对
上面定义的方法。
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
对象。对于
本示例的目的是,假设上述两个资源包文件的内容
如下:
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
下一个示例显示了一个程序,用于运行MessageSource
功能性。
请记住,所有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);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
println(message)
}
上述程序的结果输出如下:
Alligators rock!
总而言之,MessageSource
在名为beans.xml
哪
存在于类路径的根目录中。这messageSource
bean 定义是指
通过其basenames
财产。三个文件
将列表中传递给basenames
属性作为文件存在于您的根目录下
classpath 并调用format.properties
,exceptions.properties
和windows.properties
分别。
下一个示例显示传递给消息查找的参数。这些论点是
转换为String
对象并插入到查找消息中的占位符中。
<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);
}
}
class Example {
lateinit var messages: MessageSource
fun execute() {
val message = messages.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.ENGLISH)
println(message)
}
}
调用execute()
方法如下:
The userDao argument is required.
关于国际化(“i18n”),Spring 的各种MessageSource
实现遵循与标准 JDK 相同的区域设置解析和回退规则ResourceBundle
.简而言之,继续这个例子messageSource
定义
以前,如果您想解决针对英国(en-GB
) locale,您
将创建名为format_en_GB.properties
,exceptions_en_GB.properties
和windows_en_GB.properties
分别。
通常,区域设置解析由 应用。在以下示例中,(英国)消息所针对的区域设置 resolved 是手动指定的:
# in exceptions_en_GB.properties argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.UK)
println(message)
}
运行上述程序的结果输出如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用MessageSourceAware
接口来获取对任何MessageSource
这已经定义了。在ApplicationContext
实现MessageSourceAware
接口注入了
应用程序上下文的MessageSource
创建和配置 Bean 时。
因为Spring的MessageSource 基于 Java 的ResourceBundle ,它不会合并
捆绑包,但仅使用找到的第一个捆绑包。
具有相同基本名称的后续消息包将被忽略。 |
作为替代方案ResourceBundleMessageSource ,Spring 提供了一个ReloadableResourceBundleMessageSource 类。此变体支持相同的捆绑包
文件格式,但比基于标准 JDK 的 JDK 更灵活ResourceBundleMessageSource 实现。特别是,它允许阅读
来自任何 Spring 资源位置(不仅来自类路径)的文件,并支持热
重新加载捆绑属性文件(同时在两者之间有效地缓存它们)。
请参阅ReloadableResourceBundleMessageSource javadoc 了解详情。 |
1.15.2. 标准和自定义事件
事件处理ApplicationContext
通过ApplicationEvent
class 和ApplicationListener
接口。如果实现ApplicationListener
接口被部署到上下文中,每次ApplicationEvent
发布到ApplicationContext
,则通知该 bean。
从本质上讲,这是标准的 Observer 设计模式。
从 Spring 4.2 开始,活动基础设施得到了显着改进,并提供了
基于注释的模型以及
能够发布任何任意事件(即不一定
从ApplicationEvent ).当这样的对象被发布时,我们将其包装在
为您举办的活动。 |
下表描述了 Spring 提供的标准事件:
事件 | 解释 |
---|---|
|
发布时 |
|
发布时 |
|
发布时 |
|
发布时 |
|
一个特定于 Web 的事件,告诉所有 Bean HTTP 请求已得到服务。这
请求完成后发布事件。此活动只适用于
使用 Spring 的 |
|
的子类 |
您还可以创建和发布自己的自定义事件。以下示例显示了
简单类,扩展 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...
}
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
发布自定义ApplicationEvent
,调用publishEvent()
方法ApplicationEventPublisher
.通常,这是通过创建一个实现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...
}
}
class EmailService : ApplicationEventPublisherAware {
private lateinit var blockedList: List<String>
private lateinit var publisher: ApplicationEventPublisher
fun setBlockedList(blockedList: List<String>) {
this.blockedList = blockedList
}
override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
this.publisher = publisher
}
fun sendEmail(address: String, content: String) {
if (blockedList!!.contains(address)) {
publisher!!.publishEvent(BlockedListEvent(this, address, content))
return
}
// send email...
}
}
在配置时,Spring 容器检测到EmailService
实现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...
}
}
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {
lateinit var notificationAddress: String
override fun onApplicationEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
请注意ApplicationListener
通常参数化为
自定义事件 (BlockedListEvent
在前面的示例中)。这意味着onApplicationEvent()
方法可以保持类型安全,避免任何向下转换的需要。
您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,事件
侦听器同步接收事件。这意味着publishEvent()
方法
块,直到所有侦听器都处理完事件。这样做的一个好处
同步和单线程方法是,当侦听器收到事件时,它
如果事务上下文是
可用。如果需要其他事件发布策略,请参阅 javadoc
对于Spring的ApplicationEventMulticaster
接口
和SimpleApplicationEventMulticaster
配置选项的实现。
以下示例显示了用于注册和配置每个 上述类:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="[email protected]"/>
</bean>
将它们放在一起,当sendEmail()
方法emailService
bean 是
如果有任何应阻止的电子邮件,则调用 a 自定义事件BlockedListEvent
已发布。这blockedListNotifier
bean 被注册为ApplicationListener
并接收BlockedListEvent
,此时它可以
通知有关各方。
Spring 的事件机制专为 Spring Bean 之间的简单通信而设计 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring Integration 项目提供了 完全支持构建轻量级、面向模式、事件驱动的构建 建立在众所周知的 Spring 编程模型之上的架构。 |
基于注释的事件侦听器
您可以使用@EventListener
注解。这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...
}
}
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明它侦听的事件类型, 但是,这一次,使用灵活的名称,并且没有实现特定的监听器接口。 只要实际事件类型,还可以通过泛型缩小事件类型 解析其实现层次结构中的泛型参数。
如果你的方法应该监听多个事件,或者如果你想用 参数,也可以在注解本身上指定事件类型。这 以下示例显示了如何执行此作:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
还可以使用condition
属性
定义SpEL
表达,应匹配
实际调用特定事件的方法。
以下示例显示了如何重写通知程序,仅在content
属性等于my-event
:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
每SpEL
expression 根据专用上下文进行计算。下表列出了
提供给上下文的项,以便您可以将它们用于条件事件处理:
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
事件 |
root 对象 |
实际的 |
|
参数数组 |
root 对象 |
用于调用该方法的参数(作为对象数组)。 |
|
参数名称 |
评估背景 |
任何方法参数的名称。如果由于某种原因,名称不可用
(例如,因为编译后的字节码中没有调试信息),单个
参数也可以使用 |
|
请注意#root.event
允许您访问基础事件,即使您的方法
签名实际上是指已发布的任意对象。
如果您需要在处理另一个事件后发布事件,您可以将 method signature 返回应发布的事件,如以下示例所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。 |
这handleBlockedListEvent()
方法发布一个新的ListUpdateEvent
对于每个BlockedListEvent
它处理的。如果需要发布多个事件,可以返回
一个Collection
或事件数组。
异步侦听器
如果您希望特定监听器异步处理事件,您可以重用定期@Async
支持.
以下示例显示了如何执行此作:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时请注意以下限制:
-
如果异步事件侦听器抛出
Exception
,则不会传播到 访客。看AsyncUncaughtExceptionHandler
了解更多详情。 -
异步事件侦听器方法无法通过返回 价值。如果您需要发布另一个事件作为处理的结果,请注入一个
ApplicationEventPublisher
手动发布事件。
对侦听器进行排序
如果您需要在另一个侦听器之前调用一个侦听器,则可以将@Order
Comments 添加到方法声明中,如以下示例所示:
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
通用事件
您还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>
哪里T
是创建的实际实体的类型。例如,您
可以创建以下监听器定义以仅接收EntityCreatedEvent
对于一个Person
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
// ...
}
由于类型擦除,这仅在触发的事件解析泛型
事件侦听器过滤的参数(即,类似于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()));
}
}
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {
override fun getResolvableType(): ResolvableType? {
return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
}
}
这不仅适用于ApplicationEvent 但您发送的任何任意对象
一个事件。 |
1.15.3. 方便访问低级资源
为了以最佳方式使用和理解应用程序上下文,您应该熟悉
自己与 Spring 的Resource
抽象,如参考资料中所述。
应用程序上下文是ResourceLoader
,可用于加载Resource
对象。
一个Resource
本质上是 JDK 的功能更丰富的版本java.net.URL
类。
事实上,的实现Resource
包装java.net.URL
哪里
适当。一个Resource
几乎可以从任何位置获取低级资源
透明方式,包括从类路径、文件系统位置、任何地方
使用标准 URL 和一些其他变体进行描述。如果资源位置
string 是一个没有任何特殊前缀的简单路径,这些资源来自 is
特定且适合实际的应用程序上下文类型。
您可以配置部署到应用程序上下文中的 bean 以实现特殊的
回调接口,ResourceLoaderAware
,自动回调
初始化时间,应用程序上下文本身作为ResourceLoader
.
您还可以公开类型Resource
,用于访问静态资源。
它们像任何其他属性一样被注入其中。您可以指定这些Resource
属性为简单String
路径,并依赖于从这些文本自动转换
字符串到实际Resource
对象。
提供给ApplicationContext
构造函数实际上是
资源字符串,并且以简单的形式根据特定的
上下文实现。例如ClassPathXmlApplicationContext
对待简单的
location path 作为类路径位置。您还可以使用位置路径(资源字符串)
使用特殊前缀强制从类路径或 URL 加载定义,
无论实际上下文类型如何。
1.15.4. 应用程序启动跟踪
这ApplicationContext
管理 Spring 应用程序的生命周期,并提供丰富的
围绕组件的编程模型。因此,复杂的应用程序可以同样具有
复杂的组件图和启动阶段。
使用特定指标跟踪应用程序启动步骤有助于了解在哪里 在启动阶段花费了时间,但它也可以用作更好的一种方式 了解整个上下文生命周期。
这AbstractApplicationContext
(及其子类)使用ApplicationStartup
,它收集StartupStep
有关各个启动阶段的数据:
-
应用程序上下文生命周期(基本包扫描、配置类管理)
-
Bean 生命周期(实例化、智能初始化、后处理)
-
应用程序事件处理
下面是AnnotationConfigApplicationContext
:
// create a startup step and start recording
StartupStep scanPackages = 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();
// create a startup step and start recording
val scanPackages = 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
一旦创建。
开发人员还可以使用ApplicationStartup
基础设施(如果他们提供自己的)AbstractApplicationContext
子类,或者如果他们希望收集更精确的数据。
ApplicationStartup 仅在应用程序启动期间使用
核心容器;这绝不是 Java 分析器或
像 Micrometer 这样的指标库。 |
开始收集自定义StartupStep
,组件可以获取ApplicationStartup
实例直接从应用程序上下文中实现,使其组件实现ApplicationStartupAware
,
或要求ApplicationStartup
键入任何注射点。
开发人员不应使用"spring.*" 命名空间。
此命名空间保留供内部 Spring 使用,可能会发生变化。 |
1.15.5. Web 应用程序的便捷 ApplicationContext 实例化
您可以创建ApplicationContext
实例,例如,使用ContextLoader
.当然,你也可以创建ApplicationContext
实例
以编程方式使用ApplicationContext
实现。
您可以注册一个ApplicationContext
通过使用ContextLoaderListener
,作为
以下示例显示:
<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
参数。如果参数没有
exist,则监听器使用/WEB-INF/applicationContext.xml
作为默认设置。当
参数确实存在,监听器将String
通过使用预定义的
分隔符(逗号、分号和空格),并将这些值用作其中的位置
搜索应用程序上下文。还支持 Ant 样式路径模式。
示例包括/WEB-INF/*Context.xml
(对于名称以Context.xml
并且驻留在WEB-INF
目录)和/WEB-INF/**/*Context.xml
(对于WEB-INF
).
1.15.6. 部署 SpringApplicationContext
作为 Java EE RAR 文件
可以部署 SpringApplicationContext
作为 RAR 文件,将
上下文及其在 Java EE RAR 部署中所需的所有 bean 类和库 JAR
单位。这相当于引导一个独立的ApplicationContext
(仅托管在 Java EE 环境中)能够访问 Java EE 服务器设施。RAR 部署是部署无头 WAR 文件场景的更自然的替代方案——实际上,一个没有任何 HTTP 入口点的 WAR 文件,仅用于引导 SpringApplicationContext
在 Java EE 环境中。
RAR 部署非常适合不需要 HTTP 入口点但
相反,仅由消息端点和计划作业组成。在这样的上下文中,bean 可以
使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBCDataSource
实例和 JMSConnectionFactory
实例,也可以注册
平台的 JMX 服务器——全部通过 Spring 的标准事务管理和 JNDI
和 JMX 支持设施。应用程序组件也可以与应用程序交互
服务器的 JCAWorkManager
通过 Spring 的TaskExecutor
抽象化。
请参阅SpringContextResourceAdapter
class 用于 RAR 部署中涉及的配置详细信息。
对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:
-
包 所有应用程序类都转换为 RAR 文件(这是一个标准 JAR 文件,具有不同的 文件扩展名)。
-
将所有必需的库 JAR 添加到 RAR 存档的根目录中。
-
添加一个
META-INF/ra.xml
部署描述符(如javadoc 的SpringContextResourceAdapter
) 和相应的 Spring XML bean 定义文件(通常META-INF/applicationContext.xml
). -
将生成的 RAR 文件放入您的 应用程序服务器的部署目录。
这种 RAR 部署单元通常是独立的。它们不会暴露组件
对外界,甚至对同一应用程序的其他模块也不行。与
基于 RARApplicationContext 通常通过与其共享的 JMS 目标发生
其他模块。基于 RAR 的ApplicationContext 例如,还可以安排一些作业
或对文件系统中的新文件(或类似文件)做出反应。如果需要允许同步
从外部访问,它可以(例如)导出 RMI 端点,可以使用
由同一台机器上的其他应用程序模块。 |
1.16.BeanFactory
应用程序接口
这BeanFactory
API 为 Spring 的 IoC 功能提供了底层基础。
其具体合约主要用于与 Spring 的其他部分集成,以及
相关的第三方框架及其DefaultListableBeanFactory
实现
是上级的关键代表GenericApplicationContext
容器。
BeanFactory
和相关接口(例如BeanFactoryAware
,InitializingBean
,DisposableBean
)是其他框架组件的重要集成点。
由于不需要任何注释甚至反射,它们可以非常高效地实现
容器与其组件之间的交互。应用程序级 Bean 可能
使用相同的回调接口,但通常更喜欢声明性依赖
注入,通过注释或编程配置。
请注意,核心BeanFactory
API 级别及其DefaultListableBeanFactory
实现不要对配置格式或任何
要使用的组件注释。所有这些风格都是通过扩展而来的
(例如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
) 和
对共享进行作BeanDefinition
对象作为核心元数据表示形式。
这就是使 Spring 容器如此灵活和可扩展的本质。
1.16.1.BeanFactory
或ApplicationContext
?
本节解释BeanFactory
和ApplicationContext
容器级别及其对引导的影响。
您应该使用ApplicationContext
除非你有充分的理由不这样做,否则使用GenericApplicationContext
及其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现。这些是主要入口指向 Spring 的核心容器,用于所有常见目的:加载配置文件,触发类路径扫描,以编程方式注册 Bean 定义和带注释的类,以及(从 5.0 开始)注册功能 Bean 定义。
因为ApplicationContext
包括BeanFactory
是的
通常推荐在普通BeanFactory
,但已满的场景除外
需要控制 bean 处理。在ApplicationContext
(例如GenericApplicationContext
实现),检测到几种 bean
按约定(即按 Bean 名称或按 Bean 类型 - 特别是后处理器),
虽然平原DefaultListableBeanFactory
对任何特殊的豆子都不可知论。
对于许多扩展容器功能,例如注解处理和 AOP 代理,
这BeanPostProcessor
扩展点是必不可少的。
如果您只使用普通DefaultListableBeanFactory
,此类后处理器不会
默认情况下被检测并激活。这种情况可能会令人困惑,因为
您的 bean 配置实际上没有任何问题。相反,在这种情况下,
容器需要通过其他设置完全引导。
下表列出了BeanFactory
和ApplicationContext
接口和实现。
特征 | BeanFactory |
ApplicationContext |
---|---|---|
Bean 实例化/布线 |
是的 |
是的 |
集成生命周期管理 |
不 |
是的 |
自动 |
不 |
是的 |
自动 |
不 |
是的 |
方便 |
不 |
是的 |
内置 |
不 |
是的 |
向DefaultListableBeanFactory
,
您需要以编程方式调用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
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(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);
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))
// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))
// now actually do the replacement
cfg.postProcessBeanFactory(factory)
在这两种情况下,显式注册步骤都很不方便,即
为什么各种ApplicationContext
变体优于普通DefaultListableBeanFactory
在 Spring-backed 应用程序中,尤其是当
依靠BeanFactoryPostProcessor
和BeanPostProcessor
扩展实例
典型企业设置中的容器功能。
一 |
2. 资源
本章介绍了 Spring 如何处理资源以及如何使用 Spring。它包括以下主题:
2.1. 简介
Java 的标准java.net.URL
各种 URL 前缀的类和标准处理程序,
不幸的是,还不足以让所有人获得低级资源。为
例如,没有标准化的URL
可用于访问
需要从类路径或相对于ServletContext
.虽然可以为专用处理程序注册新的处理程序URL
prefixes(类似于前缀的现有处理程序,例如http:
),这通常是
相当复杂,并且URL
界面仍然缺乏一些理想的功能,
例如检查所指向资源是否存在的方法。
2.2.Resource
接口
Spring的Resource
界面位于org.springframework.core.io.
package是
旨在成为一个更强大的接口,用于抽象对低级资源的访问。这
以下列表概述了Resource
接口。请参阅Resource
javadoc 了解更多详情。
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
boolean isFile();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
ReadableByteChannel readableChannel() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
作为Resource
接口显示时,它扩展了InputStreamSource
接口。以下列表显示了InputStreamSource
接口:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
来自Resource
接口是:
-
getInputStream()
:查找并打开资源,返回InputStream
为 从资源中读取。预计每个调用都会返回一个新的InputStream
.调用方负责关闭流。 -
exists()
:返回一个boolean
指示此资源是否实际存在于 物理形式。 -
isOpen()
:返回一个boolean
指示此资源是否表示句柄 有一条开放的溪流。如果true
这InputStream
不能多次读取,并且 必须只读取一次,然后关闭以避免资源泄漏。返回false
为 所有常用资源实现,但InputStreamResource
. -
getDescription()
:返回此资源的描述,用于处理错误使用资源时的输出。这通常是完全限定的文件名或资源的实际 URL。
其他方法可以让您获得实际的URL
或File
对象表示资源(如果底层实现兼容并支持功能)。
的一些实现Resource
接口还实现了扩展的WritableResource
接口 支持写入它的资源。
Spring 本身使用Resource
抽象,作为参数类型许多方法签名。某些 Spring API 中的其他方法(例如构造函数到ApplicationContext
实现)采取一个String
以朴素或简单的形式用于创建Resource
适合
该上下文实现,或者通过String
path,让
调用方指定特定的Resource
必须创建和使用实现。
虽然Resource
interface 在 Spring 中被大量使用,而 Spring 实际上是
在您自己的代码中单独用作通用实用程序类非常方便,以便访问
资源,即使您的代码不知道或关心 Spring 的任何其他部分。
虽然这将您的代码耦合到 Spring,但它实际上只将其耦合到这一小集
实用程序类,它可以作为更强大的替代品URL
并且可以是
被认为等同于您为此目的使用的任何其他库。
这Resource 抽象不会取代功能。它把它包装在哪里
可能。例如,一个UrlResource 包装一个 URL 并使用URL 做它的
工作。 |
2.3. 内置Resource
实现
Spring 包括几个内置的Resource
实现:
如需完整列表Resource
Spring 中可用的实现,请参阅
“所有已知实现类”部分Resource
javadoc 的文档。
2.3.1.UrlResource
UrlResource
包装一个java.net.URL
并且可用于访问任何对象通常可以通过 URL 访问,例如文件、HTTPS 目标、FTP 目标和 别人。 所有 URL 都有一个标准化的String
表示形式,以便适当的标准化前缀用于指示一种 URL 类型与另一种 URL 类型。这包括file:
用于访问文件系统路径,https:
用于通过HTTPS 协议访问资源,ftp:
用于通过 FTP 等访问资源。
一个UrlResource
是由 Java 代码通过显式使用UrlResource
构造 函数
但通常在调用 API 方法时隐式创建,该方法采用String
参数表示路径。对于后一种情况,JavaBeansPropertyEditor
最终决定哪种类型的Resource
创造。如果路径字符串包含
已知(即属性编辑器)前缀(例如classpath:
),它会创建一个
适当的专业Resource
对于该前缀。但是,如果它无法识别
前缀,它假设该字符串是标准 URL 字符串,并创建UrlResource
.
2.3.2.ClassPathResource
此类表示应从类路径获取的资源。它使用线程上下文类加载器、给定类加载器或给定类加载资源。
这Resource
实现支持解析为java.io.File
如果类路径资源驻留在文件系统中,但不适用于驻留在jar 中的类路径资源,并且尚未扩展(通过 servlet 引擎或任何环境)到文件系统。为了解决这个问题,各种Resource
实现始终支持resolution 作为java.net.URL
.
一个ClassPathResource
是由 Java 代码通过显式使用ClassPathResource
构造函数,但通常在调用 API 方法时隐式创建,该方法采用String
参数表示路径。对于后一种情况,JavaBeansPropertyEditor
识别特殊前缀,classpath:
,在字符串路径上,并且创建一个ClassPathResource
在这种情况下。
2.3.3.FileSystemResource
这是一个Resource
实现java.io.File
处理。 它还支持java.nio.file.Path
handles,应用 Spring 的标准基于字符串的路径转换,但通过java.nio.file.Files
应用程序接口。 对于纯java.nio.path.Path
基于支持使用PathResource
相反。FileSystemResource
支持分辨率作为File
并作为URL
.
2.3.4.PathResource
这是一个Resource
实现java.nio.file.Path
句柄,执行所有作和转换,通过Path
应用程序接口。 它支持分辨率作为File
和 作为URL
并且还实现了扩展的WritableResource
接口。PathResource
实际上是一种纯粹的java.nio.path.Path
基于替代FileSystemResource
跟 不同createRelative
行为。
2.3.5.ServletContextResource
这是一个Resource
实现ServletContext
解释相关 Web 应用程序根目录中的相对路径的资源。
它始终支持流访问和 URL 访问,但允许java.io.File
仅访问权限
当 Web 应用程序存档被扩展并且资源物理上位于
文件系统。无论它是否被扩展并在文件系统上或被访问
直接从 JAR 或其他地方(如数据库(可以想象))实际上是
依赖于 Servlet 容器。
2.4.ResourceLoader
接口
这ResourceLoader
接口旨在由可以返回的对象实现
(即负载)Resource
实例。以下列表显示了ResourceLoader
接口定义:
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有应用程序上下文都实现了ResourceLoader
接口。因此,所有
应用程序上下文可用于获取Resource
实例。
当您调用getResource()
在特定应用程序上下文和位置路径上
specified 没有特定的前缀,则返回一个Resource
类型,即
适合该特定应用程序上下文。例如,假设以下情况
代码片段针对ClassPathXmlApplicationContext
实例:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
val template = ctx.getResource("some/resource/path/myTemplate.txt")
针对ClassPathXmlApplicationContext
,则该代码返回ClassPathResource
.如果
相同的方法针对FileSystemXmlApplicationContext
例如,它会
返回一个FileSystemResource
.对于一个WebApplicationContext
,它将返回一个ServletContextResource
.它同样会为每个上下文返回适当的对象。
因此,您可以以适合特定应用程序的方式加载资源 上下文。
另一方面,你也可以强制ClassPathResource
无论
应用程序上下文类型,通过指定特殊的classpath:
前缀,如下所示
示例显示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,您可以强制UrlResource
通过指定任何标准来使用java.net.URL
前缀。以下示例使用file
和https
前缀:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
下表总结了转换策略String
对象设置为Resource
对象:
前缀 | 示例 | 解释 |
---|---|---|
类路径: |
|
从类路径加载。 |
文件: |
|
加载为 |
https: |
|
加载为 |
(无) |
|
取决于基础 |
2.5.ResourcePatternResolver
接口
这ResourcePatternResolver
接口是ResourceLoader
接口
它定义了解决位置模式的策略(例如,Ant 样式的路径
pattern) 变成Resource
对象。
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
如上所示,该接口还定义了一个特殊的classpath*:
资源前缀
对于类路径中的所有匹配资源。请注意,资源位置为
在这种情况下,预计是一条没有占位符的路径——例如,classpath*:/config/beans.xml
.JAR 文件或类路径中的不同目录可以
包含具有相同路径和相同名称的多个文件。有关更多详细信息,请参阅应用程序上下文构造函数资源路径中的通配符及其小节
在通配符支持上,使用classpath*:
资源前缀。
一个传入的ResourceLoader
(例如,通过ResourceLoaderAware
语义)是否可以检查
它也实现了这个扩展接口。
PathMatchingResourcePatternResolver
是一个可用的独立实现
在ApplicationContext
并且也被ResourceArrayPropertyEditor
为
填充Resource[]
bean 属性。PathMatchingResourcePatternResolver
能够
将指定的资源位置路径解析为一个或多个匹配的Resource
对象。
源路径可以是与目标具有一对一映射的简单路径Resource
,或者可以包含特殊的classpath*:
前缀和/或内部
Ant 样式正则表达式(使用 Spring 的org.springframework.util.AntPathMatcher
实用程序)。后者都有效
通配符。
默认值 |
2.6.ResourceLoaderAware
接口
这ResourceLoaderAware
interface 是一个特殊的回调接口,用于识别
组件,这些组件希望提供ResourceLoader
参考。以下列表
显示了ResourceLoaderAware
接口:
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当类实现ResourceLoaderAware
并部署到应用程序上下文中
(作为 Spring 管理的 bean),它被识别为ResourceLoaderAware
通过应用程序
上下文。然后,应用程序上下文调用setResourceLoader(ResourceLoader)
,
将自身作为参数提供(请记住,Spring 中的所有应用程序上下文都实现
这ResourceLoader
接口)。
由于ApplicationContext
是一个ResourceLoader
,则 bean 还可以实现ApplicationContextAware
接口,并直接使用提供的应用程序上下文来
加载资源。但是,一般来说,最好使用专门的ResourceLoader
界面,如果这就是您所需要的。代码将仅与资源加载耦合
接口(可以被认为是一个实用程序接口),而不是整个 SpringApplicationContext
接口。
在应用组件中,您还可以依赖于ResourceLoader
如
实现ResourceLoaderAware
接口。传统的
constructor
和byType
自动布线模式(如自动布线协作者中所述)
能够提供ResourceLoader
对于构造函数参数或
setter 方法参数。为了获得更大的灵活性(包括能够
autowire 字段和多个参数方法),请考虑使用基于注释的
自动接线功能。在这种情况下,ResourceLoader
自动连接到字段中,
构造函数参数或方法参数,该参数需要ResourceLoader
类型为 long
由于相关字段、构造函数或方法携带@Autowired
注解。
有关更多信息,请参阅用@Autowired
.
加载一个或多个Resource 包含通配符的资源路径的对象
或利用特殊的classpath*: resource 前缀,请考虑使用ResourcePatternResolver 自动连接到您的
应用程序组件而不是ResourceLoader . |
2.7. 资源作为依赖项
如果 bean 本身要通过某种方式确定和提供资源路径
动态进程的 bean 使用ResourceLoader
或ResourcePatternResolver
接口来加载资源。例如,考虑负载
某种模板的模板,其中所需的特定资源取决于
用户的角色。如果资源是静态的,那么消除使用ResourceLoader
接口(或ResourcePatternResolver
interface)完全,则具有
bean 公开了Resource
它需要的属性,并期望它们被注入其中。
注入这些属性变得微不足道的是,所有应用程序上下文
注册并使用特殊的 JavaBeansPropertyEditor
,可以转换String
路径
自Resource
对象。例如,以下内容MyBean
类有一个template
类型的属性Resource
.
package example;
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
// ...
}
class MyBean(var template: Resource)
在 XML 配置文件中,template
属性可以使用简单的
string,如以下示例所示:
<bean id="myBean" class="example.MyBean">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
请注意,资源路径没有前缀。因此,由于应用程序上下文
本身将用作ResourceLoader
,则资源通过ClassPathResource
一个FileSystemResource
或ServletContextResource
根据
应用程序上下文的确切类型。
如果您需要强制执行特定的Resource
type 时,可以使用前缀。这
以下两个示例演示如何强制ClassPathResource
和UrlResource
(这
后者用于访问文件系统中的文件):
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
如果MyBean
类被重构以用于注释驱动的配置,则
路径到myTemplate.txt
可以存储在名为template.path
——例如,
在提供给 Spring 的属性文件中Environment
(参见环境抽象)。然后可以通过@Value
使用属性占位符进行注释(请参阅用@Value
).Spring会
将模板路径的值检索为字符串,并将特殊PropertyEditor
将
将字符串转换为Resource
对象要注入到MyBean
构造 函数。
以下示例演示了如何实现此目的。
@Component
public class MyBean {
private final Resource template;
public MyBean(@Value("${template.path}") Resource template) {
this.template = template;
}
// ...
}
@Component
class MyBean(@Value("\${template.path}") private val template: Resource)
如果我们想支持在多个
类路径中的位置——例如,在类路径中的多个 jar 中——我们可以
使用特殊的classpath*:
前缀和通配符来定义templates.path
key 作为classpath*:/config/templates/*.txt
.如果我们重新定义MyBean
class 如下所示,
Spring 会将模板路径模式转换为Resource
对象
可以注入MyBean
构造 函数。
@Component
public class MyBean {
private final Resource[] templates;
public MyBean(@Value("${templates.path}") Resource[] templates) {
this.templates = templates;
}
// ...
}
@Component
class MyBean(@Value("\${templates.path}") private val templates: Resource[])
2.8. 应用程序上下文和资源路径
本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 与 XML 一起使用、如何使用通配符和其他详细信息。
2.8.1. 构建应用程序上下文
应用程序上下文构造函数(通常适用于特定的应用程序上下文类型) 将字符串或字符串数组作为资源的位置路径,例如 构成上下文定义的 XML 文件。
当这样的位置路径没有前缀时,特定的Resource
类型构建自
该路径并用于加载 bean 定义取决于并且适用于
具体应用上下文。例如,请考虑以下示例,该示例创建了ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
Bean 定义是从类路径加载的,因为ClassPathResource
是
使用。但是,请考虑以下示例,该示例会创建FileSystemXmlApplicationContext
:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
现在,Bean 定义从文件系统位置加载(在本例中,相对于 当前工作目录)。
请注意,使用特殊的classpath
前缀或标准 URL 前缀
位置路径覆盖默认类型Resource
创建以加载 bean
定义。请考虑以下示例:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
用FileSystemXmlApplicationContext
从类路径加载 Bean 定义。
但是,它仍然是一个FileSystemXmlApplicationContext
.如果它随后用作ResourceLoader
,任何无前缀的路径仍被视为文件系统路径。
构建ClassPathXmlApplicationContext
实例 — 快捷方式
这ClassPathXmlApplicationContext
公开了许多构造函数以启用
方便的实例化。基本思想是你只能提供一个字符串数组
仅包含 XML 文件本身的文件名(不包含前导路径
信息),并提供Class
.这ClassPathXmlApplicationContext
然后派生
来自提供的类的路径信息。
考虑以下目录布局:
com/ example/ services.xml repositories.xml MessengerService.class
以下示例显示了如何ClassPathXmlApplicationContext
实例由
在名为services.xml
和repositories.xml
(这些在
classpath)可以实例化:
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)
请参阅ClassPathXmlApplicationContext
javadoc 以获取有关各种构造函数的详细信息。
2.8.2. 应用程序上下文构造函数资源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径(如
前面显示),每个目标都有一对一的映射Resource
或
或者,可以包含特殊的classpath*:
前缀或内部 Ant 样式模式(通过使用 Spring 的PathMatcher
实用程序)。后者都有效
通配符。
此机制的一个用途是当您需要执行组件样式的应用程序汇编时。 都 组件可以将上下文定义片段发布到已知的位置路径,并且,当使用前缀为classpath*:
,则自动拾取所有组件片段。
请注意,此通配符特定于在应用程序上下文中使用资源路径构造函数(或当您使用PathMatcher
实用程序类层次结构)并且是
在施工时解决。它与Resource
类型本身。
您不能使用classpath*:
前缀来构造实际的Resource
如
资源一次仅指向一个资源。
Ant式图案
路径位置可以包含 Ant 样式模式,如以下示例所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径位置包含 Ant 样式模式时,解析器会遵循更复杂的模式
尝试解析通配符的过程。它会产生一个Resource
对于通往
最后一个非通配符段,并从中获取 URL。如果此 URL 不是jar:
URL 或
特定于容器的变体(例如zip:
在 WebLogic 中,wsjar
在 WebSphere 中,依此类推),
一个java.io.File
从中获取,并用于通过遍历
文件系统。对于 jar URL,解析器要么获得java.net.JarURLConnection
或手动解析 jar URL,然后遍历
用于解析通配符的 jar 文件的内容。
对便携性的影响
如果指定的路径已经是file
URL(隐式的,因为基ResourceLoader
是文件系统之一或显式),通配符保证
以完全便携的方式工作。
如果指定的路径是classpath
位置,解析器必须获取最后一个
非通配符路径段 URL,方法是将Classloader.getResource()
叫。既然如此
只是路径的一个节点(不是末尾的文件),它实际上是未定义的(在ClassLoader
javadoc)在这种情况下返回的 URL 类型。在实践中,
它始终是一个java.io.File
表示目录(其中 ClassPath 资源
解析为文件系统位置)或某种 jar URL(其中 classpath 资源
解析为 jar 位置)。尽管如此,此作仍存在可移植性问题。
如果为最后一个非通配符段获取了jar URL,则解析器必须能够
获取一个java.net.JarURLConnection
或手动解析 jar URL,以便能够
遍历 jar 的内容并解析通配符。这在大多数环境中确实有效
但在其他方面失败,我们强烈建议资源的通配符解析
来自 jar 在依赖它之前,请在您的特定环境中进行彻底测试。
这classpath*:
前缀
在构造基于 XML 的应用程序上下文时,位置字符串可以使用
特殊classpath*:
prefix,如以下示例所示:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
此特殊前缀指定与给定名称匹配的所有类路径资源
必须获取(在内部,这基本上是通过调用ClassLoader.getResources(…)
),然后合并形成最终应用程序
上下文定义。
通配符类路径依赖于getResources() 基础的方法ClassLoader .由于现在大多数应用程序服务器都提供自己的ClassLoader 实现时,行为可能会有所不同,尤其是在处理 jar 文件时。一个
简单的测试,检查是否classpath* works 是使用ClassLoader 从中加载文件
在类路径上的 jar 中:getClass().getClassLoader().getResources("<someFileInsideTheJar>") .尝试此测试
具有相同名称但位于两个不同位置的文件,例如,文件
具有相同的名称和相同的路径,但在类路径上的不同 jar 中。如果
返回不适当的结果,请检查应用程序服务器文档中的设置
这可能会影响ClassLoader 行为。 |
您还可以将classpath*:
前缀加上PathMatcher
模式
位置路径的其余部分(例如classpath*:META-INF/*-beans.xml
).在这个
case 中,解决策略相当简单:AClassLoader.getResources()
call 是
用于最后一个非通配符路径段,以获取
类加载器层次结构,然后,在每个资源中,相同的PathMatcher
分辨率
前面描述的策略用于通配符子路径。
与通配符相关的其他说明
请注意classpath*:
,当与Ant风格的图案结合使用时,仅有效
在模式启动之前至少有一个根目录,除非实际的
目标文件驻留在文件系统中。这意味着诸如classpath*:*.xml
可能不会从 jar 文件的根目录中检索文件,而只能从 jar 文件的根目录中检索文件
来自扩展目录的根目录。
Spring 检索类路径条目的能力源自 JDK 的ClassLoader.getResources()
方法,它仅返回
空字符串(表示要搜索的潜在根)。Spring 评估URLClassLoader
运行时配置和java.class.path
jar 文件中的清单
同样,但这并不能保证会导致可移植行为。
扫描类路径包需要存在相应的目录
类路径中的条目。当您使用 Ant 构建 JAR 时,请不要激活 在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。 这里也强烈建议将资源放入专用目录, 避免上述搜索 JAR 文件根级别的可移植性问题。 |
Ant式图案classpath:
不保证资源找到匹配项
resources 如果要搜索的根包在多个类路径位置可用。
请考虑以下资源位置示例:
com/mycompany/package1/service-context.xml
现在考虑一个 Ant 风格的路径,有人可能会使用它来尝试查找该文件:
classpath:com/mycompany/**/service-context.xml
此类资源可能仅存在于类路径中的一个位置,但当路径(例如
前面的示例用于尝试解决它,解析器在(第一个)
返回的 URL 由getResource("com/mycompany");
.如果此基础包节点存在于
倍数ClassLoader
locations,则所需的资源可能不存在于第一个
位置找到。因此,在这种情况下,您应该更喜欢使用classpath*:
使用
相同的 Ant 样式模式,它搜索包含com.mycompany
基本包:classpath*:com/mycompany/**/service-context.xml
.
2.8.3.FileSystemResource
警告
一个FileSystemResource
未附加到FileSystemApplicationContext
(那
是,当FileSystemApplicationContext
不是实际的ResourceLoader
) 款待
绝对路径和相对路径,如您所期望的那样。相对路径相对于
当前工作目录,而绝对路径相对于
文件系统。
但是,出于向后兼容性(历史)原因,当FileSystemApplicationContext
是ResourceLoader
.这FileSystemApplicationContext
全部附着的力FileSystemResource
实例
将所有位置路径视为相对路径,无论它们是否以前导斜杠开头。
在实践中,这意味着以下示例是等效的:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
以下示例也是等效的(尽管它们不同是有意义的,因为一个case 是相对的,另一个是绝对的):
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
在实践中,如果您需要真正的绝对文件系统路径,则应避免使用
绝对路径与FileSystemResource
或FileSystemXmlApplicationContext
和
强制使用UrlResource
通过使用file:
URL 前缀。以下示例
展示如何做到这一点:
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
3. 验证、数据绑定和类型转换
将验证视为业务逻辑有利有弊,Spring 提供了
不排除其中任何一个的验证(和数据绑定)设计。
具体来说,验证不应与 Web 层绑定,并且应该易于本地化,
并且应该可以插入任何可用的验证器。考虑到这些担忧,
Spring 提供了一个Validator
既基本又非常可用的契约
在应用程序的每一层中。
数据绑定对于让用户输入动态绑定到域非常有用
应用程序模型(或用于处理用户输入的任何对象)。Spring
提供了恰如其分的名称DataBinder
就是为了做到这一点。这Validator
和DataBinder
构成validation
package,主要用于但不用于
仅限于 Web 层。
这BeanWrapper
是 Spring Framework 中的一个基本概念,并且被大量使用
的地方。但是,您可能不需要使用BeanWrapper
径直。但是,由于这是参考文档,我们认为一些解释
可能是有序的。我们解释BeanWrapper
在本章中,因为,如果你是
要使用它,在尝试将数据绑定到对象时,您很可能会这样做。
Spring的DataBinder
和较低级别的BeanWrapper
两者都使用PropertyEditorSupport
实现来分析和格式化属性值。这PropertyEditor
和PropertyEditorSupport
类型是 JavaBeans 规范的一部分,也是
在本章中解释。Spring 3 引入了一个core.convert
提供
通用类型转换工具,以及用于
设置 UI 字段值的格式。您可以使用这些包作为更简单的替代方案PropertyEditorSupport
实现。本章也讨论了它们。
Spring 通过设置基础设施和适配器支持 Java Bean 验证
Spring自己的Validator
合同。应用程序可以全局启用一次 Bean 验证,
如 Java Bean 验证中所述,并专门将其用于所有验证
需要。在 Web 层中,应用程序可以进一步注册控制器本地 SpringValidator
实例数DataBinder
,如配置DataBinder
,可以
对于插入自定义验证逻辑很有用。
3.1. 使用 Spring 的验证器接口进行验证
Spring 具有Validator
可用于验证对象的接口。这Validator
接口的工作原理是使用Errors
对象,以便在验证时,
验证者可以向Errors
对象。
考虑以下小数据对象的示例:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
class Person(val name: String, val age: Int)
下一个示例提供了Person
类通过实现
以下两种方法org.springframework.validation.Validator
接口:
-
supports(Class)
:可以这样吗Validator
验证提供的实例Class
? -
validate(Object, org.springframework.validation.Errors)
:验证给定对象 并且,如果出现验证错误,则将这些错误与给定的Errors
对象。
实现Validator
相当简单,尤其是当您知道ValidationUtils
Spring Framework 也提供的 helper 类。以下内容
示例机具Validator
为Person
实例:
public class PersonValidator implements Validator {
/**
* This Validator validates only Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
class PersonValidator : Validator {
/**
* This Validator validates only Person instances
*/
override fun supports(clazz: Class<*>): Boolean {
return Person::class.java == clazz
}
override fun validate(obj: Any, e: Errors) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
val p = obj as Person
if (p.age < 0) {
e.rejectValue("age", "negativevalue")
} else if (p.age > 110) {
e.rejectValue("age", "too.darn.old")
}
}
}
这static
rejectIfEmpty(..)
方法ValidationUtils
class 用于
拒绝name
属性(如果是)null
或空字符串。看看ValidationUtils
Java文档
查看除了前面显示的示例之外,它还提供哪些功能。
虽然当然可以实现单个Validator
类来验证每个
对于富对象中的嵌套对象,封装验证可能更好
逻辑Validator
实现。一个简单的
“丰富”对象的示例是Customer
由两个组成String
属性(名字和第二个名字)和复合体Address
对象。Address
对象
可以独立使用Customer
对象,因此AddressValidator
已经实施。如果你想要你的CustomerValidator
重用包含的逻辑
在AddressValidator
类,无需诉诸复制和粘贴,您可以
dependency-inject 或实例化AddressValidator
在您的CustomerValidator
,
如以下示例所示:
public class CustomerValidator implements Validator {
private final Validator addressValidator;
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException("The supplied [Validator] is " +
"required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException("The supplied [Validator] must " +
"support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
class CustomerValidator(private val addressValidator: Validator) : Validator {
init {
if (addressValidator == null) {
throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
}
if (!addressValidator.supports(Address::class.java)) {
throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
}
}
/*
* This Validator validates Customer instances, and any subclasses of Customer too
*/
override fun supports(clazz: Class<>): Boolean {
return Customer::class.java.isAssignableFrom(clazz)
}
override fun validate(target: Any, errors: Errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
val customer = target as Customer
try {
errors.pushNestedPath("address")
ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
} finally {
errors.popNestedPath()
}
}
}
验证错误将报告给Errors
对象传递给验证者。在这种情况下
Spring Web MVC 的 MVC,您可以使用<spring:bind/>
标记来检查错误消息,但
您还可以检查Errors
反对自己。有关
它提供的方法可以在 JavaDoc 中找到。
3.2. 将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍输出对应的消息
验证错误。在上一节所示的示例中,
我们拒绝了name
和age
领域。如果我们想使用MessageSource
,我们可以使用我们在拒绝字段时提供的错误代码来做到这一点
(在本例中为“姓名”和“年龄”)。当您调用(直接或间接地,通过使用
例如,ValidationUtils
类)rejectValue
或另一个reject
方法
从Errors
接口,底层实现不仅注册了
传入,但也注册了许多额外的错误代码。这MessageCodesResolver
确定哪些错误代码Errors
接口寄存器。默认情况下,DefaultMessageCodesResolver
,它(例如)不仅注册消息
使用您提供的代码,但也会注册包含您传递的字段名称的消息
设置为 reject 方法。因此,如果您使用rejectValue("age", "too.darn.old")
,
除了too.darn.old
code,Spring 也注册too.darn.old.age
和too.darn.old.age.int
(第一个包括字段名称,第二个包括类型
的领域)。这样做是为了方便开发人员在定位错误消息时提供帮助。
有关MessageCodesResolver
并且可以找到默认策略
在 javadoc 的MessageCodesResolver
和DefaultMessageCodesResolver
,
分别。
3.3. Bean作和BeanWrapper
这org.springframework.beans
包遵循 JavaBeans 标准。
JavaBean 是一个具有默认无参数构造函数的类,它遵循
命名约定,其中(例如)名为bingoMadness
愿意
有一个 setter 方法setBingoMadness(..)
和一个 getter 方法getBingoMadness()
.为
有关 JavaBeans 和规范的更多信息,请参阅 Javabeans。
bean 包中一个非常重要的类是BeanWrapper
接口及其
相应的实现 (BeanWrapperImpl
).正如 javadoc 中引用的那样,BeanWrapper
提供设置和获取属性值的功能(单独或在
bulk)、获取属性描述符和查询属性以确定它们是否
可读或可写。此外,BeanWrapper
提供对嵌套属性的支持,
启用子属性上的属性设置到无限深度。这BeanWrapper
还支持添加标准 JavaBeans 的功能PropertyChangeListeners
和VetoableChangeListeners
,无需在目标类中支持代码。
最后但并非最不重要的一点是,BeanWrapper
支持设置索引属性。
这BeanWrapper
通常不直接由应用程序代码使用,而是由DataBinder
和BeanFactory
.
方式BeanWrapper
Works 的名称部分表明:它将 bean 包装成
对该 Bean 执行作,例如设置和检索属性。
3.3.1. 设置和获取基本属性和嵌套属性
设置和获取属性是通过setPropertyValue
和getPropertyValue
重载方法变体BeanWrapper
.请参阅他们的 Javadoc
详。下表显示了这些约定的一些示例:
表达 | 解释 |
---|---|
|
指示属性 |
|
指示嵌套属性 |
|
指示索引属性的第三个元素 |
|
指示由 |
(如果您不打算与
这BeanWrapper
径直。如果您仅使用DataBinder
和BeanFactory
及其默认实现,您应该跳到部分PropertyEditors
.)
以下两个示例类使用BeanWrapper
获取和设置
性能:
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
class Company {
var name: String? = null
var managingDirector: Employee? = null
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
class Employee {
var name: String? = null
var salary: Float? = null
}
以下代码片段显示了如何检索和作某些
实例化的属性Company
s 和Employee
s:
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)
// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)
// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?
3.3.2. 内置PropertyEditor
实现
Spring 使用PropertyEditor
实现Object
和String
.它可以很方便
以与对象本身不同的方式表示属性。例如,一个Date
可以用人类可读的方式表示(作为String
:'2007-14-09'
),而
我们仍然可以将人类可读的形式转换回原始日期(或者,甚至
最好将以人类可读形式输入的任何日期转换回Date
对象)。这
可以通过注册类型java.beans.PropertyEditor
.在BeanWrapper
或
或者,在特定的 IoC 容器中(如上一章所述),给出它
了解如何将属性转换为所需类型的知识。有关的更多信息PropertyEditor
看的 javadocjava.beans
来自 Oracle 的包.
在 Spring 中使用属性编辑的几个示例:
-
在 Bean 上设置属性是通过使用
PropertyEditor
实现。 当您使用String
作为您声明的某个 bean 的属性的值 在 XML 文件中,Spring(如果相应属性的 setter 具有Class
参数)使用ClassEditor
尝试将参数解析为Class
对象。 -
在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种类型来完成的 之
PropertyEditor
可以在CommandController
.
Spring 内置了许多PropertyEditor
实施,让生活变得轻松。
它们都位于org.springframework.beans.propertyeditors
包。默认情况下,大多数(但不是全部,如下表所示)由BeanWrapperImpl
.如果属性编辑器可以以某种方式进行配置,您可以
仍然注册您自己的变体以覆盖默认变体。下表描述了
各种PropertyEditor
Spring 提供的实现:
类 | 解释 |
---|---|
|
字节数组的编辑器。将字符串转换为其相应的字节
交涉。默认注册者 |
|
将类表示为实际类的字符串,反之亦然。当
class 未找到,则 |
|
可自定义的属性编辑器 |
|
集合的属性编辑器,转换任何源 |
|
可自定义的属性编辑器 |
|
适用于任何的可定制属性编辑器 |
|
将字符串解析为 |
|
单向属性编辑器,可以接受字符串并生成(通过
中间 |
|
可以将字符串解析为 |
|
可以将字符串解析为 |
|
可以转换字符串(格式化为 javadoc 中定义的格式 |
|
修剪字符串的属性编辑器。可选地允许转换空字符串
变成一个 |
|
可以将 URL 的字符串表示形式解析为实际的 |
Spring 使用java.beans.PropertyEditorManager
设置属性的搜索路径
可能需要的编辑。搜索路径还包括sun.bean.editors
哪
包括PropertyEditor
诸如Font
,Color
,以及大部分
原始类型。另请注意,标准 JavaBeans 基础架构
自动发现PropertyEditor
类(无需注册它们
显式),如果它们与它们处理的类位于同一包中并且具有相同的
name 作为该类,并带有Editor
附加。例如,可以有以下内容
类和包结构,这对于SomethingEditor
类
识别并用作PropertyEditor
为Something
-typed 属性。
com chank pop Something SomethingEditor // the PropertyEditor for the Something class
请注意,您也可以使用BeanInfo
JavaBeans 机制也是如此
(此处在某种程度上进行了描述)。这
以下示例使用BeanInfo
显式注册一个或多个PropertyEditor
实例具有关联类的属性:
com chank pop Something SomethingBeanInfo // the BeanInfo for the Something class
以下 Java 源代码用于引用SomethingBeanInfo
类
员工 aCustomNumberEditor
使用age
属性的Something
类:
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
@Override
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
}
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
class SomethingBeanInfo : SimpleBeanInfo() {
override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
try {
val numberPE = CustomNumberEditor(Int::class.java, true)
val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
override fun createPropertyEditor(bean: Any): PropertyEditor {
return numberPE
}
}
return arrayOf(ageDescriptor)
} catch (ex: IntrospectionException) {
throw Error(ex.toString())
}
}
}
注册其他自定义PropertyEditor
实现
当将 bean 属性设置为字符串值时,Spring IoC 容器最终使用
标准 JavaBeansPropertyEditor
实现将这些字符串转换为
财产。Spring 预注册了许多自定义PropertyEditor
实现(例如,将
将表示为字符串的类名转换为Class
对象)。此外
Java 的标准 JavaBeansPropertyEditor
查找机制允许PropertyEditor
对于类,请适当命名并放置在与类相同的包中
它为其提供支持,以便可以自动找到它。
如果需要注册其他自定义PropertyEditors
,有几种机制
可用。最手动的方法,通常不方便或
recommended,就是使用registerCustomEditor()
方法ConfigurableBeanFactory
接口,假设你有一个BeanFactory
参考。
另一种(稍微方便一点)的机制是使用专门的豆厂
后处理器调用CustomEditorConfigurer
.虽然您可以使用 bean 工厂后处理器
跟BeanFactory
实现,则CustomEditorConfigurer
有一个
nested 属性设置,因此我们强烈建议您将其与ApplicationContext
,您可以在其中以与任何其他 Bean 类似的方式部署它,并且
它可以被自动检测和应用。
请注意,所有 bean 工厂和应用程序上下文都会自动使用许多
内置属性编辑器,通过使用BeanWrapper
自
处理属性转换。标准属性编辑器BeanWrapper
寄存器在上一节中列出。
此外ApplicationContext
也会覆盖或添加其他编辑器来处理
以适合特定应用程序上下文类型的方式进行资源查找。
标准 JavaBeansPropertyEditor
实例用于转换属性值
表示为属性的实际复杂类型的字符串。您可以使用CustomEditorConfigurer
,豆厂后处理器,方便添加
支持其他PropertyEditor
instances 到ApplicationContext
.
考虑以下示例,该示例定义了一个名为ExoticType
和
另一个名为DependsOnExoticType
,需要ExoticType
设置为属性:
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
package example
class ExoticType(val name: String)
class DependsOnExoticType {
var type: ExoticType? = null
}
当事情设置正确后,我们希望能够将类型属性分配为
string,其中PropertyEditor
转换为实际的ExoticType
实例。以下 bean 定义显示了如何设置此关系:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
这PropertyEditor
实现可能类似于以下内容:
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
// converts string representation to ExoticType object
package example
import java.beans.PropertyEditorSupport
class ExoticTypeEditor : PropertyEditorSupport() {
override fun setAsText(text: String) {
value = ExoticType(text.toUpperCase())
}
}
最后,以下示例展示了如何使用CustomEditorConfigurer
以注册新的PropertyEditor
使用ApplicationContext
,然后它将能够根据需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
用PropertyEditorRegistrar
向 Spring 容器注册属性编辑器的另一种机制是
创建并使用PropertyEditorRegistrar
.此界面在以下情况下特别有用
您需要在几种不同的情况下使用同一组属性编辑器。
您可以编写相应的注册商并在每种情况下重复使用它。PropertyEditorRegistrar
实例与名为PropertyEditorRegistry
,一个由 Spring 实现的接口BeanWrapper
(和DataBinder
).PropertyEditorRegistrar
实例特别方便
当与CustomEditorConfigurer
(此处所述),它公开了一个属性
叫setPropertyEditorRegistrars(..)
.PropertyEditorRegistrar
已添加实例
设置为CustomEditorConfigurer
以这种方式可以轻松分享DataBinder
和
Spring MVC 控制器。此外,它避免了自定义同步的需要
编辑:APropertyEditorRegistrar
有望创造新鲜感PropertyEditor
每个 Bean 创建尝试的实例。
以下示例演示如何创建自己的PropertyEditorRegistrar
实现:
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
package com.foo.editors.spring
import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {
override fun registerCustomEditors(registry: PropertyEditorRegistry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())
// you could register as many custom property editors as are required here...
}
}
另请参阅org.springframework.beans.support.ResourceEditorRegistrar
举个例子PropertyEditorRegistrar
实现。请注意,在实现registerCustomEditors(..)
方法,它会创建每个属性编辑器的新实例。
下一个示例演示如何配置CustomEditorConfigurer
并注入实例
我们的CustomPropertyEditorRegistrar
进入其中:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后(有点偏离本章的重点)给你们这些人
使用 Spring 的 MVC Web 框架,使用PropertyEditorRegistrar
在
与数据绑定 Web 控制器结合使用可以非常方便。以下内容
示例使用PropertyEditorRegistrar
在实施@InitBinder
方法:
@Controller
public class RegisterUserController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
@InitBinder
void initBinder(WebDataBinder binder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods related to registering a User
}
@Controller
class RegisterUserController(
private val customPropertyEditorRegistrar: PropertyEditorRegistrar) {
@InitBinder
fun initBinder(binder: WebDataBinder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder)
}
// other methods related to registering a User
}
这种风格的PropertyEditor
注册可以导致简洁的代码(实现
的@InitBinder
方法只有一行长),并且让PropertyEditor
注册码封装在类中,然后在尽可能多的控制器之间共享
根据需要。
3.4. Spring Type Conversion
Spring 3 引入了一个core.convert
提供通用类型转换的包
系统。系统定义一个SPI来实现类型转换逻辑和一个API
在运行时执行类型转换。在 Spring 容器中,您可以使用此系统
作为替代方案PropertyEditor
转换外部化 bean 属性值的实现
字符串添加到所需的属性类型。您还可以在
需要类型转换的应用程序。
3.4.1. 转换器SPI
实现类型转换逻辑的 SPI 是简单且强类型的,如下所示 接口定义显示:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
要创建您自己的转换器,请实现Converter
接口和参数化S
作为您要转换的类型,以及T
作为您要转换为的类型。您也可以透明地应用这样的
转换器,如果集合或数组为S
需要是
转换为数组或集合T
,前提是委托数组或集合
转换器也已注册(其中DefaultConversionService
默认情况下)。
对于对convert(S)
,则保证 source 参数不为 null。你Converter
如果转换失败,可能会抛出任何未检查的异常。具体来说,它应该抛出一个IllegalArgumentException
以报告无效的源值。
请注意确保您的Converter
实现是线程安全的。
中提供了多个转换器实现core.convert.support
打包为
一种便利。其中包括从字符串到数字和其他常见类型的转换器。
以下列表显示了StringToInteger
类,这是一个典型的Converter
实现:
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
3.4.2. 使用ConverterFactory
当您需要集中整个类层次结构的转换逻辑时
(例如,从String
自Enum
对象),您可以实现ConverterFactory
,如以下示例所示:
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
将 S 参数化为要转换的类型,将 R 参数化为定义的基本类型
可以转换为的类范围。然后实现getConverter(Class<T>)
,
其中 T 是 R 的子类。
考虑一下StringToEnumConverterFactory
举个例子:
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
3.4.3. 使用GenericConverter
当您需要复杂的Converter
实现时,请考虑使用GenericConverter
接口。 具有更灵活但类型不那么强的签名 比Converter
一个GenericConverter
支持在多个源和target 类型之间进行转换。此外,一个GenericConverter
使源和目标字段上下文可用,您可以在实现转换逻辑时使用。此类上下文允许类型转换由字段注释或在字段签名上声明的通用信息驱动。以下列表显示了GenericConverter
:
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
要实现GenericConverter
有getConvertibleTypes()
返回受支持的source→target 类型对。然后实现convert(Object, TypeDescriptor,
TypeDescriptor)
以包含您的转换逻辑。源TypeDescriptor
提供 访问保存要转换的值的源字段。目标TypeDescriptor
提供对要设置转换值的目标字段的访问。
一个很好的例子GenericConverter
是一个在 Java 数组之间进行转换的转换器和集合。这样的ArrayToCollectionConverter
内省声明目标集合类型以解析集合的元素类型。这使得每个源数组中的元素在集合被设置在目标字段上之前转换为集合元素类型。
因为GenericConverter 是一个比较复杂的 SPI 接口,你应该使用它只在你需要的时候。 喜爱Converter 或ConverterFactory 对于基本型转换需求。 |
用ConditionalGenericConverter
有时,您想要一个Converter
仅在特定条件成立时运行。 为 例如,您可能想要运行一个Converter
仅当存在特定注释时,或者您可能想要运行Converter
仅当特定方法
(例如static valueOf
方法)在目标类上定义。ConditionalGenericConverter
是GenericConverter
和ConditionalConverter
接口,用于定义此类自定义匹配条件:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
一个很好的例子ConditionalGenericConverter
是一个IdToEntityConverter
转换
在持久实体标识符和实体引用之间。这样的IdToEntityConverter
仅当目标实体类型声明静态查找器方法(例如,findAccount(Long)
).您可以在matches(TypeDescriptor, TypeDescriptor)
.
3.4.4.ConversionService
应用程序接口
ConversionService
定义一个统一的 API,用于执行类型转换逻辑
运行。转换器通常在以下立面接口后面运行:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
最ConversionService
实现也实现ConverterRegistry
哪
提供用于注册转换器的SPI。在内部,一个ConversionService
实现委托给其注册的转换器来执行类型转换逻辑。
坚固的ConversionService
实现在core.convert.support
包。GenericConversionService
通用实现是否适合
在大多数环境中使用。ConversionServiceFactory
为提供便利的工厂
创造共同点ConversionService
配置。
3.4.5. 配置ConversionService
一个ConversionService
是一个无状态对象,设计用于在应用程序中实例化
启动,然后在多个线程之间共享。在 Spring 应用程序中,您通常
配置一个ConversionService
实例(或ApplicationContext
).
Spring接住了这一点ConversionService
并在类型
转换需要由框架执行。您也可以注入此ConversionService
到你的任何 bean 中并直接调用它。
如果没有ConversionService 在 Spring 中注册,原始PropertyEditor -基于
系统。 |
注册默认值ConversionService
使用 Spring,添加以下 bean 定义
使用id
之conversionService
:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认ConversionService
可以在字符串、数字、枚举、集合、
地图和其他常见类型。要使用
自己的自定义转换器,将converters
财产。属性值可以实现
任何Converter
,ConverterFactory
或GenericConverter
接口。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
使用ConversionService
在 Spring MVC 应用程序中。请参阅 Spring MVC 章节中的转换和格式化。
在某些情况下,您可能希望在转换过程中应用格式。看这FormatterRegistry
SPI有关使用详情FormattingConversionServiceFactoryBean
.
3.4.6. 使用ConversionService
编程
要使用ConversionService
实例中,您可以注入引用
就像你对任何其他豆子一样。以下示例显示了如何执行此作:
@Service
public class MyService {
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
对于大多数用例,您可以使用convert
指定targetType
,但它
不适用于更复杂的类型,例如参数化元素的集合。
例如,如果要将List
之Integer
设置为List
之String
编程
您需要提供源和目标类型的正式定义。
幸运TypeDescriptor
提供了各种选项,使之变得简单,
如以下示例所示:
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
请注意DefaultConversionService
自动注册转换器
适用于大多数环境。这包括集合转换器、标量
转换器和基本Object
-自-String
变换 器。您可以注册相同的转换器
与任何ConverterRegistry
通过使用 staticaddDefaultConverters
方法DefaultConversionService
类。
值类型的转换器被重用于数组和集合,因此有
无需创建特定的转换器即可从Collection
之S
设置为Collection
之T
,假设标准集合处理是适当的。
3.5. Spring 字段格式化
如上一节所述,core.convert
是一个
通用型转换系统。它提供了一个统一的ConversionService
API 作为
以及强类型的Converter
SPI 用于实现从一种类型实现转换逻辑
给另一个。Spring 容器使用此系统来绑定 bean 属性值。在
此外,Spring 表达式语言 (SpEL) 和DataBinder
使用此系统
绑定字段值。例如,当 SpEL 需要强制Short
设置为Long
自
完成一个expression.setValue(Object bean, Object value)
尝试,则core.convert
系统执行强制。
现在考虑典型客户端环境的类型转换要求,例如
Web 或桌面应用程序。在此类环境中,您通常从String
支持客户端回发过程,以及返回String
以支持
视图渲染过程。此外,您经常需要进行本地化String
值。更多
常规core.convert
Converter
SPI 不满足此类格式要求
径直。为了直接解决这些问题,Spring 3 引入了一个方便的Formatter
SPI 该
提供了一个简单而强大的替代方案PropertyEditor
客户端环境的实现。
通常,您可以使用Converter
SPI 当您需要实现通用类型时
转换逻辑 — 例如,用于在java.util.Date
和Long
.
您可以使用Formatter
在客户端环境(例如 Web
application),需要解析和打印本地化字段值。这ConversionService
为两个 SPI 提供统一的类型转换 API。
3.5.1.Formatter
SPI
这Formatter
实现字段格式化逻辑的 SPI 是简单且强类型的。这
以下列表显示了Formatter
接口定义:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
从Printer
和Parser
构建块接口。这
以下列表显示了这两个接口的定义:
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}
创建您自己的Formatter
,实现Formatter
前面显示的界面。
参数化T
设置为要格式化的对象类型 - 例如,java.util.Date
.实现print()
作以打印T
为
显示在客户端区域设置中。实现parse()
作来解析T
从客户端区域设置返回的格式化表示形式。你Formatter
应该抛出一个ParseException
或IllegalArgumentException
如果解析尝试失败。拿
注意确保您的Formatter
实现是线程安全的。
这format
子包提供了几个Formatter
为了方便起见。
这number
package 提供NumberStyleFormatter
,CurrencyStyleFormatter
和PercentStyleFormatter
格式化Number
使用java.text.NumberFormat
.
这datetime
package 提供了一个DateFormatter
格式化java.util.Date
对象与
一个java.text.DateFormat
.
以下内容DateFormatter
就是一个例子Formatter
实现:
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
class DateFormatter(private val pattern: String) : Formatter<Date> {
override fun print(date: Date, locale: Locale)
= getDateFormat(locale).format(date)
@Throws(ParseException::class)
override fun parse(formatted: String, locale: Locale)
= getDateFormat(locale).parse(formatted)
protected fun getDateFormat(locale: Locale): DateFormat {
val dateFormat = SimpleDateFormat(this.pattern, locale)
dateFormat.isLenient = false
return dateFormat
}
}
Spring 团队欢迎社区驱动Formatter
贡献。请参阅 GitHub 问题进行贡献。
3.5.2. 注释驱动的格式
字段格式可以按字段类型或注释进行配置。绑定
对Formatter
实现AnnotationFormatterFactory
.以下内容
列表中显示了AnnotationFormatterFactory
接口:
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
要创建实现,请执行以下作:
-
参数化 A 为字段
annotationType
您希望与之关联 格式化逻辑 — 例如org.springframework.format.annotation.DateTimeFormat
. -
有
getFieldTypes()
返回可以使用注释的字段类型。 -
有
getPrinter()
返回一个Printer
以打印带注释的字段的值。 -
有
getParser()
返回一个Parser
解析clientValue
对于带注释的字段。
以下示例AnnotationFormatterFactory
实现绑定了@NumberFormat
格式化程序的注释,让数字样式或模式成为 指定:
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
}
}
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
override fun getFieldTypes(): Set<Class<*>> {
return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
}
override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
return configureFormatterFrom(annotation, fieldType)
}
override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
return configureFormatterFrom(annotation, fieldType)
}
private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
return if (annotation.pattern.isNotEmpty()) {
NumberStyleFormatter(annotation.pattern)
} else {
val style = annotation.style
when {
style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
else -> NumberStyleFormatter()
}
}
}
}
要触发格式化,您可以使用@NumberFormat注释字段,如下示例所示:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
格式注释 API
可移植格式注释 API 存在于org.springframework.format.annotation
包。 您可以使用@NumberFormat
格式化Number
字段,例如Double
和Long
和@DateTimeFormat
格式化java.util.Date
,java.util.Calendar
,Long
(对于毫秒时间戳)以及 JSR-310java.time
.
以下示例使用@DateTimeFormat
格式化java.util.Date
作为 ISO 日期(yyyy-MM-dd):
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
class MyModel(
@DateTimeFormat(iso=ISO.DATE) private val date: Date
)
3.5.3.FormatterRegistry
SPI
这FormatterRegistry
是用于注册格式化器和转换器的 SPI。FormattingConversionService
是FormatterRegistry
适合人群
大多数环境。您可以通过编程或声明方式配置此变体
作为 Spring bean,例如通过使用FormattingConversionServiceFactoryBean
.因为这个
实现也实现ConversionService
,可以直接配置
与 Spring 的DataBinder
和 Spring 表达式语言 (SpEL)。
以下列表显示了FormatterRegistry
SPI:
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addPrinter(Printer<?> printer);
void addParser(Parser<?> parser);
void addFormatter(Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}
如前面的列表所示,您可以按字段类型或注释注册格式化程序。
这FormatterRegistry
SPI 允许您集中配置格式规则,而不是
在控制器之间复制此类配置。例如,您可能想要
强制所有日期字段都以特定方式格式化,或者将字段设置为特定的
注释以某种方式格式化。使用共享的FormatterRegistry
,则定义
这些规则一次,并在需要格式化时应用它们。
3.5.4.FormatterRegistrar
SPI
FormatterRegistrar
是一个 SPI,用于通过
FormatterRegistry 中。以下列表显示了其接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
一个FormatterRegistrar
在注册多个相关转换器时很有用,并且
给定格式类别的格式化程序,例如日期格式。也可以是
在声明性注册不足的情况下很有用,例如,当格式化程序
需要在与其自身不同的特定字段类型下建立索引<T>
或者当
注册一个Printer
/Parser
双。下一节将提供有关
转换器和格式化程序注册。
3.5.5. 在Spring MVC中配置格式化
请参阅 Spring MVC 章节中的转换和格式化。
3.6. 配置全局日期和时间格式
默认情况下,日期和时间字段未使用@DateTimeFormat
从
字符串,使用DateFormat.SHORT
风格。如果您愿意,可以通过以下方式更改此设置
定义您自己的全局格式。
为此,请确保 Spring 不注册默认格式化程序。相反,请注册 在以下人员的帮助下手动格式化程序:
-
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
-
org.springframework.format.datetime.DateFormatterRegistrar
例如,以下 Java 配置注册一个全局yyyyMMdd
格式:
@Configuration
public class AppConfig {
@Bean
public FormattingConversionService conversionService() {
// Use the DefaultFormattingConversionService but do not register defaults
DefaultFormattingConversionService conversionService =
new DefaultFormattingConversionService(false);
// Ensure @NumberFormat is still supported
conversionService.addFormatterForFieldAnnotation(
new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar dateTimeRegistrar = new DateTimeFormatterRegistrar();
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
dateTimeRegistrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar dateRegistrar = new DateFormatterRegistrar();
dateRegistrar.setFormatter(new DateFormatter("yyyyMMdd"));
dateRegistrar.registerFormatters(conversionService);
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): FormattingConversionService {
// Use the DefaultFormattingConversionService but do not register defaults
return DefaultFormattingConversionService(false).apply {
// Ensure @NumberFormat is still supported
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
// Register JSR-310 date conversion with a specific global format
val dateTimeRegistrar = DateTimeFormatterRegistrar()
dateTimeRegistrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
dateTimeRegistrar.registerFormatters(this)
// Register date conversion with a specific global format
val dateRegistrar = DateFormatterRegistrar()
dateRegistrar.setFormatter(DateFormatter("yyyyMMdd"))
dateRegistrar.registerFormatters(this)
}
}
}
如果您更喜欢基于 XML 的配置,则可以使用FormattingConversionServiceFactoryBean
.以下示例显示了如何执行此作:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="registerDefaultFormatters" value="false" />
<property name="formatters">
<set>
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterRegistrar">
<property name="dateFormatter">
<bean class="org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean">
<property name="pattern" value="yyyyMMdd"/>
</bean>
</property>
</bean>
</set>
</property>
</bean>
</beans>
请注意,在 Web 中配置日期和时间格式时,还有额外的注意事项 应用。请参阅 WebMVC 转换和格式化 或 WebFlux 转换和格式化。
3.7. Java Bean 验证
Spring Framework 提供对 Java Bean Validation API 的支持。
3.7.1. Bean 验证概述
Bean Validation 通过约束声明和 Java 应用程序的元数据。要使用它,请使用 声明性验证约束,然后由运行时强制执行。有 内置约束,你也可以定义自己的自定义约束。
考虑以下示例,该示例显示了一个简单的PersonForm
具有两个属性的模型:
public class PersonForm {
private String name;
private int age;
}
class PersonForm(
private val name: String,
private val age: Int
)
Bean Validation 允许您声明约束,如以下示例所示:
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
class PersonForm(
@get:NotNull @get:Size(max=64)
private val name: String,
@get:Min(0)
private val age: Int
)
然后,Bean Validation 验证器根据声明的 约束。有关 Bean Validation 的一般信息,请参阅 Bean Validation API。请参阅 Hibernate Validator 文档 具体约束。了解如何将 Bean 验证提供程序设置为 Spring 豆子,继续阅读。
3.7.2. 配置 Bean 验证提供程序
Spring 为 Bean Validation API 提供全面支持,包括引导
Bean Validation 提供程序作为 Spring bean。这可以让你注入一个javax.validation.ValidatorFactory
或javax.validation.Validator
无论验证在哪里
在您的应用程序中需要。
您可以使用LocalValidatorFactoryBean
将默认验证器配置为 Spring
bean,如以下示例所示:
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
前面示例中的基本配置触发 bean 验证,以通过 使用其默认的引导机制。Bean 验证提供程序,例如 Hibernate Validator 的 Validator 应存在于类路径中并被自动检测。
注入验证器
LocalValidatorFactoryBean
实现两者javax.validation.ValidatorFactory
和javax.validation.Validator
,以及 Spring 的org.springframework.validation.Validator
.
您可以将对这些接口中的任何一个的引用注入到需要调用的 bean 中
验证逻辑。
您可以注入对javax.validation.Validator
如果您更喜欢使用 Bean
验证 API,如以下示例所示:
import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import javax.validation.Validator;
@Service
class MyService(@Autowired private val validator: Validator)
您可以注入对org.springframework.validation.Validator
如果你的豆子
需要 Spring Validation API,如以下示例所示:
import org.springframework.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import org.springframework.validation.Validator
@Service
class MyService(@Autowired private val validator: Validator)
配置自定义约束
每个 bean 验证约束由两部分组成:
-
一个
@Constraint
声明约束及其可配置属性的注释。 -
实现
javax.validation.ConstraintValidator
实现 约束的行为。
要将声明与实现相关联,每个@Constraint
注解
引用相应的ConstraintValidator
实现类。在运行时,一个ConstraintValidatorFactory
实例化引用的实现,当
约束注释。
默认情况下,LocalValidatorFactoryBean
配置一个SpringConstraintValidatorFactory
使用 Spring 创建ConstraintValidator
实例。这可以让您的自定义ConstraintValidators
像任何其他 Spring Bean 一样受益于依赖注入。
以下示例显示了自定义@Constraint
声明后跟关联的ConstraintValidator
使用 Spring 进行依赖注入的实现:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
// ...
}
import javax.validation.ConstraintValidator
class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {
// ...
}
如前面的示例所示,一个ConstraintValidator
实现可以有其依赖关系@Autowired
和任何其他春豆一样。
弹簧驱动方法验证
您可以集成 Bean Validation 1.1 支持的方法验证功能(并且,作为
自定义扩展,也由 Hibernate Validator 4.3) 通过MethodValidationPostProcessor
豆子定义:
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
要获得 Spring 驱动的方法验证的条件,需要对所有目标类进行注释
与 Spring 的@Validated
注释,也可以选择声明验证
组。看MethodValidationPostProcessor
有关 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息。
其他配置选项
默认值LocalValidatorFactoryBean
配置足以满足大多数需求
例。各种 Bean 验证有许多配置选项
结构,从消息插值到遍历解析。请参阅LocalValidatorFactoryBean
javadoc 以获取有关这些选项的更多信息。
3.7.3. 配置DataBinder
从 Spring 3 开始,您可以配置DataBinder
实例与Validator
.一次
配置,您可以调用Validator
通过调用binder.validate()
.任何验证Errors
会自动添加到活页夹的BindingResult
.
以下示例显示如何使用DataBinder
以编程方式调用验证
绑定到目标对象后的逻辑:
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()
// bind to the target object
binder.bind(propertyValues)
// validate the target object
binder.validate()
// get BindingResult that includes any validation errors
val results = binder.bindingResult
您还可以配置DataBinder
与多个Validator
实例通过dataBinder.addValidators
和dataBinder.replaceValidators
.这在
将全局配置的 bean 验证与 Spring 相结合Validator
配置
本地在 DataBinder 实例上。请参阅 Spring MVC 验证配置。
3.7.4. Spring MVC 3 验证
请参阅 Spring MVC 章节中的验证。
4. Spring 表达式语言 (SpEL)
Spring 表达式语言(简称“SpEL”)是一种功能强大的表达式语言,它 支持在运行时查询和作对象图。语言语法是 类似于统一 EL,但提供了附加功能,最显着的是方法调用和 基本字符串模板功能。
虽然还有其他几种可用的 Java 表达式语言——OGNL、MVEL 和 JBoss EL 的 Spring 表达式语言,仅举几例——Spring 表达式语言的创建是为了提供 Spring 社区,具有一种支持良好的表达式语言,可在所有 Spring 产品组合中的产品。它的语言特性是由 Spring 产品组合中项目的要求,包括工具要求 在 Spring Tools for Eclipse 中支持代码完成。 也就是说,SpEL 基于一个与技术无关的 API,它允许其他表达式语言 如果需要,应集成实施。
而 SpEL 是 Spring 中表达评估的基础 portfolio,它不直接绑定到 Spring,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是 独立表达式语言。这需要创建一些引导 基础设施类,例如 parser。大多数 Spring 用户无需处理 此基础结构,并且只能创作表达式字符串以进行评估。 这种典型用途的一个例子是将 SpEL 集成到创建 XML 或 基于注释的 Bean 定义,如 表达式支持定义 Bean 定义中所示。
本章介绍表达式语言的功能、其 API 及其语言
语法。在几个地方,Inventor
和Society
类用作目标
用于表达式评估的对象。这些类声明和用于
填充它们列在本章末尾。
表达式语言支持以下功能:
-
文字表达式
-
布尔运算符和关系运算符
-
正则表达式
-
类表达式
-
访问属性、数组、列表和映射
-
方法调用
-
关系运算符
-
分配
-
调用构造函数
-
Bean 参考资料
-
阵列构造
-
内联列表
-
内联地图
-
三元运算符
-
变量
-
用户定义的功能
-
藏品投影
-
系列选择
-
模板化表达式
4.1. 评估
本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在语言参考中找到。
以下代码介绍了 SpEL API 来计算文字字符串表达式Hello World
.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 | 消息变量的值为'Hello World' . |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 | 消息变量的值为'Hello World' . |
您最有可能使用的 SpEL 类和接口位于org.springframework.expression
package 及其子包,例如spel.support
.
这ExpressionParser
接口负责解析表达式字符串。在
前面的示例中,表达式 string 是一个字符串文字,由周围的单个表示
引号。这Expression
接口负责评估先前定义的
表达式字符串。可以抛出的两个异常,ParseException
和EvaluationException
,调用parser.parseExpression
和exp.getValue
,
分别。
SpEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。
在下面的方法调用示例中,我们调用concat
字符串文字上的方法:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 | 的值message 现在是“Hello World!”。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | 的值message 现在是“Hello World!”。 |
以下调用 JavaBean 属性的示例调用String
属性Bytes
:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 | 此行将文字转换为字节数组。 |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | 此行将文字转换为字节数组。 |
SpEL 还支持使用标准点表示法(例如prop1.prop2.prop3
)以及相应的属性值设置。
也可以访问公共字段。
以下示例显示如何使用点表示法获取文字的长度:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1 | 'Hello World'.bytes.length 给出了文字的长度。 |
val parser = SpelExpressionParser()
// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1 | 'Hello World'.bytes.length 给出了文字的长度。 |
可以调用 String 的构造函数,而不是使用字符串文字,如下所示 示例显示:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 | 构造一个新的String 从字面意思开始,并使其成为大写。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
1 | 构造一个新的String 从字面意思开始,并使其成为大写。 |
请注意通用方法的使用:public <T> T getValue(Class<T> desiredResultType)
.
使用此方法无需将表达式的值转换为所需的值
结果类型。一EvaluationException
如果该值无法强制转换为
类型T
或使用注册类型转换器进行转换。
SpEL 更常见的用法是提供一个被计算的表达式字符串
针对特定对象实例(称为根对象)。以下示例显示
如何检索name
属性Inventor
class 或
创建布尔条件:
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
4.1.1. 理解EvaluationContext
这EvaluationContext
在计算要解析的表达式时使用 interface
属性、方法或字段,并帮助执行类型转换。Spring 提供了两个
实现。
-
SimpleEvaluationContext
:公开基本 SpEL 语言特性的子集,并且 配置选项,用于不需要完整范围的表达式类别 SpEL 语言语法,并且应该受到有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的筛选器。 -
StandardEvaluationContext
:公开全套 SpEL 语言特性和 配置选项。您可以使用它来指定默认根对象并配置 每一种可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 bean 引用。它还要求
显式选择表达式中属性和方法的支持级别。
默认情况下,create()
静态工厂方法仅允许对属性的读取访问。
您还可以获得构建器来配置所需的确切支持级别,定位
以下一种或某种组合:
-
习惯
PropertyAccessor
仅(无反射) -
只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SpEL 使用 Spring 核心中可用的转换服务
(org.springframework.core.convert.ConversionService
).此转换服务自带
具有许多用于常见转换的内置转换器,但也完全可扩展,因此
您可以在类型之间添加自定义转化。此外,它是
泛型感知。这意味着,当您在
表达式,SpEL 尝试转换以保持任何对象的类型正确性
它遇到。
这在实践中意味着什么?假设赋值,使用setValue()
,正在使用
将List
财产。属性的类型实际上是List<Boolean>
.斯佩尔
认识到列表的元素需要转换为Boolean
以前
被放置在其中。以下示例显示了如何执行此作:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b is false
Boolean b = simple.booleanList.get(0);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")
// b is false
val b = simple.booleanList[0]
4.1.2. 解析器配置
可以使用解析器配置来配置 SpEL 表达式解析器
对象 (org.springframework.expression.spel.SpelParserConfiguration
).配置
对象控制某些表达式组件的行为。例如,如果您
index 添加到数组或集合中,并且指定索引处的元素为null
、SpEL
可以自动创建元素。当使用由
属性引用链。如果索引到数组或列表并指定索引
超过数组或列表当前大小的末尾,SpEL 可以自动
增加数组或列表以容纳该索引。为了在
指定索引,SpEL 将尝试使用元素类型的默认值创建元素
构造函数,然后再设置指定值。如果元素类型没有
默认构造函数,null
将被添加到数组或列表中。如果没有内置
或知道如何设置值的自定义转换器,null
将保留在数组中或
list 在指定索引中。以下示例演示如何自动增长
列表:
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
class Demo {
var list: List<String>? = null
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3]")
val demo = Demo()
val o = expression.getValue(demo)
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
4.1.3. SpEL编译
Spring Framework 4.1 包括一个基本的表达式编译器。表达式通常是 interpreted,这在评估过程中提供了很大的动态灵活性,但 无法提供最佳性能。对于偶尔的表达式用法, 这很好,但是,当被其他组件(如 Spring Integration)使用时, 性能可能非常重要,并且没有真正需要活力。
SpEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为并使用它 类以实现更快的表达式评估。由于缺乏打字 表达式时,编译器使用在解释评估期间收集的信息 执行编译时表达式的。例如,它不知道类型 纯粹来自表达式的属性引用,但在第一次解释期间 评估,它会发现它是什么。当然,基于这样的派生编译 如果各种表达式元素的类型,信息以后可能会造成麻烦 随着时间的推移而变化。因此,编译最适合 类型信息不会在重复求值时更改。
考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
由于前面的表达式涉及数组访问,因此某些属性取消引用 和数字运算,性能提升可能非常明显。在一个例子中 微基准测试运行 50000 次迭代,使用 解释器,并且仅使用表达式的编译版本仅 3 毫秒。
编译器配置
默认情况下,编译器未打开,但您可以在以下两个中的任何一个中打开它 不同的方式。您可以使用解析器配置过程将其打开 (前面讨论过)或使用 Spring 属性 当 SpEL 用法嵌入到另一个组件中时。本节讨论 这些选项。
编译器可以在三种模式之一下运行,这些模式在org.springframework.expression.spel.SpelCompilerMode
枚举。模式如下:
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释评估之后。如果编译后的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。 -
MIXED
:在混合模式下,表达式在解释和编译之间静默切换 随时间变化的模式。经过一定数量的解释运行后,它们切换到编译 form 和,如果编译的表单出现问题(例如类型更改,如 前面所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译表单并切换到它。基本上 用户进入的异常IMMEDIATE
模式在内部处理。
IMMEDIATE
模式存在,因为MIXED
mode 可能会导致表达式出现问题
有副作用。如果编译的表达式在部分成功后爆炸,则它
可能已经做了一些影响系统状态的事情。如果这
,调用方可能不希望它在解释模式下静默地重新运行,
因为表达式的一部分可能运行两次。
选择模式后,使用SpelParserConfiguration
以配置解析器。这
以下示例显示了如何执行此作:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
指定编译器模式时,还可以指定类加载器(允许传递 null)。 编译的表达式在提供的任何下创建的子类加载器中定义。 重要的是要确保,如果指定了类加载器,它可以看到 表达式评估过程。如果未指定类加载器,则使用缺省类加载器 (通常是表达式评估期间运行的线程的上下文类加载程序)。
配置编译器的第二种方法是在将 SpEL 嵌入到某些
其他组件,并且可能无法通过配置来配置它
对象。在这些情况下,可以将spring.expression.compiler.mode
属性通过 JVM 系统属性(或通过SpringProperties
机制)设置为SpelCompilerMode
枚举值 (off
,immediate
或mixed
).
4.2. Bean 定义中的表达式
您可以将 SpEL 表达式与基于 XML 或基于注释的配置元数据一起使用,用于
定义BeanDefinition
实例。在这两种情况下,定义表达式的语法都是
形式#{ <expression string> }
.
4.2.1. XML配置
可以使用表达式设置属性或构造函数参数值,如下所示 示例显示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
应用程序上下文中的所有 bean 都可作为预定义变量使用,其
普通豆名。这包括标准上下文 bean,例如environment
(类型org.springframework.core.env.Environment
)以及systemProperties
和systemEnvironment
(类型Map<String, Object>
) 以访问运行时环境。
以下示例显示了对systemProperties
bean 作为 SpEL 变量:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
请注意,您不必在此处为预定义变量添加符号前缀。#
您还可以按名称引用其他 bean 属性,如以下示例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
</bean>
4.2.2. 注释配置
要指定默认值,可以将@Value
字段、方法、
以及方法或构造函数参数。
以下示例设置字段的默认值:
public class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
以下示例显示了属性 setter 方法上的等效项:
public class PropertyValueTestBean {
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class PropertyValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
Autowired 方法和构造函数也可以使用@Value
注释,如下所示
示例显示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
private lateinit var defaultLocale: String
@Autowired
fun configure(movieFinder: MovieFinder,
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
this.movieFinder = movieFinder
this.defaultLocale = defaultLocale
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
// ...
}
4.3. 语言参考
本节介绍 Spring 表达式语言的工作原理。它涵盖以下内容 主题:
4.3.1. 文字表达式
SpEL 支持以下类型的文字表达式。
-
字符串
-
数值:整数 (
int
或long
)、十六进制 (int
或long
)、实数 (float
或double
) -
布尔值:
true
或false
-
零
字符串可以用单引号 () 或双引号 () 分隔。自
在用单引号括起来的字符串文字中包含单引号
标记,使用两个相邻的单引号字符。同样,要包含双精度
用双引号括起来的字符串文字中的引号,请使用两个
相邻的双引号字符。'
"
数字支持使用负号、指数表示法和小数点。
默认情况下,实数是通过使用Double.parseDouble()
.
以下列表显示了文字的简单用法。通常,它们不用于 像这样的隔离,而是作为更复杂表达式的一部分——例如, 在逻辑比较运算符的一侧使用文字或作为 方法。
ExpressionParser parser = new SpelExpressionParser();
// evaluates to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
// evaluates to "Tony's Pizza"
String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evaluates to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
val parser = SpelExpressionParser()
// evaluates to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String
// evaluates to "Tony's Pizza"
val pizzaParlor = parser.parseExpression("'Tony''s Pizza'").value as String
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
// evaluates to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
val trueValue = parser.parseExpression("true").value as Boolean
val nullValue = parser.parseExpression("null").value
4.3.2. 属性、数组、列表、映射和索引器
使用属性引用进行导航很容易。为此,请使用句点来指示嵌套的
属性值。的实例Inventor
类pupin
和tesla
,是
填充了 Classes 中列出的数据,用于
examples 部分。要“向下”导航对象图并获取特斯拉的出生年份和
普平的出生城市,我们用以下表达方式:
// evaluates to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
// evaluates to 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
允许属性名称的第一个字母不区分大小写。因此,表达式可以写成 |
数组和列表的内容是使用方括号表示法获得的,如以下示例所示:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Inventions Array
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// Members List
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
context, ieee, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
context, ieee, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// Inventions Array
// evaluates to "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String::class.java)
// Members List
// evaluates to "Nikola Tesla"
val name = parser.parseExpression("members[0].name").getValue(
context, ieee, String::class.java)
// List and Array navigation
// evaluates to "Wireless communication"
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
context, ieee, String::class.java)
映射的内容是通过在 括弧。 在以下示例中,由于officers
map 是字符串,我们可以指定 字符串:
// Officer's Dictionary
Inventor pupin = parser.parseExpression("officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
societyContext, "Croatia");
// Officer's Dictionary
val pupin = parser.parseExpression("officers['president']").getValue(
societyContext, Inventor::class.java)
// evaluates to "Idvor"
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
societyContext, String::class.java)
// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
societyContext, "Croatia")
4.3.3. 内联列表
您可以使用表示法直接在表达式中表达列表。{}
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>
{}
本身意味着一个空列表。出于性能原因,如果列表本身是
完全由固定文字组成,创建一个常量列表来表示
表达式(而不是在每个评估上构建一个新列表)。
4.3.4. 内联映射
您还可以使用{key:value}
表示法。这
以下示例显示了如何执行此作:
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, *>
val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<*, *>
{:}
本身意味着一张空地图。出于性能原因,如果地图本身是
由固定文字或其他嵌套常量结构(列表或映射)组成,一个
创建常量映射来表示表达式(而不是在
每个评估)。映射键的引用是可选的(除非键包含句点
(.
)).上面的示例不使用带引号的键。
4.3.5. 数组构造
您可以使用熟悉的 Java 语法构建数组,并可选择提供初始值设定项 在构造时填充数组。以下示例显示了如何执行此作:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
当前无法在构造多维数组时提供初始值设定项。
4.3.6. 方法
您可以使用典型的 Java 编程语法调用方法。您还可以调用方法 在文字上。还支持变量参数。以下示例演示如何 调用方法:
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)
// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean::class.java)
4.3.7. 运算符
Spring 表达式语言支持以下类型的运算符:
关系运算符
关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)使用标准运算符表示法来支持。这 以下列表显示了运算符的一些示例:
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
大于和小于比较 如果您更喜欢数字比较,请避免基于数字的比较 |
除了标准关系运算符外,SpEL 还支持instanceof
和常规
基于表达式matches
算子。以下列表显示了两者的示例:
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
val falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
// evaluates to false
val falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
小心原始类型,因为它们会立即被框入其
包装器类型。例如1 instanceof T(int) 评估为false 而1 instanceof T(Integer) 评估为true 不出所料。 |
每个符号运算符也可以指定为纯字母等效符。这 避免了所使用的符号对文档类型具有特殊含义的问题 表达式嵌入的(例如在 XML 文档中)。文本等价物是:
-
lt
(<
) -
gt
(>
) -
le
(<=
) -
ge
(>=
) -
eq
(==
) -
ne
(!=
) -
div
(/
) -
mod
(%
) -
not
(!
).
所有文本运算符都不区分大小写。
逻辑运算符
SpEL 支持以下逻辑运算符:
-
and
(&&
) -
or
(||
) -
not
(!
)
以下示例演示如何使用逻辑运算符:
// -- AND --
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- AND --
// evaluates to false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)
// evaluates to true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- OR --
// evaluates to true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)
// evaluates to true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- NOT --
// evaluates to false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
数学运算符
您可以对数字和字符串使用加法运算符 ()。您可以使用
减法 ()、乘法 () 和除法 () 运算符仅适用于数字。
您还可以对数字使用模数 () 和指数幂 () 运算符。
强制执行标准运算符优先级。以下示例显示了数学
使用中的运算符:+
-
*
/
%
^
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
val testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000
// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
赋值运算符
要设置属性,请使用赋值运算符 ()。这通常在
调用=
setValue
但也可以在调用getValue
.以下内容
清单显示了使用赋值运算符的两种方法:
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
// alternatively
String aleks = parser.parseExpression(
"name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic")
// alternatively
val aleks = parser.parseExpression(
"name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)
4.3.8. 类型
您可以使用特殊的T
运算符来指定java.lang.Class
(这
类型)。静态方法也使用此运算符调用。这StandardEvaluationContext
使用TypeLocator
查找类型,以及StandardTypeLocator
(可以替换)是在了解java.lang
包。这意味着T()
对java.lang
package 不需要完全限定,但所有其他类型引用都必须是。这
以下示例演示如何使用T
算子:
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)
val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)
val trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean::class.java)
4.3.9. 构造函数
您可以使用new
算子。您应该使用
所有类型的限定类名称,但位于java.lang
包
(Integer
,Float
,String
,依此类推)。以下示例演示如何使用new
运算符来调用构造函数:
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
val einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor::class.java)
// create new Inventor instance within the add() method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))")
.getValue(societyContext)
4.3.10. 变量
您可以使用#variableName
语法。变量
通过使用setVariable
方法EvaluationContext
实现。
有效的变量名称必须由以下一个或多个受支持的变量组成 字符。
|
以下示例演示如何使用变量。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")
parser.parseExpression("name = #newName").getValue(context, tesla)
println(tesla.name) // "Mike Tesla"
这#this
和#root
变量
这#this
变量始终被定义并引用当前评估对象
(解决不限定的引用)。这#root
变量始终是
定义并引用根上下文对象。虽然#this
可能因
表达式被计算,#root
总是指根。以下示例
展示如何使用#this
和#root
变量:
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
"#primes.?[#this>10]").getValue(context) as List<Int>
4.3.11. 函数
您可以通过注册可在
表达式字符串。该函数通过EvaluationContext
.这
以下示例显示如何注册用户定义的函数:
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)
例如,请考虑以下反转字符串的实用工具方法:
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
fun reverseString(input: String): String {
val backwards = StringBuilder(input.length)
for (i in 0 until input.length) {
backwards.append(input[input.length - 1 - i])
}
return backwards.toString()
}
然后,您可以注册并使用上述方法,如以下示例所示:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)
val helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String::class.java)
4.3.12. Bean 引用
如果已配置了 bean 解析器,则可以
使用符号从表达式中查找 bean。以下示例演示了如何
为此:@
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)
要访问工厂 Bean 本身,您应该改为在 Bean 名称前加上一个符号。
以下示例显示了如何执行此作:&
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)
4.3.13. 三元运算符(if-then-else)
您可以使用三元运算符在内部执行 if-then-else 条件逻辑 表达式。以下列表显示了一个最小示例:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
val falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
在本例中,布尔值false
结果返回字符串值'falseExp'
.一个更多
现实示例如下:
parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
parser.parseExpression("name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"
val queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
请参阅下一节关于 Elvis 运算符的更短语法 三元运算符。
4.3.14. 猫王操作员
Elvis 运算符是三元运算符语法的缩写,用于 Groovy 语言。 使用三元运算符语法时,通常必须重复变量两次,因为 以下示例显示:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
相反,您可以使用猫王运算符(因与猫王的发型相似而得名)。 以下示例显示如何使用 Elvis 运算符:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'
以下列表显示了一个更复杂的示例:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Elvis Presley
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Nikola Tesla
tesla.setName(null)
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Elvis Presley
您可以使用 Elvis 运算符在表达式中应用默认值。以下内容
示例展示了如何在
这将注入系统属性 |
4.3.15. 安全导航操作员
安全导航操作员用于避免NullPointerException
并来自
Groovy 语言。通常,当您有对对象的引用时,您可能需要验证
在访问对象的方法或属性之前,它不是 null。为避免这种情况,请
安全导航运算符返回 null 而不是抛出异常。以下内容
示例显示了如何使用安全导航运算符:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
var city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city) // Smiljan
tesla.setPlaceOfBirth(null)
city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String::class.java)
println(city) // null - does not throw NullPointerException!!!
4.3.16. 集合选择
选择是一项功能强大的表达式语言功能,可让您将 通过从其条目中选择,将 source 集合添加到另一个集合中。
选择使用.?[selectionExpression]
.它过滤集合和
返回一个包含原始元素子集的新集合。例如
选择让我们可以轻松获得塞尔维亚发明人名单,如以下示例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext);
val list = parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
数组和任何实现java.lang.Iterable
或java.util.Map
.对于列表或数组,选择标准将根据每个
单个元素。针对地图,将根据每个地图评估选择条件
条目(Java 类型的对象Map.Entry
).每个地图条目都有其key
和value
可作为属性访问,以便在选择中使用。
以下表达式返回一个新映射,该映射由 条目值小于 27 的原始地图:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
val newMap = parser.parseExpression("map.?[value<27]").getValue()
除了返回所有选定元素外,还可以仅检索第一个或
最后一个元素。要获得与选择匹配的第一个元素,语法为.^[selectionExpression]
.要获取最后匹配的选择,语法为.$[selectionExpression]
.
4.3.17. 集合投影
投影允许集合驱动子表达式的计算,结果是
一个新系列。投影的语法是.![projectionExpression]
.例如
假设我们有一份发明家名单,但想要他们出生的城市名单。
实际上,我们希望为发明人中的每个条目评估“placeOfBirth.city”
列表。以下示例使用投影来执行此作:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
数组和任何实现java.lang.Iterable
或java.util.Map
.使用贴图驱动投影时,投影表达式为
根据映射中的每个条目(表示为 JavaMap.Entry
).结果
地图上的投影是一个列表,其中包含对投影的评估
表达式。
4.3.18. 表达式模板
表达式模板允许将文字文本与一个或多个求值块混合使用。
每个计算块都用前缀和后缀字符分隔,您可以
定义。常见的选择是用作分隔符,如以下示例所示
显示:#{ }
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
val randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
TemplateParserContext()).getValue(String::class.java)
// evaluates to "random number is 0.7038186818312008"
通过连接文字文本来计算字符串'random number is '
使用
计算分隔符内表达式的结果(在本例中为
的#{ }
random()
方法)。第二个参数parseExpression()
方法
是ParserContext
.这ParserContext
接口用于影响如何
分析表达式以支持表达式模板功能。
的定义TemplateParserContext
遵循:
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
class TemplateParserContext : ParserContext {
override fun getExpressionPrefix(): String {
return "#{"
}
override fun getExpressionSuffix(): String {
return "}"
}
override fun isTemplate(): Boolean {
return true
}
}
4.4. 示例中使用的类
本节列出了本章示例中使用的类。
package org.spring.samples.spel.inventor;
import java.util.Date;
import java.util.GregorianCalendar;
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
public Inventor(String name, String nationality) {
GregorianCalendar c= new GregorianCalendar();
this.name = name;
this.nationality = nationality;
this.birthdate = c.getTime();
}
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
this.nationality = nationality;
this.birthdate = birthdate;
}
public Inventor() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public PlaceOfBirth getPlaceOfBirth() {
return placeOfBirth;
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
this.placeOfBirth = placeOfBirth;
}
public void setInventions(String[] inventions) {
this.inventions = inventions;
}
public String[] getInventions() {
return inventions;
}
}
class Inventor(
var name: String,
var nationality: String,
var inventions: Array<String>? = null,
var birthdate: Date = GregorianCalendar().time,
var placeOfBirth: PlaceOfBirth? = null)
package org.spring.samples.spel.inventor;
public class PlaceOfBirth {
private String city;
private String country;
public PlaceOfBirth(String city) {
this.city=city;
}
public PlaceOfBirth(String city, String country) {
this(city);
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String s) {
this.city = s;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
class PlaceOfBirth(var city: String, var country: String? = null) {
package org.spring.samples.spel.inventor;
import java.util.*;
public class Society {
private String name;
public static String Advisors = "advisors";
public static String President = "president";
private List<Inventor> members = new ArrayList<Inventor>();
private Map officers = new HashMap();
public List getMembers() {
return members;
}
public Map getOfficers() {
return officers;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMember(String name) {
for (Inventor inventor : members) {
if (inventor.getName().equals(name)) {
return true;
}
}
return false;
}
}
package org.spring.samples.spel.inventor
import java.util.*
class Society {
val Advisors = "advisors"
val President = "president"
var name: String? = null
val members = ArrayList<Inventor>()
val officers = mapOf<Any, Any>()
fun isMember(name: String): Boolean {
for (inventor in members) {
if (inventor.name == name) {
return true
}
}
return false
}
}
5. 使用 Spring 进行面向方面的编程
面向方面编程 (AOP) 通过以下方式补充面向对象编程 (OOP) 提供了另一种思考计划结构的方式。模块化的关键单位 在 OOP 中是类,而在 AOP 中,模块化的单位是方面。方面 实现跨越关注点(例如事务管理)的模块化 多种类型和对象。(这种关注通常被称为“跨领域”关注 在 AOP 文献中。
Spring 的关键组件之一是 AOP 框架。而 Spring IoC container 不依赖于 AOP(这意味着如果您不想,则无需使用 AOP ),AOP 补充了 Spring IoC,以提供非常强大的中间件解决方案。
AOP 在 Spring Framework 中用于:
-
提供声明式企业服务。最重要的此类服务是声明式事务管理。
-
允许用户实现自定义方面,通过 AOP 补充他们对 OOP 的使用。
如果您只对通用声明性服务或其他预打包服务感兴趣 声明式中间件服务(如池化)时,您不需要直接使用 Spring AOP,可以跳过本章的大部分内容。 |
5.1. AOP 概念
让我们首先定义一些核心的 AOP 概念和术语。这些条款不是 特定于Spring。不幸的是,AOP 术语并不是特别直观。 但是,如果 Spring 使用自己的术语,那就更令人困惑了。
-
方面:跨多个类的关注点的模块化。 事务管理是企业 Java 中跨领域关注的一个很好的例子 应用。在 Spring AOP 中,方面是通过使用常规类实现的 (基于模式的方法)或使用
@Aspect
注释(@AspectJ样式)。 -
连接点:程序执行过程中的点,例如执行 方法或异常的处理。在 Spring AOP 中,连接点始终 表示方法执行。
-
建议:某个方面在特定连接点执行的作。不同类型的 建议包括“周围”、“之前”和“之后”建议。(讨论了建议类型 稍后。许多 AOP 框架,包括 Spring,将建议建模为拦截器和 在连接点周围保持拦截器链。
-
切入点:匹配连接点的谓词。建议与 切入点表达式,并在与切入点匹配的任何连接点处运行(例如, 执行具有特定名称的方法)。匹配的连接点的概念 by 切入点表达式是 AOP 的核心,Spring 使用 AspectJ 切入点 表达式语言。
-
简介:代表类型声明其他方法或字段。Spring AOP 允许您向任何 建议对象。例如,您可以使用引言使 bean 实现
IsModified
接口,以简化缓存。(引言称为 inter 类型声明。 -
目标对象:由一个或多个方面建议的对象。也称为 “建议对象”。由于 Spring AOP 是使用运行时代理实现的,因此 对象始终是代理对象。
-
AOP 代理:AOP 框架为实现方面而创建的对象 合约(建议方法执行等)。在 Spring Framework 中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。
-
编织:将各个方面与其他应用程序类型或对象链接起来,以创建一个 建议对象。这可以在编译时完成(使用 AspectJ 编译器,对于 示例)、加载时间或运行时。Spring AOP 和其他纯 Java AOP 框架一样, 在运行时执行编织。
Spring AOP 包括以下类型的建议:
-
通知之前:在连接点之前运行但没有 阻止执行流继续到连接点的能力(除非它抛出 一个例外)。
-
返回通知后:连接点完成后要运行的通知 通常(例如,如果方法返回而不抛出异常)。
-
抛出建议后:如果方法通过抛出 例外。
-
在(最后)建议之后:无论以何种方式运行建议 连接点退出(正常或异常返回)。
-
围绕建议:围绕连接点(例如方法调用)的建议。 这是最有力的建议。周围建议可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续到连接点或通过返回其 自己的返回值或抛出异常。
围绕建议是最普遍的建议。由于 Spring AOP 和 AspectJ 一样,
提供全方位的建议类型,我们建议您使用最不强大的
可以实现所需行为的 advise 类型。例如,如果您只需要
使用方法的返回值更新缓存,最好实现
在返回建议后,比周围建议,尽管周围建议可以完成
同样的事情。使用最具体的建议类型提供了更简单的编程模型
出错的可能性较小。例如,您不需要调用proceed()
方法JoinPoint
用于周围建议,因此,您不能不调用它。
所有通知参数都是静态类型的,以便您可以使用
适当的类型(例如,方法执行的返回值的类型)而不是
比Object
阵 列。
切入点匹配的连接点的概念是AOP的关键,它区分了 它来自仅提供拦截的旧技术。切入点使建议成为 独立于面向对象层次结构的目标。例如,您可以应用 围绕向一组跨越 多个对象(例如服务层中的所有业务作)。
5.2. Spring AOP 功能和目标
Spring AOP 是用纯 Java 实现的。无需特殊编译 过程。Spring AOP 不需要控制类加载器层次结构,因此 适用于 Servlet 容器或应用程序服务器。
Spring AOP 目前仅支持方法执行连接点(建议执行 Spring bean 上的方法)。未实现现场拦截,但支持 可以在不破坏核心 Spring AOP API 的情况下添加字段拦截。如果您需要 要建议字段访问和更新连接点,请考虑使用诸如 AspectJ 之类的语言。
Spring AOP 的 AOP 方法与大多数其他 AOP 框架不同。目的是 不提供最完整的AOP实现(虽然Spring AOP相当 有能力)。相反,目的是在 AOP 实施和 Spring IoC,帮助解决企业应用中的常见问题。
因此,例如,Spring 框架的 AOP 功能通常用于 与 Spring IoC 容器结合使用。使用普通 bean 配置方面 定义语法(尽管这允许强大的“自动代理”功能)。这是一个 与其他 AOP 实现的关键区别。有些事情你不能做 使用 Spring AOP 轻松或高效地建议非常细粒度的对象(通常, domain 对象)。在这种情况下,AspectJ 是最佳选择。但是,我们的 经验是,Spring AOP 为大多数问题提供了很好的解决方案 适用于 AOP 的企业 Java 应用程序。
Spring AOP 从不努力与 AspectJ 竞争提供全面的 AOP 溶液。我们认为,无论是基于代理的框架,如 Spring AOP,还是成熟的 像 AspectJ 这样的框架很有价值,它们是互补的,而不是在 竞争。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以启用 在一致的基于 Spring 的应用程序中 AOP 的所有用途 架构。此集成不会影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP 保持向后兼容。有关 Spring AOP API 的讨论,请参阅下一章。
Spring Framework 的核心原则之一是非侵入性。这 是你不应该被迫引入特定于框架的类的想法,并且 与您的业务或域模型接口。但是,在某些地方,Spring 框架 确实为您提供了将特定于 Spring Framework 的依赖项引入 代码库。为您提供此类选项的基本原理是,在某些情况下,它 可能更容易阅读或编码某些特定的功能 一种方式。但是,Spring Framework(几乎)总是为您提供选择:您拥有 自由地做出明智的决定,选择最适合您的特定用途 案例或场景。 与本章相关的一个选择是 AOP 框架(和 选择哪个 AOP 样式)。您可以选择 AspectJ、Spring AOP 或两者兼而有之。你 还可以选择 @AspectJ 注释样式方法或 Spring XML 配置样式的方法。本章选择引入 首先@AspectJ式的方法不应被视为春季队的标志 更倾向于@AspectJ注释样式的方法,而不是 Spring XML 配置样式。 请参阅选择要使用的 AOP 声明样式,以更完整地讨论 每种风格。 |
5.3. AOP 代理
Spring AOP 默认使用 AOP 代理的标准 JDK 动态代理。这 允许代理任何接口(或一组接口)。
Spring AOP 也可以使用 CGLIB 代理。这是代理类所必需的,而不是 接口。缺省情况下,如果业务对象未实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见)的情况下,您可以强制使用 CGLIB 需要建议未在接口上声明的方法或您需要在哪里声明 将代理对象作为具体类型传递给方法。
重要的是要掌握 Spring AOP 是基于代理的事实。请参阅了解 AOP 代理,全面检查这到底是什么 实施细节实际上意味着。
5.4. @AspectJ支持
@AspectJ指的是一种将方面声明为常规 Java 类的样式,并使用 附注。@AspectJ样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。Spring 使用 AspectJ 提供的库解释与 AspectJ 5 相同的注释 用于切入点解析和匹配。不过,AOP 运行时仍然是纯 Spring AOP,并且 不依赖于 AspectJ 编译器或编织器。
使用 AspectJ 编译器和编织器可以使用完整的 AspectJ 语言和 在将 AspectJ 与 Spring 应用程序一起使用中讨论。 |
5.4.1. 启用@AspectJ支持
要在 Spring 配置中使用@AspectJ方面,您需要启用 Spring 对 基于@AspectJ方面配置 Spring AOP 和基于 无论他们是否受到这些方面的建议。通过自动代理,我们的意思是,如果 Spring 确定一个 bean 是由一个或多个方面建议的,它会自动生成 该 bean 的代理,用于拦截方法调用并确保通知运行 根据需要。
可以通过 XML 或 Java 样式配置启用@AspectJ支持。在任一
case,您还需要确保 AspectJ 的aspectjweaver.jar
库位于
应用程序的类路径(版本 1.8 或更高版本)。此库可在lib
目录 AspectJ 发行版或 Maven Central 存储库。
5.4.2. 声明一个方面
启用@AspectJ支持后,在应用程序上下文中定义的任何 bean
类,该类是@AspectJ方面(具有@Aspect
注释)自动
被 Spring 检测到并用于配置 Spring AOP。接下来的两个示例显示了
对于不太有用的方面,需要最少的定义。
两个示例中的第一个示例显示了应用程序中的常规 Bean 定义
上下文,该上下文指向具有@Aspect
注解:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>
两个示例中的第二个示例显示了NotVeryUsefulAspect
类定义,
其中用org.aspectj.lang.annotation.Aspect
注解;
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
package org.xyz
import org.aspectj.lang.annotation.Aspect;
@Aspect
class NotVeryUsefulAspect
方面(用@Aspect
) 可以有方法和字段,与任何
其他类。它们还可以包含切入点、建议和介绍(inter-type)
声明。
通过组件扫描自动检测各个方面 您可以在 Spring XML 配置中将 aspect 类注册为常规 bean,
通过@Bean 方法@Configuration 类,或者让 Spring 通过
类路径扫描 — 与任何其他 Spring 管理的 Bean 相同。但是,请注意@Aspect 注释不足以在类路径中进行自动检测。为此
目的,您需要添加一个单独的@Component 注释(或者,或者,自定义
stereotype 注释,根据 Spring 的组件扫描程序的规则)。 |
将方面与其他方面提供建议? 在 Spring AOP 中,方面本身不能成为来自其他方面建议的目标
方面。这@Aspect 类上的注释将其标记为一个方面,因此排除
它来自自动代理。 |
5.4.3. 声明切入点
切入点确定感兴趣的连接点,从而使我们能够控制当通知运行时。Spring AOP 仅支持 Spring 的方法执行连接点bean,因此您可以将切入点视为与 Spring 上方法的执行相匹配 豆。 切入点声明由两部分组成:一个签名,包括名称和任何参数和一个切入点表达式,用于准确确定我们感兴趣的方法执行。在 AOP 的 @AspectJ 注释样式中,切入点签名由常规方法定义提供,切入点表达式是通过使用@Pointcut
注释(用作切入点签名的方法必须有一个void
返回类型)。
一个示例可能有助于区分切入点特征和切入点表达式。以下示例定义了名为anyOldTransfer
那 匹配任何名为transfer
:
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
形成@Pointcut
注释是常规的
AspectJ 切入点表达式。有关 AspectJ 切入点语言的完整讨论,请参阅
AspectJ 的
编程指南(以及对于扩展的 AspectJ 5
Developer's Notebook)或有关 AspectJ 的书籍之一(例如 Colyer 的 Eclipse AspectJ)
等人,或 AspectJ in Action,作者:Ramnivas Laddad)。
支持的切入点标号
Spring AOP 支持以下 AspectJ 切入点标号 (PCD) 用于切入点 表达 式:
-
execution
:用于匹配方法执行连接点。这是主要的 使用 Spring AOP 时使用的切入点指示符。 -
within
:限制匹配到某些类型(执行 使用 Spring AOP 时在匹配类型中声明的方法)。 -
this
:限制匹配到连接点(使用 Spring 时方法的执行 AOP),其中 bean 引用(Spring AOP 代理)是给定类型的实例。 -
target
:限制与连接点的匹配(使用 Spring AOP),其中目标对象(被代理的应用程序对象)是一个实例 给定类型的。 -
args
:限制匹配到连接点(使用 Spring 时方法的执行 AOP),其中参数是给定类型的实例。 -
@target
:限制与连接点的匹配(使用 Spring AOP),其中执行对象的类具有给定类型的注释。 -
@args
:限制匹配到连接点(使用 Spring 时方法的执行 AOP),其中传递的实际参数的运行时类型具有 给定类型。 -
@within
:限制匹配以连接具有给定 注释(在类型中声明的方法在给定注释时 使用 Spring AOP)。 -
@annotation
:将匹配限制为连接点的主体 (在 Spring AOP 中运行的方法)具有给定的注释。
由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面的讨论
的切入点指示符给出的定义比您在
AspectJ 编程指南。此外,AspectJ 本身具有基于类型的语义,并且
执行连接点,两者this
和target
引用同一对象:该
执行该方法的对象。Spring AOP 是一个基于代理的系统,并区分了
在代理对象本身之间(绑定到this
)和
proxy(绑定到target
).
由于 Spring 的 AOP 框架基于代理的性质,目标对象内的调用 根据定义,不会被拦截。对于 JDK 代理,只有公共接口方式 代理上的调用可以被拦截。使用 CGLIB 时,公共和受保护的方法调用 代理被拦截(如有必要,甚至包可见的方法)。然而 通过代理进行的常见交互应始终通过公共签名进行设计。 请注意,切入点定义通常与任何截获的方法相匹配。如果切入点严格意义上是仅限公开的,即使在具有通过代理进行潜在的非公开交互的 CGLIB 代理场景中,也需要相应地定义它。 如果您的拦截需要包括目标中的方法调用甚至构造函数 类,考虑使用 Spring 驱动的原生 AspectJ 编织来代替 Spring 基于代理的 AOP 框架。这构成了一种不同的AOP使用模式 具有不同的特点,所以一定要让自己熟悉编织 在做出决定之前。 |
Spring AOP 还支持一个名为bean
.此 PCD 可让您限制
连接点与特定命名的 Spring Bean 或一组命名的
Spring bean(使用通配符时)。这bean
PCD有以下形式:
bean(idOrNameOfBean)
bean(idOrNameOfBean)
这idOrNameOfBean
token 可以是任何 Spring Bean 的名称。有限通配符
提供了使用该字符的支持,因此,如果您建立一些命名
conventions 中,您可以编写一个*
bean
PCD表达
以选择它们。与其他切入点指示符一样,该bean
PCD罐
与 (and) 一起使用,&&
||
(或),以及!
(否定)运算符也是如此。
这 这 |
组合切入点表达式
您可以使用以下命令组合切入点表达式&&,
||
和!
.也可以参考
按名称切割点表达式。以下示例显示了三个切入点表达式:
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} (1)
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} (2)
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} (3)
1 | anyPublicOperation 如果方法执行连接点表示执行,则匹配
任何公共方法。 |
2 | inTrading 如果方法执行在交易模块中,则匹配。 |
3 | tradingOperation 如果方法执行代表
交易模块。 |
@Pointcut("execution(public * *(..))")
private fun anyPublicOperation() {} (1)
@Pointcut("within(com.xyz.myapp.trading..*)")
private fun inTrading() {} (2)
@Pointcut("anyPublicOperation() && inTrading()")
private fun tradingOperation() {} (3)
1 | anyPublicOperation 如果方法执行连接点表示执行,则匹配
任何公共方法。 |
2 | inTrading 如果方法执行在交易模块中,则匹配。 |
3 | tradingOperation 如果方法执行代表
交易模块。 |
最佳实践是从较小的命名中构建更复杂的切入点表达式 组件,如前所示。当按名称引用切入点时,正常的 Java 可见性 规则适用(您可以看到相同类型的私有切入点,在 层次结构、任何地方的公共切入点等)。可见性不影响切入点 匹配。
共享通用切入点定义
在使用企业应用程序时,开发人员通常希望从多个方面引用应用程序和特定作集的模块。 我们 建议定义一个CommonPointcuts
捕获常见切入点表达式的方面为此目的。此类方面通常类似于以下示例:
package com.xyz.myapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcuts {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.myapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.myapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.myapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
* the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
package com.xyz.myapp
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
@Aspect
class CommonPointcuts {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.myapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.web..*)")
fun inWebLayer() {
}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.myapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.service..*)")
fun inServiceLayer() {
}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.myapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.dao..*)")
fun inDataAccessLayer() {
}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
* the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
fun businessService() {
}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
fun dataAccessOperation() {
}
}
您可以在需要pointcut 表达式的地方引用在此类方面中定义的切入点。例如,要使服务层具有事务性,您可以编写以下内容:
<aop:config>
<aop:advisor
pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
这<aop:config>
和<aop:advisor>
元素在基于架构的AOP支持中进行了讨论。这
事务元素在事务管理中进行了讨论。
例子
Spring AOP 用户可能会使用execution
切入点指示符最常使用。
执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除返回类型模式 (ret-type-pattern
在前面的片段中),
名称模式和参数模式是可选的。返回类型模式确定
方法的返回类型必须是什么才能匹配连接点。 最常用作返回类型模式。它匹配任何返回
类型。仅当方法返回给定的
类型。名称模式与方法名称匹配。可以将通配符用作 all 或
名称模式的一部分。如果指定声明类型模式,
包括尾随*
*
.
将其连接到名称模式组件。
参数模式稍微复杂一些:匹配
方法,而不采用任何参数,而()
(..)
匹配任意数量(零个或多个)参数。
该模式匹配采用任何类型的一个参数的方法。(*)
(*,String)
匹配接受两个参数的方法。第一个可以是任何类型,而
second 必须是String
.咨询语言
AspectJ 编程指南的语义部分了解更多信息。
以下示例显示了一些常见的切入点表达式:
-
任何公共方法的执行:
execution(public * *(..))
-
名称以
set
:execution(* set*(..))
-
执行由
AccountService
接口:execution(* com.xyz.service.AccountService.*(..))
-
执行
service
包:execution(* com.xyz.service.*.*(..))
-
执行服务包或其子包之一中定义的任何方法:
execution(* com.xyz.service..*.*(..))
-
服务包中的任何连接点(仅在 Spring AOP 中执行方法):
within(com.xyz.service.*)
-
服务包内的任何连接点(仅在 Spring AOP 中执行方法)或其 子包:
within(com.xyz.service..*)
-
代理实现
AccountService
接口:this(com.xyz.service.AccountService)
this
更常以装订形式使用。请参阅声明通知部分,了解如何使代理对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中目标对象 实现
AccountService
接口:target(com.xyz.service.AccountService)
target
更常以装订形式使用。请参阅声明建议部分 了解如何使目标对象在通知正文中可用。 -
任何接受单个参数的连接点(仅在 Spring AOP 中执行方法) 运行时传递的参数是
Serializable
:args(java.io.Serializable)
args
更常以装订形式使用。请参阅声明建议部分 了解如何在通知正文中使方法参数可用。请注意,本例中给出的切入点与
execution(* *(java.io.Serializable))
.如果运行时传递的参数为Serializable
,如果方法签名声明单个 type 的参数Serializable
. -
任何连接点(仅在 Spring AOP 中执行方法),其中目标对象具有
@Transactional
注解:@target(org.springframework.transaction.annotation.Transactional)
您还可以使用 @target
以装订形式。请参阅声明建议部分 如何使注释对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中声明的类型为 target 对象有一个
@Transactional
注解:@within(org.springframework.transaction.annotation.Transactional)
您还可以使用 @within
以装订形式。请参阅声明建议部分 如何使注释对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中执行方法具有
@Transactional
注解:@annotation(org.springframework.transaction.annotation.Transactional)
您还可以使用 @annotation
以装订形式。请参阅声明建议部分 了解如何使注释对象在通知正文中可用。 -
任何接受单个参数的连接点(仅在 Spring AOP 中执行方法) 传递的参数的运行时类型具有
@Classified
注解:@args(com.xyz.security.Classified)
您还可以使用 @args
以装订形式。请参阅声明建议部分 如何在通知正文中使注释对象可用。 -
名为 Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法)
tradeService
:bean(tradeService)
-
Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法)具有匹配通配符表达式
*Service
:bean(*Service)
写好的切入点
在编译过程中,AspectJ 处理切入点以优化匹配 性能。 检查代码并确定每个连接点是否匹配(静态或动态)给定的切入点是一个成本高昂的过程。(动态匹配意味着匹配无法从静态分析中完全确定,并且在代码中放置了一个测试,以确定代码运行时是否存在实际匹配)。在第一次遇到切入点声明时,AspectJ 将其重写为匹配的最佳形式 过程。 这是什么意思?基本上,切入点是用 DNF(析取范式)重写的,切入点的组件被排序,以便那些组件首先检查评估成本较低的组件。这意味着您不必担心关于了解各种切入点指示符的性能并可能提供它们在切入点声明中以任何顺序。
但是,AspectJ 只能使用它所告知的内容。为了获得最佳性能matching,您应该考虑他们试图实现的目标并缩小搜索范围定义中尽可能多的匹配空间。现有的指示符自然分为三组之一:kinded、sscopeing 和 contextual:
-
kinded 指示符选择特定类型的连接点:
execution
,get
,set
,call
和handler
. -
范围标号选择一组连接感兴趣点 (可能有很多种):
within
和withincode
-
上下文指示符根据上下文匹配(并可选择绑定):
this
,target
和@annotation
写得好的切入点应至少包括前两种类型(kinded 和 范围界定)。您可以包括要基于的上下文指示符进行匹配 连接点上下文或绑定该上下文以在通知中使用。仅提供一个 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),这是由于额外的处理和分析。范围 指示符的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速 消除不应进一步处理的连接点组。一个好的 如果可能的话,切入点应始终包含一个。
5.4.4. 声明通知
通知与切入点表达式相关联,并在切入点表达式之前、之后或周围运行 与切入点匹配的方法执行。切入点表达式可以是 对命名点切入或就地声明的切入点表达式的简单引用。
建议前
您可以使用@Before
注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
如果我们使用就地切入点表达式,我们可以将前面的示例重写为 以下示例:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
fun doAccessCheck() {
// ...
}
}
退货后建议
返回通知后,当匹配的方法执行正常返回时运行。
您可以使用@AfterReturning
注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
您可以有多个建议声明(以及其他成员), 都在同一个方面。我们在这些中仅显示一个建议声明 示例来集中每个效果。 |
有时,您需要在通知正文中访问返回的实际值。
您可以使用@AfterReturning
绑定返回值以获取
访问权限,如以下示例所示:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning(
pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning = "retVal")
fun doAccessCheck(retVal: Any) {
// ...
}
}
中使用的名称returning
属性必须对应于参数的名称
在建议方法中。当方法执行返回时,返回值将传递给
advice 方法作为相应的参数值。一个returning
子句
将匹配限制为仅返回
指定的类型(在本例中,Object
,与任何返回值匹配)。
请注意,在以下情况下,不可能返回完全不同的引用 返回建议后使用。
抛出建议后
抛出建议后,当匹配的方法执行退出时,通过抛出
例外。您可以使用@AfterThrowing
注释,作为
以下示例显示:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doRecoveryActions() {
// ...
}
}
通常,您希望通知仅在抛出给定类型的异常时运行,并且您还经常需要访问通知正文中抛出的异常。 您可以 使用throwing
属性来限制匹配(如果需要 - 使用Throwable
否则作为异常类型),并将抛出的异常绑定到 advice 参数。以下示例显示了如何执行此作:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing(
pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing = "ex")
fun doRecoveryActions(ex: DataAccessException) {
// ...
}
}
中使用的名称throwing
属性必须对应于通知方法中的参数名称。当方法执行通过抛出异常退出时,异常作为相应的参数值传递给通知方法。 一个throwing
第
还将匹配限制为仅抛出
指定类型 (DataAccessException
,在本例中)。
请注意 |
在(最后)建议之后
After (finally) 通知在匹配的方法执行退出时运行。它是由
使用@After
注解。在建议后必须准备好处理正常和
异常返回条件。它通常用于释放资源等
目的。以下示例显示了如何使用 after finally 建议:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@Aspect
class AfterFinallyExample {
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doReleaseLock() {
// ...
}
}
请注意 |
周边建议
最后一种建议是围绕建议。围绕建议“围绕”匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行并确定何时、如何运行,甚至该方法是否真的可以运行。 如果您需要在方法之前和之后共享状态,通常会使用 Around 建议 以线程安全的方式执行 - 例如,启动和停止计时器。
始终使用满足您要求的最不强大的建议形式。 例如,如果之前的建议足以满足您的需求,请不要使用周围建议。 |
Around 建议是通过使用@Around
注解。这
方法应声明Object
作为其返回类型,以及方法的第一个参数
必须是ProceedingJoinPoint
.在建议方法的正文中,您必须
调用proceed()
在ProceedingJoinPoint
为了让底层方法
跑。调用proceed()
没有参数将导致调用方的原始
调用基础方法时提供给底层方法的参数。高级使用
情况下,存在proceed()
方法,该方法接受
参数 (Object[]
).数组中的值将用作
调用 underlying 方法。
的行为 Spring 采用的方法更简单,与其基于代理的
仅执行语义。只有在编译 |
周围通知返回的值是调用者看到的返回值
方法。例如,如果简单缓存方面具有
one 或调用proceed()
(并返回该值)。请注意proceed
可以在周围建议的正文中调用一次、多次或根本不调用。都
其中是合法的。
如果将 around 建议方法的返回类型声明为void ,null 将始终返回给调用者,从而有效地忽略任何调用的结果
之proceed() .因此,建议使用 around 通知方法声明返回
类型Object .通知方法通常应返回从
调用proceed() ,即使基础方法具有void 返回类型。
但是,通知可以选择返回缓存值、包装值或其他一些值
值取决于用例。 |
以下示例显示了如何使用 around 建议:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint
@Aspect
class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return retVal
}
}
通知参数
Spring 提供完全类型的建议,这意味着您可以在
advice 签名(正如我们之前在返回和抛出示例中看到的那样),而不是
与Object[]
数组。我们看到如何进行论证和其他上下文
本节后面的建议机构可用的值。首先,我们来看看如何
写通用建议,了解建议当前建议的方法。
访问当前JoinPoint
任何通知方法都可以声明类型为org.aspectj.lang.JoinPoint
.请注意,声明第一个需要周围建议
type 的参数ProceedingJoinPoint
,它是JoinPoint
.
这JoinPoint
interface 提供了许多有用的方法:
-
getArgs()
:返回方法参数。 -
getThis()
:返回代理对象。 -
getTarget()
:返回目标对象。 -
getSignature()
:返回正在建议的方法的描述。 -
toString()
:打印所建议方法的有用描述。
有关更多详细信息,请参阅 javadoc。
将参数传递给通知
我们已经看到了如何绑定返回值或异常值(使用 after
返回并在抛出建议后)。使参数值可用于通知
body,可以使用args
.如果使用参数名称代替
类型名称args
expression,则相应参数的值传递为
调用通知时的参数值。一个例子应该使这一点更清楚。
假设你想建议执行 DAO作,这些作需要Account
object 作为第一个参数,并且您需要访问通知正文中的帐户。
你可以写以下内容:
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
// ...
}
这args(account,..)
切入点表达式的一部分有两个用途。首先,它
将匹配限制为仅那些方法执行,其中该方法至少采用一个
参数,传递给该参数的参数是Account
.
其次,它使实际的Account
对象,通过account
参数。
另一种编写方式是声明一个切入点,该切入点“提供”了Account
对象值,然后引用命名的切入点
从建议。这将如下所示:
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}
@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
// ...
}
有关更多详细信息,请参阅 AspectJ 编程指南。
代理对象 (this
)、目标对象 (target
)和注释(@within
,@target
,@annotation
和@args
)都可以以类似的方式绑定。下一个
两个示例展示了如何匹配用@Auditable
注释并提取审计代码:
两个示例中的第一个示例显示了@Auditable
注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)
两个示例中的第二个示例显示了与执行相匹配的建议@Auditable
方法:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
val code = auditable.value()
// ...
}
通知参数和泛型
Spring AOP 可以处理类声明和方法参数中使用的泛型。假设 您有一个如下所示的泛型类型:
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
interface Sample<T> {
fun sampleGenericMethod(param: T)
fun sampleGenericCollectionMethod(param: Collection<T>)
}
您可以通过以下方式将方法类型的拦截限制为某些参数类型 将 advice 参数绑定到要截获方法的参数类型:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
// Advice implementation
}
此方法不适用于泛型集合。因此,您不能定义 切入点,如下所示:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
// Advice implementation
}
为了做到这一点,我们必须检查集合的每个元素,这不是
合理,因为我们也无法决定如何治疗null
一般值。实现
与此类似,您必须将参数键入Collection<?>
和手动
检查元素的类型。
确定参数名称
通知调用中的参数绑定依赖于匹配切入点中使用的名称 表达式添加到通知和切入点方法签名中声明的参数名称。
本节可以互换使用术语参数和参数,因为 AspectJ API 将参数名称称为参数名称。 |
Spring AOP 使用以下内容ParameterNameDiscoverer
要确定的实现
参数名称。每个发现者都有机会发现参数名称,并且
第一个成功的发现者获胜。如果注册的发现者都不具备能力
确定参数名称时,将抛出异常。
AspectJAnnotationParameterNameDiscoverer
-
使用显式的参数名称 由用户通过
argNames
属性或 切入点注释。有关详细信息,请参阅显式参数名称。 KotlinReflectionParameterNameDiscoverer
-
使用 Kotlin 反射 API 确定 参数名称。仅当类路径上存在此类 API 时,才使用此发现器。 GraalVM 本机映像不支持。
StandardReflectionParameterNameDiscoverer
-
使用标准
java.lang.reflect.Parameter
用于确定参数名称的 API。要求使用-parameters
标志javac
.Java 8+ 上的推荐方法。 LocalVariableTableParameterNameDiscoverer
-
分析可用的局部变量表 在通知类的字节码中,从调试信息中确定参数名称。 要求使用调试符号 (
-g:vars
至少)。荒废的 从 Spring Framework 6.0 开始,在 Spring Framework 6.1 中删除,有利于编译 code 替换为-parameters
.GraalVM 本机映像不支持,除非相应的 类文件作为资源存在于映像中。 AspectJAdviceParameterNameDiscoverer
-
从切入点推断参数名称 表达
returning
和throwing
第。有关所用算法的详细信息,请参阅 javadoc。
显式参数名称
@AspectJ建议和切入点注释具有可选的argNames
属性
可用于指定带注释的方法的参数名称。
@AspectJ如果 AspectJ 编译器( 同样,如果@AspectJ方面已使用 |
以下示例演示如何使用argNames
属性:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code and bean
}
如果第一个参数的类型为JoinPoint
,ProceedingJoinPoint
或JoinPoint.StaticPart
,您可以从argNames
属性。例如,如果修改上述通知以接收联接
point 对象,则argNames
属性不需要包含它:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code, bean, and jp
}
对类型的第一个参数给予的特殊处理JoinPoint
,ProceedingJoinPoint
或JoinPoint.StaticPart
特别方便建议
不收集任何其他连接点上下文的方法。在这种情况下,您可以
省略argNames
属性。例如,以下建议不需要声明
这argNames
属性:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
// ... use jp
}
继续使用参数
我们之前提到过,我们将描述如何编写一个proceed
调用
在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是
以确保通知签名按顺序绑定每个方法参数。
以下示例显示了如何执行此作:
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
accountHolderNamePattern: String): Any {
val newPattern = preProcess(accountHolderNamePattern)
return pjp.proceed(arrayOf<Any>(newPattern))
}
在许多情况下,无论如何都会执行此绑定(如前面的示例所示)。
建议订购
当多个建议都想在同一连接点运行时会发生什么? Spring AOP 遵循与 AspectJ 相同的优先级规则来确定通知的顺序 执行。最高优先级建议首先“在进入的路上”运行(因此,给定两块 之前的建议,优先级最高的优先顺序)。“在离开的路上”来自一个 连接点,最高优先级通知最后运行(因此,给定两个 After 建议,优先级最高的将排在第二位)。
当在不同方面定义的两条建议都需要同时运行时
join point,除非另有指定,否则执行顺序是未定义的。您可以
通过指定优先级来控制执行顺序。这是在正常情况下完成的
Spring 方式,通过实现org.springframework.core.Ordered
界面
aspect 类或使用@Order
注解。给定两个方面,
aspect 从Ordered.getOrder()
(或注释值)具有
优先级越高。
特定方面的每种不同建议类型在概念上都是要适用的
直接到连接点。因此,一个 从 Spring Framework 5.2.7 开始,在相同的 当两个相同类型的建议(例如,两个 |
5.4.5. 简介
引言(在 AspectJ 中称为类型间声明)使切面能够声明 建议对象实现给定接口,并提供 该接口代表这些对象。
您可以使用@DeclareParents
注解。这个注释
用于声明匹配类型具有新的父级(因此得名)。例如
给定一个名为UsageTracked
以及该接口的实现,名为DefaultUsageTracked
,以下方面声明服务的所有实现者
接口还实现了UsageTracked
接口(例如,通过 JMX 进行统计):
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
@Aspect
class UsageTracking {
companion object {
@DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
lateinit var mixin: UsageTracked
}
@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
}
要实现的接口由带注释的字段的类型决定。这value
属性的@DeclareParents
注释是 AspectJ 类型的模式。任何
匹配类型的 bean 实现了UsageTracked
接口。请注意,在
在前面示例的建议之前,服务 bean 可以直接用作
的实现UsageTracked
接口。如果以编程方式访问 bean,
你会写下:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
5.4.6. 方面实例化模型
这是一个高级主题。如果您刚刚开始使用 AOP,您可以放心地跳过 直到以后。 |
默认情况下,应用程序中每个方面都有一个实例
上下文。AspectJ 称之为单例实例化模型。可以定义
具有替代生命周期的方面。Spring 支持 AspectJ 的perthis
和pertarget
实例化模型;percflow
,percflowbelow
和pertypewithin
目前没有
支持。
您可以声明一个perthis
aspect 通过指定perthis
子句中的@Aspect
注解。请考虑以下示例:
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {
private int someState;
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
public void recordServiceUsage() {
// ...
}
}
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {
private val someState: Int = 0
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
fun recordServiceUsage() {
// ...
}
}
在前面的示例中,的perthis
子句是一个方面实例
为执行业务服务的每个唯一服务对象创建 (每个唯一的
绑定到this
在与切入点表达式匹配的连接点处)。方面
instance 在首次在服务对象上调用方法时创建。这
当服务对象超出范围时,方面将超出范围。方面之前
实例创建时,其中的任何通知都不会运行。一旦方面实例
,则其中声明的通知在匹配的连接点运行,但仅
当服务对象是与此方面关联的对象时。查看 AspectJ
编程指南,了解更多信息per
第。
这pertarget
实例化模型的工作方式与perthis
,但它
在匹配的连接点处为每个唯一目标对象创建一个方面实例。
5.4.7. AOP 示例
现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起来做 有用的东西。
业务服务的执行有时会由于并发问题而失败(对于
例如,死锁失败者)。如果重试作,则很可能会成功
在下一次尝试中。对于适合重试的业务服务
条件(冪等作,不需要返回给用户进行冲突
resolution),我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException
.这是一个明显跨越的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想重试作,所以我们需要使用 around 建议,以便我们可以
叫proceed
多次。以下列表显示了基本方面实现:
@Aspect
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
@Aspect
class ConcurrentOperationExecutor : Ordered {
private val DEFAULT_MAX_RETRIES = 2
private var maxRetries = DEFAULT_MAX_RETRIES
private var order = 1
fun setMaxRetries(maxRetries: Int) {
this.maxRetries = maxRetries
}
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
numAttempts++
try {
return pjp.proceed()
} catch (ex: PessimisticLockingFailureException) {
lockFailureException = ex
}
} while (numAttempts <= this.maxRetries)
throw lockFailureException
}
}
请注意,该方面实现了Ordered
接口,以便我们可以设置
高于交易建议的方面(我们每次都希望有新的交易
重试)。这maxRetries
和order
属性都由 Spring 配置。这
主要作发生在doConcurrentOperation
围绕建议。请注意,对于
moment,我们将重试逻辑应用于每个businessService()
.我们尝试继续,
如果我们失败了PessimisticLockingFailureException
,我们重试,除非
我们已经用尽了所有的重试尝试。
相应的 Spring 配置如下:
<aop:aspectj-autoproxy/>
<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
为了优化切面,使其仅重试幂等作,我们可以定义以下内容Idempotent
注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation
然后,我们可以使用注释来注释服务作的实现。变化
到仅重试幂等作的方面涉及细化切入点
表达式,以便仅@Idempotent
作匹配,如下所示:
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
// ...
}
5.5. 基于模式的AOP支持
如果您更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持
使用aop
namespace 标签。完全相同的切入点表达式和建议类型
与使用@AspectJ样式一样,支持。因此,在本节中,我们将重点关注
该语法,并让读者参考上一节中的讨论
(@AspectJ支持)用于理解编写切入点表达式和绑定
的建议参数。
要使用本节中描述的 aop 命名空间标签,您需要导入spring-aop
schema,如基于 XML Schema 的配置中所述。请参阅AOP架构,了解如何导入aop
Namespace。
在 Spring 配置中,所有方面和顾问元素都必须放置在
一<aop:config>
元素(可以有多个<aop:config>
元素中的
应用程序上下文配置)。一<aop:config>
元素可以包含切入点,
advisor 和 aspect 元素(请注意,这些元素必须按该顺序声明)。
这<aop:config> 配置样式大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议
not being woven),如果你已经通过使用BeanNameAutoProxyCreator 或类似的东西。推荐的使用模式是
仅使用<aop:config> style 或仅AutoProxyCreator style 和
永远不要混合它们。 |
5.5.1. 声明切面
当您使用模式支持时,方面是定义为 bean 的常规 Java 对象 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 对象的方法,切入点和通知信息被捕获在 XML 中。
您可以使用<aop:aspect>
元素,并引用后备 bean
通过使用ref
属性,如以下示例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支持方面 (aBean
在这种情况下)当然可以配置和
像任何其他 Spring Bean 一样注入依赖。
5.5.2. 声明切入点
您可以在<aop:config>
元素,让切入点
定义在多个方面和顾问之间共享。
表示服务层中任何业务服务执行的切入点可以 定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式 @AspectJ支持中所述的语言。如果使用基于架构的声明 样式时,您可以引用类型 (@Aspects) 中定义的命名点切入 切入点表达式。定义上述切入点的另一种方法如下:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.CommonPointcuts.businessService()"/>
</aop:config>
假设你有一个CommonPointcuts
方面,如共享公共切入点定义中所述。
然后,在切面内声明切入点与声明顶级切入点非常相似, 如以下示例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
与@AspectJ方面大致相同,使用基于模式的
定义样式可以收集连接点上下文。例如,以下切入点
收集this
object 作为连接点上下文并将其传递给通知:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
必须声明通知以接收收集的连接点上下文,方法是包含 参数,如下所示:
public void monitor(Object service) {
// ...
}
fun monitor(service: Any) {
// ...
}
组合切入点子表达式时,&&
在 XML 中很尴尬
文档,因此您可以使用and
,or
和not
关键字代替&&
,||
和!
分别。例如,前面的切入点可以更好地写成
遵循:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
请注意,以这种方式定义的切入点由其 XML 引用id
并且不能是
用作命名点切入以形成复合切入点。中的命名切入点支座
因此,基于模式的定义风格比@AspectJ提供的定义风格更受限制
风格。
5.5.3. 声明通知
基于模式的 AOP 支持使用与 @AspectJ 样式相同的五种建议,并且它们具有 完全相同的语义。
建议前
before 通知在匹配的方法执行之前运行。它是在<aop:aspect>
通过使用<aop:before>
元素,如以下示例所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
这里dataAccessOperation
是id
在顶部定义的切入点 (<aop:config>
)
水平。要改为内联定义切入点,请将pointcut-ref
属性替换为
一个pointcut
属性,如下所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
正如我们在讨论@AspectJ样式时所指出的,使用命名切入点可以 显着提高代码的可读性。
这method
属性标识方法(doAccessCheck
) 提供
建议。必须为方面元素引用的 bean 定义此方法
其中包含建议。在执行数据访问作之前(方法
与切入点表达式匹配的连接点),则doAccessCheck
方面方法
bean 被调用。
退货后建议
返回通知后,当匹配的方法执行正常完成时运行。是的
在<aop:aspect>
与之前的建议相同。以下示例
显示如何声明它:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
与@AspectJ样式一样,您可以在通知正文中获取返回值。
为此,请使用returning
属性来指定参数的名称,以指定该参数的名称。
应传递返回值,如以下示例所示:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
这doAccessCheck
方法必须声明一个名为retVal
.这个的类型
参数以与@AfterReturning
.为
例如,您可以按如下方式声明方法签名:
public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...
抛出建议后
抛出建议后,当匹配的方法执行退出时,通过抛出
例外。它是在<aop:aspect>
通过使用after-throwing
元素
如以下示例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
与@AspectJ样式一样,您可以在通知正文中获取抛出的异常。
为此,请使用throwing
属性,以指定参数的名称
应传递异常,如以下示例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
这doRecoveryActions
方法必须声明一个名为dataAccessEx
.
此参数的类型以与@AfterThrowing
.例如,方法签名可以声明如下:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
在(最后)建议之后
在(最终)通知运行之后,无论匹配的方法执行如何退出。
您可以使用after
元素,如以下示例所示:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是围绕建议。围绕建议“围绕”匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行并确定何时、如何运行,甚至该方法是否真的可以运行。 如果您需要在方法之前和之后共享状态,通常会使用 Around 建议 以线程安全的方式执行 - 例如,启动和停止计时器。
始终使用满足您要求的最不强大的建议形式。 例如,如果之前的建议足以满足您的需求,请不要使用周围建议。 |
您可以使用aop:around
元素。建议方法应
宣Object
作为其返回类型,并且该方法的第一个参数必须是
类型ProceedingJoinPoint
.在通知方法的正文中,您必须调用proceed()
在ProceedingJoinPoint
以便运行底层方法。
调用proceed()
没有参数将导致调用者的原始参数
在调用基础方法时提供给它。对于高级用例,有
是proceed()
接受参数数组的方法
(Object[]
).数组中的值将用作基础
方法。有关通话的注意事项,请参阅 Around Adviceproceed
使用Object[]
.
以下示例显示了如何在 XML 中声明通知:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
的实现doBasicProfiling
建议可以与
@AspectJ示例(当然,减去注释),如以下示例所示:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return pjp.proceed()
}
通知参数
基于模式的声明样式支持完全类型的通知,其方式与
描述@AspectJ支持——通过按名称匹配切入点参数
通知方法参数。有关详细信息,请参阅通知参数。如果您愿意
显式指定通知方法的参数名称(不依赖于
检测策略),您可以使用arg-names
属性,其处理方式与argNames
属性(如确定参数名称中所述)。
以下示例演示如何在 XML 中指定参数名称:
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
这arg-names
属性接受以逗号分隔的参数名称列表。
下面稍微更复杂的基于 XSD 的方法示例显示了 一些与许多强类型参数结合使用的建议:
package x.y.service;
public interface PersonService {
Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
public Person getPerson(String name, int age) {
return new Person(name, age);
}
}
package x.y.service
interface PersonService {
fun getPerson(personName: String, age: Int): Person
}
class DefaultPersonService : PersonService {
fun getPerson(name: String, age: Int): Person {
return Person(name, age)
}
}
接下来是方面。请注意,该profile(..)
方法接受许多
强类型参数,其中第一个恰好是用于
继续方法调用。此参数的存在表明profile(..)
用作around
建议,如以下示例所示:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch
class SimpleProfiler {
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
val clock = StopWatch("Profiling for '$name' and '$age'")
try {
clock.start(call.toShortString())
return call.proceed()
} finally {
clock.stop()
println(clock.prettyPrint())
}
}
}
最后,以下示例 XML 配置会影响 针对特定连接点的前面建议:
<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">
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
<bean id="personService" class="x.y.service.DefaultPersonService"/>
<!-- this is the actual advice itself -->
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
expression="execution(* x.y.service.PersonService.getPerson(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
请考虑以下驱动程序脚本:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
PersonService person = (PersonService) ctx.getBean("personService");
person.getPerson("Pengo", 12);
}
}
fun main() {
val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
val person = ctx.getBean("personService") as PersonService
person.getPerson("Pengo", 12)
}
使用这样的 Boot 类,我们将在标准输出上获得类似于以下内容的输出:
StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- 00000 ? execution(getFoo)
建议订购
当需要在同一连接点运行多个通知时(执行方法)
排序规则如通知排序中所述。优先顺序
between aspect 是通过order
属性中的<aop:aspect>
元素或
通过添加@Order
对支持该方面的 bean 进行注释,或者通过
bean 实现了Ordered
接口。
与同一 例如,给定一个 作为一般经验法则,如果您发现您定义了多条建议
在同一个 |
5.5.4. 简介
引言(在 AspectJ 中称为类型间声明)让切面声明 建议对象实现给定接口并提供 该接口代表这些对象。
您可以使用aop:declare-parents
元素aop:aspect
.
您可以使用aop:declare-parents
元素来声明匹配类型具有新的父级(因此得名)。
例如,给定一个名为UsageTracked
以及该接口的实现,名为DefaultUsageTracked
,以下方面声明服务的所有实现者
接口还实现了UsageTracked
接口。(为了曝光统计数据
例如,通过 JMX。
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.CommonPointcuts.businessService()
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
支持usageTracking
然后 bean 将包含以下方法:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
要实现的接口由implement-interface
属性。这
的值types-matching
属性是 AspectJ 类型模式。任何 bean
matching 类型实现了UsageTracked
接口。请注意,在之前
前面示例的建议,Service Bean 可以直接用作
这UsageTracked
接口。要以编程方式访问 bean,您可以编写
以后:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
5.5.6. 顾问
“顾问”的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接等价物。顾问就像一个小 独立的方面,只有一条建议。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的通知接口之一。顾问可以利用 AspectJ 切入点表达式。
Spring 通过<aop:advisor>
元素。你最
通常看到它与交易建议结合使用,交易建议也有自己的
Spring 中的命名空间支持。以下示例显示了顾问:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
以及pointcut-ref
属性,也可以使用pointcut
属性来定义内联的切入点表达式。
要定义顾问的优先级,以便通知可以参与排序,
使用order
属性来定义Ordered
顾问的价值。
5.5.7. AOP 模式示例
本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。
业务服务的执行有时会由于并发问题而失败(对于
例如,死锁失败者)。如果重试作,则很可能会成功
在下一次尝试中。对于适合重试的业务服务
条件(冪等作,不需要返回给用户进行冲突
resolution),我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException
.这是一个明显跨越的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想重试作,所以我们需要使用 around 建议,以便我们可以
叫proceed
多次。以下列表显示了基本方面实现
(这是一个使用模式支持的常规 Java 类):
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
class ConcurrentOperationExecutor : Ordered {
private val DEFAULT_MAX_RETRIES = 2
private var maxRetries = DEFAULT_MAX_RETRIES
private var order = 1
fun setMaxRetries(maxRetries: Int) {
this.maxRetries = maxRetries
}
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
numAttempts++
try {
return pjp.proceed()
} catch (ex: PessimisticLockingFailureException) {
lockFailureException = ex
}
} while (numAttempts <= this.maxRetries)
throw lockFailureException
}
}
请注意,该方面实现了Ordered
接口,以便我们可以设置
高于交易建议的方面(我们每次都希望有新的交易
重试)。这maxRetries
和order
属性都由 Spring 配置。这
主要作发生在doConcurrentOperation
围绕建议方法。我们尝试 进行。 如果我们在PessimisticLockingFailureException
,我们再试一次,除非我们已经用尽了所有的重试尝试。
该类与@AspectJ示例中使用的类相同,但具有 注释已删除。 |
对应的Spring配置如下:
<aop:config>
<aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:around
pointcut-ref="idempotentOperation"
method="doConcurrentOperation"/>
</aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
<property name="maxRetries" value="3"/>
<property name="order" value="100"/>
</bean>
请注意,目前,我们假设所有业务服务都是幂等的。如果
事实并非如此,我们可以细化该方面,使其仅真正重试
幂等作,通过引入Idempotent
注释和使用注释
对服务作的实现进行注释,如以下示例所示:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
// marker annotation
}
这
更改为仅重试幂等作涉及细化
切入点表达式,以便仅@Idempotent
作匹配,如下所示:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
5.6. 选择要使用的AOP声明样式
一旦您确定某个方面是实现给定的最佳方法 要求,您如何决定在使用 Spring AOP 或 AspectJ 之间以及在 方面语言(代码)样式、@AspectJ 注释样式还是 Spring XML 样式?这些 决策受多种因素影响,包括应用要求、 开发工具,以及团队对 AOP 的熟悉程度。
5.6.1. Spring AOP 还是 Full AspectJ?
使用最简单的东西。Spring AOP 比使用完整的 AspectJ 更简单,因为 无需将 AspectJ 编译器/编织器引入您的开发中 并构建流程。如果您只需要建议在 Spring 上执行作 beans,Spring AOP 是正确的选择。如果需要建议不受 Spring 容器(例如域对象,通常),您需要使用 方面J.如果您想建议加入点以外的连接点,您还需要使用 AspectJ 简单的方法执行(例如,字段获取或设置连接点等)。
当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果您不使用 Java 5+,则已为您做出选择:使用代码样式。如果方面发挥大作用 角色,并且您可以使用 AspectJ 开发工具(AJDT)插件,AspectJ 语言语法是 首选选项。它更干净、更简单,因为语言是有目的的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中不起主要作用,您可能需要考虑使用 @AspectJ样式,在 IDE 中坚持常规 Java 编译,并添加 构建脚本的方面编织阶段。
5.6.2. Spring AOP 的 @AspectJ 还是 XML?
如果您选择使用 Spring AOP,则可以选择 @AspectJ 或 XML 样式。 需要考虑各种权衡。
XML 样式可能是现有 Spring 用户最熟悉的,并且有正版 POJOs。当使用 AOP 作为配置企业服务的工具时,XML 可以是一个很好的 选择(一个好的测试是您是否认为切入点表达式是 配置)。对于 XML 样式,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。
XML 样式有两个缺点。首先,它没有完全封装 在一个地方实现它所解决的要求。DRY 原理说 任何部分都应该有一个单一的、明确的、权威的表示 系统内的知识。使用 XML 样式时,了解需求如何 在后备 Bean 类的声明和 XML 中拆分 配置文件。使用@AspectJ样式时,将封装此信息 在单个模块中:方面。其次,XML 样式在 它可以表达比@AspectJ风格:只有“单例”方面实例化模型 ,并且无法组合在 XML 中声明的命名切入点。 例如,在@AspectJ样式中,您可以编写如下内容:
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
@Pointcut("execution(* get*())")
fun propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}
在 XML 样式中,您可以声明前两个切入点:
<aop:pointcut id="propertyAccess"
expression="execution(* get*())"/>
<aop:pointcut id="operationReturningAnAccount"
expression="execution(org.xyz.Account+ *(..))"/>
XML 方法的缺点是无法定义accountPropertyAccess
通过结合这些定义来切入点。
@AspectJ样式支持额外的实例化模型和更丰富的切入点 组成。它的优点是将方面保持为模块化单元。它还具有 @AspectJ方面可以通过以下方式理解(并因此被消费)的优势 Spring AOP 和 AspectJ。因此,如果您以后决定需要 AspectJ 的功能 要实现其他要求,您可以轻松迁移到经典的 AspectJ 设置。 总的来说,Spring 团队更喜欢@AspectJ样式来定制方面,而不是简单的 企业服务的配置。
5.7. 混合方面类型
通过使用自动代理支持,完全可以混合@AspectJ风格方面,
模式定义<aop:aspect>
方面<aop:advisor>
声明的顾问,甚至代理人
以及相同配置中其他样式的拦截器。所有这些都已实现
通过使用相同的底层支持机制,可以毫无困难地共存。
5.8. 代理机制
Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的代理创建代理
目标对象。JDK 动态代理内置于 JDK 中,而 CGLIB 是常见的
开源类定义库(重新打包到spring-core
).
如果要代理的目标对象至少实现了一个接口,则 JDK 动态 代理。目标类型实现的所有接口都是代理的。 如果目标对象未实现任何接口,则创建 CGLIB 代理。
如果要强制使用 CGLIB 代理(例如,代理每个方法 为目标对象定义,而不仅仅是由其接口实现的对象), 你可以这样做。但是,您应该考虑以下问题:
-
使用 CGLIB,
final
不能建议方法,因为它们不能在 运行时生成的子类。 -
从 Spring 4.0 开始,代理对象的构造函数不再被调用两次, 因为 CGLIB 代理实例是通过 Objenesis 创建的。仅当你的 JVM 这样做时 不允许绕过构造函数,则可能会看到双重调用和 来自 Spring 的 AOP 支持的相应调试日志条目。
要强制使用 CGLIB 代理,请将proxy-target-class
属性 的<aop:config>
元素设置为 true,如下所示:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
要在使用 @AspectJ 自动代理支持时强制 CGLIB 代理,请将proxy-target-class
属性的<aop:aspectj-autoproxy>
元素设置为true
, 如下:
<aop:aspectj-autoproxy proxy-target-class="true"/>
倍数 需要明确的是,使用 |
5.8.1. 了解 AOP 代理
Spring AOP 是基于代理的。掌握 在你编写自己的方面或使用任何方面之前,最后一个陈述的实际含义 Spring Framework 提供的基于 Spring AOP 的方面。
首先考虑这样一个场景:你有一个普通的、未代理的、 nothing-special-about-it,直接对象引用,如下所示 代码片段显示:
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar()
}
fun bar() {
// some logic...
}
}
如果在对象引用上调用方法,则该方法将直接在 该对象引用,如下图和列表所示:

public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
fun main() {
val pojo = SimplePojo()
// this is a direct method call on the 'pojo' reference
pojo.foo()
}
当客户端代码具有的引用是代理时,情况会略有变化。考虑一下 下图和代码片段:

public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
这里要理解的关键是,客户端代码中的main(..)
方法
的Main
类具有对代理的引用。这意味着该方法调用该
对象引用是对代理的调用。因此,代理可以委托给所有
与该特定方法调用相关的拦截器(通知)。然而
一旦调用最终到达目标对象(SimplePojo
引用
在这种情况下),它可能对自身进行的任何方法调用,例如this.bar()
或this.foo()
,将针对this
引用,而不是代理。
这具有重要意义。这意味着自我调用不会产生
在与方法调用相关的建议中,获得运行的机会。
好吧,那么对此该怎么办呢?最佳方法(使用术语“最佳” 松散地在这里)是重构你的代码,以便不会发生自我调用。 这确实需要您做一些工作,但这是最好的、侵入性最小的方法。 下一种方法绝对是可怕的,我们犹豫是否要准确指出它 因为它太可怕了。你可以(尽管这对我们来说很痛苦)完全束缚逻辑 在你的类中到 Spring AOP,如以下示例所示:
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this works, but... gah!
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
这将您的代码完全耦合到 Spring AOP,并且它使类本身意识到 事实上,它正在 AOP 上下文中使用,这与 AOP 背道而驰。它 在创建代理时还需要一些额外的配置,因为 以下示例显示:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
factory.isExposeProxy = true
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
最后,必须注意的是,AspectJ 没有这个自我调用问题,因为 它不是基于代理的 AOP 框架。
5.9. @AspectJ代理的编程创建
除了在配置中声明方面之外,还可以使用<aop:config>
或<aop:aspectj-autoproxy>
,也可以以编程方式创建代理,为目标对象提供建议。有关 Spring AOP API 的完整详细信息,请参阅下一章。在这里,我们想重点关注自动的能力使用@AspectJ方面创建代理。
您可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory
类 为一个或多个@AspectJ方面建议的目标对象创建代理。此类的基本用法非常简单,如以下示例所示:
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
// create a factory that can generate a proxy for the given target object
val factory = AspectJProxyFactory(targetObject)
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager::class.java)
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker)
// now get the proxy object...
val proxy = factory.getProxy<Any>()
有关更多信息,请参阅 javadoc。
5.10. 将 AspectJ 与 Spring 应用程序一起使用
到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。在本节中,我们将探讨如何使用 AspectJ 编译器或编织器来代替此外,如果您的需求超出了 Spring AOP 提供的功能,则还可以使用 Spring AOP 独自。
Spring 附带了一个小型 AspectJ aspect 库,该库可在您的分发中独立使用spring-aspects.jar
.您需要按顺序将其添加到类路径中
使用其中的方面。使用 AspectJ 依赖注入具有 Spring 的域对象和其他 Spring 方面 对于 AspectJ 讨论
该库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面讨论了如何
依赖注入使用 AspectJ 编译器编织的 AspectJ 方面。最后,Spring 框架中使用 AspectJ 的加载时编织介绍了 Spring 应用程序的加载时编织
使用 AspectJ。
5.10.1. 使用 AspectJ 使用 Spring 注入依赖性注入域对象
Spring 容器实例化并配置应用程序中定义的 bean
上下文。也可以要求 bean 工厂配置预先存在的
对象,给定包含要应用的配置的 Bean 定义的名称。spring-aspects.jar
包含一个利用
允许注入任何对象的依赖项的能力。该支持旨在
用于在任何容器控制之外创建的对象。域对象
通常属于这一类,因为它们通常是使用new
运算符或 ORM 工具作为数据库查询的结果。
这@Configurable
注释将类标记为符合 Spring 驱动的条件
配置。在最简单的情况下,您可以纯粹将其用作标记注释,如
以下示例显示:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
package com.xyz.myapp.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable
class Account {
// ...
}
当以这种方式用作标记接口时,Spring 会配置
带注释的类型 (Account
,在本例中),使用 bean 定义(通常
prototype-scoped),与完全限定类型名称同名
(com.xyz.myapp.domain.Account
).由于 bean 的默认名称是
完全限定的名称,声明原型定义的便捷方式
是省略id
属性,如以下示例所示:
<bean class="com.xyz.myapp.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
如果要显式指定要使用的原型 Bean 定义的名称,请 可以直接在注释中执行此作,如以下示例所示:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
package com.xyz.myapp.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable("account")
class Account {
// ...
}
Spring 现在查找名为account
并将其用作
配置 new 的定义Account
实例。
您还可以使用自动装配来避免在
都。要让 Spring 应用自动布线,请使用autowire
属性的@Configurable
注解。您可以指定@Configurable(autowire=Autowire.BY_TYPE)
或@Configurable(autowire=Autowire.BY_NAME)
用于按类型或名称自动接线,
分别。作为替代方案,最好指定显式的、注释驱动的
依赖注入@Configurable
豆子通过@Autowired
或@Inject
在字段或方法级别(有关更多详细信息,请参阅基于注释的容器配置)。
最后,您可以为新中的对象引用启用 Spring 依赖项检查
使用dependencyCheck
属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)
).如果此属性为
设置为true
,Spring 在配置后验证所有属性(其中
不是原语或集合)已被设置。
请注意,单独使用注释不会执行任何作。它是AnnotationBeanConfigurerAspect
在spring-aspects.jar
作用于
注释。从本质上讲,该方面说,“从初始化返回后
一个类型的新对象,用@Configurable
,配置新创建的对象
根据注释的属性使用 Spring“。在这种情况下,
“初始化”是指新实例化的对象(例如,实例化的对象
使用new
运算符)以及Serializable
正在进行的对象
反序列化(例如,通过 readResolve())。
上一段的关键短语之一是“本质上”。在大多数情况下,
“从新对象的初始化中返回后”的确切语义是
好。在这种情况下,“初始化后”意味着依赖项是
在构建对象后注入。这意味着依赖项
不可用于类的构造函数体。如果你想要
依赖项在构造函数体运行之前注入,因此
可用于构造函数的主体,您需要在 Java
Kotlin
|
为此,必须使用 AspectJ 编织器编织带注释的类型。您可以
使用构建时的 Ant 或 Maven 任务来执行此作(例如,请参阅 AspectJ 开发
环境指南)或加载时编织(请参阅 Spring 框架中使用 AspectJ 进行加载时编织)。这AnnotationBeanConfigurerAspect
本身需要由 Spring 配置(以便获得
对用于配置新对象的 Bean 工厂的引用)。如果你
使用基于 Java 的配置,可以添加@EnableSpringConfigured
到任何@Configuration
类,如下所示:
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
@Configuration
@EnableSpringConfigured
class AppConfig {
}
如果您更喜欢基于 XML 的配置,则 Springcontext
Namespace定义了一个方便的context:spring-configured
元素,您可以按如下方式使用它:
<context:spring-configured/>
实例@Configurable
在配置方面之前创建的对象
导致向调试日志发出消息,并且没有配置
发生的对象。一个示例可能是 Spring 配置中的 bean,它创建了
domain 对象。在这种情况下,您可以使用depends-on
bean 属性来手动指定 bean 依赖于
配置方面。以下示例演示如何使用depends-on
属性:
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
不激活@Configurable 通过 Bean Configurer 方面进行处理,除非您
实际上意味着在运行时依赖其语义。特别是,请确保您这样做
不使用@Configurable 在注册为常规 Spring Bean 的 Bean 类上
与容器。这样做会导致双重初始化,一次通过
容器和一次通过方面。 |
单元测试@Configurable
对象
的目标之一@Configurable
支持是启用独立的单元测试
域对象的属性,而没有与硬编码查找相关的困难。
如果@Configurable
类型没有被 AspectJ 编织,注释没有影响
在单元测试期间。您可以在对象中设置模拟或存根属性引用
测试并照常进行。如果@Configurable
类型已被 AspectJ 编织,
您仍然可以像往常一样在容器外部进行单元测试,但您会看到一条警告
每次构造@Configurable
对象,表示它有
未由 Spring 配置。
使用多个应用程序上下文
这AnnotationBeanConfigurerAspect
用于实现@Configurable
支持
是 AspectJ 单例方面。单例方面的作用域与作用域相同
之static
members:每个类加载器都有一个方面实例来定义类型。
这意味着,如果在同一类加载器中定义多个应用程序上下文
层次结构,您需要考虑在哪里定义@EnableSpringConfigured
bean 和
放置位置spring-aspects.jar
在类路径上。
考虑一个典型的 Spring Web 应用程序配置,它有一个共享的父应用程序
定义通用业务服务的上下文,支持这些服务所需的一切,
以及每个 Servlet 的一个子应用程序上下文(其中包含特定的定义
到该 servlet)。所有这些上下文都共存于同一个类加载器层次结构中,
因此,AnnotationBeanConfigurerAspect
只能保存对其中一个的引用。
在这种情况下,我们建议定义@EnableSpringConfigured
共享中的 bean
(父)应用程序上下文。这定义了您可能想要的服务
注入到域对象中。结果是无法配置域对象
使用
@Configurable机制(无论如何,这可能不是你想做的事情)。
在同一容器中部署多个 Web 应用程序时,请确保每个
Web 应用程序加载spring-aspects.jar
通过使用自己的类加载器
(例如,通过将spring-aspects.jar
在WEB-INF/lib
).如果spring-aspects.jar
仅添加到容器范围的类路径(因此由共享父级
类加载器),所有 Web 应用程序共享相同的 aspect 实例(这可能是
不是你想要的)。
5.10.2. AspectJ 的其他 Spring 方面
除了@Configurable
方面spring-aspects.jar
包含一个 AspectJ方面,您可以使用它来驱动 Spring 对类型和方法的事务管理用@Transactional
注解。 这主要适用于以下用户:想要在 Spring 容器之外使用 Spring 框架的事务支持。
解释的方面@Transactional
注释是AnnotationTransactionAspect
.使用此方面时,必须对
实现类(或该类中的方法或两者兼而有之),而不是接口(如果
any) 的类实现。AspectJ 遵循 Java 的规则,即对
接口不会继承。
一个@Transactional
类上的注释指定
类中任何公共作的执行。
一个@Transactional
类中方法的注释将覆盖默认值
类注释(如果存在)给出的交易语义。任何方法
可见性可以进行注释,包括私有方法。注释非公共方法
直接是获取执行此类方法的事务分界的唯一方法。
从 Spring Framework 4.2 开始,spring-aspects 提供了一个类似的方面,提供
与标准完全相同的功能javax.transaction.Transactional 注解。检查JtaAnnotationTransactionAspect 了解更多详情。 |
对于想要使用 Spring 配置和事务的 AspectJ 程序员
管理支持,但不想(或不能)使用注释,spring-aspects.jar
还包含abstract
您可以扩展以提供自己的切入点的方面
定义。请参阅AbstractBeanConfigurerAspect
和AbstractTransactionAspect
方面以获取更多信息。例如,以下内容
摘录显示了如何编写一个方面来配置对象的所有实例
使用与
完全限定的类名:
public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {
public DomainObjectConfiguration() {
setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());
}
// the creation of a new bean (any object in the domain model)
protected pointcut beanCreation(Object beanInstance) :
initialization(new(..)) &&
CommonPointcuts.inDomainModel() &&
this(beanInstance);
}
5.10.3. 使用 Spring IoC 配置 AspectJ 方面
当您将 AspectJ 方面与 Spring 应用程序一起使用时,很自然地想要和
期望能够使用 Spring 配置这些方面。AspectJ 运行时本身是
负责 aspect 创建,以及配置 AspectJ-created 的手段
方面通过 Spring 取决于 AspectJ 实例化模型(per-xxx
条款)
由方面使用。
大多数 AspectJ 方面都是单例方面。这些的配置
方面很容易。您可以创建一个 bean 定义,该定义引用 aspect 类型为
normal 并包含factory-method="aspectOf"
bean 属性。这确保了
Spring 通过向 AspectJ 请求它来获取 aspect 实例,而不是尝试创建
实例本身。以下示例演示如何使用factory-method="aspectOf"
属性:
<bean id="profiler" class="com.xyz.profiler.Profiler"
factory-method="aspectOf"> (1)
<property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>
1 | 请注意factory-method="aspectOf" 属性 |
非单例方面更难配置。但是,可以通过以下方式做到这一点
创建原型 Bean 定义并使用@Configurable
支持spring-aspects.jar
在创建 Bean 后配置 aspect 实例
AspectJ 运行时。
如果你有一些@AspectJ方面要用 AspectJ 编织(例如,
对域模型类型使用加载时编织)和您想要的其他@AspectJ方面
与 Spring AOP 一起使用,并且这些方面都在 Spring 中配置,您
需要告诉 Spring AOP @AspectJ自动代理支持的哪个确切子集
配置中定义的@AspectJ方面应用于自动代理。您可以
为此,请使用一个或多个<include/>
元素<aop:aspectj-autoproxy/>
声明。每<include/>
元素指定一个名称模式,并且只有带有
与至少一种模式匹配的名称用于 Spring AOP 自动代理
配置。以下示例演示如何使用<include/>
元素:
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被名称误导<aop:aspectj-autoproxy/> 元素。使用它
导致创建 Spring AOP 代理。@AspectJ样式的方面
这里使用声明,但不涉及 AspectJ 运行时。 |
5.10.4. 在 Spring 框架中使用 AspectJ 进行加载时编织
加载时间编织 (LTW) 是指将 AspectJ 方面编织成 应用程序的类文件,因为它们被加载到 Java 虚拟机 (JVM) 中。 本节的重点是在 Spring 框架。本节不是对 LTW 的一般介绍。有关 LTW 的细节和仅使用 AspectJ 配置 LTW(Spring 不是 完全涉及),请参阅 AspectJ 的 LTW 部分 开发环境指南。
Spring Framework 为 AspectJ LTW 带来的价值在于实现了许多
对织造过程进行更细粒度的控制。“Vanilla”AspectJ LTW 是通过使用
Java (5+) 代理,通过在启动
JVM.因此,它是一个 JVM 范围的设置,在某些情况下可能没问题,但通常是
有点太粗糙了。启用 Spring 的 LTW 允许您在
每-ClassLoader
基础,哪个粒度更细,哪个可以做更多
在“单 JVM-多应用程序”环境中的意义(例如在典型的
应用服务器环境)。
此外,在某些环境中,此支持使
加载时编织,而无需对应用程序服务器的启动进行任何修改
需要添加的脚本-javaagent:path/to/aspectjweaver.jar
或(正如我们所描述的
在本节后面)-javaagent:path/to/spring-instrument.jar
.开发人员配置
应用程序上下文,以启用加载时编织,而不是依赖管理员
通常负责部署配置(例如启动脚本)的人。
现在推销已经结束,让我们首先浏览一个 AspectJ 的快速示例 LTW 的 Spring,然后是 例。有关完整示例,请参阅 Petclinic 示例应用程序。
第一个例子
假设您是负责诊断的应用程序开发人员 系统中某些性能问题的原因。而不是爆发一个 分析工具,我们将打开一个简单的分析方面,让我们 快速获取一些性能指标。然后,我们可以应用更细粒度的分析 工具立即到该特定区域。
此处提供的示例使用 XML 配置。您还可以配置和
将@AspectJ与 Java 配置一起使用。具体来说,您可以使用@EnableLoadTimeWeaving 注释作为替代<context:load-time-weaver/> (详情见下文)。 |
以下示例显示了分析方面,这并不花哨。 它是一个基于时间的分析器,使用 @AspectJ 样式的方面声明:
package foo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;
@Aspect
public class ProfilingAspect {
@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
System.out.println(sw.prettyPrint());
}
}
@Pointcut("execution(public * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
package foo
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order
@Aspect
class ProfilingAspect {
@Around("methodsToBeProfiled()")
fun profile(pjp: ProceedingJoinPoint): Any {
val sw = StopWatch(javaClass.simpleName)
try {
sw.start(pjp.getSignature().getName())
return pjp.proceed()
} finally {
sw.stop()
println(sw.prettyPrint())
}
}
@Pointcut("execution(public * foo..*.*(..))")
fun methodsToBeProfiled() {
}
}
我们还需要创建一个META-INF/aop.xml
文件,以通知 AspectJ 编织者
我们想编织我们的ProfilingAspect
进入我们的班级。这个文件约定,即
Java 类路径上存在一个(或多个文件),称为META-INF/aop.xml
是
标准 AspectJ.以下示例显示了aop.xml
文件:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="foo.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="foo.ProfilingAspect"/>
</aspects>
</aspectj>
现在我们可以继续配置中特定于 Spring 的部分。我们需要
配置LoadTimeWeaver
(稍后解释)。此加载时间编织器是
负责将方面配置编织在一个或
更多META-INF/aop.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"
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">
<!-- a service object; we will be profiling its methods -->
<bean id="entitlementCalculationService"
class="foo.StubEntitlementCalculationService"/>
<!-- this switches on the load-time weaving -->
<context:load-time-weaver/>
</beans>
现在,所有必需的工件(方面、META-INF/aop.xml
文件,并且 Spring 配置)就位,我们可以创建以下内容
驱动程序类,并带有main(..)
演示 LTW 实际效果的方法:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
(EntitlementCalculationService) ctx.getBean("entitlementCalculationService");
// the profiling aspect is 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
package foo
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
val entitlementCalculationService = ctx.getBean("entitlementCalculationService") as EntitlementCalculationService
// the profiling aspect is 'woven' around this method execution
entitlementCalculationService.calculateEntitlement()
}
我们还有最后一件事要做。本节的引言确实说过,可以
在每个ClassLoader
与 Spring 的基础,这是真的。
但是,对于此示例,我们使用Java代理(Spring随附)来打开LTW。
我们使用以下命令运行Main
前面显示的类:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
这-javaagent
是用于指定和启用代理的标志
检测在 JVM 上运行的程序。Spring Framework 附带了这样的
agent,则InstrumentationSavingAgent
,它打包在spring-instrument.jar
作为-javaagent
参数
前面的示例。
执行Main
程序看起来像下一个例子。(我已经引入了一个Thread.sleep(..)
语句添加到calculateEntitlement()
实现,以便分析器实际捕获 0毫秒(01234
毫秒不是 AOP 引入的开销)。以下列表显示了我们在运行分析器时获得的输出:
Calculating entitlement StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
由于这个 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于建议Spring 豆。以下对Main
程序产生相同的结果 结果:
package foo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Main {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("beans.xml", Main.class);
EntitlementCalculationService entitlementCalculationService =
new StubEntitlementCalculationService();
// the profiling aspect will be 'woven' around this method execution
entitlementCalculationService.calculateEntitlement();
}
}
package foo
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main(args: Array<String>) {
ClassPathXmlApplicationContext("beans.xml")
val entitlementCalculationService = StubEntitlementCalculationService()
// the profiling aspect will be 'woven' around this method execution
entitlementCalculationService.calculateEntitlement()
}
请注意,在前面的程序中,我们如何引导 Spring 容器,然后创建一个新的实例StubEntitlementCalculationService
完全在外面Spring的背景。分析建议仍然被编织进去。
诚然,这个例子很简单。然而,Spring 中 LTW 支持的基础知识在前面的例子中都已经介绍过了,本节的其余部分详细解释了每个配置和用法背后的“为什么”。
这ProfilingAspect 这个例子中使用的可能是基本的,但它非常有用。这是一个开发人员可以在开发过程中使用的开发时方面的好例子然后轻松地从正在部署的应用程序的构建中排除进入 UAT 或生产。 |
方面
您在 LTW 中使用的方面必须是 AspectJ 方面。你可以将它们写在AspectJ 语言本身,或者你可以用 @AspectJ 样式编写你的方面。然后你的方面都是有效的 AspectJ 和 Spring AOP 方面。此外,编译后的方面类需要在类路径上可用。
“元输入/aop.xml”
AspectJ LTW 基础结构使用一个或多个META-INF/aop.xml
位于 Java 类路径上的文件(直接或更典型的是 jar 文件中)。
该文件的结构和内容详见 AspectJ 参考的 LTW 部分
文档。因为aop.xml
文件是 100% AspectJ,我们在这里不再进一步描述它。
所需库 (JARS)
至少,您需要以下库才能使用 Spring Framework 的支持 对于 AspectJ LTW:
-
spring-aop.jar
-
aspectjweaver.jar
如果您使用 Spring 提供的代理来启用 仪器仪表,您还需要:
-
spring-instrument.jar
弹簧配置
Spring LTW 支持的关键组件是LoadTimeWeaver
接口(在org.springframework.instrument.classloading
package)和众多实现
其中与春季发行版一起发货。一个LoadTimeWeaver
负责
添加一个或多个java.lang.instrument.ClassFileTransformers
设置为ClassLoader
在
运行时,它为各种有趣的应用程序打开了大门,其中之一
恰好是方面的 LTW。
如果您不熟悉运行时类文件转换的概念,请参阅
javadoc API 文档java.lang.instrument 打包后再继续。
虽然该文档并不全面,但至少您可以看到关键接口
和类(供您阅读本节时参考)。 |
配置LoadTimeWeaver
对于特定的ApplicationContext
可以像
添加一行。(请注意,您几乎肯定需要使用ApplicationContext
作为 Spring 容器 — 通常,一个BeanFactory
莫
足够了,因为 LTW 支持BeanFactoryPostProcessors
.)
要启用 Spring Framework 的 LTW 支持,您需要配置一个LoadTimeWeaver
,
这通常是通过使用@EnableLoadTimeWeaving
注释,如下所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}
或者,如果您更喜欢基于 XML 的配置,请使用<context:load-time-weaver/>
元素。请注意,该元素在context
Namespace。以下示例演示如何使用<context:load-time-weaver/>
:
<?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:load-time-weaver/>
</beans>
上述配置会自动定义和注册一些特定于 LTW 的
基础设施 Bean,例如LoadTimeWeaver
和AspectJWeavingEnabler
给你的。
默认值LoadTimeWeaver
是DefaultContextLoadTimeWeaver
类,该类尝试
装饰自动检测到LoadTimeWeaver
.的确切类型LoadTimeWeaver
即“自动检测”取决于您的运行时环境。
下表总结了各种LoadTimeWeaver
实现:
运行时环境 | LoadTimeWeaver 实现 |
---|---|
在 Apache Tomcat 中运行 |
|
在 GlassFish 中运行(仅限于 EAR 部署) |
|
|
|
在 IBM 的 WebSphere 中运行 |
|
在 Oracle 的 WebLogic 中运行 |
|
JVM 从 Spring 开始 |
|
回退,期望底层 ClassLoader 遵循通用约定
(即 |
|
请注意,该表仅列出了LoadTimeWeavers
当您
使用DefaultContextLoadTimeWeaver
.您可以准确指定哪个LoadTimeWeaver
实现以使用。
要指定特定的LoadTimeWeaver
使用 Java 配置时,实现LoadTimeWeavingConfigurer
接口并覆盖getLoadTimeWeaver()
方法。
以下示例指定ReflectiveLoadTimeWeaver
:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {
@Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig : LoadTimeWeavingConfigurer {
override fun getLoadTimeWeaver(): LoadTimeWeaver {
return ReflectiveLoadTimeWeaver()
}
}
如果使用基于 XML 的配置,则可以指定完全限定的类名
作为weaver-class
属性<context:load-time-weaver/>
元素。同样,以下示例指定了ReflectiveLoadTimeWeaver
:
<?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:load-time-weaver
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</beans>
这LoadTimeWeaver
由配置定义和注册的,可以稍后
使用众所周知的名称从 Spring 容器中检索,loadTimeWeaver
.
请记住,LoadTimeWeaver
仅作为 Spring LTW 的机制存在
基础结构,以添加一个或多个ClassFileTransformers
.实际的ClassFileTransformer
LTW 是ClassPreProcessorAgentAdapter
(从
这org.aspectj.weaver.loadtime
package) 类。请参阅ClassPreProcessorAgentAdapter
class 了解更多详细信息,因为具体如何
实际进行的编织超出了本文档的范围。
还有一个配置的最后一个属性需要讨论:aspectjWeaving
属性(或aspectj-weaving
如果您使用 XML)。此属性控制 LTW
是否启用。它接受三个可能的值之一,默认值为autodetect
如果该属性不存在。下表总结了这三个
可能的值:
注释值 | XML 值 | 解释 |
---|---|---|
|
|
AspectJ 编织已打开,并且根据需要在加载时编织 Aspect 。 |
|
|
LTW 已关闭。加载时没有任何方面是编织的。 |
|
|
如果 Spring LTW 基础设施可以找到至少一个 |
特定于环境的配置
最后一部分包含您需要的任何其他设置和配置 当您在应用程序服务器和 Web 等环境中使用 Spring 的 LTW 支持时 器皿。
Tomcat、JBoss、WebSphere、WebLogic
Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server
提供通用应用ClassLoader
能够本地检测。Spring的
原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。
如前所述,您可以简单地启用加载时编织。
具体来说,您不需要修改 JVM 启动脚本来添加-javaagent:path/to/spring-instrument.jar
.
请注意,在 JBoss 上,您可能需要禁用应用程序服务器扫描以防止它
在应用程序实际启动之前加载类。快速解决方法是将
到您的工件中,一个名为WEB-INF/jboss-scanning.xml
内容如下:
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 应用程序
在不受支持的环境中需要类检测时
特定LoadTimeWeaver
实现中,JVM 代理是通用解决方案。
对于这种情况,Spring 提供了InstrumentationLoadTimeWeaver
这需要一个
特定于 Spring 的(但非常通用的)JVM 代理,spring-instrument.jar
、自动检测
由 common@EnableLoadTimeWeaving
和<context:load-time-weaver/>
设置。
要使用它,您必须通过提供 以下 JVM 选项:
-javaagent:/path/to/spring-instrument.jar
请注意,这需要修改 JVM 启动脚本,这可能会阻止您 在应用程序服务器环境中使用它(取决于您的服务器和您的 运营策略)。也就是说,对于每个 JVM 一个应用程序部署,例如独立 Spring Boot 应用程序,您通常在任何情况下都可以控制整个 JVM 设置。
5.11. 更多资源
有关 AspectJ 的更多信息,请访问 AspectJ 网站。
阿德里安·科利尔 (Adrian Colyer) 等人的《日食 AspectJ》。(Addison-Wesley,2005)提供了一个 全面的介绍和参考 AspectJ 语言。
AspectJ in Action,第二版,作者:Ramnivas Laddad(曼宁,2009 年)名列前茅 推荐。这本书的重点是 AspectJ,但很多通用的 AOP 主题都是 探索过(在某种程度上)。
6. Spring AOP API
上一章描述了 Spring 对 AOP 的支持,具有@AspectJ和基于模式 方面定义。在本章中,我们将讨论较低级别的 Spring AOP API。对于普通 应用程序,我们建议将 Spring AOP 与 AspectJ 切入点一起使用,如 上一章。
6.1. Spring中的切入点API
本节介绍 Spring 如何处理关键的切入点概念。
6.1.1. 概念
Spring 的切入点模型支持独立于通知类型的切入点重用。您可以 用相同的切入点瞄准不同的建议。
这org.springframework.aop.Pointcut
interface 是中央接口,用于
针对特定类和方法的建议。完整界面如下:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
拆分Pointcut
接口分为两部分,允许重用类和方法
匹配部件和细粒度组合作(例如执行“并集”
使用另一个方法匹配器)。
这ClassFilter
接口用于将切入点限制为一组给定的目标
类。如果matches()
方法始终返回 true,则所有目标类都是
匹配。以下列表显示了ClassFilter
接口定义:
public interface ClassFilter {
boolean matches(Class clazz);
}
这MethodMatcher
接口通常更重要。完整界面如下:
public interface MethodMatcher {
boolean matches(Method m, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method m, Class<?> targetClass, Object... args);
}
这matches(Method, Class)
方法用于测试该切入点是否曾经
匹配目标类上的给定方法。当 AOP
创建代理是为了避免在每次方法调用时进行测试。如果
双参数matches
方法返回true
对于给定的方法,并且isRuntime()
方法,MethodMatcher 返回true
,则三参数匹配方法是
在每个方法调用时调用。这允许切入点查看传递的参数
到目标通知开始前的方法调用。
最MethodMatcher
实现是静态的,这意味着它们的isRuntime()
方法
返回false
.在这种情况下,三参数matches
方法永远不会被调用。
如果可能,请尝试使切入点静态,允许 AOP 框架缓存 创建AOP代理时的切入点评估结果。 |
6.1.2. 切入点的作
Spring 支持对切入点的作(特别是并集和交集)。
联合表示任一切入点匹配的方法。
交点是指两个切入点匹配的方法。
联合通常更有用。
您可以使用org.springframework.aop.support.Pointcuts
类或使用ComposablePointcut
类。但是,使用 AspectJ 切入点
表达式通常是一种更简单的方法。
6.1.3. AspectJ 表达式切入点
从 2.0 开始,Spring 使用的最重要的切入点类型是org.springframework.aop.aspectj.AspectJExpressionPointcut
.这是一个切入点
使用 AspectJ 提供的库来解析 AspectJ 切入点表达式字符串。
有关支持的 AspectJ 切入点基元的讨论,请参阅上一章。
6.1.4. 便利切入点实现
Spring 提供了几种方便的切入点实现。您可以使用其中的一些 径直;其他的则旨在在特定于应用程序的切入点中进行子类化。
静态切入点(Static Pointcuts)
静态切入点基于方法和目标类,不能考虑 方法的参数。对于大多数用途,静态切入点已经足够了,而且是最好的。 Spring 只能在首次调用方法时评估一次静态切入点。 之后,无需在每次方法调用时再次评估切入点。
本节的其余部分描述了一些静态切入点实现,这些实现是 包含在 Spring 中。
正则表达式切入点
指定静态切入点的一种明显方法是正则表达式。几个AOP
除了 Spring 之外的框架使这成为可能。org.springframework.aop.support.JdkRegexpMethodPointcut
是通用常规
使用 JDK 中正则表达式支持的表达式切入点。
使用JdkRegexpMethodPointcut
类,则可以提供模式字符串列表。
如果其中任何一个是匹配的,则切入点的计算结果为true
.(因此,
生成的切入点实际上是指定图案的并集。
以下示例演示如何使用JdkRegexpMethodPointcut
:
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
Spring 提供了一个名为RegexpMethodPointcutAdvisor
,这让我们
还引用Advice
(请记住,一个Advice
可以拦截,在建议之前,
抛出建议等)。在幕后,Spring 使用JdkRegexpMethodPointcut
.
用RegexpMethodPointcutAdvisor
简化了布线,因为 One Bean 封装了两者
切入点和建议,如以下示例所示:
<bean id="settersAndAbsquatulateAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="beanNameOfAopAllianceInterceptor"/>
</property>
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
您可以使用RegexpMethodPointcutAdvisor
与任何Advice
类型。
6.1.5. 切入点超类
Spring 提供了有用的切入点超类来帮助您实现自己的切入点。
因为静态切入点最有用,所以你可能应该对StaticMethodMatcherPointcut
. 这只需要实现一个abstract 方法(尽管您可以覆盖其他方法来自定义行为)。 这 以下示例演示如何将子类化StaticMethodMatcherPointcut
:
class TestStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}
class TestStaticPointcut : StaticMethodMatcherPointcut() {
override fun matches(method: Method, targetClass: Class<*>): Boolean {
// return true if custom criteria match
}
}
还有用于动态切入点的超类。您可以将自定义切入点与任何建议类型一起使用。
6.2. Spring 中的建议 API
现在我们可以检查 Spring AOP 如何处理建议。
6.2.1. 通知生命周期
每个建议都是春豆。建议实例可以在所有建议的 对象或对每个建议对象都是唯一的。这对应于每个类或 每个实例的建议。
最常使用每类建议。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。他们只是根据方法和论点采取行动。
每个实例的建议适用于介绍,以支持混合。在这种情况下, 建议将状态添加到代理对象。
您可以在同一 AOP 代理中混合使用共享通知和每个实例通知。
6.2.2. Spring 中的通知类型
Spring 提供了多种建议类型,并且可扩展以支持 任意建议类型。本节介绍基本概念和标准通知类型。
围绕建议的拦截
Spring 中最基本的建议类型是围绕建议的拦截。
Spring 符合 AOPAlliance
使用方法的建议的接口
拦截。实现的类MethodInterceptor
并且围绕建议实施也应该实施
以下接口:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
这MethodInvocation
参数设置为invoke()
method 公开了
调用,目标连接点,AOP代理和方法的参数。这invoke()
方法应返回调用的结果:连接的返回值
点。
以下示例显示了一个简单的MethodInterceptor
实现:
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object rval = invocation.proceed();
System.out.println("Invocation returned");
return rval;
}
}
class DebugInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation): Any {
println("Before: invocation=[$invocation]")
val rval = invocation.proceed()
println("Invocation returned")
return rval
}
}
请注意对proceed()
方法MethodInvocation
.这会继续进行
拦截器链朝向连接点。大多数拦截器调用此方法和
返回其返回值。但是,一个MethodInterceptor
,就像任何周围的建议一样,可以
返回不同的值或抛出异常,而不是调用 proceed 方法。
但是,您不想在没有充分理由的情况下这样做。
MethodInterceptor 实现提供与其他符合 AOP 联盟的 AOP 的互作性
实现。本节其余部分讨论的其他建议类型
实现常见的AOP概念,但以特定于Spring的方式。虽然有优势
在使用最具体的建议类型时,请坚持使用MethodInterceptor 周围建议如果
您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点
目前在框架之间不可互作,AOP 联盟也无法互作
当前定义切入点界面。 |
建议前
一种更简单的建议类型是之前的建议。这不需要MethodInvocation
对象,因为它仅在进入方法之前被调用。
before 通知的主要优点是无需调用proceed()
方法,因此,不可能无意中无法继续
拦截器链。
以下列表显示了MethodBeforeAdvice
接口:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring 的 API 设计将允许 字段在建议之前,尽管通常的对象适用于字段拦截,但它是 Spring 不太可能实现它。
请注意,返回类型为void
.Before 通知可以在联接之前插入自定义行为
point 运行,但无法更改返回值。如果 before 建议抛出
异常,它会停止拦截器链的进一步执行。例外
向上传播拦截器链。如果未选中或签名为
调用的方法,它直接传递给客户端。否则,它是
被 AOP 代理包装在未经检查的异常中。
以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
class CountingBeforeAdvice : MethodBeforeAdvice {
var count: Int = 0
override fun before(m: Method, args: Array<Any>, target: Any?) {
++count
}
}
建议之前可以用于任何切入点。 |
投掷建议
如果引发连接点,则在返回连接点后调用抛出建议
异常。Spring 提供打字投掷建议。请注意,这意味着org.springframework.aop.ThrowsAdvice
interface 不包含任何方法。这是一个
标记接口,标识给定对象实现一个或多个类型化抛出
建议方法。这些应采用以下形式:
afterThrowing([Method, args, target], subclassOfThrowable)
只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于建议方法是否对方法感兴趣,并且 参数。接下来的两个列表显示了作为抛掷建议示例的类。
如果RemoteException
被抛出(包括来自子类):
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
class RemoteThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with remote exception
}
}
与前面的
建议,下一个示例声明了四个参数,以便它可以访问调用的方法 method
参数和目标对象。如果ServletException
被抛出:
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
// Do something with all arguments
}
}
最后一个示例说明了如何在单个类中使用这两种方法
处理两者RemoteException
和ServletException
.任意数量的投掷建议
方法可以组合在单个类中。以下列表显示了最后一个示例:
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
class CombinedThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with remote exception
}
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
// Do something with all arguments
}
}
如果 throws-advice 方法本身抛出异常,它会覆盖 原始异常(即,它更改了向用户抛出的异常)。覆盖 exception 通常是一个 RuntimeException,它与任何方法兼容 签名。但是,如果 throws-advice 方法抛出已检查的异常,则它必须 匹配目标方法的声明异常,因此在某种程度上是 耦合到特定的目标方法签名。不要抛出未声明的选中 与目标方法的签名不兼容的异常! |
投掷建议可用于任何切入点。 |
退货后建议
Spring 中的返回通知后必须实现org.springframework.aop.AfterReturningAdvice
界面,如下表所示:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
返回通知后可以访问返回值(它无法修改), 调用的方法、方法的参数和目标。
返回通知后的以下内容会计算所有成功的方法调用,这些方法调用具有 not throwed 异常:
public class CountingAfterReturningAdvice implements AfterReturningAdvice {
private int count;
public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
class CountingAfterReturningAdvice : AfterReturningAdvice {
var count: Int = 0
private set
override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
++count
}
}
此建议不会更改执行路径。如果它抛出异常,则是 抛出拦截器链而不是返回值。
返回后,建议可用于任何切入点。 |
介绍建议
Spring 将介绍建议视为一种特殊的拦截建议。
引言需要一个IntroductionAdvisor
和IntroductionInterceptor
那
实现以下接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
这invoke()
继承自 AOP 联盟的方法MethodInterceptor
接口必须
实施介绍。也就是说,如果调用的方法位于引入的
接口,引入拦截器负责处理方法调用——它
无法调用proceed()
.
介绍建议不能与任何切入点一起使用,因为它仅适用于课堂,
而不是方法,水平。您只能将介绍建议与IntroductionAdvisor
,它有以下方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
没有MethodMatcher
因此,没有Pointcut
与介绍相关
建议。只有类过滤是合乎逻辑的。
这getInterfaces()
方法返回此顾问引入的接口。
这validateInterfaces()
方法在内部使用,以查看
引入的接口可以通过配置的IntroductionInterceptor
.
考虑 Spring 测试套件中的一个示例,假设我们想要 将以下接口引入一个或多个对象:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
interface Lockable {
fun lock()
fun unlock()
fun locked(): Boolean
}
这说明了混合。我们希望能够将建议对象转换为Lockable
,
无论它们的类型如何,并调用锁定和解锁方法。如果我们将lock()
方法,我们
希望所有 setter 方法抛出一个LockedException
.因此,我们可以添加一个方面
提供了使对象不可变的能力,而无需他们对此一无所知:
AOP 的一个很好的例子。
首先,我们需要一个IntroductionInterceptor
这完成了繁重的工作。在这个
case,我们将org.springframework.aop.support.DelegatingIntroductionInterceptor
便利类。我们可以实施IntroductionInterceptor
直接,但使用DelegatingIntroductionInterceptor
对于大多数情况来说是最好的。
这DelegatingIntroductionInterceptor
旨在将介绍委托给
实际实现引入的接口,隐蔽使用拦截
这样做。您可以使用构造函数参数将委托设置为任何对象。这
默认委托(当使用无参数构造函数时)是this
.因此,在下一个示例中,
委托是LockMixin
的子类DelegatingIntroductionInterceptor
.
给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor
实例
查找委托实现的所有接口(除了IntroductionInterceptor
)并支持针对其中任何一个的介绍。
子类,例如LockMixin
可以调用suppressInterface(Class intf)
抑制不应公开的接口的方法。然而,无论有多少
接口IntroductionInterceptor
准备支持,IntroductionAdvisor
used 控制实际公开的接口。一
引入的接口隐藏目标对同一接口的任何实现。
因此LockMixin
延伸DelegatingIntroductionInterceptor
和机具Lockable
本身。超类会自动拾取Lockable
可以支持
介绍,所以我们不需要具体说明。我们可以引入任意数量的
接口。
请注意使用locked
实例变量。这有效地添加了额外的状态
到目标对象中持有的那个。
以下示例显示了示例LockMixin
类:
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {
private boolean locked;
public void lock() {
this.locked = true;
}
public void unlock() {
this.locked = false;
}
public boolean locked() {
return this.locked;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
throw new LockedException();
}
return super.invoke(invocation);
}
}
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
private var locked: Boolean = false
fun lock() {
this.locked = true
}
fun unlock() {
this.locked = false
}
fun locked(): Boolean {
return this.locked
}
override fun invoke(invocation: MethodInvocation): Any? {
if (locked() && invocation.method.name.indexOf("set") == 0) {
throw LockedException()
}
return super.invoke(invocation)
}
}
通常,您不需要覆盖invoke()
方法。这DelegatingIntroductionInterceptor
实现(调用delegate
方法 如果
引入该方法,否则朝着连接点前进)通常
够。在本例中,我们需要添加一个检查:不能调用任何 setter 方法
如果处于锁定模式。
所需的引言只需要保留一个不同的LockMixin
实例并指定引入的接口(在本例中,仅Lockable
).更复杂的示例可能会参考引言
拦截器(将被定义为原型)。在这种情况下,没有
与LockMixin
,因此我们使用new
. 以下示例显示了我们的LockMixinAdvisor
类:
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
我们可以非常简单地应用这个顾问,因为它不需要配置。(但是,它不可能使用IntroductionInterceptor
没有IntroductionAdvisor
.) 与介绍一样,顾问必须是每个实例,因为它是有状态的。我们需要一个不同的实例LockMixinAdvisor
,因此LockMixin
,对于每个建议对象。顾问程序包括建议对象的 州。
我们可以通过使用Advised.addAdvisor()
方法或(推荐的方式)在 XML 配置中,就像任何其他顾问一样。所有代理创建下面讨论的选择,包括“自动代理创建者”,正确处理介绍和有状态混合。
6.3. Spring 中的 Advisor API
在 Spring 中,Advisor 是一个方面,它仅包含与关联的通知对象与切入点表达式。
除了介绍的特殊情况外,任何顾问都可以获得任何建议。org.springframework.aop.support.DefaultPointcutAdvisor
是最常用的
顾问类。它可以与MethodInterceptor
,BeforeAdvice
或ThrowsAdvice
.
可以在 Spring 中在同一个 AOP 代理中混合使用 advisor 和 advise 类型。 为 例如,您可以在一个代理配置中使用 advice、throws advice 和before advice 的拦截。Spring 自动创建必要的拦截器 链。
6.4. 使用ProxyFactoryBean
创建 AOP 代理
如果您使用 Spring IoC 容器(ApplicationContext
或BeanFactory
) 用于您的业务对象(您应该如此!),您想使用 Spring 的 AOP 之一FactoryBean
实现。 (请记住,工厂 bean 引入了一层间接层,让它创建不同类型的对象。
Spring AOP 支持还在盖子下使用工厂 bean。 |
在 Spring 中创建 AOP 代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean
. 这可以完全控制切入点、任何适用的建议及其顺序。但是,有更简单的如果您不需要此类控制,则更可取的选项。
6.4.1. 基础知识
这ProxyFactoryBean
,像其他弹簧一样FactoryBean
实现,引入了间接级别。如果您定义了ProxyFactoryBean
叫foo
,对象 参考foo
看不到ProxyFactoryBean
实例本身,但是一个对象由getObject()
方法ProxyFactoryBean
. 这 方法创建一个包装目标对象的 AOP 代理。
使用ProxyFactoryBean
或其他 IoC 感知创建 AOP 代理的类是建议和切入点也可以由 IoC 管理。这是一项强大的功能,支持某些难以实现的方法使用其他 AOP 框架实现。例如,建议本身可能引用应用程序对象(除了目标,该对象应该在任何 AOP 中可用框架),受益于依赖注入提供的所有可插拔性。
6.4.2. JavaBean 属性
与大多数人共同FactoryBean
Spring 提供的实现,则ProxyFactoryBean
class 本身就是一个 JavaBean。它的属性用于:
-
指定要代理的目标。
-
指定是否使用 CGLIB(稍后将介绍,另请参阅基于 JDK 和 CGLIB 的代理)。
一些关键属性继承自org.springframework.aop.framework.ProxyConfig
(Spring 中所有 AOP 代理工厂的超类)。这些关键属性包括
以下内容:
-
proxyTargetClass
:true
如果要代理目标类,而不是 目标类的接口。如果此属性值设置为true
,然后是 CGLIB 代理 创建(但另请参阅基于 JDK 和 CGLIB 的代理)。 -
optimize
:控制是否对代理应用积极优化 通过 CGLIB 创建。除非您完全 了解相关 AOP 代理如何处理优化。目前使用 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。 -
frozen
:如果代理配置为frozen
,对配置的更改是不再允许。这既可以作为轻微的优化,也适用于以下情况当您不希望调用者能够作代理时(通过Advised
interface) 创建代理后。此属性的默认值为false
,因此允许更改(例如添加其他建议)。 -
exposeProxy
:确定当前代理是否应在ThreadLocal
以便目标可以访问它。如果目标需要获取代理和exposeProxy
属性设置为true
,目标可以使用AopContext.currentProxy()
方法。
特定于的其他属性ProxyFactoryBean
包括以下内容:
-
proxyInterfaces
:数组String
接口名称。如果未提供此值,则 CGLIB 使用目标类的代理(但另请参阅基于 JDK 和 CGLIB 的代理)。 -
interceptorNames
:一个String
数组Advisor
、拦截器或其他建议名称 应用。订购很重要,先到先得。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。这些名称是当前工厂中的 bean 名称,包括祖先中的 bean 名称 工厂。您不能在这里提及 bean 引用,因为这样做会导致
ProxyFactoryBean
忽略通知的单例设置。您可以在拦截器名称后附加星号 ()。这样做会导致 应用名称以星号前部分开头的所有 Advisor Bean 待应用。您可以在使用“全局”顾问中找到使用此功能的示例。
*
-
singleton:工厂是否应该返回单个对象,无论如何 通常
getObject()
方法被调用。几个FactoryBean
实施提供 这样的方法。默认值为true
.如果您想使用有状态建议 - 对于 例如,对于有状态的 mixin - 使用原型建议以及单例值false
.
6.4.3. 基于JDK和CGLIB的代理
本节是关于如何ProxyFactoryBean
选择为特定目标创建基于 JDK 的代理或基于 CGLIB 的代理
对象(要代理)。
的行为ProxyFactoryBean 关于创建基于 JDK 或 CGLIB 的
代理在 Spring 1.2.x 和 2.0 版本之间发生了变化。这ProxyFactoryBean 现在
在自动检测接口方面表现出与TransactionProxyFactoryBean 类。 |
如果要代理的目标对象的类(以下简称为
目标类)不实现任何接口,基于 CGLIB 的代理是
创建。这是最简单的场景,因为 JDK 代理是基于接口的,没有
接口意味着 JDK 代理甚至不可能。您可以插入目标 Bean
并通过设置interceptorNames
财产。请注意,一个
创建基于 CGLIB 的代理,即使proxyTargetClass
属性的ProxyFactoryBean
已设置为false
.(这样做没有意义,而且是最好的
从 bean 定义中删除,因为它充其量是多余的,最坏的情况是多余的
令人困惑。
如果目标类实现一个(或多个)接口,则代理类型为
created 取决于ProxyFactoryBean
.
如果proxyTargetClass
属性的ProxyFactoryBean
已设置为true
,
创建基于 CGLIB 的代理。这是有道理的,并且符合
最小惊喜原则。即使proxyInterfaces
属性的ProxyFactoryBean
已设置为一个或多个完全限定的接口名称,事实
该proxyTargetClass
属性设置为true
导致基于 CGLIB
代理生效。
如果proxyInterfaces
属性的ProxyFactoryBean
已设置为一个或多个
完全限定的接口名称,则创建基于 JDK 的代理。创建的
proxy 实现在proxyInterfaces
财产。如果目标类恰好实现了比
中指定的proxyInterfaces
财产,这一切都很好,但那些
返回的代理不会实现其他接口。
如果proxyInterfaces
属性的ProxyFactoryBean
还没有设定,但是
目标类确实实现了一个(或多个)接口,即ProxyFactoryBean
自动检测目标类实际执行的事实
实现至少一个接口,并创建一个基于 JDK 的代理。接口
实际代理的是目标类的所有接口
实现。实际上,这与提供每个
目标类实现到proxyInterfaces
财产。然而
它的工作量明显减少,也不易出现印刷错误。
6.4.4. 代理接口
考虑一个简单的例子ProxyFactoryBean
在行动。此示例涉及:
-
代理的目标 Bean。这是
personTarget
bean 定义 例子。 -
一
Advisor
和Interceptor
用于提供建议。 -
用于指定目标对象的 AOP 代理 Bean 定义(
personTarget
豆子), 代理接口,以及要应用的建议。
以下列表显示了示例:
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<property name="target" ref="personTarget"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
请注意,interceptorNames
属性采用String
,其中包含
当前工厂的拦截器或顾问。您可以使用顾问、拦截器、之前、之后
返回,并抛出建议对象。顾问的顺序很重要。
您可能想知道为什么该列表不包含 bean 引用。原因是
如果ProxyFactoryBean 设置为false ,它必须能够
返回独立的代理实例。如果任何顾问本身是原型,则
独立实例需要返回,因此必须能够获得
出厂时原型的实例。持有推荐信是不够的。 |
这person
前面显示的 bean 定义可以用来代替Person
实现,作为
遵循:
Person person = (Person) factory.getBean("person");
val person = factory.getBean("person") as Person;
同一 IoC 上下文中的其他 bean 可以表达对它的强类型依赖关系,如 使用普通的 Java 对象。以下示例显示了如何执行此作:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
这PersonUser
此示例中的 class 公开了 typePerson
.就
它担心,AOP 代理可以透明地代替“真实”人使用
实现。但是,它的类将是一个动态代理类。这是可能的
将其转换为Advised
界面(稍后讨论)。
您可以使用匿名
内豆。只有ProxyFactoryBean
定义不同。这
仅为完整起见,包含建议。以下示例演示如何使用
匿名内豆:
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<!-- Use inner bean, not local reference to target -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
使用匿名内部 bean 的优点是只有一个类型的对象Person
.如果我们想要,这很有用
防止应用程序上下文的用户获取对 UN-advised 的引用
对象或需要避免 Spring IoC 自动布线的任何歧义。还有,
可以说,一个优势在于ProxyFactoryBean
定义是独立的。
但是,有时能够从
工厂实际上可能是一个优势(例如,在某些测试场景中)。
6.4.5. 代理类
如果您需要代理一个类,而不是一个或多个接口,该怎么办?
想象一下,在我们前面的示例中,没有Person
接口。我们需要提供建议
一个名为Person
没有实现任何业务接口。在这种情况下,您
可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将proxyTargetClass
属性ProxyFactoryBean
前面显示为true
.虽然最好
程序到接口而不是类,能够建议没有
实现接口在处理遗留代码时非常有用。(一般来说,Spring
不是规定性的。虽然它使应用良好实践变得容易,但它避免了强制
特殊方法。
如果你愿意,你可以在任何情况下强制使用 CGLIB,即使你确实有 接口。
CGLIB 代理的工作原理是在运行时生成目标类的子类。Spring 配置此生成的子类,以将方法调用委托给原始目标。这 子类用于实现 Decorator 模式,编织 vice。
CGLIB 代理通常应该对用户透明。但是,也存在一些问题 要考虑:
-
Final
不能建议方法,因为它们不能被覆盖。 -
无需将 CGLIB 添加到类路径中。从 Spring 3.2 开始,CGLIB 被重新打包 并包含在 spring-core JAR 中。换句话说,基于 CGLIB 的 AOP 工作“出于 盒子“,JDK 动态代理也是如此。
CGLIB 代理和动态代理之间的性能差异很小。 在这种情况下,性能不应成为决定性的考虑因素。
6.4.6. 使用“全局”顾问
通过将星号附加到拦截器名称,所有具有匹配 bean 名称的顾问 星号之前的部分将添加到顾问链中。这可以派上用场 如果您需要添加一组标准的“全球”顾问。以下示例定义 两位全球顾问:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
6.5. 简明代理定义
特别是在定义事务代理时,您最终可能会得到许多类似的代理 定义。使用父 Bean 和子 Bean 定义以及内部 Bean 定义,可以产生更干净、更简洁的代理定义。
首先,我们为代理创建一个父模板 bean 定义,如下所示:
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
这本身永远不会实例化,因此它实际上可能是不完整的。然后,每个代理 需要创建一个子 Bean 定义,它包装了 proxy 作为内部 bean 定义,因为目标无论如何都不会单独使用。 以下示例显示了这样的子 Bean:
<bean id="myService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MyServiceImpl">
</bean>
</property>
</bean>
您可以覆盖父模板中的属性。在以下示例中, 我们覆盖事务传播设置:
<bean id="mySpecialService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springframework.samples.MySpecialServiceImpl">
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
请注意,在父 Bean 示例中,我们将父 Bean 定义显式标记为
通过设置abstract
属性设置为true
,如前所述,因此它实际上可能永远不会
实例。应用程序上下文(但不是简单的 bean 工厂),默认情况下,
预实例化所有单例。因此,这很重要(至少对于单例 bean 来说是这样)
如果你有一个(父)bean 定义,你打算只用作模板,
并且此定义指定了一个类,您必须确保将abstract
属性设置为true
.否则,应用程序上下文实际上会尝试
预实例化它。
6.6. 使用ProxyFactory
使用 Spring 以编程方式创建 AOP 代理很容易。这使您可以使用 不依赖 Spring IoC 的 Spring AOP。
目标对象实现的接口是 自动代理。以下列表显示了为目标对象创建代理,其中一个 拦截器和一名顾问:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
val factory = ProxyFactory(myBusinessInterfaceImpl)
factory.addAdvice(myMethodInterceptor)
factory.addAdvisor(myAdvisor)
val tb = factory.proxy as MyBusinessInterface
第一步是构造一个类型为org.springframework.aop.framework.ProxyFactory
.您可以使用目标创建此
对象,如前面的示例所示,或指定要在备用
构造 函数。
您可以添加建议(将拦截器作为一种专门的建议)、顾问或两者兼而有之
并纵他们以维持生命ProxyFactory
.如果您添加IntroductionInterceptionAroundAdvisor
,您可以使代理实现额外的
接口。
上也有方便的方法ProxyFactory
(继承自AdvisedSupport
)
允许您添加其他建议类型,例如之前和抛出建议。AdvisedSupport
是两者的超类ProxyFactory
和ProxyFactoryBean
.
将 AOP 代理创建与 IoC 框架集成是大多数人的最佳实践 应用。我们建议您使用 AOP 从 Java 代码外部化配置, 正如你一般应该做的那样。 |
6.7.作建议对象
无论您如何创建 AOP 代理,您都可以通过使用org.springframework.aop.framework.Advised
接口。任何 AOP 代理都可以转换为此
接口,无论它实现了哪些其他接口。此接口包括
以下方法:
Advisor[] getAdvisors();
void addAdvice(Advice advice) throws AopConfigException;
void addAdvice(int pos, Advice advice) throws AopConfigException;
void addAdvisor(Advisor advisor) throws AopConfigException;
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
int indexOf(Advisor advisor);
boolean removeAdvisor(Advisor advisor) throws AopConfigException;
void removeAdvisor(int index) throws AopConfigException;
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
boolean isFrozen();
fun getAdvisors(): Array<Advisor>
@Throws(AopConfigException::class)
fun addAdvice(advice: Advice)
@Throws(AopConfigException::class)
fun addAdvice(pos: Int, advice: Advice)
@Throws(AopConfigException::class)
fun addAdvisor(advisor: Advisor)
@Throws(AopConfigException::class)
fun addAdvisor(pos: Int, advisor: Advisor)
fun indexOf(advisor: Advisor): Int
@Throws(AopConfigException::class)
fun removeAdvisor(advisor: Advisor): Boolean
@Throws(AopConfigException::class)
fun removeAdvisor(index: Int)
@Throws(AopConfigException::class)
fun replaceAdvisor(a: Advisor, b: Advisor): Boolean
fun isFrozen(): Boolean
这getAdvisors()
方法返回一个Advisor
对于每个顾问、拦截器或
已添加到工厂的其他通知类型。如果您添加了Advisor
这
此索引处返回的顾问是您添加的对象。如果您添加了
拦截器或其他建议类型,Spring 将其包装在一个顾问中,并带有
始终返回的切入点true
.因此,如果您添加了MethodInterceptor
、顾问
为此索引返回的是DefaultPointcutAdvisor
返回您的MethodInterceptor
以及匹配所有类和方法的切入点。
这addAdvisor()
方法可用于添加任何Advisor
.通常,顾问持有
切入点和建议是通用的DefaultPointcutAdvisor
,您可以将其与
任何建议或切入点(但不用于介绍)。
默认情况下,即使曾经是代理,也可以添加或删除顾问或拦截器 已被创建。唯一的限制是无法添加或删除 介绍顾问,因为工厂的现有代理不显示界面 改变。(您可以从工厂获取新的代理以避免此问题。
以下示例演示了将 AOP 代理转换为Advised
接口和检查以及
纵其建议:
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());
// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
val advised = myObject as Advised
val advisors = advised.advisors
val oldAdvisorCount = advisors.size
println("$oldAdvisorCount advisors")
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(DebugInterceptor())
// Add selective advice using a pointcut
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
是否建议修改对业务对象的建议是否可取(没有双关语)是值得怀疑的,尽管毫无疑问,存在合法的用例。但是,它在开发中非常有用(例如,在测试中)。我们有时发现能够以拦截器或其他建议的形式添加测试代码非常有用,进入我们想要测试的方法调用。(例如,建议可以进入为该方法创建的事务,也许可以运行 SQL 来检查数据库是否正确更新,然后再将事务标记为回滚。 |
根据您创建代理的方式,您通常可以将frozen
旗。 在该情况下,该Advised
isFrozen()
方法返回true
,以及任何修改的尝试
通过添加或删除的建议会导致AopConfigException
.能力
在某些情况下,冻结建议对象的状态很有用(例如,将
防止调用代码删除安全拦截器)。
6.8. 使用“自动代理”工具
到目前为止,我们已经考虑了通过使用ProxyFactoryBean
或
类似工厂豆。
Spring 还允许我们使用“自动代理”bean 定义,它可以自动 代理选择的 Bean 定义。这是建立在 Spring 的“bean 后处理器”之上的 基础设施,它允许在容器加载时修改任何 Bean 定义。
在此模型中,您在 XML Bean 定义文件中设置了一些特殊的 Bean 定义
以配置自动代理基础结构。这使您可以声明目标
符合自动代理的条件。您无需使用ProxyFactoryBean
.
有两种方法可以做到这一点:
-
通过使用引用当前上下文中特定 bean 的自动代理创建器。
-
值得单独考虑的自动代理创建的特例: 由源级元数据属性驱动的自动代理创建。
6.8.1. 自动代理 Bean 定义
本节介绍由org.springframework.aop.framework.autoproxy
包。
BeanNameAutoProxyCreator
这BeanNameAutoProxyCreator
class 是一个BeanPostProcessor
自动创建
名称与文字值或通配符匹配的 bean 的 AOP 代理。以下内容
示例演示了如何创建BeanNameAutoProxyCreator
豆:
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="jdk*,onlyJdk"/>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
与ProxyFactoryBean
,有一个interceptorNames
属性而不是列表
的拦截器,以允许原型顾问正确行为。命名为“拦截器”
可以是顾问或任何建议类型。
与一般的自动代理一样,使用的主要点BeanNameAutoProxyCreator
是
将相同的配置一致地应用于多个对象,并将最小体积
配置。它是将声明性事务应用于多个
对象。
名称匹配的 Bean 定义,例如jdkMyBean
和onlyJdk
在前面的
示例,是具有目标类的普通旧 Bean 定义。AOP 代理是
由BeanNameAutoProxyCreator
.同样的建议也适用
到所有匹配的豆子。请注意,如果使用顾问(而不是
前面的示例),切入点可能以不同的方式应用于不同的 bean。
DefaultAdvisorAutoProxyCreator
一个更通用且极其强大的自动代理创建器是DefaultAdvisorAutoProxyCreator
.这会自动将符合条件的顾问应用于
当前上下文,无需在自动代理中包含特定的 bean 名称
advisor 的 bean 定义。它具有相同的优点,即配置一致,并且
避免重复BeanNameAutoProxyCreator
.
使用此机制涉及:
-
指定
DefaultAdvisorAutoProxyCreator
bean 定义。 -
在相同或相关上下文中指定任意数量的顾问。请注意,这些必须是顾问,而不是拦截器或其他建议。这是必要的,因为必须有一个切入点来评估,以检查每个通知的资格到候选 bean 定义。
这DefaultAdvisorAutoProxyCreator
自动评估每个顾问程序中包含的切入点,以查看它应该应用于每个业务对象的哪些(如果有)建议(例如businessObject1
和businessObject2
在示例中)。
这意味着可以自动将任意数量的顾问应用于每个业务 对象。 如果任何顾问程序中没有切入点与业务对象中的任何方法匹配,对象不会被代理。当为新的业务对象添加 Bean 定义时,如有必要,它们会自动代理。
一般来说,自动代理的优点是使调用者或依赖项无法获得非建议对象。 叫getBean("businessObject1")
关于这个ApplicationContext
返回一个 AOP 代理,而不是目标业务对象。(前面显示的“内部bean”习惯用法也提供了这个好处。
以下示例创建了一个DefaultAdvisorAutoProxyCreator
bean 和其他本节讨论的元素:
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor"/>
</bean>
<bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
<bean id="businessObject1" class="com.mycompany.BusinessObject1">
<!-- Properties omitted -->
</bean>
<bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
这DefaultAdvisorAutoProxyCreator
如果您想将相同的建议一致地应用于许多业务对象,则非常有用。一旦基础架构定义到位,您可以添加新的业务对象,而无需包含特定的代理配置。您还可以轻松地添加其他方面(例如,跟踪或性能监控方面),只需对配置进行最小的更改。
这DefaultAdvisorAutoProxyCreator
提供对筛选的支持(通过使用
约定,以便仅评估某些顾问,这允许使用多个
配置不同,AdvisorAutoProxyCreators 在同一工厂中)和排序。
顾问可以实现org.springframework.core.Ordered
接口以确保
如果这是一个问题,请正确订购。这TransactionAttributeSourceAdvisor
用于
前面的示例具有可配置的订单值。默认设置为无序。
6.9. 使用TargetSource
实现
Spring 提供了TargetSource
,以org.springframework.aop.TargetSource
接口。该接口负责
返回实现连接点的“目标对象”。这TargetSource
每次 AOP 代理处理方法时都会要求实现目标实例
调用。
使用 Spring AOP 的开发人员通常不需要直接使用TargetSource
实现,但
这提供了一种支持池化、热插拔和其他
复杂的目标。例如,池化TargetSource
可以返回不同的目标
实例,通过使用池来管理实例。
如果未指定TargetSource
,则默认实现用于包装
local 对象。每次调用都会返回相同的目标(正如您所期望的那样)。
本节的其余部分介绍了 Spring 提供的标准目标源以及如何使用它们。
使用自定义目标源时,您的目标通常需要是原型 而不是单例 bean 定义。这允许 Spring 创建一个新的目标 实例。 |
6.9.1. 热插拔目标源
这org.springframework.aop.target.HotSwappableTargetSource
存在是为了让目标
的 AOP 代理进行切换,同时让调用方保留对它的引用。
更改目标源的目标立即生效。这HotSwappableTargetSource
是线程安全的。
您可以使用swap()
方法,如以下示例所示:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
val swapper = beanFactory.getBean("swapper") as HotSwappableTargetSource
val oldTarget = swapper.swap(newTarget)
以下示例显示了所需的 XML 定义:
<bean id="initialTarget" class="mycompany.OldTarget"/>
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>
<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="swapper"/>
</bean>
前面的swap()
调用更改可交换 bean 的目标。持有
引用该 bean 时不知道更改,但立即开始点击
新目标。
虽然这个例子没有添加任何建议(没有必要向
使用TargetSource
)、任何TargetSource
可与
任意建议。
6.9.2. 池化目标源
使用池化目标源提供与无状态会话类似的编程模型 EJB,其中维护相同实例的池,并调用方法。 去释放池中的对象。
Spring 池化和 SLSB 池化之间的一个关键区别在于,Spring 池化可以 应用于任何 POJO。与一般的 Spring 一样,此服务可以应用于 非侵入性方式。
Spring 为 Commons Pool 2.2 提供支持,它提供了一个
相当有效的池化实现。您需要commons-pool
Jar 在您的
应用程序的类路径以使用此功能。您还可以将子类化org.springframework.aop.target.AbstractPoolingTargetSource
支持任何其他
池化 API。
Commons Pool 1.5+ 也受支持,但从 Spring Framework 4.2 开始被弃用。 |
以下列表显示了示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
请注意,目标对象 (businessObjectTarget
在前面的示例中)必须是
原型。这允许PoolingTargetSource
实现创建新实例
目标的,以根据需要扩大池。请参阅javadoc 的AbstractPoolingTargetSource
以及您希望用于信息的具体子类
关于它的属性。maxSize
是最基本的,总是保证在场。
在这种情况下,myInterceptor
是需要
在同一 IoC 上下文中定义。但是,您无需指定拦截器
使用池化。如果您只想要池化而不需要其他建议,请不要将interceptorNames
财产。
您可以将 Spring 配置为能够将任何池化对象转换为org.springframework.aop.target.PoolingConfig
接口,用于公开信息
通过介绍池的配置和当前规模。你
需要定义类似于以下内容的顾问:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
此顾问是通过调用AbstractPoolingTargetSource
类,因此使用MethodInvokingFactoryBean
.这
顾问姓名 (poolConfigAdvisor
,此处)必须位于
这ProxyFactoryBean
,这将公开池化对象。
演员阵容定义如下:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
通常不需要池化无状态服务对象。我们认为它不应该 是默认选择,因为大多数无状态对象自然是线程安全的,并且实例 如果缓存了资源,则池化是有问题的。 |
使用自动代理可以实现更简单的池化。您可以将TargetSource
实现
由任何自动代理创建者使用。
6.9.3. 原型目标源
设置“原型”目标源类似于设置池TargetSource
.在这个
情况下,每次方法调用时都会创建一个目标的新实例。虽然
在现代 JVM 中创建新对象的成本并不高,连接
new 对象(满足其 IoC 依赖项)可能更昂贵。因此,您不应该
没有充分理由使用这种方法。
为此,您可以修改poolTargetSource
定义如下
(为了清楚起见,我们还更改了名称):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标 Bean 的名称。继承用于TargetSource
实现以确保命名的一致性。与池化目标一样
source,目标 Bean 必须是原型 Bean 定义。
6.9.4.ThreadLocal
目标来源
ThreadLocal
如果需要为每个对象创建对象,则目标源非常有用
传入请求(即每个线程)。的概念ThreadLocal
提供 JDK 范围的
将资源透明地存储在线程旁边。设置ThreadLocalTargetSource
与其他类型的解释几乎相同
目标源,如以下示例所示:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal 实例会出现严重问题(可能导致内存泄漏)
在多线程和多类加载器环境中错误地使用它们。你
应该始终考虑将 threadlocal 包装在其他类中,并且永远不要直接使用
这ThreadLocal 本身(包装器类除外)。此外,你应该
始终记住正确设置和取消设置(后者仅涉及调用ThreadLocal.set(null) ) 线程本地资源。取消设置应在
无论如何,因为不取消设置可能会导致有问题的行为。Spring的ThreadLocal 支持会为您执行此作,并且应始终考虑使用ThreadLocal 没有其他正确处理代码的实例。 |
6.10. 定义新的通知类型
Spring AOP 被设计为可扩展的。而拦截实施策略 目前在内部使用,可以支持 除了围绕建议的拦截之外,before,抛出建议,以及 在返回建议后。
这org.springframework.aop.framework.adapter
package 是一个 SPI 包,它允许
在不更改核心框架的情况下添加对新自定义通知类型的支持。
自定义的唯一约束Advice
type 是它必须实现org.aopalliance.aop.Advice
marker 接口。
请参阅org.springframework.aop.framework.adapter
javadoc 了解更多信息。
7. 零安全
尽管 Java 不允许您使用其类型系统来表达 null 安全性,但 Spring Framework
现在在org.springframework.lang
套餐让你
声明 API 和字段的可空性:
-
@Nullable
:注释,以指示 特定参数、返回值或字段可以是null
. -
@NonNull
:注释,以指示特定的 参数、返回值或字段不能是null
(参数/返回值不需要 和字段,其中@NonNullApi
和@NonNullFields
apply 分别)。 -
@NonNullApi
:包级别的注释 将非 null 声明为参数和返回值的默认语义。 -
@NonNullFields
:包上的注释 级别,该级别将非 null 声明为字段的默认语义。
Spring Framework 本身利用了这些注释,但它们也可以用于任何 基于 Spring 的 Java 项目,用于声明空安全 API 和可选的空安全字段。 泛型类型参数、varargs 和数组元素可空性尚不支持,但 应包含在即将发布的版本中,请参阅 SPR-15942 了解最新信息。可空性声明应在 Spring Framework 版本,包括次要版本。方法内部使用的类型的可空性 正文不属于此功能的范围。
其他常见库(例如 Reactor 和 Spring Data)提供空安全 API,这些 API 使用类似的可空性安排,为 Spring 应用程序开发人员。 |
7.1. 用例
除了为 Spring Framework API 可空性提供显式声明之外,IDE(例如 IDEA 或 Eclipse)可以使用这些注释来提供有用的与空安全相关的警告,以避免NullPointerException
在运行时。
它们还用于使 Kotlin 项目中的 Spring API 为空安全,因为 Kotlin 本身支持 null 安全。更多详情可在 Kotlin 支持文档中找到。
7.2. JSR-305 元注解
Spring 注解使用 JSR 305 注解(一种休眠但广泛传播的 JSR)进行元注解。JSR-305 元注解让工具提供商如 IDEA 或 Kotlin 以通用方式提供空安全支持,而无需对 Spring 注解的硬代码支持。
没有必要也不建议将 JSR-305 依赖项添加到项目类路径中以利用 Spring 空安全 API。只有使用基于 Spring 的库等项目才应在其代码库中使用null-safety 注释com.google.code.findbugs:jsr305:3.0.2
跟compileOnly
Gradle 配置或 Mavenprovided
作用域以避免编译警告。
8. 数据缓冲区和编解码器
Java NIO 提供ByteBuffer
但许多库在上面构建了自己的字节缓冲区 API,
特别是对于重用缓冲区和/或使用直接缓冲区的网络作
有利于性能。例如,Netty 具有ByteBuf
层次结构,Undertow 使用
XNIO、Jetty 使用池字节缓冲区和要释放的回调,依此类推。
这spring-core
模块提供了一组抽象来处理各种字节缓冲区
API 如下所示:
-
DataBufferFactory
抽象数据缓冲区的创建。 -
DataBuffer
表示可以池化的字节缓冲区。 -
DataBufferUtils
为数据缓冲区提供实用程序方法。 -
编解码器将数据缓冲区流解码或编码为更高级别的对象。
8.1.DataBufferFactory
DataBufferFactory
用于通过以下两种方式之一创建数据缓冲区:
-
分配一个新的数据缓冲区,如果已知,可以选择预先指定容量,即 即使实现
DataBuffer
可以按需增长和缩小。 -
包装现有的
byte[]
或java.nio.ByteBuffer
,它用 一个DataBuffer
实施,这不涉及分配。
请注意,WebFlux 应用程序不会创建DataBufferFactory
直接而是
通过ServerHttpResponse
或ClientHttpRequest
在客户端。
工厂的类型取决于底层客户端或服务器,例如NettyDataBufferFactory
对于 Reactor Netty,DefaultDataBufferFactory
对于其他人来说。
8.2.DataBuffer
这DataBuffer
接口提供与java.nio.ByteBuffer
而且还
带来了一些额外的好处,其中一些是受到 Netty 的启发ByteBuf
.
以下是部分福利列表:
-
使用独立位置进行读写,即不需要调用
flip()
自 在读取和写入之间交替。 -
按需扩展容量,如
java.lang.StringBuilder
. -
池化缓冲区和参考计数
PooledDataBuffer
. -
将缓冲区查看为
java.nio.ByteBuffer
,InputStream
或OutputStream
. -
确定给定字节的索引或最后一个索引。
8.3.PooledDataBuffer
正如 ByteBuffer 的 Javadoc 中所解释的, 字节缓冲区可以是直接的,也可以是非直接的。直接缓冲区可能驻留在 Java 堆之外 这消除了本机 I/O作的复制需求。这使得直接缓冲区 对于通过套接字接收和发送数据特别有用,但它们也更多 创建和发布成本高昂,这导致了池化缓冲区的想法。
PooledDataBuffer
是DataBuffer
这有助于引用计数,其中对于字节缓冲池至关重要。它是如何工作的?当一个PooledDataBuffer
是 分配的引用计数为 1。调用retain()
增加计数,而
调用release()
递减它。只要计数高于 0,缓冲区为
保证不放。当计数减少到 0 时,池化缓冲区可以
released,这实际上可能意味着缓冲区的保留内存将返回到
内存池。
请注意,而不是在PooledDataBuffer
直接,在大多数情况下更好
使用DataBufferUtils
将发布或保留应用于DataBuffer
仅当它是PooledDataBuffer
.
8.4.DataBufferUtils
DataBufferUtils
提供了许多实用方法来对数据缓冲区进行作:
-
将数据缓冲区流连接到一个缓冲区中,可能具有零副本,例如通过 复合缓冲区,如果基础字节缓冲区 API 支持。
-
转
InputStream
或蔚来Channel
到Flux<DataBuffer>
,反之亦然Publisher<DataBuffer>
到OutputStream
或蔚来Channel
. -
释放或保留
DataBuffer
如果缓冲区是PooledDataBuffer
. -
跳过或从字节流中获取,直到特定的字节计数。
8.5. 编解码器
这org.springframework.core.codec
package 提供以下策略接口:
-
Encoder
编码Publisher<T>
转换为数据缓冲区流。 -
Decoder
解码Publisher<DataBuffer>
转换为更高级别对象的流。
这spring-core
模块提供byte[]
,ByteBuffer
,DataBuffer
,Resource
和String
编码器和解码器实现。这spring-web
模块添加 Jackson JSON,
Jackson Smile、JAXB2、协议缓冲区和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器。
8.6. 使用DataBuffer
使用数据缓冲区时,必须特别注意确保释放缓冲区 因为它们可以合并。我们将使用编解码器来说明 这是如何工作的,但这些概念更普遍地适用。让我们看看编解码器必须做什么 内部管理数据缓冲区。
一个Decoder
在创建更高级别之前,最后读取输入数据缓冲区
对象,因此它必须按如下方式释放它们:
-
如果
Decoder
只需读取每个输入缓冲区并准备好 立即释放它,它可以通过DataBufferUtils.release(dataBuffer)
. -
如果
Decoder
正在使用Flux
或Mono
运算符,例如flatMap
,reduce
和 其他在内部预取和缓存数据项,或者使用filter
,skip
,以及其他遗漏项目的项目,然后doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
必须添加到组合链,以确保此类缓冲区在被丢弃之前被释放,可能是也是错误或取消信号的结果。 -
如果
Decoder
以任何其他方式保留一个或多个数据缓冲区,它必须确保它们在完全读取时被释放,或者在出现错误或取消信号时释放发生在缓存的数据缓冲区被读取和释放之前。
请注意DataBufferUtils#join
提供了一种安全有效的数据聚合方式
缓冲区流到单个数据缓冲区中。同样skipUntilByteCount
和takeUntilByteCount
是解码器使用的其他安全方法。
一Encoder
分配其他人必须读取(和释放)的数据缓冲区。所以一个Encoder
没什么可做的。然而,一个Encoder
如果出现以下情况,必须注意释放数据缓冲区
使用数据填充缓冲区时发生序列化错误。例如:
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
// serialize and populate buffer..
release = false;
}
finally {
if (release) {
DataBufferUtils.release(buffer);
}
}
return buffer;
val buffer = factory.allocateBuffer()
var release = true
try {
// serialize and populate buffer..
release = false
} finally {
if (release) {
DataBufferUtils.release(buffer)
}
}
return buffer
的消费者Encoder
负责释放它接收到的数据缓冲区。在 WebFlux 应用程序中,Encoder
用于写入 HTTP 服务器响应,或写入客户端 HTTP 请求,在这种情况下,释放数据缓冲区是编写到服务器响应或客户端请求的代码的责任。
请注意,在 Netty 上运行时,有用于解决缓冲区泄漏问题的调试选项。
9. 日志记录
从 Spring Framework 5.0 开始,Spring 实现了自己的 Commons Logging 桥 在spring-jcl
模块。 该实现检查类路径中是否存在 Log4j 2.xAPI 和 SLF4J 1.7 API,并使用第一个作为logging 实现,回退到 Java 平台的核心日志记录工具(也称为称为 JUL 或java.util.logging
),如果 Log4j 2.x 和 SLF4J 都不可用。
将 Log4j 2.x 或 Logback(或其他 SLF4J 提供程序)放在您的类路径中,无需任何额外的桥接器,并让框架自动适应您的选择。有关更多信息,请参阅 Spring引导日志记录参考文档。
Spring 的 Commons Logging 变体仅用于基础设施日志记录核心框架和扩展中的目的。 对于应用程序代码中的日志记录需求,最好直接使用 Log4j 2.x、SLF4J 或 JUL。 |
一个Log
可以通过以下方式检索实现org.apache.commons.logging.LogFactory
如下面的例子。
public class MyBean {
private final Log log = LogFactory.getLog(getClass());
// ...
}
class MyBean {
private val log = LogFactory.getLog(javaClass)
// ...
}
10. 附录
10.1. XML模式
附录的这一部分列出了与核心容器相关的 XML 模式。
10.1.1.util
图式
顾名思义,util
标签处理常见的实用程序配置
问题,例如配置集合、引用常量等。
要使用util
schema,您需要在顶部有以下前导码
Spring XML 配置文件的(代码片段中的文本引用了
correct schema,以便util
命名空间可供您使用):
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<!-- bean definitions here -->
</beans>
用<util:constant/>
考虑以下 bean 定义:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
前面的配置使用 SpringFactoryBean
实现(FieldRetrievingFactoryBean
) 设置isolation
bean 上的属性
设置为java.sql.Connection.TRANSACTION_SERIALIZABLE
不断。这是
一切都很好,但它很冗长,并且(不必要地)暴露了 Spring 的内部
为最终用户提供管道。
下面基于 XML Schema 的版本更简洁,清楚地表达了 developer's intent (“inject this constant value”),读起来更好:
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
从字段值设置 Bean 属性或构造函数参数
FieldRetrievingFactoryBean
是一个FactoryBean
检索static
或非静态字段值。通常是
用于检索public
static
final
常量,然后可用于设置
另一个 bean 的属性值或构造函数参数。
以下示例显示了如何static
字段,通过使用staticField
财产:
<bean id="myField"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>
还有一个方便的使用形式,其中static
字段被指定为 bean
name,如以下示例所示:
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
这确实意味着不再有任何选择的豆子id
是(所以任何其他
指代它的 bean 也必须使用这个较长的名称),但这种形式非常
定义简洁,非常方便用作内 bean,因为id
没有
为 bean 引用指定,如以下示例所示:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
您还可以访问另一个 bean 的非静态(实例)字段,如
在 API 文档中描述FieldRetrievingFactoryBean
类。
将枚举值作为属性或构造函数参数注入 bean 是
Spring很容易做。您实际上不需要做任何事情或了解任何事情
Spring 内部(甚至关于类,例如FieldRetrievingFactoryBean
).
以下示例枚举显示了注入枚举值是多么容易:
package javax.persistence;
public enum PersistenceContextType {
TRANSACTION,
EXTENDED
}
package javax.persistence
enum class PersistenceContextType {
TRANSACTION,
EXTENDED
}
现在考虑以下类型的 setterPersistenceContextType
以及相应的 bean 定义:
package example;
public class Client {
private PersistenceContextType persistenceContextType;
public void setPersistenceContextType(PersistenceContextType type) {
this.persistenceContextType = type;
}
}
package example
class Client {
lateinit var persistenceContextType: PersistenceContextType
}
<bean class="example.Client">
<property name="persistenceContextType" value="TRANSACTION"/>
</bean>
用<util:property-path/>
请考虑以下示例:
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<bean id="testBean.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
前面的配置使用 SpringFactoryBean
实现(PropertyPathFactoryBean
) 创建一个 bean(类型为int
) 调用testBean.age
那
的值等于age
属性的testBean
豆。
现在考虑以下示例,该示例添加了一个<util:property-path/>
元素:
<!-- target bean to be referenced by name -->
<bean id="testBean" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 10, which is the value of property 'age' of bean 'testBean' -->
<util:property-path id="name" path="testBean.age"/>
的值path
属性的<property-path/>
元素遵循beanName.beanProperty
.在这种情况下,它会选择age
名为testBean
.它的价值age
属性是10
.
用<util:property-path/>
设置 Bean 属性或构造函数参数
PropertyPathFactoryBean
是一个FactoryBean
计算给定target 对象上的属性路径。目标对象可以直接指定,也可以通过 bean 名称指定。然后,您可以使用此value 在另一个 bean 定义中作为属性值或构造函数 论点。
以下示例按名称显示了用于另一个 Bean 的路径:
<!-- target bean to be referenced by name -->
<bean id="person" class="org.springframework.beans.TestBean" scope="prototype">
<property name="age" value="10"/>
<property name="spouse">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="11"/>
</bean>
</property>
</bean>
<!-- results in 11, which is the value of property 'spouse.age' of bean 'person' -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetBeanName" value="person"/>
<property name="propertyPath" value="spouse.age"/>
</bean>
在以下示例中,根据内部 Bean 评估路径:
<!-- results in 12, which is the value of property 'age' of the inner bean -->
<bean id="theAge"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
<property name="targetObject">
<bean class="org.springframework.beans.TestBean">
<property name="age" value="12"/>
</bean>
</property>
<property name="propertyPath" value="age"/>
</bean>
还有一个快捷方式表单,其中 bean 名称是属性路径。以下示例显示了快捷方式表单:
<!-- results in 10, which is the value of property 'age' of bean 'person' -->
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
这种形式确实意味着豆子的名字没有选择。任何对它的引用
还必须使用相同的id
,这是路径。如果用作内衬
bean,完全不需要引用它,如下例所示:
<bean id="..." class="...">
<property name="age">
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
</property>
</bean>
您可以在实际定义中专门设置结果类型。这不是必需的 对于大多数用例,但有时它可能很有用。有关更多信息,请参阅 javadoc 此功能。
用<util:properties/>
请考虑以下示例:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>
前面的配置使用 SpringFactoryBean
实现(PropertiesFactoryBean
) 实例化一个java.util.Properties
具有值的实例
从提供的Resource
位置)。
以下示例使用util:properties
元素来进行更简洁的表示:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
用<util:list/>
请考虑以下示例:
<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
前面的配置使用 SpringFactoryBean
实现(ListFactoryBean
) 创建一个java.util.List
实例并使用取值对其进行初始化
从提供的sourceList
.
以下示例使用<util:list/>
元素来进行更简洁的表示:
<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</util:list>
您还可以显式控制List
实例化的,并且
通过使用list-class
属性<util:list/>
元素。为
例如,如果我们真的需要一个java.util.LinkedList
要实例化,我们可以使用
以下配置:
<util:list id="emails" list-class="java.util.LinkedList">
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>d'[email protected]</value>
</util:list>
如果没有list-class
属性,则容器选择List
实现。
用<util:map/>
请考虑以下示例:
<!-- creates a java.util.Map instance with values loaded from the supplied 'sourceMap' -->
<bean id="emails" class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="sourceMap">
<map>
<entry key="pechorin" value="[email protected]"/>
<entry key="raskolnikov" value="[email protected]"/>
<entry key="stavrogin" value="[email protected]"/>
<entry key="porfiry" value="[email protected]"/>
</map>
</property>
</bean>
前面的配置使用 SpringFactoryBean
实现(MapFactoryBean
) 创建一个java.util.Map
使用键值对初始化的实例
取自提供的'sourceMap'
.
以下示例使用<util:map/>
元素来进行更简洁的表示:
<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
<entry key="pechorin" value="[email protected]"/>
<entry key="raskolnikov" value="[email protected]"/>
<entry key="stavrogin" value="[email protected]"/>
<entry key="porfiry" value="[email protected]"/>
</util:map>
您还可以显式控制Map
实例化的,并且
通过使用'map-class'
属性<util:map/>
元素。为
例如,如果我们真的需要一个java.util.TreeMap
要实例化,我们可以使用
以下配置:
<util:map id="emails" map-class="java.util.TreeMap">
<entry key="pechorin" value="[email protected]"/>
<entry key="raskolnikov" value="[email protected]"/>
<entry key="stavrogin" value="[email protected]"/>
<entry key="porfiry" value="[email protected]"/>
</util:map>
如果没有'map-class'
属性,则容器选择Map
实现。
用<util:set/>
请考虑以下示例:
<!-- creates a java.util.Set instance with values loaded from the supplied 'sourceSet' -->
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
<property name="sourceSet">
<set>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</set>
</property>
</bean>
前面的配置使用 SpringFactoryBean
实现(SetFactoryBean
) 创建一个java.util.Set
实例初始化,取值
从提供的sourceSet
.
以下示例使用<util:set/>
元素来进行更简洁的表示:
<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</util:set>
您还可以显式控制Set
实例化的,并且
通过使用set-class
属性<util:set/>
元素。为
例如,如果我们真的需要一个java.util.TreeSet
要实例化,我们可以使用
以下配置:
<util:set id="emails" set-class="java.util.TreeSet">
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</util:set>
如果没有set-class
属性,则容器选择Set
实现。
10.1.2.aop
图式
这aop
标签处理在 Spring 中配置 AOP 的所有内容,包括 Spring 的
自己的基于代理的 AOP 框架以及 Spring 与 AspectJ AOP 框架的集成。
这些标签在标题为 Spring 面向方面编程的章节中得到了全面介绍。
为了完整起见,要使用aop
schema,你需要有
Spring XML 配置文件顶部的以下前导码(
snippet 引用正确的架构,以便aop
Namespace
可供您使用):
<?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">
<!-- bean definitions here -->
</beans>
10.1.3.context
图式
这context
标签处理ApplicationContext
与管道相关的配置——也就是说,通常不是对最终用户很重要的 bean,而是对最终用户很重要的 bean
春季的很多“咕噜声”工作,比如BeanfactoryPostProcessors
.以下内容
代码段引用正确的架构,以便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">
<!-- bean definitions here -->
</beans>
用<property-placeholder/>
该元素激活替换${…}
占位符,这些占位符针对
指定的属性文件(作为 Spring 资源位置)。这个元素
是一种便利机制,它设置了一个PropertySourcesPlaceholderConfigurer
给你的。如果您需要对特定PropertySourcesPlaceholderConfigurer
setup,您可以自己显式将其定义为 bean。
用<annotation-config/>
此元素激活 Spring 基础设施以检测 bean 类中的注释:
-
Spring的
@Configuration
型 -
@Autowired
/@Inject
,@Value
和@Lookup
-
JSR-250 的
@Resource
,@PostConstruct
和@PreDestroy
(如果有) -
JAX-WS 的
@WebServiceRef
和 EJB 3 的@EJB
(如果有) -
JPA 的
@PersistenceContext
和@PersistenceUnit
(如果有) -
Spring的
@EventListener
或者,您可以选择显式激活个人BeanPostProcessors
对于这些注释。
此元素不会激活 Spring 的@Transactional 注解; 您可以使用<tx:annotation-driven/> 元素。同样,Spring 的缓存注释也需要显式启用。 |
用<component-scan/>
此元素在基于注释的容器配置部分中进行了详细说明。
用<load-time-weaver/>
此元素在 Spring Framework 中使用 AspectJ 进行加载时编织的部分中进行了详细介绍。
用<spring-configured/>
此元素在使用 AspectJ 向 Spring 依赖注入域对象的部分中进行了详细说明。
用<mbean-export/>
此元素在有关配置基于注释的 MBean 导出的部分中进行了详细说明。
10.1.4. Bean 模式
最后但并非最不重要的一点是,我们在beans
图式。 这些元素从框架诞生之初就一直在 Spring 中。各种元素的示例 在beans
此处未显示模式,因为它们被非常全面地涵盖在依赖关系和配置中详细介绍(事实上,在整个章节中)。
请注意,您可以将零个或多个键值对添加到<bean/>
XML 定义。如果有的话,对这些额外的元数据做什么完全取决于你自己的自定义逻辑(因此通常只有在你按照描述编写自己的自定义元素时才有用在标题为 XML 模式创作的附录中)。
以下示例显示了<meta/>
元素在周围环境的上下文中<bean/>
(请注意,如果没有任何逻辑来解释它,元数据实际上毫无用处就目前而言)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="x.y.Foo">
<meta key="cacheName" value="foo"/> (1)
<property name="name" value="Rick"/>
</bean>
</beans>
1 | 这是示例meta 元素 |
在前面的示例中,您可以假设有一些逻辑使用bean 定义并设置一些使用提供的元数据的缓存基础结构。
10.2. XML架构创作
从 2.0 版开始,Spring 具有一种将基于模式的扩展添加到 用于定义和配置 bean 的基本 Spring XML 格式。本节涵盖 如何编写您自己的自定义 XML Bean 定义解析器和 将此类解析器集成到 Spring IoC 容器中。
为了方便创作使用架构感知 XML 编辑器的配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema。如果您不是 熟悉 Spring 当前标准附带的 XML 配置扩展 Spring 发行版,您应该首先阅读上一节关于 XML Schemas 的部分。
要创建新的 XML 配置扩展,请执行以下作:
对于一个统一的示例,我们创建了一个
XML 扩展(自定义 XML 元素),允许我们配置SimpleDateFormat
(从java.text
包)。当我们完成时,
我们将能够定义类型SimpleDateFormat
如下:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
(我们包括更详细的 本附录后面将介绍示例。第一个简单示例的目的是引导您 通过制作自定义扩展的基本步骤。
10.2.1. 创作架构
创建用于 Spring 的 IoC 容器的 XML 配置扩展以
创作 XML 架构来描述扩展。对于我们的示例,我们使用以下架构
配置SimpleDateFormat
对象:
<!-- myns.xsd (inside package org/springframework/samples/xml) -->
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mycompany.example/schema/myns"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="dateformat">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType"> (1)
<xsd:attribute name="lenient" type="xsd:boolean"/>
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
1 | 指示的行包含所有可识别标签的扩展基数
(这意味着他们有一个id 属性,我们可以将其用作
容器)。我们可以使用此属性,因为我们导入了 Spring 提供的beans Namespace。 |
前面的架构允许我们配置SimpleDateFormat
对象直接在
XML 应用程序上下文文件,使用<myns:dateformat/>
元素,如下所示
示例显示:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
请注意,在创建基础结构类后,前面的 XML 片段是 与以下 XML 片段基本相同:
<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd HH:mm"/>
<property name="lenient" value="true"/>
</bean>
前面两个片段中的第二个
在容器中创建一个 bean(由名称dateFormat
类型SimpleDateFormat
) 并设置了几个属性。
基于模式的创建配置格式的方法允许紧密集成 使用具有架构感知 XML 编辑器的 IDE。通过使用正确创作的架构,您可以 可以使用自动完成功能让用户在多个配置选项之间进行选择 在枚举中定义。 |
10.2.2. 编写NamespaceHandler
除了模式之外,我们还需要一个NamespaceHandler
解析
Spring 在解析配置文件时遇到的这个特定命名空间。对于此示例,NamespaceHandler
应该负责myns:dateformat
元素。
这NamespaceHandler
界面具有三种方式:
-
init()
:允许初始化NamespaceHandler
并由 在使用处理程序之前弹簧。 -
BeanDefinition parse(Element, ParserContext)
:当 Spring 遇到 顶级元素(不嵌套在 Bean 定义或不同的命名空间中)。 此方法本身可以注册 Bean 定义,返回 Bean 定义,或两者兼而有之。 -
BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)
:叫 当 Spring 遇到不同命名空间的属性或嵌套元素时。一个或多个 bean 定义的装饰(例如)与 Spring 支持的作用域一起使用。我们首先突出显示一个简单的示例,而不使用装饰,然后我们在一个更高级的示例中展示装饰。
虽然你可以编写自己的代码NamespaceHandler
对于整个命名空间(因此提供解析命名空间中每个元素的代码),通常情况下,Spring XML 配置文件中的每个顶级 XML 元素都会产生单个 bean 定义(如在我们的例子中,其中单个<myns:dateformat/>
元素的结果是单个SimpleDateFormat
bean 定义)。Spring 具有多个支持此场景的便利类。在下面的示例中,我们使用NamespaceHandlerSupport
类:
package org.springframework.samples.xml;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("dateformat", new SimpleDateFormatBeanDefinitionParser());
}
}
package org.springframework.samples.xml
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class MyNamespaceHandler : NamespaceHandlerSupport {
override fun init() {
registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
}
}
你可能会注意到,实际上并没有太多的解析逻辑。事实上,NamespaceHandlerSupport
类有一个内置的概念 代表团。 它支持任意数量的注册BeanDefinitionParser
实例,当它需要解析其中的元素时,它会委托给该实例 Namespace。 这种对关注点的干净分离让NamespaceHandler
处理编排其命名空间中所有自定义元素的解析,同时委托给BeanDefinitionParsers
来完成 XML 解析的繁重工作。 这 意味着每个BeanDefinitionParser
仅包含用于解析单个custom 元素的逻辑,正如我们在下一步中看到的那样。
10.2.3. 使用BeanDefinitionParser
一个BeanDefinitionParser
如果NamespaceHandler
遇到 XML
已映射到特定 bean 定义解析器的类型的元素
(dateformat
在这种情况下)。换句话说,BeanDefinitionParser
是
负责解析架构中定义的一个不同的顶级 XML 元素。在
解析器,我们可以访问 XML 元素(因此也可以访问其子元素),以便
我们可以解析自定义 XML 内容,如以下示例所示:
package org.springframework.samples.xml;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
import java.text.SimpleDateFormat;
public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { (1)
protected Class getBeanClass(Element element) {
return SimpleDateFormat.class; (2)
}
protected void doParse(Element element, BeanDefinitionBuilder bean) {
// this will never be null since the schema explicitly requires that a value be supplied
String pattern = element.getAttribute("pattern");
bean.addConstructorArgValue(pattern);
// this however is an optional property
String lenient = element.getAttribute("lenient");
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
}
}
}
1 | 我们使用 Spring 提供的AbstractSingleBeanDefinitionParser 处理很多
创建单个BeanDefinition . |
2 | 我们提供AbstractSingleBeanDefinitionParser superclass 的类型是我们的
单BeanDefinition 代表。 |
package org.springframework.samples.xml
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser
import org.springframework.util.StringUtils
import org.w3c.dom.Element
import java.text.SimpleDateFormat
class SimpleDateFormatBeanDefinitionParser : AbstractSingleBeanDefinitionParser() { (1)
override fun getBeanClass(element: Element): Class<*>? { (2)
return SimpleDateFormat::class.java
}
override fun doParse(element: Element, bean: BeanDefinitionBuilder) {
// this will never be null since the schema explicitly requires that a value be supplied
val pattern = element.getAttribute("pattern")
bean.addConstructorArgValue(pattern)
// this however is an optional property
val lenient = element.getAttribute("lenient")
if (StringUtils.hasText(lenient)) {
bean.addPropertyValue("lenient", java.lang.Boolean.valueOf(lenient))
}
}
}
1 | 我们使用 Spring 提供的AbstractSingleBeanDefinitionParser 处理很多
创建单个BeanDefinition . |
2 | 我们提供AbstractSingleBeanDefinitionParser superclass 的类型是我们的
单BeanDefinition 代表。 |
在这个简单的情况下,这就是我们需要做的。我们的单曲的创作BeanDefinition
由AbstractSingleBeanDefinitionParser
超类,因为是 bean 定义的唯一标识符的提取和设置。
10.2.4. 注册处理程序和模式
编码完成。剩下要做的就是制作 Spring XML
解析基础设施,了解我们的自定义元素。我们通过注册我们的自定义来做到这一点namespaceHandler
以及两个特殊用途属性文件中的自定义 XSD 文件。这些
属性文件都放置在META-INF
目录和
例如,可以与 JAR 文件中的二进制类一起分发。Spring
XML 解析基础结构通过使用
这些特殊属性文件,其格式将在接下来的两节中详细介绍。
写作META-INF/spring.handlers
名为spring.handlers
包含 XML 模式 URI 到
命名空间处理程序类。对于我们的示例,我们需要编写以下内容:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(这:
character 是 Java 属性格式中的有效分隔符,因此:
字符需要使用反斜杠转义。
键值对的第一部分(键)是与自定义关联的 URI
namespace 扩展名,并且需要与targetNamespace
属性,如自定义 XSD 架构中指定的那样。
编写“META-INF/spring.schemas”
名为spring.schemas
包含 XML 模式位置的映射
(与架构声明一起在使用架构作为一部分的 XML 文件中引用
的xsi:schemaLocation
属性)添加到类路径资源。需要此文件
以防止 Spring 绝对必须使用默认值EntityResolver
这需要
Internet 访问以检索架构文件。如果在此中指定映射
properties 文件中,Spring 搜索模式(在本例中,myns.xsd
在org.springframework.samples.xml
package)在类路径上。
以下代码片段显示了我们需要为自定义架构添加的行:
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(请记住,:
字符必须转义。
建议您将 XSD 文件(或多个文件)部署在
这NamespaceHandler
和BeanDefinitionParser
类路径上的类。
10.2.5. 在 Spring XML 配置中使用自定义扩展
使用您自己实现的自定义扩展与使用
Spring 提供的“自定义”扩展之一。以下内容
示例使用自定义<dateformat/>
在前面的步骤中开发的元素
在 Spring XML 配置文件中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myns="http://www.mycompany.example/schema/myns"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.mycompany.example/schema/myns http://www.mycompany.com/schema/myns/myns.xsd">
<!-- as a top-level bean -->
<myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/> (1)
<bean id="jobDetailTemplate" abstract="true">
<property name="dateFormat">
<!-- as an inner bean -->
<myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
</property>
</bean>
</beans>
1 | 我们的定制豆子。 |
10.2.6. 更详细的示例
本节介绍一些更详细的自定义 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"
xmlns:foo="http://www.foo.example/schema/component"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.foo.example/schema/component http://www.foo.example/schema/component/component.xsd">
<foo:component id="bionic-family" name="Bionic-1">
<foo:component name="Mother-1">
<foo:component name="Karate-1"/>
<foo:component name="Sport-1"/>
</foo:component>
<foo:component name="Rock-1"/>
</foo:component>
</beans>
前面的配置将自定义扩展嵌套在彼此之间。类实际上是由<foo:component/>
元素是Component
类(如下一个示例所示)。请注意Component
类不会公开setter 方法components
财产。 这使得它变得困难(或者更确切地说是不可能)为Component
类。以下列表显示了Component
类:
package com.foo;
import java.util.ArrayList;
import java.util.List;
public class Component {
private String name;
private List<Component> components = new ArrayList<Component> ();
// mmm, there is no setter method for the 'components'
public void addComponent(Component component) {
this.components.add(component);
}
public List<Component> getComponents() {
return components;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.foo
import java.util.ArrayList
class Component {
var name: String? = null
private val components = ArrayList<Component>()
// mmm, there is no setter method for the 'components'
fun addComponent(component: Component) {
this.components.add(component)
}
fun getComponents(): List<Component> {
return components
}
}
此问题的典型解决方案是创建自定义FactoryBean
这会公开一个
setter 属性的components
财产。以下列表显示了这样的自定义FactoryBean
:
package com.foo;
import org.springframework.beans.factory.FactoryBean;
import java.util.List;
public class ComponentFactoryBean implements FactoryBean<Component> {
private Component parent;
private List<Component> children;
public void setParent(Component parent) {
this.parent = parent;
}
public void setChildren(List<Component> children) {
this.children = children;
}
public Component getObject() throws Exception {
if (this.children != null && this.children.size() > 0) {
for (Component child : children) {
this.parent.addComponent(child);
}
}
return this.parent;
}
public Class<Component> getObjectType() {
return Component.class;
}
public boolean isSingleton() {
return true;
}
}
package com.foo
import org.springframework.beans.factory.FactoryBean
import org.springframework.stereotype.Component
class ComponentFactoryBean : FactoryBean<Component> {
private var parent: Component? = null
private var children: List<Component>? = null
fun setParent(parent: Component) {
this.parent = parent
}
fun setChildren(children: List<Component>) {
this.children = children
}
override fun getObject(): Component? {
if (this.children != null && this.children!!.isNotEmpty()) {
for (child in children!!) {
this.parent!!.addComponent(child)
}
}
return this.parent
}
override fun getObjectType(): Class<Component>? {
return Component::class.java
}
override fun isSingleton(): Boolean {
return true
}
}
这效果很好,但它向最终用户暴露了大量的 Spring 管道。我们是什么 要做的是编写一个自定义扩展,隐藏所有这些 Spring 管道。 如果我们坚持前面描述的步骤,我们就开始 通过创建 XSD 架构来定义自定义标记的结构,如下所示 列表显示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/component"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/component"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="component">
<xsd:complexType>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="component"/>
</xsd:choice>
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="name" use="required" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
再次按照前面描述的过程,
然后我们创建一个自定义NamespaceHandler
:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class ComponentNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("component", new ComponentBeanDefinitionParser());
}
}
package com.foo
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class ComponentNamespaceHandler : NamespaceHandlerSupport() {
override fun init() {
registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
}
}
接下来是定制BeanDefinitionParser
.请记住,我们正在创造
一个BeanDefinition
它描述了一个ComponentFactoryBean
.以下内容
列表显示我们的自定义BeanDefinitionParser
实现:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import java.util.List;
public class ComponentBeanDefinitionParser extends AbstractBeanDefinitionParser {
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
return parseComponentElement(element);
}
private static AbstractBeanDefinition parseComponentElement(Element element) {
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean.class);
factory.addPropertyValue("parent", parseComponent(element));
List<Element> childElements = DomUtils.getChildElementsByTagName(element, "component");
if (childElements != null && childElements.size() > 0) {
parseChildComponents(childElements, factory);
}
return factory.getBeanDefinition();
}
private static BeanDefinition parseComponent(Element element) {
BeanDefinitionBuilder component = BeanDefinitionBuilder.rootBeanDefinition(Component.class);
component.addPropertyValue("name", element.getAttribute("name"));
return component.getBeanDefinition();
}
private static void parseChildComponents(List<Element> childElements, BeanDefinitionBuilder factory) {
ManagedList<BeanDefinition> children = new ManagedList<BeanDefinition>(childElements.size());
for (Element element : childElements) {
children.add(parseComponentElement(element));
}
factory.addPropertyValue("children", children);
}
}
package com.foo
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.support.ManagedList
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser
import org.springframework.beans.factory.xml.ParserContext
import org.springframework.util.xml.DomUtils
import org.w3c.dom.Element
import java.util.List
class ComponentBeanDefinitionParser : AbstractBeanDefinitionParser() {
override fun parseInternal(element: Element, parserContext: ParserContext): AbstractBeanDefinition? {
return parseComponentElement(element)
}
private fun parseComponentElement(element: Element): AbstractBeanDefinition {
val factory = BeanDefinitionBuilder.rootBeanDefinition(ComponentFactoryBean::class.java)
factory.addPropertyValue("parent", parseComponent(element))
val childElements = DomUtils.getChildElementsByTagName(element, "component")
if (childElements != null && childElements.size > 0) {
parseChildComponents(childElements, factory)
}
return factory.getBeanDefinition()
}
private fun parseComponent(element: Element): BeanDefinition {
val component = BeanDefinitionBuilder.rootBeanDefinition(Component::class.java)
component.addPropertyValue("name", element.getAttribute("name"))
return component.beanDefinition
}
private fun parseChildComponents(childElements: List<Element>, factory: BeanDefinitionBuilder) {
val children = ManagedList<BeanDefinition>(childElements.size)
for (element in childElements) {
children.add(parseComponentElement(element))
}
factory.addPropertyValue("children", children)
}
}
最后,需要向 Spring XML 基础设施注册各种工件,
通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件,如下所示:
# in 'META-INF/spring.handlers' http\://www.foo.example/schema/component=com.foo.ComponentNamespaceHandler
# in 'META-INF/spring.schemas' http\://www.foo.example/schema/component/component.xsd=com/foo/component.xsd
“普通”元素上的自定义属性
编写您自己的自定义解析器和相关工件并不难。然而 有时这样做不是正确的。考虑一个需要 将元数据添加到现有的 Bean 定义。在这种情况下,您当然 不想编写自己的整个自定义扩展。相反,你只是 想要向现有 Bean 定义元素添加其他属性。
通过另一个示例,假设您为 服务对象(它不知道)访问集群 JCache,并且您希望确保 named JCache 实例在周围集群中急切启动。 以下列表显示了这样的定义:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
jcache:cache-name="checking.account">
<!-- other dependencies here... -->
</bean>
然后我们可以创建另一个BeanDefinition
当'jcache:cache-name'
属性。这BeanDefinition
然后初始化
为我们命名的 JCache。我们还可以修改现有的BeanDefinition
对于'checkingAccountService'
因此它依赖于这个新的
JCache 初始化BeanDefinition
.以下列表显示了我们的JCacheInitializer
:
package com.foo;
public class JCacheInitializer {
private String name;
public JCacheInitializer(String name) {
this.name = name;
}
public void initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
package com.foo
class JCacheInitializer(private val name: String) {
fun initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
现在我们可以继续使用自定义扩展。首先,我们需要创作 描述自定义属性的 XSD 架构,如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/jcache"
elementFormDefault="qualified">
<xsd:attribute name="cache-name" type="xsd:string"/>
</xsd:schema>
接下来,我们需要创建关联的NamespaceHandler
如下:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
new JCacheInitializingBeanDefinitionDecorator());
}
}
package com.foo
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class JCacheNamespaceHandler : NamespaceHandlerSupport() {
override fun init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
JCacheInitializingBeanDefinitionDecorator())
}
}
接下来,我们需要创建解析器。请注意,在这种情况下,因为我们将解析
XML 属性,我们编写一个BeanDefinitionDecorator
而不是BeanDefinitionParser
.
以下列表显示了我们的BeanDefinitionDecorator
实现:
package com.foo;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class JCacheInitializingBeanDefinitionDecorator implements BeanDefinitionDecorator {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder holder,
ParserContext ctx) {
String initializerBeanName = registerJCacheInitializer(source, ctx);
createDependencyOnJCacheInitializer(holder, initializerBeanName);
return holder;
}
private void createDependencyOnJCacheInitializer(BeanDefinitionHolder holder,
String initializerBeanName) {
AbstractBeanDefinition definition = ((AbstractBeanDefinition) holder.getBeanDefinition());
String[] dependsOn = definition.getDependsOn();
if (dependsOn == null) {
dependsOn = new String[]{initializerBeanName};
} else {
List dependencies = new ArrayList(Arrays.asList(dependsOn));
dependencies.add(initializerBeanName);
dependsOn = (String[]) dependencies.toArray(EMPTY_STRING_ARRAY);
}
definition.setDependsOn(dependsOn);
}
private String registerJCacheInitializer(Node source, ParserContext ctx) {
String cacheName = ((Attr) source).getValue();
String beanName = cacheName + "-initializer";
if (!ctx.getRegistry().containsBeanDefinition(beanName)) {
BeanDefinitionBuilder initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer.class);
initializer.addConstructorArg(cacheName);
ctx.getRegistry().registerBeanDefinition(beanName, initializer.getBeanDefinition());
}
return beanName;
}
}
package com.foo
import org.springframework.beans.factory.config.BeanDefinitionHolder
import org.springframework.beans.factory.support.AbstractBeanDefinition
import org.springframework.beans.factory.support.BeanDefinitionBuilder
import org.springframework.beans.factory.xml.BeanDefinitionDecorator
import org.springframework.beans.factory.xml.ParserContext
import org.w3c.dom.Attr
import org.w3c.dom.Node
import java.util.ArrayList
class JCacheInitializingBeanDefinitionDecorator : BeanDefinitionDecorator {
override fun decorate(source: Node, holder: BeanDefinitionHolder,
ctx: ParserContext): BeanDefinitionHolder {
val initializerBeanName = registerJCacheInitializer(source, ctx)
createDependencyOnJCacheInitializer(holder, initializerBeanName)
return holder
}
private fun createDependencyOnJCacheInitializer(holder: BeanDefinitionHolder,
initializerBeanName: String) {
val definition = holder.beanDefinition as AbstractBeanDefinition
var dependsOn = definition.dependsOn
dependsOn = if (dependsOn == null) {
arrayOf(initializerBeanName)
} else {
val dependencies = ArrayList(listOf(*dependsOn))
dependencies.add(initializerBeanName)
dependencies.toTypedArray()
}
definition.setDependsOn(*dependsOn)
}
private fun registerJCacheInitializer(source: Node, ctx: ParserContext): String {
val cacheName = (source as Attr).value
val beanName = "$cacheName-initializer"
if (!ctx.registry.containsBeanDefinition(beanName)) {
val initializer = BeanDefinitionBuilder.rootBeanDefinition(JCacheInitializer::class.java)
initializer.addConstructorArg(cacheName)
ctx.registry.registerBeanDefinition(beanName, initializer.getBeanDefinition())
}
return beanName
}
}
最后,我们需要向 Spring XML 基础设施注册各种工件
通过修改META-INF/spring.handlers
和META-INF/spring.schemas
文件,如下所示:
# in 'META-INF/spring.handlers' http\://www.foo.example/schema/jcache=com.foo.JCacheNamespaceHandler
# in 'META-INF/spring.schemas' http\://www.foo.example/schema/jcache/jcache.xsd=com/foo/jcache.xsd
10.3. 应用程序启动步骤
附录的这一部分列出了现有的StartupSteps
核心容器被检测。
每个启动步骤的名称和详细信息不是公共合同的一部分,并且 可能会发生变化;这被视为核心容器的实现细节,并将紧随其后 它的行为发生了变化。 |
名称 | 描述 | 标签 |
---|---|---|
|
Bean 及其依赖项的实例化。 |
|
|
初始化 |
|
|
创建 |
|
|
扫描基本包。 |
|
|
豆子后处理阶段。 |
|
|
调用 |
|
|
调用 |
|
|
通过注册组件类 |
|
|
使用 CGLIB 代理增强配置类。 |
|
|
配置类解析阶段使用 |
|
|
应用程序上下文刷新阶段。 |