参考文档的这一部分涵盖了所有技术 绝对是 Spring Framework 不可或缺的一部分。
其中最重要的是 Spring Framework 的控制反转 (IoC) 容器。 紧随其后的是 Spring Framework 的 IoC 容器的彻底处理 全面覆盖 Spring 面向方面的编程 (AOP) 技术。 Spring Framework 有自己的 AOP 框架,在概念上很容易 了解并成功解决了 AOP 要求的 80% 最佳点 在 Java 企业编程中。
Spring与AspectJ的集成覆盖率(目前最丰富 特性 — 当然也是 Java 企业领域最成熟的 AOP 实现) 还提供。
1. IoC 容器
本章介绍 Spring 的控制反转 (IoC) 容器。
1.1. Spring IoC 容器和 Bean 简介
本章介绍了 Spring Framework 的控制反转实现 (IoC)原则。IoC 也称为依赖注入 (DI)。这是一个过程,其中 对象仅通过以下方式定义其依赖关系(即它们使用的其他对象) 构造函数参数、工厂方法的参数或在 对象实例,在构造或从工厂方法返回后。容器 然后在创建 Bean 时注入这些依赖项。这个过程从根本上说是 Bean 本身的逆(因此得名,控制反转) 使用直接控制其依赖项的实例化或位置 类或机制(如服务定位器模式)的构造。
和包是基础
用于 Spring Framework 的 IoC 容器。BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的
对象。ApplicationContext
是 的子接口。它补充说:org.springframework.beans
org.springframework.context
BeanFactory
-
更轻松地与 Spring 的 AOP 功能集成
-
消息资源处理(用于国际化)
-
活动发布
-
特定于应用程序层的上下文,例如用于 Web 应用程序的上下文。
WebApplicationContext
简而言之,提供了配置框架和基本功能,
并添加了更多特定于企业的功能。是 和 的完全超集,以独占方式使用
在本章中对 Spring 的 IoC 容器进行了描述。有关使用的更多信息
而不是 请参阅介绍 BeanFactory
API
的部分。BeanFactory
ApplicationContext
ApplicationContext
BeanFactory
BeanFactory
ApplicationContext,
在 Spring 中,构成应用程序主干并受管理的对象 被 Spring IoC 容器称为 bean。Bean 是一个对象,它是 由 Spring IoC 容器实例化、组装和管理。否则,一个 Bean 只是应用程序中众多对象中的一个。Bean 和依赖项 其中,都反映在容器使用的配置元数据中。
1.2. 容器概述
该接口表示 Spring IoC
容器,并负责实例化、配置和组装
豆。容器获取有关哪些对象的指令
通过读取配置元数据来实例化、配置和组装。这
配置元数据以 XML、Java 注释或 Java 代码表示。它让
您可以表达组成应用程序的对象和丰富的相互依赖关系
在这些对象之间。org.springframework.context.ApplicationContext
提供了该接口的几种实现
与春天。在独立应用程序中,通常会创建一个
ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext
的实例。
虽然 XML 一直是定义配置元数据的传统格式,但您可以
指示容器使用 Java 注释或代码作为元数据格式,方法是
提供少量 XML 配置,以声明方式启用对这些配置
其他元数据格式。
ApplicationContext
在大多数应用程序方案中,实例化一个或
Spring IoC 容器的更多实例。例如,在 Web 应用程序场景中,一个
文件中简单的八行(左右)样板 Web 描述符 XML
通常,应用程序就足够了(请参阅 Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用
Spring Tools for
Eclipse(Eclipse 驱动的开发
环境),您只需单击几下鼠标即可轻松创建此样板配置,或者
击 键。web.xml
下图显示了 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 框架的一部分。 因此,您可以使用 Java 来定义应用程序类外部的 bean,而不是 比 XML 文件。要使用这些新功能,请参阅
@Configuration
、@Bean
@Import
、 和@DependsOn
注释。
Spring 配置由至少一个 Bean
组成,通常由多个 Bean 组成
容器必须管理的定义。基于 XML 的配置元数据配置这些
bean 作为顶级元素中的元素。爪哇岛
配置通常在类中使用 -annotated
方法。<bean/>
<beans/>
@Bean
@Configuration
这些 Bean 定义对应于构成应用程序的实际对象。
通常,您可以定义服务层对象、数据访问对象 (DAO) 和表示
对象(如 Struts 实例)、基础设施对象(如 Hibernate 、 JMS 等)。通常,不配置
容器中的细粒度域对象,因为它通常是负责的
用于创建和加载域对象的 DAO 和业务逻辑。但是,您可以使用
Spring 与 AspectJ 的集成,用于配置在外部创建的对象
对 IoC 容器的控制。请参阅使用 AspectJ
使用 Spring
进行依赖注入域对象。Action
SessionFactories
Queues
以下示例显示了基于 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 | 该属性是一个字符串,用于标识单个
Bean 定义。id |
2 | 该属性定义 Bean
的类型,并使用完全限定的
类名。class |
该属性的值是指协作对象。XML
此示例中未显示对协作对象的引用。有关详细信息,请参阅依赖项。id
1.2.2. 实例化容器
位置路径
提供给构造函数的是资源字符串,这些字符串让
来自各种外部资源的容器加载配置元数据,例如
作为本地文件系统,Java 等。ApplicationContext
CLASSPATH
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在了解了
Spring 的 IoC 容器之后,您可能还想更多地了解 Spring 的抽象(如 参考资料 中所述),它提供了方便
用于从 URI 语法中定义的位置读取 InputStream 的机制。具体而言,路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。 |
以下示例显示了服务层对象配置文件:(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>
在前面的示例中,服务层由类组成
和两个数据访问对象,其类型为 和 (基于
在 JPA 对象关系映射标准上)。该元素是指
JavaBean 属性的名称,该元素引用另一个 Bean 的名称
定义。元素之间的这种联系表达了
协作对象。有关配置对象依赖关系的详细信息,请参阅依赖关系。PetStoreServiceImpl
JpaAccountDao
JpaItemDao
property
name
ref
id
ref
编写基于 XML 的配置元数据
让 Bean 定义跨越多个 XML 文件可能很有用。通常,每个人 XML 配置文件表示体系结构中的逻辑层或模块。
您可以使用应用程序上下文构造函数从所有这些
Bean 定义中加载 Bean 定义
XML 片段。此构造函数采用多个位置,如上一节所示。或者,使用一个或多个
从另一个文件加载 Bean 定义的元素的出现,或者
文件。以下示例演示如何执行此操作:Resource
<import/>
<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
定义是从三个文件装入的:、 和 。所有位置路径都是
相对于执行导入的定义文件,因此必须在
与执行导入的文件相同的目录或类路径位置,而 和 必须位于
导入文件的位置。如您所见,前导斜杠被忽略。但是,鉴于
如果这些路径是相对的,最好不要使用斜杠。这
要导入的文件的内容(包括顶级元素)必须
根据 Spring Schema,是有效的 XML bean 定义。services.xml
messageSource.xml
themeSource.xml
services.xml
messageSource.xml
themeSource.xml
resources
<beans/>
可以(但不建议)使用
相对“../“路径。这样做会创建对当前文件之外的依赖关系
应用。具体而言,不建议将此引用用于 URL(对于
example, ),其中运行时解析过程选择
“最近”类路径根目录,然后查看其父目录。类路径
配置更改可能会导致选择不同的、不正确的目录。 您始终可以使用完全限定的资源位置,而不是相对路径:
for
example,或 .但是,是
意识到您正在将应用程序的配置耦合到特定的绝对配置
地点。对于这种绝对值,通常最好保持间接
位置 — 例如,通过针对 JVM 解析的“${...}”占位符
运行时的系统属性。 |
命名空间本身提供了 import
指令功能。进一步
选择中提供了超出纯 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 配置命名空间。它还允许导入 XML
bean 定义文件。importBeans
1.2.3. 使用容器
是能够维护的先进工厂的接口
不同 Bean 及其依赖项的注册表。通过使用方法,您可以检索 bean 的实例。ApplicationContext
T
getBean(String name, Class<T> requiredType)
允许您读取 Bean 定义并访问它们,如下所示
示例显示:ApplicationContext
// 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();
对于 Groovy 配置,引导看起来非常相似。它有不同的背景 实现类,它是 Groovy 感知的(但也理解 XML bean 定义)。 以下示例显示了 Groovy 配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是与读卡器结合使用
delegates — 例如,对于 XML 文件,如下所示
示例显示:GenericApplicationContext
XmlBeanDefinitionReader
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
您还可以使用 for Groovy
文件,如下所示
示例显示:GroovyBeanDefinitionReader
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
你可以在同一个
从不同的配置源读取 Bean 定义。ApplicationContext
然后,您可以使用来检索 Bean
的实例。该接口还有其他一些用于检索 Bean 的方法,但理想情况下,您的应用程序
代码永远不应该使用它们。事实上,你的应用程序代码应该完全没有对该方法的调用,因此根本不依赖于
Spring API。例如
Spring 与 Web 框架的集成为各种 Web 提供了依赖注入
框架组件,例如控制器和 JSF 管理的 bean,允许您声明
通过元数据(例如自动布线注释)对特定 Bean 的依赖关系。getBean
ApplicationContext
getBean()
1.3. Bean 概述
Spring IoC 容器管理一个或多个
bean。这些 Bean 是使用
提供给容器的配置元数据(例如,以 XML 定义的形式)。<bean/>
在容器本身中,这些 Bean
定义表示为对象,其中包含(除其他信息外)以下元数据:BeanDefinition
-
包限定类名:通常为 bean 正在定义。
-
Bean 行为配置元素,用于说明 Bean 在 容器(范围、生命周期回调等)。
-
对 Bean 完成其工作所需的其他 Bean 的引用。这些 引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置设置,例如大小 池的限制或要在管理 连接池。
此元数据转换为构成每个 Bean 定义的一组属性。 下表描述了这些属性:
财产 | 解释... |
---|---|
类 |
|
名字 |
|
范围 |
|
构造函数参数 |
|
性能 |
|
自动接线方式 |
|
延迟初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了包含有关如何创建特定
bean,这些实现还允许注册现有的
在容器外部创建的对象(由用户创建)。这是通过访问
ApplicationContext 通过方法,该方法返回
实现。 支持
此注册通过 和 方法。但是,典型的应用程序仅适用于通过常规定义的 Bean
Bean 定义元数据。ApplicationContext
BeanFactory
getBeanFactory()
DefaultListableBeanFactory
DefaultListableBeanFactory
registerSingleton(..)
registerBeanDefinition(..)
Bean 元数据和手动提供的单例实例需要尽早注册 为了让容器在自动接线过程中正确地推理它们 和其他内省步骤。在覆盖现有元数据和现有元数据时 在某种程度上支持单例实例,在 运行时(与对工厂的实时访问同时进行)不受官方支持,并且可能 导致并发访问异常和/或 Bean 容器中的不一致状态。 |
1.3.1. 命名 Bean
每个 Bean 都有一个或多个标识符。这些标识符在 承载 Bean 的容器。一个 Bean 通常只有一个标识符。但是,如果它 需要多个,多余的可以被视为别名。
在基于 XML
的配置元数据中,您可以使用属性、属性或
两者都指定 Bean 标识符。该属性允许您指定
正好是一个 ID。通常,这些名称是字母数字(“myBean”,
'someService' 等),但它们也可以包含特殊字符。如果你想
为 Bean 引入其他别名,您也可以在属性中指定它们,用逗号 ()、分号 () 或空格分隔。作为
历史说明,在 Spring 3.1 之前的版本中,该属性是
定义为一种类型,用于约束可能的字符。自 3.1 起,
它被定义为一种类型。请注意,bean 的唯一性仍然是
由容器强制执行,但不再由 XML
解析器强制执行。id
name
id
name
,
;
id
xsd:ID
xsd:string
id
您不需要为 Bean 提供 a 或
an。如果未显式提供 或,则容器会为该 Bean 生成一个唯一名称。然而
如果要按名称引用该 bean,则通过使用元素或
服务定位器样式查找时,必须提供名称。
不提供名称的动机与使用 inner 有关
Bean 和自动布线协作者。name
id
name
id
ref
通过类路径中的组件扫描,Spring
会为 unnamed 生成 bean 名称
组件,遵循前面描述的规则:本质上,采用简单的类名
并将其初始字符转换为小写。然而,在(不寻常的)特殊
当有多个字符以及第一个和第二个字符时的情况
是大写的,原来的外壳被保留下来。这些规则与
定义(Spring 在这里使用)。java.beans.Introspector.decapitalize
|
在 Bean 定义之外为 Bean 添加别名
在 Bean 定义本身中,您可以使用
属性指定的最多一个名称和任意数量的其他名称的组合
属性中的名称。这些名称可以是同一 Bean 的等效别名
并且在某些情况下很有用,例如让应用程序中的每个组件
通过使用特定于该组件的 Bean 名称来引用公共依赖关系
本身。id
name
指定实际定义 Bean
的所有别名并不总是足够的,
然而。有时需要为已定义的 Bean 引入别名
别处。这在配置拆分的大型系统中很常见
在每个子系统中,每个子系统都有自己的一组对象定义。
在基于 XML 的配置元数据中,可以使用该元素完成
这。以下示例演示如何执行此操作:<alias/>
<alias name="fromName" alias="toName"/>
在这种情况下,一个名为
bean(在同一个容器中)也可以,
使用此别名定义后,称为 。fromName
toName
例如,子系统 A 的配置元数据可以通过
的名称。子系统 B 的配置元数据可以参考
名称为 的 DataSource。编写主应用程序时
使用这两个子系统时,主应用程序通过
的名称。要让所有三个名称都引用同一个对象,您可以
将以下别名定义添加到配置元数据中:subsystemA-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
的配置元数据,请指定对象的类型(或类)
这将在元素的属性中实例化。此属性(在内部是实例上的属性)通常是必需的。(有关例外情况,请参阅使用实例工厂方法实例化和 Bean 定义继承。
可以通过以下两种方式之一使用该属性:class
<bean/>
class
Class
BeanDefinition
Class
-
通常,指定在容器 本身通过反射性地调用其构造函数来直接创建 Bean,在某种程度上 等效于带有运算符的 Java 代码。
new
-
指定包含工厂方法的实际类,即 调用以创建对象,在不太常见的情况下,容器调用类上的工厂方法来创建 bean。返回的对象类型 从工厂方法的调用可以是同一个类,也可以是另一个类 完全类。
static
static
static
使用构造函数进行实例化
当您通过构造函数方法创建 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 时,请使用该特性指定包含该工厂方法和属性的类
named 指定工厂方法本身的名称。你应该是
能够调用此方法(使用可选参数,如后所述)并返回实时
对象,随后将其视为通过构造函数创建。
这种 Bean
定义的一个用途是在遗留代码中调用工厂。class
static
factory-method
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;
}
}
有关向工厂方法提供(可选)参数的机制的详细信息 并在对象出厂返回后设置对象实例属性, 详见依赖关系和配置。
使用实例工厂方法进行实例化
类似于通过静态实例化
工厂方法,使用实例进行实例化 工厂方法调用非静态
容器中现有 Bean 的方法,以创建新 Bean。要使用此
机制,将属性留空,并在属性中,
指定当前(或父级或上级)容器中包含
要调用以创建对象的实例方法。设置
工厂方法本身。以下示例显示
如何配置这样的 bean:class
factory-bean
factory-method
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
下面的示例演示相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类还可以保存多个工厂方法,如以下示例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
下面的示例演示相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明,工厂 Bean 本身可以通过 依赖注入 (DI)。请参阅依赖项和 详细配置。
在
Spring 文档中,“工厂 bean”是指在
Spring 容器,通过实例或静态工厂方法创建对象。相比之下,(注意大写)指的是特定于
Spring 的 FactoryBean
实现类。FactoryBean |
确定 Bean 的运行时类型
确定特定 Bean
的运行时类型并非易事。中的指定类
Bean 元数据定义只是一个初始类引用,可能合并
使用声明的工厂方法或作为可能导致
Bean 的运行时类型不同,或者在实例级别的情况下根本不设置。
工厂方法(通过指定的名称进行解析)。
此外,AOP 代理可以使用基于接口的代理将 Bean 实例包装起来,并使用
目标 Bean 的实际类型(仅其实现的接口)的有限暴露。FactoryBean
factory-bean
了解特定 Bean
的实际运行时类型的推荐方法是
对指定 Bean 名称的调用。这需要以上所有条件
案例,并返回调用的对象类型
将返回相同的 Bean
名称。BeanFactory.getType
BeanFactory.getBean
1.4. 依赖关系
典型的企业应用程序不包含单个对象(或 春天的说法)。即使是最简单的应用程序也有一些对象可以协同工作 呈现最终用户认为是连贯的应用程序。下一节将介绍如何 您从定义许多独立的 Bean 定义到完全实现 对象协作以实现目标的应用程序。
1.4.1. 依赖注入
依赖关系注入 (DI) 是对象定义其依赖关系的过程 (即,它们工作的其他对象)仅通过构造函数参数, 工厂方法的参数,或在对象实例上设置的属性 它是从工厂方法构造或返回的。然后容器注入这些 创建 Bean 时的依赖项。这个过程从根本上说是相反的(因此 名称 Inversion of Control) 的 Bean 本身控制实例化 或者通过使用类的直接构造来定位其依赖项的位置,或者 服务定位器模式。
使用 DI 原则,代码更简洁,当对象 提供了它们的依赖项。该对象不查找其依赖项,而是查找 不知道依赖项的位置或类。因此,您的课程变得更加容易 测试,特别是当依赖项位于接口或抽象基类上时, 允许在单元测试中使用存根或模拟实现。
DI 有两种主要变体: 基于构造函数 依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI
是通过容器调用构造函数来实现的,构造函数具有
参数的数目,每个参数表示一个依赖项。调用工厂方法
用具体的参数来构造 Bean 几乎是等价的,这个讨论
以类似方式处理构造函数和工厂方法的参数。这
下面的示例演示了一个只能使用构造函数进行依赖注入的类
注射:static
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...
}
请注意,此类没有什么特别之处。这是一个 POJO 不依赖于特定于容器的接口、基类或注释。
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型进行。如果没有 bean 定义的构造函数参数中存在潜在的歧义, 在 Bean 定义中定义构造函数参数的顺序是顺序 其中这些参数在 Bean 正在实例化。请考虑以下类:
package x.y;
public class ThingOne {
public 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
时,类型是已知的,并且可以进行匹配(就像
情况与前面的示例)。当使用简单类型时,例如 ,Spring 无法确定值的类型,因此无法匹配
按类型,无需帮助。请考虑以下类:<value>true</value>
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
在前面的方案中,如果满足以下条件,容器可以使用类型匹配和简单类型
通过使用属性显式指定构造函数参数的类型,
如以下示例所示:type
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
可以使用该属性显式指定构造函数参数的索引,
如以下示例所示:index
<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;
}
}
基于 Setter 的依赖注入
基于 Setter 的 DI 是通过容器调用
setter 方法完成的
调用无参数构造函数或无参数工厂方法后的 bean 到
实例化你的 bean。static
下面的示例演示了一个类,该类只能通过使用 pure 二传手注射。这个类是传统的 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...
}
它支持基于构造函数和基于
setter 的 bean DI
管理。它还支持基于 setter 的 DI,因为某些依赖项已经存在
通过构造函数方法注入。您可以按以下形式配置依赖项
a ,您可以将其与实例结合使用
将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不工作
直接使用这些类(即以编程方式),而是使用 XML 定义、带注释的组件(即用 、 等注释的类)或基于
Java 的类中的方法。
然后,这些源在内部转换为 的实例并用于
加载整个 Spring IoC
容器实例。ApplicationContext
BeanDefinition
PropertyEditor
bean
@Component
@Controller
@Bean
@Configuration
BeanDefinition
依赖项解析过程
容器按如下方式执行 Bean 依赖项解析:
-
使用配置元数据创建并初始化 描述所有 bean。配置元数据可以通过 XML、Java 代码或 附注。
ApplicationContext
-
对于每个 bean,其依赖关系以属性、构造函数的形式表示 参数,或静态工厂方法的参数(如果您使用它而不是 普通构造函数)。当 Bean 是 实际创建。
-
每个属性或构造函数参数都是要设置的值的实际定义,或者 对容器中另一个 Bean 的引用。
-
作为值的每个属性或构造函数参数都是从其指定的值转换而来的 格式设置为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将字符串格式提供的值转换为所有内置类型,如 、 、 、 等。
int
long
String
boolean
Spring 容器在创建容器时验证每个 Bean 的配置。 但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。 将创建单例范围并设置为预实例化(缺省值)的 Bean 创建容器时。作用域在 Bean 作用域中定义。否则 仅当请求 Bean 时,才会创建该 Bean。创建 Bean 可能会导致 要创建的 Bean 的图形,作为 Bean 的依赖关系及其依赖关系 创建并分配依赖项(等)。请注意,分辨率不匹配 这些依赖项可能会延迟出现,即在首次创建受影响的 Bean 时出现。
一般来说,你可以相信
Spring 会做正确的事情。它检测配置问题,
例如对不存在的 Bean 和循环依赖项的引用,在容器中
加载时间。Spring 在以下情况下设置属性并尽可能晚地解析依赖关系
Bean 实际上是被创建的。这意味着 Spring 容器已经加载
如果存在
创建该对象或其依赖项之一时出现问题——例如,Bean 抛出
由于属性缺失或无效而导致的异常。这可能会延迟
一些配置问题的可见性是实现的原因
缺省预实例化单例 Bean。以一些前期时间和内存为代价
在实际需要它们之前创建这些 bean,您会发现配置问题
创建时,而不是以后。您仍然可以覆盖此默认值
行为,以便单例 Bean 延迟初始化,而不是急切地初始化
预先实例化。ApplicationContext
ApplicationContext
如果不存在循环依赖关系,则当一个或多个协作 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;
}
}
在前面的示例中,将 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;
}
}
Bean 定义中指定的构造函数参数用作
的构造函数。ExampleBean
现在考虑这个例子的一个变体,其中,Spring不是使用构造函数,而是
被告知调用工厂方法以返回对象的实例:static
<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;
}
}
工厂方法的参数由元素提供,
与实际使用构造函数完全相同。类的类型为
工厂方法返回的类型不必与
包含工厂方法(尽管在此示例中是工厂方法)。实例
(非静态)工厂方法可以以基本相同的方式使用(旁
从使用属性而不是属性),所以我们
不要在这里讨论这些细节。static
<constructor-arg/>
static
factory-bean
class
1.4.2. 依赖关系和配置详解
如上一节所述,您可以定义 Bean
属性和构造函数参数作为对其他受管 Bean(协作者)的引用
或作为内联定义的值。Spring 基于 XML 的配置元数据支持
其中的子元素类型和 元素
目的。<property/>
<constructor-arg/>
直线值(基元、字符串等)
元素的特性指定属性或构造函数
参数作为人类可读的字符串表示形式。Spring 的转换服务用于转换这些
从 A 到属性或参数的实际类型的值。
以下示例显示了正在设置的各种值:value
<property/>
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-namespace 进行更简洁的操作 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 容器使用 JavaBeans
机制将元素内的文本转换为实例。这
是一个很好的捷径,并且是 Spring 团队支持使用
属性样式上的嵌套元素。<value/>
java.util.Properties
PropertyEditor
<value/>
value
元素idref
该元素只是传递 (字符串值
- 不是
容器中另一个 Bean 对 or
元素的引用。以下示例演示如何使用它:idref
id
<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>
第一种形式比第二种形式更可取,因为使用标记可以
容器在部署时验证引用的命名 Bean 是否实际
存在。在第二个变体中,不对传递的值执行验证
到 bean 的属性。拼写错误只会被发现(大多数
可能是致命的结果),当 Bean 实际实例化时。如果 Bean 是原型 Bean,则此拼写错误和产生的异常
可能只有在部署容器很久之后才会被发现。idref
targetName
client
client
client
4.0 bean 中不再支持元素的属性
XSD,因为它不再提供常规引用的值。改变
升级到 4.0
架构时的现有引用。local idref bean idref
local idref bean |
元素的常见位置(至少在
Spring 2.0 之前的版本中)
带来价值的是 Bean 定义中 AOP 拦截器的配置。在指定
拦截器名称可防止拦截器 ID 拼写错误。<idref/>
ProxyFactoryBean
<idref/>
对其他 Bean(协作者)的引用
该元素是 or
定义元素内的最后一个元素。在这里,您将 Bean 的指定属性的值设置为
引用由容器管理的另一个 Bean(协作者)。引用的 Bean
是要设置其属性的 Bean 的依赖项,并按需初始化
在设置属性之前根据需要。(如果协作者是单例 Bean,它可能
已由容器初始化。所有引用最终都是对
另一个对象。范围和验证取决于您是指定 ID 还是名称
其他对象通过 or 属性。ref
<constructor-arg/>
<property/>
bean
parent
通过标签的属性指定目标
bean 是最
通用形式,并允许在同一容器中创建对任何 Bean 的引用,或者
父容器,无论它是否位于同一 XML 文件中。属性的值可以与目标 Bean 的属性相同,也可以相同
作为目标 Bean 属性中的值之一。以下示例
演示如何使用元素:bean
<ref/>
bean
id
name
ref
<ref bean="someBean"/>
通过该属性指定目标 Bean
将创建对 Bean 的引用
它位于当前容器的父容器中。该属性的值可以与目标 Bean 的属性相同,也可以与目标 Bean 的属性之一相同。
目标 Bean 属性中的值。目标 Bean 必须位于
当前容器的父容器。您应该主要使用此 bean 引用变体
当您具有容器层次结构并且希望将现有 Bean 包装在父 Bean 中时
具有与父 Bean 同名的代理的容器。以下一对
listings
显示了如何使用该属性:parent
parent
id
name
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>
4.0
bean 中不再支持元素的属性
XSD,因为它不再提供常规引用的值。改变
升级到 4.0
架构时的现有引用。local ref bean ref
local ref bean |
内豆
or 元素中的元素定义了一个
Inner
Bean,如以下示例所示:<bean/>
<property/>
<constructor-arg/>
<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 或名称。如果指定,则容器
不使用此类值作为标识符。容器还会忽略
创建,因为内部 Bean 始终是匿名的,并且始终与外部
豆。无法独立访问内部 Bean 或将它们注入
协作 Bean 而不是进入封闭的 Bean。scope
在极端情况下,可以接收来自自定义作用域的销毁回调,例如,对于单例 Bean 中包含的请求作用域的内部 Bean。创作 的内部 Bean 实例绑定到其包含的 bean,但销毁回调允许它 参与请求范围的生命周期。这种情况并不常见。内豆 通常只是简单地共享其包含 Bean 的范围。
收集
、 、 和 元素设置属性
和 Java 类型 、 、 和 的参数
分别。以下示例演示如何使用它们:<list/>
<set/>
<map/>
<props/>
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">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
映射键或值的值,或设置值的值,也可以是 以下元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring
容器还支持合并集合。应用程序
开发人员可以定义父元素、元素或元素
并具有子元素、 或元素继承和
重写父集合中的值。也就是说,子集合的值为
将父集合和子集合的元素与子集合的元素合并的结果
重写父集合中指定的值的集合元素。<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">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
请注意,在 Bean
定义的属性元素上使用该属性。当 Bean 解析时
并由容器实例化,生成的实例具有一个集合,其中包含将子集合与父集合合并的结果。以下列表
显示结果:merge=true
<props/>
adminEmails
child
child
adminEmails
Properties
adminEmails
adminEmails
administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk
子集合的值集继承了
parent ,并且子项的值将覆盖
父集合。Properties
<props/>
support
此合并行为同样适用于
、 和 集合类型。在元素的特定情况下,语义
与集合类型(即值集合的概念)相关联。父级的值位于所有子级列表的
值。对于 、 和 集合类型,则无排序
存在。因此,对于作为基础的集合类型,没有有效的排序语义
容器的关联 、 和 实现类型
内部使用。<list/>
<map/>
<set/>
<list/>
List
ordered
Map
Set
Properties
Map
Set
Properties
集合合并的局限性
不能合并不同的集合类型(如
a 和 )。如果你
尝试这样做,会抛出适当的。该属性必须是
在较低的继承子定义中指定。指定属性
父集合定义是多余的,不会导致所需的合并。Map
List
Exception
merge
merge
强类型集合
由于 Java
对泛型类型的支持,您可以使用强类型集合。
也就是说,可以声明一个类型,使其只能包含
(例如)元素。如果使用 Spring 依赖注入
强类型化成 bean,您可以利用 Spring 的
类型转换支持,以便将强类型实例的元素转换为适当的类型,然后再将其添加到 .
以下 Java 类和 Bean 定义显示了如何执行此操作:Collection
String
Collection
Collection
Collection
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当准备注射 Bean
的属性时,泛型
有关强类型的元素类型的信息是
可通过反射获得。因此,Spring 的类型转换基础结构可以识别
各种值元素作为 类型,字符串值 (, , 和 ) 被转换为实际类型。accounts
something
Map<String,
Float>
Float
9.99
2.75
3.99
Float
Null 和空字符串值
Spring 将属性等的空参数视为空参数。这
以下基于 XML 的配置元数据代码段将属性设置为空值 (“”)。Strings
email
String
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
该元素处理值。下面的清单显示了一个示例:<null/>
null
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等同于以下 Java 代码:
exampleBean.setEmail(null);
带有 p 命名空间的 XML 快捷方式
p-namespace
允许您使用元素的属性(而不是嵌套元素)来描述您的属性值和/或协作
bean。bean
<property/>
Spring 支持具有命名空间的可扩展配置格式,
它们基于 XML 架构定义。中讨论的配置格式
本章在 XML 模式文档中定义。但是,未定义 p-namespace
在 XSD 文件中,并且仅存在于 Spring 的核心中。beans
以下示例显示了两个 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="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
该示例显示了在 Bean
定义中调用的 p 命名空间中的一个属性。
这告诉 Spring 包含一个属性声明。如前所述,
p-namespace 没有架构定义,因此您可以设置属性的名称
添加到属性名称。email
下一个示例包括另外两个 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 到 Bean 的引用,第二个 Bean 定义用作
属性来执行完全相同的操作。在本例中,是属性名称,
而该部分表明这不是一个直线值,而是一个
引用另一个 bean。<property name="spouse"
ref="jane"/>
john
jane
p:spouse-ref="jane"
spouse
-ref
p 命名空间不如标准 XML 格式灵活。例如,格式
用于声明属性引用会与以 结尾的属性冲突,而
标准 XML 格式则不然。我们建议您仔细选择方法,并
将此信息传达给您的团队成员,以避免生成使用所有
同时采用三种方法。Ref |
带有 c 命名空间的 XML 快捷方式
类似于 Spring 中引入的带有 p-namespace 的 XML 快捷方式,即
c-namespace
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="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
命名空间使用与命名空间相同的约定(尾随
Bean 引用),用于按构造函数名称设置构造函数参数。同样地
它需要在 XML 文件中声明,即使它未在 XSD 架构中定义
(它存在于 Spring 核心内部)。c:
p:
-ref
对于构造函数参数名称不可用的极少数情况(通常如果 字节码是在没有调试信息的情况下编译的),你可以回退到 参数索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
由于 XML 语法的原因,索引表示法要求存在前导
,
因为 XML 属性名称不能以数字开头(即使某些 IDE 允许这样做)。
相应的索引表示法也可用于元素,但
不常用,因为在那里通常只有简单的声明顺序就足够了。_ <constructor-arg>
|
在实践中,构造函数解析机制在匹配方面非常有效 参数,因此除非您确实需要,否则我们建议使用 name 表示法 在整个配置过程中。
复合属性名称
在设置 Bean
属性时,可以使用复合属性名称或嵌套属性名称,只要
路径的所有组件(最终属性名称除外)都不是 。考虑
以下 Bean 定义:null
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
Bean 有一个属性,该属性具有一个属性,并且该
final 属性被设置为值 。为了
这要起作用,属性和属性不得
在 bean 构建之后。否则,将抛出
a。something
fred
bob
sammy
sammy
123
fred
something
bob
fred
null
NullPointerException
1.4.3.
使用depends-on
如果一个 Bean 是另一个 Bean
的依赖项,这通常意味着一个 Bean 被设置为
另一个人的财产。通常,在基于 XML 的配置元数据中使用 <ref/>
元素来实现此目的。但是,有时依赖关系
豆子不那么直接。例如,当类中的静态初始值设定项需要
触发,例如用于数据库驱动程序注册。该属性可以
显式强制一个或多个 Bean 在使用此元素的 Bean 之前初始化
已初始化。以下示例使用该属性来表示
对单个 Bean 的依赖性:depends-on
depends-on
<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" />
该属性既可以指定初始化时间依赖关系,也可以指定
仅在单例 Bean 的情况下,相应的
销毁时间依赖性。定义关系的依赖 Bean
在给定的 Bean 本身被销毁之前,给定的 Bean 首先被销毁。
因此,还可以控制关机顺序。depends-on depends-on depends-on
|
1.4.4. 延迟初始化的 Bean
缺省情况下,实现会急切地创建和配置所有单例 Bean 作为初始化的一部分
过程。通常,这种预实例化是可取的,因为
立即发现配置或周围环境,而不是数小时
甚至几天后。当不希望出现此行为时,可以防止
通过将 Bean 定义标记为
延迟初始化。延迟初始化的 Bean 告诉 IoC 容器创建一个 Bean
实例,而不是在启动时。ApplicationContext
在 XML
中,此行为由元素上的属性控制,如以下示例所示:lazy-init
<bean/>
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当上述配置被 、
在启动时不会急切地预先实例化,
而 Bean 是急切地预先实例化的。ApplicationContext
lazy
ApplicationContext
not.lazy
但是,当延迟初始化的 Bean 是单例
Bean 的依赖项时,该 Bean 是
未延迟初始化,则在
启动,因为它必须满足单例的依赖关系。延迟初始化的 Bean
被注入到其他未延迟初始化的单例 Bean 中。ApplicationContext
还可以使用元素上的属性在容器级别控制延迟初始化,如以下示例所示: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 的配置元数据(请参阅依赖关系注入)时,您
可以指定具有元素属性的 Bean 定义的自动连线模式。自动接线功能有四种模式。指定自动接线
每个 bean,因此可以选择自动连接哪些。下表描述了
四种自动接线模式:autowire
<bean/>
模式 | 解释 |
---|---|
|
(默认)没有自动接线。Bean
引用必须由元素定义。改变
对于较大的部署,不建议使用默认设置,因为指定
协作者明确地提供了更大的控制力和清晰度。在某种程度上,它
记录系统的结构。 |
|
按属性名称自动布线。Spring
寻找与
需要自动连线的属性。例如,如果 Bean 定义设置为
autowire by name,它包含一个属性(即,它有一个方法),Spring 寻找一个 bean 定义,命名并使用
它来设置属性。 |
|
如果
中正好存在一个属性类型的 bean,则允许自动连接属性
容器。如果存在多个异常,则会引发致命异常,这表示
您不能对该 Bean 使用自动接线。如果没有匹配
beans,则没有任何反应(未设置该属性)。 |
|
类似于但适用于构造函数参数。如果没有
容器中构造函数参数类型的一个 bean,则会引发致命错误。 |
使用或自动布线模式,您可以将阵列和
类型化集合。在这种情况下,容器内的所有自动连接候选项
匹配提供预期的类型以满足依赖关系。您可以自动接线
强类型实例(如果预期的密钥类型为 )。自动连接实例的值由与预期类型匹配的所有 Bean
实例组成,并且实例的键包含相应的 Bean 名称。byType
constructor
Map
String
Map
Map
自动布线的局限性和缺点
当自动布线在整个项目中一致使用时,它的效果最好。如果自动接线是 一般不使用,开发人员可能会混淆仅使用它来连接一个或 两个 Bean 定义。
考虑自动布线的局限性和缺点:
-
中的显式依赖项和设置始终覆盖 自动接线。不能自动连接简单属性,例如基元、和(以及此类简单属性的数组)。此限制是 按设计。
property
constructor-arg
Strings
Classes
-
自动布线不如显式布线精确。虽然,如上表所述, Spring 小心翼翼地避免猜测,以防出现可能意想不到的歧义 结果。Spring 管理的对象之间的关系不再 明确记录。
-
接线信息可能不适用于可能从中生成文档的工具 一个 Spring 容器。
-
容器中的多个 Bean 定义可能与 setter 方法或构造函数参数要自动连接。对于数组、集合或实例,这不一定是问题。但是,对于依赖项 期望单个值,这种歧义不会被任意解决。如果没有唯一的 bean 定义可用时,将引发异常。
Map
在后一种情况下,您有以下几种选择:
从自动布线中排除 Bean
基于每个
Bean,您可以从自动布线中排除 Bean。在 Spring 的 XML 格式中,将
元素的属性。容器
使该特定 Bean 定义对自动布线基础结构不可用
(包括注释样式配置,例如@Autowired
)。autowire-candidate
<bean/>
false
该属性旨在仅影响基于类型的自动布线。
它不会影响按名称划分的显式引用,即使
指定的 Bean 未标记为 AutoWire 候选项。因此,自动接线
但是,如果名称匹配,则按名称注入 bean。autowire-candidate |
您还可以根据针对 Bean
名称的模式匹配来限制自动连线候选项。这
顶级元素在其属性中接受一个或多个模式。例如,限制自动连线候选状态
对于名称以 结尾的任何 Bean,请提供值 。自
提供多种模式,在逗号分隔的列表中定义它们。Bean 定义属性的显式值 或 始终采用
优先。对于此类 Bean,模式匹配规则不适用。<beans/>
default-autowire-candidates
Repository
*Repository
true
false
autowire-candidate
这些技术对于您永远不想被注射到其他豆子中的豆子很有用 通过自动布线的豆子。这并不意味着排除的 Bean 本身不能由 使用自动布线。相反,Bean 本身不是自动连接其他 Bean 的候选者。
1.4.6. 方法注入
在大多数应用场景中,容器中的大多数 Bean 都是单例的。当单例 Bean 需要 与另一个单例 Bean 协作或需要协作的非单例 Bean 协作 对于另一个非单例 Bean,通常通过定义一个依赖关系来处理依赖关系 Bean 作为 other 的属性。当 Bean 生命周期 不同。假设单例 Bean A 需要使用非单例(原型)Bean B, 也许在 A 上的每个方法调用上。容器仅创建单例 Bean A 一次,因此只有一次机会来设置属性。容器不能 每次需要 Bean B 时,都为 Bean A 提供一个新的 Bean 实例。
一个解决方案是放弃一些控制反转。你可以使
bean 通过实现接口来感知容器,
并通过对容器进行 getBean(“B”)
调用来请求 (a
通常是新的)bean B 实例,每次 Bean A 需要它时。以下示例
显示了此方法:ApplicationContextAware
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
上述内容是不可取的,因为业务代码知道并耦合到 Spring 框架。方法注入,Spring IoC 的一个有点高级的功能 容器,可让您干净地处理此用例。
Lookup 方法注入
查找方法注入是容器覆盖 容器管理的 bean,并返回 容器。查找通常涉及原型 Bean,如所描述的场景所示 在上一节中。Spring 框架 通过使用 CGLIB 库中的字节码生成来实现此方法注入,以 动态生成一个重写该方法的子类。
|
对于上一个代码片段中的类,
Spring 容器动态地重写该方法的实现。该类没有任何 Spring 依赖项,因为
重新设计的示例显示:CommandManager
createCommand()
CommandManager
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();
}
在包含要注入的方法的客户端类中(在此
case),要注入的方法需要以下形式的签名:CommandManager
<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>
标识为 的 Bean 调用自己的方法
每当它需要 Bean 的新实例时。必须小心部署
Bean 作为原型,如果这确实是需要的。如果是
一个单例,每次都返回相同的 Bean
实例。commandManager
createCommand()
myCommand
myCommand
myCommand
或者,在基于注释的组件模型中,您可以声明查找
方法,如下例所示:@Lookup
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更通俗地说,您可以依靠目标 bean 针对 查找方法的声明返回类型:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
请注意,您通常应该使用 存根实现,以便它们与 Spring 的组件兼容 默认情况下忽略抽象类的扫描规则。此限制不 适用于显式注册或显式导入的 Bean 类。
访问不同作用域的目标
Bean 的另一种方法是 / 注入点。请参见作用域内的 Bean
作为依赖项。 您可能还会发现(在包中)很有用。 |
任意方法替换
与查找方法注入相比,方法注入的一种不太有用的形式是能够 将受管 Bean 中的任意方法替换为另一个方法实现。你 可以安全地跳过本节的其余部分,直到您真正需要此功能。
使用基于 XML
的配置元数据,可以使用该元素执行以下操作
对于已部署的 Bean,将现有方法实现替换为另一个方法实现。考虑
下面的类,它有一个我们想要重写的方法:replaced-method
computeValue
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
实现接口的类提供新的方法定义,如以下示例所示:org.springframework.beans.factory.support.MethodReplacer
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
用于部署原始类并指定方法覆盖的 Bean 定义将 类似于以下示例:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
可以使用元素中的一个或多个元素来指示要重写的方法的方法签名。签名
因为只有当方法重载和多个变体时,参数才是必需的
存在于类中。为方便起见,参数的类型字符串可以是
完全限定类型名称的子字符串。例如,以下全部匹配:<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
The following table describes the supported scopes:
Scope | Description |
---|---|
(Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
|
Scopes a single bean definition to any number of object instances. |
|
将单个
Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是
每个 HTTP 请求都有自己的 Bean 实例,该实例是在单个 Bean 的背面创建的
定义。仅在 Web 感知 Spring 的上下文中有效。 |
|
将单个
Bean 定义的范围限定为 HTTP 的生命周期。仅在
Web 感知 Spring 的上下文。 |
|
将单个
Bean 定义的范围限定为 .仅在
Web 感知 Spring 的上下文。 |
|
将单个
Bean 定义的范围限定为 .仅在
Web 感知 Spring 的上下文。 |
从 Spring 3.0
开始,线程作用域可用,但默认情况下未注册。为
有关详细信息,请参阅 SimpleThreadScope 的文档。
有关如何注册此范围或任何其他自定义范围的说明,请参阅使用自定义范围。
|
1.5.1. 单例作用域
只管理一个单例 Bean 的一个共享实例,并且对 Bean 的所有请求 具有与该 Bean 定义匹配的一个或多个 ID 将导致该特定 Bean 实例由 Spring 容器返回。
换句话说,当您定义一个 Bean 定义并且它的作用域为 单例,Spring IoC 容器只创建对象的一个实例 由该 Bean 定义定义。这个单个实例存储在这样的缓存中 单例 Bean,以及该命名 Bean 的所有后续请求和引用 返回缓存的对象。下图显示了单例作用域的工作原理:

Spring 的单例 Bean 概念不同于 四人帮 (GoF) 模式书。GoF 单例对 对象,以便为每个类创建一个且只有一个特定类的实例 ClassLoader 中。Spring 单例的范围最好描述为每个容器 和每豆。这意味着,如果您为 单个 Spring 容器,Spring 容器创建且只有一个实例 由该 Bean 定义定义的类。单一实例作用域是默认作用域 在春天。要在 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 发出请求时。也就是说,豆子
注入到另一个 Bean 中,或者您通过
容器。通常,您应该对所有有状态 Bean 使用 prototype 作用域,并且
无状态 Bean 的单例作用域。getBean()
下图说明了 Spring 原型范围:

(数据访问对象 (DAO) 通常不会配置为原型,因为典型的 DAO 不成立 任何对话状态。对我们来说,重用 单例图。
以下示例在 XML 中将 Bean 定义为原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring 不管理 原型豆。容器实例化、配置和以其他方式组装 原型对象并将其交给客户,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 不调用生命周期回调。客户端代码必须清理原型范围的 对象并释放原型 Bean 所持有的昂贵资源。要获得 Spring 容器来释放原型范围的 bean 所持有的资源,尝试使用 自定义 Bean 后处理器,它包含对 需要清理的豆子。
在某些方面,Spring 容器在原型范围的
Bean 中的作用是
替换 Java 运算符。超过该时间点的所有生命周期管理都必须
由客户处理。(有关 Spring 中 Bean 生命周期的详细信息
容器,请参阅生命周期回调。new
1.5.3. 具有原型 Bean 依赖关系的单例 Bean
当您使用依赖于原型 Bean 的单例范围的 Bean 时,请注意 依赖项在实例化时解析。因此,如果依赖注入 原型范围的 Bean 转换为单例范围的 Bean,实例化一个新的原型 Bean 然后依赖注入到单例 Bean 中。原型实例是唯一的 曾经提供给单例范围的 Bean 的实例。
但是,假设您希望单例范围的 Bean 获取 原型范围的 bean 在运行时重复。您不能依赖注入 原型范围的 Bean 复制到单例 Bean 中,因为该注入只发生 一次,当 Spring 容器实例化单例 Bean 并解析 并注入其依赖项。如果您需要原型 Bean 的新实例,则位于 运行时多次,请参阅方法注入。
1.5.4. 请求、会话、应用程序和 WebSocket 作用域
、、 和 作用域仅可用
如果您使用 Web 感知的 Spring 实现(例如 )。如果将这些作用域与常规 Spring IoC 容器一起使用,
比如抱怨的 , an
关于一个未知的 Bean 范围被抛出。request
session
application
websocket
ApplicationContext
XmlWebApplicationContext
ClassPathXmlApplicationContext
IllegalStateException
初始 Web 配置
为了支持在 、 、 和 级别(Web
范围的 Bean)上确定 Bean 的作用域,一些次要的初始配置是
在定义 Bean 之前是必需的。(此初始设置不是必需的
对于标准范围:和
.)request
session
application
websocket
singleton
prototype
如何完成此初始设置取决于特定的 Servlet 环境。
如果您在 Spring Web MVC
中访问作用域内的 bean,实际上,在请求中
由Spring处理,无需特殊设置。
已经暴露了所有相关状态。DispatcherServlet
DispatcherServlet
如果您使用 Servlet 2.5
Web 容器,并且在 Spring 之外处理请求(例如,使用 JSF 或 Struts 时),则需要注册 .
对于 Servlet 3.0+,可以使用接口以编程方式完成此操作。或者,对于较旧的容器,将以下声明添加到
Web 应用程序的文件:DispatcherServlet
org.springframework.web.context.request.RequestContextListener
ServletRequestListener
WebApplicationInitializer
web.xml
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果您的侦听器设置有问题,请考虑使用
Spring 的 .筛选器映射取决于周围的网络
应用程序配置,因此您必须根据需要进行更改。以下列表
显示了 Web 应用程序的筛选器部分:RequestContextFilter
<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
、 、 和 都完全
同样的事情,即将 HTTP 请求对象绑定到正在服务的
那个要求。这使得请求范围和会话范围的 Bean 进一步可用
在呼叫链中向下。RequestContextListener
RequestContextFilter
Thread
请求范围
请考虑以下 Bean 定义的 XML 配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器通过使用每个
HTTP 请求的 Bean 定义来创建 Bean 的新实例。也就是说,Bean 的范围限定在 HTTP 请求级别。您可以更改内部
根据需要创建的实例的状态,因为其他实例
从同一 Bean 定义创建时,看不到这些状态更改。
它们特定于单个请求。当请求完成处理时,
范围限定为请求的 Bean
将被丢弃。LoginAction
loginAction
loginAction
loginAction
当使用注解驱动的组件或
Java 配置时,注解
可用于将组件分配给示波器。以下示例演示如何
为此,请执行以下操作:@RequestScope
request
@RequestScope
@Component
public class LoginAction {
// ...
}
会话范围
请考虑以下 Bean 定义的 XML 配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring容器通过使用单个HTTP的生命周期的bean定义来创建bean的新实例。在其他
words,则 bean 的范围实际上在 HTTP 级别。如
使用请求范围的 Bean,您可以更改实例的内部状态,即
随心所欲地创建,知道其他 HTTP 实例也
使用从同一 Bean 定义创建的实例时看不到这些
状态更改,因为它们特定于单个 HTTP 。当
HTTP 最终被丢弃,作用域为该特定 HTTP 的 Bean
也会被丢弃。UserPreferences
userPreferences
Session
userPreferences
Session
Session
userPreferences
Session
Session
Session
使用注释驱动的组件或 Java
配置时,可以使用注释将组件分配给作用域。@SessionScope
session
@SessionScope
@Component
public class UserPreferences {
// ...
}
应用范围
请考虑以下 Bean 定义的 XML 配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器通过对整个
Web 应用程序使用一次 Bean 定义来创建 Bean 的新实例。也就是说,Bean
的作用域在级别上,并存储为常规属性。这有点类似于 Spring 单例 bean,但
在两个重要方面有所不同:它是 per 的单例,而不是 per Spring(在任何给定的 Web 应用程序中可能有多个),
它实际上是公开的,因此作为属性可见。AppPreferences
appPreferences
appPreferences
ServletContext
ServletContext
ServletContext
ApplicationContext
ServletContext
使用注释驱动的组件或 Java
配置时,可以使用注释将组件分配给作用域。这
以下示例演示如何执行此操作:@ApplicationScope
application
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
WebSocket 作用域
WebSocket 作用域与 WebSocket 会话的生命周期相关联,适用于 基于 WebSocket 应用程序的 STOMP,有关详细信息,请参阅 WebSocket 范围。
作用域内的 Bean 作为依赖项
Spring IoC 容器不仅管理对象(bean)的实例化, 还有协作者(或依赖关系)的连接。如果要注入(对于 example) 一个 HTTP 请求范围的 bean 到另一个生存期较长的 bean 中,你可以 选择注入 AOP 代理来代替作用域内的 bean。也就是说,您需要注射 一个代理对象,它公开与作用域对象相同的公共接口,但可以 还从相关范围(例如 HTTP 请求)中检索实际目标对象 并将方法调用委托给实际对象。
您还可以在作用域为
的 bean 之间使用
然后通过可序列化的中间代理进行引用
因此能够在反序列化时重新获取目标单例 bean。 当针对作用域的
Bean 进行声明时,每个方法
调用共享代理会导致创建一个新的目标实例,该实例的
然后呼叫被转接。 此外,作用域代理并不是从
生命周期安全的时尚。您也可以声明您的注入点(即
构造函数或 setter 参数或自动连线字段)作为 ,
允许调用以按需检索当前实例
需要的时间 — 无需保留实例或单独存储。 作为扩展变体,您可以声明哪个交付
其他几个访问变体,包括 和 . 此的
JSR-330 变体被调用,并与声明和每次检索尝试的相应调用一起使用。
有关 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 | 定义代理的行。 |
若要创建此类代理,请将子元素插入到作用域内
Bean 定义(请参阅选择要创建的代理类型和基于 XML 模式的配置)。
为什么 bean 的定义范围在 和 custom-scope
关卡需要元素吗?
考虑以下单例 Bean 定义,并将其与
您需要为上述范围定义什么(请注意,以下 Bean 定义是不完整的):<aop:scoped-proxy/>
request
session
<aop:scoped-proxy/>
userPreferences
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例 Bean
() 注入了引用
添加到 HTTP 范围的 bean ()。这里的要点是 Bean 是一个单例:它每
容器及其依赖项(在本例中只有一个,即 bean)是
也只注射一次。这意味着 Bean 仅在
完全相同的对象(即最初注入的对象)。userManager
Session
userPreferences
userManager
userPreferences
userManager
userPreferences
这不是将生存期较短的作用域
Bean 注入
生存期较长的作用域 bean(例如,注入 HTTP 范围的协作
Bean 作为单例 Bean 的依赖项)。相反,您需要一个对象,并且在 HTTP 的生命周期内,您需要一个对象
特定于 HTTP 。因此,容器创建一个对象,该对象
公开与类完全相同的公共接口(理想情况下
对象),可以从作用域机制(HTTP 请求等)获取实际对象
第四)。容器将此代理对象注入到 Bean 中,该 Bean 是
不知道此引用是代理。在此示例中,当实例在依赖项注入的对象上调用方法时,它实际上是在调用代理上的方法。然后,代理从(在本例中)HTTP
获取真实对象,并委托
方法调用到检索到的实际对象。Session
userManager
Session
userPreferences
Session
UserPreferences
UserPreferences
UserPreferences
Session
userManager
UserPreferences
UserManager
UserPreferences
UserPreferences
Session
UserPreferences
因此,在将 和 bean
注入协作对象时,您需要以下(正确且完整的)配置,如以下示例所示
显示:request-
session-scoped
<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
容器为标记为
元素,则创建基于 CGLIB 的类代理。<aop:scoped-proxy/>
CGLIB 代理仅拦截公共方法调用!不要调用非公共方法 在这样的代理上。它们不会委派给实际的作用域目标对象。 |
或者,您可以配置
Spring 容器以创建标准 JDK
此类作用域 Bean 的基于接口的代理,通过指定
元素的属性。使用 JDK
基于接口的代理意味着您不需要额外的库
应用程序类路径来影响此类代理。但是,这也意味着
作用域 Bean 必须实现至少一个接口,并且所有协作者
将作用域 Bean 注入到其中,必须通过其
接口。以下示例显示了基于接口的代理:false
proxy-target-class
<aop:scoped-proxy/>
<!-- 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 容器中,需要实现接口,如下所述
部分。有关如何实现自己的作用域的想法,请参阅随 Spring Framework 本身和 Scope
javadoc 提供的实现。
其中更详细地解释了您需要实现的方法。org.springframework.beans.factory.config.Scope
Scope
该接口有四种方法用于从作用域中获取对象,将它们从
范围,让它们被摧毁。Scope
例如,会话作用域实现返回会话作用域的 bean(如果它 不存在,该方法在将 Bean 绑定到 会议供将来参考)。以下方法从 基础范围:
Object get(String name, ObjectFactory<?> objectFactory)
例如,会话范围实现从
基础会话。应该返回对象,但如果
找不到具有指定名称的对象。以下方法从中删除对象
基础范围:null
Object remove(String name)
以下方法注册一个回调,当作用域出现以下情况时,作用域应调用该回调: 已销毁或当范围内的指定对象被销毁时:
void registerDestructionCallback(String name, Runnable destructionCallback)
有关销毁回调的更多信息,请参阅 javadoc 或 Spring scope 实现。
以下方法获取基础作用域的对话标识符:
String getConversationId()
对于每个范围,此标识符都不同。对于会话范围的实现,此 identifier 可以是会话标识符。
使用自定义范围
在编写并测试一个或多个自定义实现后,您需要使
Spring 容器可识别您的新范围。以下方法是核心
向 Spring 容器注册新的方法:Scope
Scope
void registerScope(String scopeName, Scope scope);
此方法在可用的接口上声明
通过 Spring 附带的大多数具体实现的属性。ConfigurableBeanFactory
BeanFactory
ApplicationContext
该方法的第一个参数是与
一个范围。Spring 容器本身中此类名称的示例是 和 。该方法的第二个参数是实际实例
您希望注册和使用的自定义实现。registerScope(..)
singleton
prototype
registerScope(..)
Scope
假设您编写自定义实现,然后注册它,如下所示
在下一个示例中。Scope
下一个示例使用
,它包含在 Spring 中,但不是
默认注册。对于您自己的自定义实现,这些说明是相同的。SimpleThreadScope Scope
|
Scope threadScope = new 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>
当您将实现的声明放在一个声明中时,作用域是工厂
Bean 本身,而不是对象
返回自
。<aop:scoped-proxy/> <bean> FactoryBean getObject()
|
1.6. 自定义 Bean 的性质
Spring Framework 提供了许多接口,可用于自定义性质 一颗豆子。本部分按如下方式对它们进行分组:
1.6.1. 生命周期回调
要与容器对 Bean
生命周期的管理进行交互,您可以实现
Spring 和接口。容器要求前者,后者让 bean
在初始化和销毁 Bean
时执行某些操作。InitializingBean
DisposableBean
afterPropertiesSet()
destroy()
JSR-250
和注解通常被认为是最好的
在现代 Spring 应用程序中接收生命周期回调的练习。使用这些
注解意味着你的 Bean 没有耦合到 Spring 特定的接口。
有关详细信息,请参阅使用 如果您不想使用
JSR-250 注解,但仍想删除
耦合、考虑和 Bean 定义元数据。 |
在内部,Spring Framework
使用实现来处理任何
Callback 接口,它可以找到并调用相应的方法。如果您需要定制
功能或其他生命周期行为 Spring 默认不提供,您可以
实现一个自己。有关详细信息,请参阅容器扩展点。BeanPostProcessor
BeanPostProcessor
除了初始化和销毁回调之外,Spring
管理的对象还可以
还要实现接口,以便这些对象可以参与
启动和关闭过程,由容器自身的生命周期驱动。Lifecycle
本节将介绍生命周期回调接口。
初始化回调
该接口让 bean
在容器上设置了所有必要的属性后,执行初始化工作
豆。该接口指定单个方法:org.springframework.beans.factory.InitializingBean
InitializingBean
void afterPropertiesSet() throws Exception;
我们建议您不要使用该接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
@PostConstruct
注释或
指定 POJO 初始化方法。对于基于 XML 的配置元数据,
可以使用该属性指定具有 void 的方法的名称
无参数签名。通过 Java 配置,可以使用 的属性。请参阅接收生命周期回调。请看以下示例:InitializingBean
init-method
initMethod
@Bean
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
前面的示例与以下示例的效果几乎完全相同 (由两个列表组成):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
但是,前面两个示例中的第一个示例没有将代码耦合到 Spring。
销毁回调
实现该接口可以让
bean 在包含它的容器被销毁时得到一个回调。该接口指定单个方法:org.springframework.beans.factory.DisposableBean
DisposableBean
void destroy() throws Exception;
建议不要使用回调接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
@PreDestroy
注释或
指定 Bean 定义支持的泛型方法。使用基于 XML 的
配置元数据,则可以使用 .
通过 Java 配置,可以使用 的属性。请参阅接收生命周期回调。请考虑以下定义:DisposableBean
destroy-method
<bean/>
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)
}
}
前面的定义与下面的定义具有几乎完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面两个定义中的第一个定义没有将代码耦合到 Spring。
您可以为元素的属性分配一个特殊值,该值指示
Spring 自动检测特定 Bean 类上的公共或方法。 (实现或因此匹配的任何类。您还可以将
元素属性上的此特殊值,用于将此行为应用于整个 Bean 集(请参阅缺省初始化和销毁方法)。请注意,这是
Java
配置的默认行为。destroy-method <bean> (inferred) close shutdown java.lang.AutoCloseable java.io.Closeable (inferred) default-destroy-method <beans>
|
默认初始化和销毁方法
当您编写不使用
特定于 Spring 的接口和回调接口,你
通常使用名称(如 、 、 等)编写方法
上。理想情况下,此类生命周期回调方法的名称在
项目,以便所有开发人员使用相同的方法名称并确保一致性。InitializingBean
DisposableBean
init()
initialize()
dispose()
您可以将 Spring
容器配置为“查找”命名初始化并销毁
每个 Bean 上的回调方法名称。这意味着您作为应用程序
开发人员可以编写应用程序类并使用名为 的初始化回调,而无需为每个 Bean 配置属性
定义。Spring IoC 容器在创建 Bean 时调用该方法(并在
根据前面描述的标准生命周期回调约定)。此功能还强制执行一致的命名约定
初始化和销毁方法回调。init()
init-method="init"
假设您的初始化回调方法已命名,并且您的
destroy
回调方法被命名为 。然后,您的类类似于
以下示例: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.");
}
}
}
然后,您可以在类似于以下内容的 Bean 中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级元素上存在该属性
属性使 Spring IoC 容器识别在 Bean 上调用的方法
类作为初始化方法回调。当创建并组装 Bean 时,如果
Bean
类有这样一个方法,它会在适当的时间被调用。default-init-method
<beans/>
init
可以通过使用顶级元素上的属性来类似地配置销毁方法回调(即在
XML 中)。default-destroy-method
<beans/>
其中现有 Bean
类已经具有以差异命名的回调方法
使用约定,您可以通过指定(在 XML 中)覆盖默认值
方法名称,方法是使用 本身的 和
属性。init-method
destroy-method
<bean/>
Spring 容器保证调用配置的初始化回调
在向 Bean 提供所有依赖项之后。因此,初始化
callback 是在原始 Bean 引用上调用的,这意味着 AOP 拦截器等
第四条尚未应用于 bean。首先完全创建一个目标 Bean,然后
然后应用带有拦截器链的 AOP 代理(例如)。如果目标
Bean 和代理是分开定义的,您的代码甚至可以与 RAW 进行交互
目标 bean,绕过代理。因此,将
拦截器,因为这样做会耦合
将 Bean 定位到其代理或拦截器,并在代码时留下奇怪的语义
直接与原始目标 Bean 交互。init
结合生命周期机制
从 Spring 2.5 开始,您有三个选项来控制 Bean 生命周期行为:
-
InitializingBean
和DisposableBean
回调接口 -
自定义和方法
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
管理的对象都可以实现该接口。然后,当本身接收到启动和停止信号时(例如,对于停止/重新启动
scenario at runtime),它将这些调用级联到所有实现
在此上下文中定义。它通过委托给 ,显示
在以下列表中:Lifecycle
ApplicationContext
Lifecycle
LifecycleProcessor
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,它本身就是接口的扩展。它还添加了另外两种方法,用于对正在刷新的上下文做出反应
并关闭。LifecycleProcessor
Lifecycle
请注意,常规接口是普通的
显式启动和停止通知的协定,并不意味着在上下文中自动启动
刷新时间。为了对特定 Bean 的自动启动(包括启动阶段)进行细粒度控制,
请考虑改为实施。 另外,请注意,不能保证在销毁之前发出停止通知。
在定期关闭时,所有 Bean 首先收到停止通知,然后再
正在传播常规销毁回调。但是,在热刷新期间
上下文的生存期或停止刷新尝试时,仅调用 destroy 方法。 |
启动和关闭调用的顺序可能很重要。如果“依赖”
关系存在于任意两个对象之间,依赖方在其之后开始
依赖关系,它会在依赖关系之前停止。然而,有时,直接
依赖关系未知。您可能只知道某种类型的对象应该开始
在另一种类型的对象之前。在这些情况下,接口定义
另一个选项,即在其超级接口上定义的方法。下面的清单显示了接口的定义: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
定义的 stop 方法接受回调。任何
实现必须在该实现的
关机过程已完成。这样可以在必要时异步关闭,因为
接口的默认实现 ,等待对象组的超时值
在每个阶段中调用该回调。默认的每阶段超时为 30 秒。
您可以通过定义在上下文中命名的 Bean 来覆盖缺省的生命周期处理器实例。如果只想修改超时,
定义以下内容就足够了:SmartLifecycle
run()
LifecycleProcessor
DefaultLifecycleProcessor
lifecycleProcessor
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,该接口定义了
刷新和关闭上下文。后者驱动关机
进程,就好像被显式调用了一样,但它发生在上下文
关闭。另一方面,“refresh”回调启用了 Bean 的另一个功能。刷新上下文时(在所有对象都已
实例化并初始化),该回调将被调用。此时,
默认生命周期处理器检查每个对象的方法返回的布尔值。如果 ,则该对象是
从该点开始,而不是等待上下文的 or 的显式调用
它自己的方法(与上下文刷新不同,上下文启动不会发生
对于标准上下文实现,自动)。值和任何
如前所述,“依赖”关系决定了启动顺序。LifecycleProcessor
stop()
SmartLifecycle
SmartLifecycle
isAutoStartup()
true
start()
phase
在非 Web 应用程序中正常关闭 Spring IoC 容器
本部分仅适用于非
Web 应用程序。Spring 基于 Web 的实现已经有了代码,可以优雅地关闭
关闭相关 Web 应用程序时的 Spring IoC 容器。 |
如果您在非 Web 应用程序环境中使用 Spring 的 IoC 容器(对于 例如,在富客户端桌面环境中),将关闭钩子注册到 JVM 中。这样做可以确保正常关闭,并在 单例 Bean,以便释放所有资源。您仍必须配置 并正确实现这些销毁回调。
若要注册关闭挂钩,请调用以下方法
在接口上声明,如以下示例所示: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...
}
}
1.6.2. 和ApplicationContextAware
BeanNameAware
当创建实现接口的对象实例时,将提供该实例
并参考那个.下面的清单显示了定义
接口的:ApplicationContext
org.springframework.context.ApplicationContextAware
ApplicationContext
ApplicationContextAware
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean 可以以编程方式操作创建它们的
通过接口或将引用强制转换为已知
此接口的子类(例如 ,它公开了
附加功能)。一种用途是以编程方式检索其他 bean。
有时此功能很有用。但是,一般来说,您应该避免它,因为
它将代码耦合到 Spring 中,并且不遵循 Inversion of Control 风格,
其中协作者作为属性提供给 bean。提供对文件资源的访问、发布应用程序事件、
并访问 .ApplicationContext
的其他功能中介绍了这些附加功能。ApplicationContext
ApplicationContext
ConfigurableApplicationContext
ApplicationContext
MessageSource
自动布线是获取对
的引用的另一种替代方法。传统和自动布线模式
(如自动布线协作器中所述)可以为构造函数参数或
setter 方法参数提供类型的依赖关系,
分别。为了获得更大的灵活性,包括自动连接字段和
多种参数方法,使用基于注释的自动布线功能。如果你这样做了,
自动连接到字段、构造函数参数或方法
如果字段、构造函数或
所讨论的方法带有注释。有关更多信息,请参阅使用 @Autowired
。ApplicationContext
constructor
byType
ApplicationContext
ApplicationContext
ApplicationContext
@Autowired
当创建一个实现接口的类时,该类将提供
对在其关联对象定义中定义的名称的引用。以下列表
显示了 BeanNameAware 接口的定义:ApplicationContext
org.springframework.beans.factory.BeanNameAware
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
The callback is invoked after
population of normal bean properties but before an
initialization callback such as or a custom
init-method.InitializingBean.afterPropertiesSet()
1.6.3.
其他接口Aware
除了 和 (前面讨论过),
Spring 提供了广泛的回调接口,让 Bean 向容器指示
它们需要一定的基础设施依赖性。作为一般规则,名称表示
依赖项类型。下表总结了最重要的接口:ApplicationContextAware
BeanNameAware
Aware
Aware
名字 | 注入的依赖关系 | 解释... |
---|---|---|
|
声明。 |
|
|
封闭
. |
|
|
类装入器用于装入 Bean 类。 |
|
|
声明。 |
|
|
声明 Bean 的名称。 |
|
|
定义的 weaver,用于在加载时处理类定义。 |
|
|
配置了用于解析消息的策略(支持参数化和 国际化)。 |
|
|
Spring JMX 通知发布者。 |
|
|
已配置加载程序,用于对资源进行低级别访问。 |
|
|
当前容器运行在。仅在
Web 感知的 Spring
中有效。 |
|
|
当前容器运行在。仅在
Web 感知的 Spring
中有效。 |
再次请注意,使用这些接口将您的代码绑定到 Spring API,并且不会 遵循控制反转风格。因此,我们推荐它们用于基础设施 需要以编程方式访问容器的 bean。
1.7. Bean 定义继承
Bean 定义可以包含大量配置信息,包括构造函数 参数、属性值和特定于容器的信息,例如初始化 方法、静态工厂方法名称等。子 Bean 定义继承 来自父定义的配置数据。子定义可以覆盖某些 值或根据需要添加其他值。使用父 Bean 和子 Bean 定义可以节省很多 打字。实际上,这是一种模板形式。
如果以编程方式使用接口,则子 Bean
定义由类表示。大多数用户不工作
和他们在这个层面上。相反,它们在类中以声明方式配置 Bean 定义
例如 .使用基于 XML 的配置时
元数据,您可以使用以下属性来指示子 Bean 定义,
将父 Bean 指定为此属性的值。以下示例演示如何
为此,请执行以下操作:ApplicationContext
ChildBeanDefinition
ClassPathXmlApplicationContext
parent
<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 类(如果没有) 指定,但也可以覆盖它。在后一种情况下,子 Bean 类必须 与父级兼容(即,它必须接受父级的属性值)。
子 Bean 定义继承作用域、构造函数参数值、属性值和
方法从父级重写,并带有添加新值的选项。任何作用域,初始化
指定的方法、销毁方法或出厂方法设置
覆盖相应的父设置。static
其余设置始终取自子定义:取决于, AutoWire 模式、依赖关系检查、单例和惰性初始化。
前面的示例使用
属性。如果父定义未指定类,则显式
根据需要标记父 Bean 定义,如以下示例所示
显示:abstract
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 无法单独实例化,因为它是不完整的,而且它是
也显式标记为 。当定义是 时,它是
只能用作纯模板 Bean 定义,用作 的父定义
子定义。尝试通过引用
将其作为另一个 Bean 的 ref 属性,或者使用
父 Bean 标识返回错误。同样,容器的内部方法会忽略定义为
抽象。abstract
abstract
abstract
getBean()
preInstantiateSingletons()
ApplicationContext 默认情况下,预实例化所有单例。因此,它是
重要(至少对于单例 Bean),如果您有一个(父)Bean 定义
您打算仅将其用作模板,并且此定义指定了一个类,则您
必须确保将 abstract 属性设置为 true,否则应用程序
上下文实际上会(尝试)预先实例化 bean。abstract |
1.8. 容器扩展点
通常,应用程序开发人员不需要对实现类进行子类化。相反,Spring
IoC 容器可以通过插入来扩展
特殊集成接口的实现。接下来的几节将介绍这些内容
集成接口。ApplicationContext
1.8.1. 使用BeanPostProcessor
该接口定义了可以实现的回调方法
提供你自己的(或覆盖容器的默认)实例化逻辑、依赖项
解析逻辑等。如果要在
Spring 容器完成实例化、配置和初始化 bean,您可以
插入一个或多个自定义实现。BeanPostProcessor
BeanPostProcessor
您可以配置多个实例,并且可以控制顺序
通过设置属性来运行这些实例。
仅当实现接口时,才能设置此属性。如果你自己编写,你应该考虑实现
界面也是如此。有关详细信息,请参阅 BeanPostProcessor
和
Ordered
接口的
javadoc。另请参阅注释
在程序化上
注册 BeanPostProcessor
实例。BeanPostProcessor
BeanPostProcessor
order
BeanPostProcessor
Ordered
BeanPostProcessor
Ordered
要更改实际的
Bean 定义(即定义 Bean 的蓝图),
您需要改用 ,如使用 |
该接口包括
正好是两个回调方法。当此类注册为后处理器时
容器,对于容器创建的每个 Bean 实例,
后处理器在容器之前从容器获取回调
初始化方法(例如或任何
declared 方法),并在任何 Bean 初始化回调之后调用。
后处理器可以对 Bean 实例执行任何操作,包括忽略
完全回调。Bean 后处理器通常检查回调接口,
或者它可以用代理包装 bean。一些 Spring AOP 基础结构类是
作为 Bean 后处理器实现,以提供代理包装逻辑。org.springframework.beans.factory.config.BeanPostProcessor
InitializingBean.afterPropertiesSet()
init
自动检测
实现接口的配置元数据。将这些 Bean 注册为后处理器,以便可以调用它们
后来,在豆子创建时。Bean 后处理器可以部署在容器的
与任何其他豆子一样。ApplicationContext
BeanPostProcessor
ApplicationContext
请注意,当使用 factory 方法声明
a
配置类,工厂方法的返回类型应该是实现
类本身或至少是接口,清楚地表明该 Bean 的后处理器性质。否则,在完全创建它之前,无法按类型自动检测它。
由于 a 需要尽早实例化才能应用于
初始化上下文中的其他
bean,这种早期类型检测至关重要。BeanPostProcessor
@Bean
org.springframework.beans.factory.config.BeanPostProcessor
ApplicationContext
BeanPostProcessor
以编程方式注册实例
虽然推荐的注册方法是通过自动检测(如前所述),但您可以注册它们
通过使用该方法以编程方式针对 A。当您需要评估条件逻辑时,这可能很有用
注册,甚至用于在层次结构中跨上下文复制 Bean 后处理器。
但请注意,以编程方式添加的实例不遵循
界面。在这里,注册顺序决定了顺序
的执行。另请注意,以编程方式注册的实例
总是在通过自动检测注册之前处理,无论任何
显式排序。BeanPostProcessor
BeanPostProcessor ApplicationContext ConfigurableBeanFactory addBeanPostProcessor BeanPostProcessor Ordered BeanPostProcessor
|
BeanPostProcessor 实例和 AOP
自动代理实现接口的类是特殊的,并经过处理
因容器而异。它们的所有实例和 Bean
直接引用在启动时实例化,作为特殊启动阶段的一部分
的 .接下来,注册所有实例
以分类的方式应用于容器中的所有其他豆子。因为AOP
自动代理是作为自身实现的,无论是实例还是它们直接引用的 bean,都不符合自动代理的条件,并且,
因此,不要将方面编织到其中。 对于任何此类
Bean,您都应该看到一条信息性日志消息:。 如果您使用
autowiring 或(可能会回退到自动接线)将 bean 连接到您的 bean 中,Spring
可能会访问意外的 bean
在搜索类型匹配的依赖项候选项时,因此,使它们
不符合自动代理或其他类型的 Bean 后处理条件。例如,如果你
有一个依赖项,其中字段或 setter 名称没有注释
直接对应于 Bean 的声明名称,不使用 name 属性,
Spring 访问其他 Bean
以按类型匹配它们。 |
以下示例演示如何编写、注册和使用实例
在 .BeanPostProcessor
ApplicationContext
示例:Hello World, -styleBeanPostProcessor
第一个示例说明了基本用法。该示例显示了一个自定义实现,该实现将每个
Bean 的方法调用为
它由容器创建,并将生成的字符串打印到系统控制台。BeanPostProcessor
toString()
下面的清单显示了自定义实现类定义:BeanPostProcessor
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
以下元素使用
: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>
请注意 just 是如何定义的。它没有
甚至有一个名字,而且,因为它是一个 bean,所以它可以像你一样被依赖注入
其他豆类。(前面的配置还定义了一个由 Groovy 支持的 Bean
脚本。Spring 动态语言支持在标题为动态语言支持的章节中有详细介绍。InstantiationTracingBeanPostProcessor
以下 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);
}
}
上述应用程序的输出如下所示:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
1.8.2. 使用BeanFactoryPostProcessor
我们查看的下一个扩展点是
.的语义
此接口类似于 的接口,但有一个主要
区别:对 Bean 配置元数据进行操作。
也就是说,Spring IoC 容器允许读取
配置元数据,并可能在容器实例化之前对其进行更改
除实例以外的任何 bean。org.springframework.beans.factory.config.BeanFactoryPostProcessor
BeanPostProcessor
BeanFactoryPostProcessor
BeanFactoryPostProcessor
BeanFactoryPostProcessor
您可以配置多个实例,并且可以控制
这些实例通过设置属性来运行。
但是,只有在实现接口时才能设置此属性。如果你自己写,你应该
也可以考虑实现接口。有关更多详细信息,请参阅 BeanFactoryPostProcessor
和 Ordered
接口的
javadoc。BeanFactoryPostProcessor
BeanFactoryPostProcessor
order
BeanFactoryPostProcessor
Ordered
BeanFactoryPostProcessor
Ordered
如果要更改实际的
Bean 实例(即创建的对象
),那么您需要改用 (前面在使用 此外,实例的作用域是按容器划分的。这只相关
如果使用容器层次结构。如果将 a 合一
容器,它仅适用于该容器中的 Bean 定义。Bean 定义
在一个容器中不会由另一个容器中的实例进行后处理
容器,即使两个容器都属于同一层次结构。 |
当 Bean 工厂后处理器在
中声明时,它会自动运行,以便将更改应用于
定义容器。Spring 包含许多预定义的 Bean 工厂
后处理器,例如 和 .您还可以使用自定义 — 例如,注册自定义属性编辑器。ApplicationContext
PropertyOverrideConfigurer
PropertySourcesPlaceholderConfigurer
BeanFactoryPostProcessor
自动检测部署到其中的任何 Bean
实现接口。它使用这些豆子作为豆子工厂
后处理器,在适当的时间。您可以将这些后处理器 Bean 部署为
你会任何其他豆子。ApplicationContext
BeanFactoryPostProcessor
与
s 一样,您通常不希望将 s 配置为延迟初始化。如果没有其他 Bean 引用 ,则该后处理器将根本不会被实例化。
因此,将忽略将其标记为延迟初始化,并且即使您在元素的声明中将属性设置为
,也会急切地实例化。BeanPostProcessor BeanFactoryPostProcessor Bean(Factory)PostProcessor Bean(Factory)PostProcessor default-lazy-init true <beans
/> |
示例:类名替换PropertySourcesPlaceholderConfigurer
您可以使用 将属性值外部化
使用标准 Java 格式从单独文件中的 Bean 定义。
这样做使部署应用程序的人员能够自定义特定于环境的人员
属性,例如数据库 URL 和密码,而没有复杂性或风险
修改容器的一个或多个主 XML
定义文件。PropertySourcesPlaceholderConfigurer
Properties
请考虑以下基于 XML
的配置元数据片段,其中定义了 with 占位符值:DataSource
<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>
该示例显示了从外部文件配置的属性。在运行时,
a 应用于替换某些
DataSource 的属性。要替换的值被指定为
form ,它遵循 Ant 和 log4j 以及 JSP EL 样式。Properties
PropertySourcesPlaceholderConfigurer
${property-name}
实际值来自标准 Java
格式的另一个文件:Properties
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
因此,字符串在运行时替换为值“sa”,并且
这同样适用于与属性文件中的键匹配的其他占位符值。
检查大多数属性中的占位符和
Bean
定义的属性。此外,您可以自定义占位符前缀和后缀。${jdbc.username}
PropertySourcesPlaceholderConfigurer
使用 Spring 2.5
中引入的命名空间,您可以配置属性占位符
具有专用的配置元素。您可以提供一个或多个位置作为
属性中的逗号分隔列表,如以下示例所示:context
location
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
不仅在指定的文件中查找属性。缺省情况下,如果在指定的属性文件中找不到属性,
它检查 Spring 属性和常规 Java
属性。PropertySourcesPlaceholderConfigurer
Properties
Environment
System
您可以使用
来替换类名,该类名
当您必须在运行时选择特定的实现类时,有时很有用。
以下示例演示如何执行此操作:
如果无法在运行时将类解析为有效类,则解析
Bean
在即将创建时失败,这是在非惰性初始化 Bean 的阶段。 |
示例:PropertyOverrideConfigurer
另一个 Bean 工厂后处理器类似于
,但与后者不同的是,原始定义
Bean 属性可以有默认值,也可以完全没有值。如果覆盖文件没有特定 Bean 属性的条目,那么缺省值
使用上下文定义。PropertyOverrideConfigurer
PropertySourcesPlaceholderConfigurer
Properties
请注意,Bean
定义不知道被覆盖,因此它不知道
从 XML 定义文件中可以立即看出覆盖配置器正在
使用。如果多个实例定义不同的
由于覆盖机制,同一 Bean
属性的值,最后一个属性优先。PropertyOverrideConfigurer
属性文件配置行采用以下格式:
beanName.property=value
下面的清单显示了该格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
此示例文件可以与容器定义一起使用,该容器定义包含名为
has 和 properties 的
bean。dataSource
driver
url
还支持复合属性名称,只要路径的每个组件
除了被覆盖的最终属性已经是非 null(大概是初始化的
由构造函数提供)。在以下示例中,bean 的 property 的 property
设置为标量值:sammy
bob
fred
tom
123
tom.fred.bob.sammy=123
指定的替代值始终是文本值。它们不会被翻译成 Bean 引用。当 XML Bean 中的原始值时,此约定也适用 definition 指定 Bean 引用。 |
使用 Spring 2.5
中引入的命名空间,可以配置
使用专用配置元素重写属性,如以下示例所示:context
<context:property-override location="classpath:override.properties"/>
1.8.3.
使用FactoryBean
您可以为以下对象实现接口
本身就是工厂。org.springframework.beans.factory.FactoryBean
该接口是可插入到 Spring IoC
容器的
实例化逻辑。如果您有复杂的初始化代码,则最好用
Java 与(可能)冗长的 XML 相反,您可以创建自己的 ,在该类中编写复杂的初始化,然后插入
自定义到容器中。FactoryBean
FactoryBean
FactoryBean
该接口提供三种方法:FactoryBean<T>
-
T getObject()
:返回此工厂创建的对象的实例。这 实例可以共享,具体取决于此工厂是否返回单例 或原型。 -
boolean isSingleton()
:如果返回单例或其他方式,则返回。此方法的默认实现返回 。true
FactoryBean
false
true
-
Class<?> getObjectType()
:返回方法返回的对象类型 或者如果事先不知道类型。getObject()
null
这个概念和界面在Spring中的许多地方都有使用
框架。Spring 附带了 50 多个接口实现
本身。FactoryBean
FactoryBean
当您需要向容器请求实际实例本身而不是
它产生的 bean,在 bean 前面加上 & 符号 () 时
调用 的方法。因此,对于给定的 ,调用容器将返回
的乘积,而调用则返回实例本身。FactoryBean
id
&
getBean()
ApplicationContext
FactoryBean
id
myBean
getBean("myBean")
FactoryBean
getBean("&myBean")
FactoryBean
1.9. 基于注解的容器配置
XML 设置的替代方法基于注释的配置,它依赖于
用于连接组件的字节码元数据,而不是尖括号声明。
开发人员不使用 XML 来描述 Bean 连接,而是移动配置
通过使用相关类、方法的注释或
字段声明。如示例中所述:AutowiredAnnotationBeanPostProcessor
,使用
a 与注释结合使用是扩展
Spring IoC 容器。例如,Spring 2.0 引入了强制执行的可能性
带有
@Required
注释的必需属性。春天
2.5 使得遵循相同的通用方法来驱动 Spring 的依赖性成为可能
注射。从本质上讲,注释提供了与
在
Autowiring Collaborators 中进行了描述,但具有更细粒度的控制和更广泛的控制
适用性。Spring 2.5 还添加了对 JSR-250 注解的支持,例如 和 .Spring 3.0 增加了对 JSR-330 的支持(依赖
Injection for Java) 注释包含在包中,例如 和 .有关这些注释的详细信息,请参阅相关部分。BeanPostProcessor
@Autowired
@PostConstruct
@PreDestroy
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
该注解适用于 bean 属性 setter
方法,如下所示
例:@Required
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
此注释指示受影响的 Bean
属性必须填充在
配置时间,通过 Bean 定义中的显式属性值或通过
自动接线。如果受影响的 Bean 属性尚未
填充。这允许急切和显式的失败,避免以后出现实例或类似情况。我们仍然建议您将断言放入
Bean 类本身(例如,进入 init 方法)。这样做会强制执行所需的
引用和值,即使您在容器外部使用类也是如此。NullPointerException
|
注解 和 是正式的
从 Spring Framework 5.1 开始不推荐使用,转而使用构造函数注入
必需的设置(或自定义实现或自定义方法以及 Bean 属性 setter 方法)。 |
1.9.2.
使用@Autowired
JSR 330
的注解可以代替 Spring 的注解
本节中包含的示例。有关详细信息,请参阅此处。 |
您可以将注释应用于构造函数,如以下示例所示:@Autowired
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从 Spring
Framework 4.3 开始,不再对此类构造函数进行注解
如果目标 Bean 一开始只定义一个构造函数,则这是必需的。但是,如果
有几个构造函数可用,并且至少没有主/默认构造函数
必须对其中一个构造函数进行注释,以便指示
容器使用哪一个。有关详细信息,请参阅有关构造函数解析的讨论。 |
您还可以将注解应用于传统的 setter 方法,
如以下示例所示:@Autowired
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.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;
}
// ...
}
您也可以应用于字段,甚至可以将其与构造函数混合使用,因为
以下示例显示:@Autowired
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
确保您的目标组件(例如,或
)
由用于 -annotated 的类型一致声明
注入点。否则,注入可能会因运行时出现“未找到类型匹配项”错误而失败。 对于通过类路径扫描找到的
XML 定义的 Bean 或组件类,容器
通常事先知道混凝土类型。但是,对于工厂方法,您需要
以确保声明的返回类型具有足够的表现力。对于组件
实现多个接口或用于可能由其引用的组件
实现类型,请考虑在工厂中声明最具体的返回类型
方法(至少与参考 Bean 的注射点所要求的具体程度一样)。 |
您还可以指示 Spring 提供
所有特定类型的 bean,方法是将注解添加到字段或方法中
需要该类型的数组,如以下示例所示:ApplicationContext
@Autowired
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
这同样适用于类型化集合,如以下示例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
目标 Bean
可以实现接口或使用
或标准注释(如果您希望数组或列表中的项目)
按特定顺序排序。否则,他们的订单将遵循注册
容器中相应目标 Bean
定义的顺序。 您可以在目标类级别和方法上声明注释,
可能用于单个 Bean 定义(如果有多个定义
使用相同的 Bean 类)。 价值观可能会影响注入点的优先级,
但请注意,它们不会影响单例启动顺序,这是一个
由依赖关系和声明确定的正交关注点。 请注意,标准批注在级别上不可用,因为它不能在方法上声明。它的语义可以建模
通过与每种类型的单个 Bean
组合的值。 |
即使是类型化实例也可以自动连接,只要预期的密钥类型为
。
映射值包含预期类型的所有 bean,键包含
相应的 Bean 名称,如以下示例所示:Map
String
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
缺省情况下,当给定的候选 Bean 没有匹配的候选 Bean 可用时,自动布线将失败 注射点。对于声明的数组、集合或映射,至少一个 匹配元素是预期的。
默认行为是将带批注的方法和字段视为指示必需
依赖。您可以更改此行为,如以下示例所示:
使框架能够跳过不令人满意的注入点,方法是将其标记为
非必需(即,通过将属性设置为):required
@Autowired
false
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果非必需方法的依赖项(或其 依赖项,如果有多个参数)不可用。非必填字段将 在这种情况下,根本不会填充,而是保留其默认值。
注入的构造函数和工厂方法参数是一种特例,因为由于
Spring 的构造函数,属性的含义略有不同
可能处理多个构造函数的解析算法。构造 函数
默认情况下,工厂方法参数实际上是必需的,但有一些特殊的参数
单构造函数方案中的规则,例如多元素注入点(数组、
集合、映射)解析为空实例,如果没有匹配的 Bean 可用。这
允许一种通用的实现模式,其中所有依赖项都可以在
唯一的多参数构造函数 — 例如,声明为单个公共构造函数
没有注释。required
@Autowired
@Autowired
任何给定
bean 类只有一个构造函数可以声明属性设置为 ,指示构造函数在用作
Spring 时自动连接
豆。因此,如果该属性保留为其默认值 ,
只能用 批注单个构造函数。如果多个构造函数
声明注解,它们都必须声明才能
被视为自动布线的候选者(类似于 XML 中)。
具有最多依赖项的构造函数,可通过匹配来满足
将选择 Spring 容器中的 bean。如果所有候选人都不能满意,
然后将使用主/默认构造函数(如果存在)。同样,如果一个类
声明多个构造函数,但没有一个构造函数被注释,然后用
将使用主/默认构造函数(如果存在)。如果一个类只声明一个
构造函数,它将始终被使用,即使没有注释。请注意,一个
带注释的构造函数不必是公共的。 建议使用 of
属性,而不是 setter 方法上已弃用的注释。将属性设置为指示
自动布线不需要该属性,如果
不能自动接线。另一方面,它更强大,因为它强制执行
属性,如果未定义任何值,
引发相应的异常。 |
或者,您可以表达特定依赖项的非必需性质
通过 Java 8 的 ,如以下示例所示:java.util.Optional
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从 Spring Framework 5.0
开始,您还可以使用注解(任何类型的注解
在任何软件包中 — 例如,来自 JSR-305)或仅利用
Kotlin 内置的 null-safety
支持:@Nullable
javax.annotation.Nullable
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
您还可以用于众所周知的可解析接口
依赖项:、、、、和 。这些接口及其扩展
接口,例如 或 是
自动解析,无需特殊设置。以下示例自动连线
一个对象:@Autowired
BeanFactory
ApplicationContext
Environment
ResourceLoader
ApplicationEventPublisher
MessageSource
ConfigurableApplicationContext
ResourcePatternResolver
ApplicationContext
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
、 、 和 注解由
Spring 实现处理。这意味着您无法应用这些批注
在您自己的类型或类型(如果有)中。
这些类型必须使用 XML 或 Spring 方法显式“连接”。 |
1.9.3.
使用@Primary
由于按类型自动布线可能会导致多个候选者,因此通常需要
更好地控制选择过程。实现此目的的一种方法是使用 Spring 的注解。 表示应该给出一个特定的 bean
当多个 Bean 是要自动连接到单值的候选者时的首选项
屬地。如果候选者中正好存在一个主 Bean,则它将成为
自动连线值。@Primary
@Primary
请考虑以下配置为
主要:firstMovieCatalog
MovieCatalog
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
使用上述配置,以下内容会自动连接到:MovieRecommender
firstMovieCatalog
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
相应的 Bean 定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 使用限定符微调基于注解的自动布线
@Primary
是按类型使用自动布线的有效方法,当一个实例
可以确定主要候选人。当您需要更好地控制选择过程时,
你可以使用 Spring 的注解。您可以关联限定符值
使用特定参数,缩小类型匹配集的范围,使特定的 Bean 是
为每个参数选择。在最简单的情况下,这可以是一个简单的描述性值,因为
如以下示例所示:@Qualifier
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
您还可以在单个构造函数参数上指定注释,或者
方法参数,如以下示例所示:@Qualifier
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 | 具有限定符值的 bean
与构造函数参数连接,该参数
具有相同的值。main |
2 | 具有限定符值的 bean
与构造函数参数连接,该参数
具有相同的值。action |
对于回退匹配,Bean
名称被视为缺省限定符值。因此,您
可以使用 of 而不是嵌套的限定符元素来定义 bean,前导
到相同的匹配结果。但是,尽管您可以使用此约定来引用
从名称上讲,特定的 bean 从根本上说是关于类型驱动的注入
可选语义限定符。这意味着限定符值,即使带有 bean 名称也是如此
回退,始终在类型匹配集中缩小语义。他们没有
在语义上表达了对唯一 bean 的引用。良好的限定符值是 or or ,表示特定组件的特征
独立于 bean ,在匿名 Bean 的情况下可以自动生成
定义,如上例中的定义。id
main
@Autowired
id
main
EMEA
persistent
id
如前所述,限定符也适用于类型化集合,例如,to
.在这种情况下,所有匹配的 bean,根据 声明的
限定符,作为集合注入。这意味着限定符不一定是
独特。相反,它们构成了过滤标准。例如,您可以定义
具有相同限定符值 “action” 的多个 bean,所有这些 bean 都是
注入到带有 注释的
.Set<MovieCatalog>
MovieCatalog
Set<MovieCatalog>
@Qualifier("action")
让限定符值在类型匹配中根据目标
Bean 名称进行选择
候选,不需要在注入点进行注释。
如果没有其他分辨率指示符(例如限定符或主要标记),
对于非唯一依赖关系情况,Spring 会匹配注入点名称
(即字段名称或参数名称),然后选择
同名候选人(如有)。 |
也就是说,如果您打算按名称来表达注释驱动的注入,请不要
主要使用 ,即使它能够按 bean 名称进行选择
类型匹配候选项。请改用 JSR-250 注解,即
语义定义,通过特定目标组件的唯一名称来标识其,并使用
声明的类型与匹配过程无关。 有相当
不同的语义:按类型选择候选 Bean 后,指定的限定符值仅在那些类型选择的候选项中考虑(例如,
将限定符与标有相同限定符标签的 Bean
进行匹配。@Autowired
@Resource
@Autowired
String
account
对于本身定义为集合的
Bean,或数组类型是一个很好的解决方案,它通过唯一名称来引用特定的集合或数组 bean。
也就是说,从 4.3 开始,您也可以通过 Spring 的类型匹配算法来匹配 collection、、 和数组类型,只要元素类型信息
保留在返回类型签名或集合继承层次结构中。
在这种情况下,可以使用限定符值在相同类型的集合中进行选择。
如上一段所述。Map
@Resource
Map
@Autowired
@Bean
从 4.3 开始,还考虑了注入的自引用(即引用
返回到当前注入的 bean)。请注意,自我注入是一种后备措施。
对其他组件的常规依赖始终具有优先权。从这个意义上说,自我
推荐人不参与常规候选人的选择,因此在
特别是从不主要。相反,它们最终总是以最低优先级结束。
在实践中,您应该仅将自我引用用作最后的手段(例如,对于
通过 Bean 的事务代理在同一实例上调用其他方法)。
在这种情况下,请考虑将受影响的方法分解到单独的委托 Bean 中。
或者,您可以使用 ,它可以获取返回当前 Bean 的代理
以其独特的名称。@Autowired
@Resource
尝试从同一配置类上的方法注入结果是
实际上也是一个自我引用的场景。要么延迟解析此类引用
在实际需要它的方法签名中(与自动连线字段相反)
或将受影响的方法声明为 ,
将它们与包含的配置类实例及其生命周期分离。
否则,此类 Bean 仅在回退阶段考虑,并具有匹配的 Bean
在被选为主要候选的其他配置类上(如果可用)。 |
@Autowired
适用于字段、构造函数和多参数方法,允许
在参数级别通过限定符注释缩小范围。相反,仅支持具有单个参数的字段和 Bean 属性 setter 方法。
因此,如果您的注射目标是
构造函数或多参数方法。@Resource
您可以创建自己的自定义限定符注释。为此,请定义一个注释和
在定义中提供注释,如以下示例所示:@Qualifier
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
然后,您可以为自动连线字段和参数提供自定义限定符,因为 以下示例显示:
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
定义的信息。您可以将标签添加为标签的子元素,然后指定 and 以匹配您的自定义限定符注释。该类型与
批注的完全限定类名。或者,为了方便起见,如果没有风险
存在冲突的名称,则可以使用短类名。以下示例
演示了这两种方法:<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>
在“类路径扫描”和“托管组件”中,您可以看到基于注释的替代方法 以 XML 格式提供限定符元数据。具体而言,请参阅为限定符元数据提供注释。
在某些情况下,使用不带值的注释可能就足够了。这可以是 当注释服务于更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 在没有 Internet 连接时可以搜索的目录。首先,定义 简单注释,如以下示例所示:
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class 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 | 此元素指定限定符。 |
您还可以定义接受命名属性的自定义限定符注释
添加到或代替 simple 属性。如果多个属性值是
然后在要自动连接的字段或参数上指定,Bean 定义必须匹配
将所有这些属性值视为 AutoWire 候选项。举个例子,
请考虑以下注释定义:value
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
在本例中是一个枚举,定义如下:Format
enum class Format {
VHS, DVD, BLURAY
}
要自动连接的字段使用自定义限定符进行注释,并包含值
对于这两个属性: 和 ,如以下示例所示:genre
format
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. 使用泛型作为自动接线限定符
除了注释之外,还可以使用 Java
泛型类型
作为资格的隐含形式。例如,假设您有以下内容
配置:@Qualifier
@Configuration
class MyConfiguration {
@Bean
fun stringStore() = StringStore()
@Bean
fun integerStore() = IntegerStore()
}
假设前面的 Bean 实现了一个泛型接口(即
和 ),则可以将该接口和泛型接口
用作限定符,如以下示例所示:Store<String>
Store<Integer>
@Autowire
Store
@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 lateinit var s: List<Store<Integer>>
1.9.6. 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一个允许您注册自己的自定义限定符
注解类型,即使它们没有用 Spring 的注解进行注解。
以下示例演示如何使用:BeanFactoryPostProcessor
@Qualifier
CustomAutowireConfigurer
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
通过以下方式确定自动连线候选项:AutowireCandidateResolver
-
每个 Bean 定义的值
autowire-candidate
-
元素上可用的任何模式
default-autowire-candidates
<beans/>
-
存在批注和任何已注册的自定义批注 用
@Qualifier
CustomAutowireConfigurer
当多个 Bean
符合自动连线候选条件时,“主”的确定为
如下所示:如果候选定义中只有一个 Bean 定义的属性设置为
,则选择该定义。primary
true
1.9.7.
注入@Resource
Spring 还支持使用 JSR-250
注解进行注入
() 在字段或 Bean 属性 setter 方法上。
这是 Java EE 中的常见模式:例如,在 JSF 管理的 Bean 和 JAX-WS 中
端点。Spring 也支持 Spring 管理的对象的这种模式。@Resource
javax.annotation.Resource
@Resource
采用
name 属性。默认情况下,Spring 将该值解释为
要注入的 Bean 名称。换句话说,它遵循名称语义,
如以下示例所示:
class SimpleMovieLister {
@Resource(name="myMovieFinder") (1)
private lateinit var movieFinder:MovieFinder
}
1 | 此行注入 .@Resource |
如果未显式指定名称,则默认名称派生自字段名称或
setter 方法。如果是字段,则采用字段名称。如果是 setter 方法,
它采用 Bean 属性名称。以下示例将具有 bean
named 注入到其 setter 方法中:movieFinder
class SimpleMovieLister {
@set:Resource
private lateinit var movieFinder: MovieFinder
}
随注释提供的名称被解析为
可识别的 Bean 名称。
如果显式配置 Spring 的 SimpleJndiBeanFactory ,则可以通过
JNDI 解析这些名称。但是,我们建议您依赖默认行为和
使用 Spring 的 JNDI
查找功能来保持间接级别。ApplicationContext CommonAnnotationBeanPostProcessor
|
在未指定明确名称的排他性使用情况下,以及类似
to ,查找主要类型匹配项,而不是特定的命名 Bean
并解析众所周知的可解析依赖项:、、、和接口。@Resource
@Autowired
@Resource
BeanFactory
ApplicationContext
ResourceLoader
ApplicationEventPublisher
MessageSource
因此,在以下示例中,该字段首先查找
Bean
命名为“customerPreferenceDao”,然后回退到该类型的主类型匹配:customerPreferenceDao
CustomerPreferenceDao
class MovieRecommender {
@Resource
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Resource
private lateinit var context: ApplicationContext (1)
// ...
}
1 | 该字段基于已知的可解析依赖项类型注入:。context ApplicationContext |
1.9.8.
使用@Value
@Value
通常用于注入外部化属性:
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig
以及以下文件:application.properties
catalog.name=MovieCatalog
在这种情况下,参数和字段将等于该值。catalog
MovieCatalog
Spring
提供了一个默认的宽松嵌入值解析器。它将尝试解决
属性值,如果无法解析,则为属性名称(例如)
将作为值注入。如果你想保持对不存在的严格控制
值,您应该声明一个 bean,如下所示
示例显示:${catalog.name}
PropertySourcesPlaceholderConfigurer
@Configuration
class AppConfig {
@Bean
fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
使用
JavaConfig 进行配置时,该方法必须是 .PropertySourcesPlaceholderConfigurer @Bean static
|
使用上述配置可确保在无法解析任何占位符时
Spring 初始化失败。也可以使用 、 等方法或自定义
占位符。${}
setPlaceholderPrefix
setPlaceholderSuffix
setValueSeparator
默认情况下,Spring
Boot 配置了一个 bean
将从 和
文件中获取属性。PropertySourcesPlaceholderConfigurer application.properties application.yml
|
Spring
提供的内置转换器支持允许自动处理简单的类型转换(例如,到 或 example)。多个逗号分隔的值可以是
自动转换为数组,无需额外努力。Integer
int
String
可以按如下方式提供默认值:
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
Spring 使用幕后处理
将值转换为目标类型的过程。如果你想
为您自己的自定义类型提供转换支持,您可以提供自己的 Bean 实例,如下例所示:BeanPostProcessor
ConversionService
String
@Value
ConversionService
@Configuration
class AppConfig {
@Bean
fun conversionService(): ConversionService {
return DefaultFormattingConversionService().apply {
addConverter(MyCustomConverter())
}
}
}
当包含 SpEL
表达式时,该值将是动态的
在运行时计算,如以下示例所示:@Value
@Component
class MovieRecommender(
@Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)
SpEL 还支持使用更复杂的数据结构:
@Component
class MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)
1.9.9. 使用 和@PostConstruct
@PreDestroy
不仅识别注释
还有 JSR-250 生命周期注解:和 .在 Spring 2.5 中引入,对这些
注解提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方法。前提是在 Spring 中注册,
在生命周期的同一时间点调用带有这些注释之一的方法
作为对应的 Spring 生命周期接口方法或显式声明的回调
方法。在以下示例中,缓存在初始化时预填充,并且
销毁后清除:CommonAnnotationBeanPostProcessor
@Resource
javax.annotation.PostConstruct
javax.annotation.PreDestroy
CommonAnnotationBeanPostProcessor
ApplicationContext
class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
fun clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制。
像 一样,和
注释类型是
从 JDK 6 到 8 的标准 Java 库。但是,整个包在 JDK 9 中与核心 Java 模块分离,并最终在
JDK 11.如果需要,需要通过 Maven 获取工件
现在是中心,只需像任何其他库一样添加到应用程序的类路径中。 |
1.10. 类路径扫描和托管组件
本章中的大多数示例都使用 XML
来指定生成
每个都在 Spring 容器中。上一节
(基于注解的容器配置)演示了如何提供大量配置
通过源级注释的元数据。然而,即使在这些例子中,“基础”
Bean 定义在 XML 文件中显式定义,而注释仅驱动
依赖项注入。本节介绍用于隐式检测
候选组件通过扫描类路径。候选组件是
与过滤条件匹配,并注册相应的 Bean 定义
容器。这样就无需使用 XML 来执行 Bean 注册。相反,你
可以使用注释(例如,)、AspectJ 类型表达式或您自己的表达式
自定义过滤条件,用于选择哪些类已注册 Bean 定义
容器。BeanDefinition
@Component
从 Spring 3.0
开始,Spring JavaConfig 项目提供的许多功能包括
核心 Spring 框架的一部分。这允许您使用 Java 来定义 bean,而不是
而不是使用传统的XML文件。请查看 、 、 和
注释,了解如何使用这些新功能的示例。 |
1.10.1. 和进一步的构造型注解@Component
注解是履行角色或
存储库的构造型(也称为数据访问对象或 DAO)。用途包括
此标记是异常的自动转换,如异常转换中所述。@Repository
Spring 提供了进一步的构造型注解:、
和 。 是任何 Spring 管理的组件的通用构造型。、 和 是 for 的专业化
更具体的用例(在持久性、服务和演示中)
层)。因此,您可以使用 来注释组件类,但是,通过使用 、 或相反,您的类更适合通过工具进行处理或关联
有方面。例如,这些构造型注释是
切入点。、 和 也可以
在 Spring Framework 的未来版本中携带其他语义。因此,如果你是
选择使用或用于服务层,是
显然是更好的选择。同样,如前所述,已经
支持作为持久性层中自动异常转换的标记。@Component
@Service
@Controller
@Component
@Repository
@Service
@Controller
@Component
@Component
@Repository
@Service
@Controller
@Repository
@Service
@Controller
@Component
@Service
@Service
@Repository
1.10.2. 使用元注解和组合注解
Spring 提供的许多注解都可以用作
自己的代码。元注释是可以应用于另一个注释的注释。
例如,前面提到的注解是用 进行元注解的,如以下示例所示:@Service
@Component
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
1 | 以与
相同的方式处理原因。@Component @Service @Component
|
您还可以组合元注释来创建“组合注释”。例如
Spring MVC 的注解由 和
组成。@RestController
@Controller
@ResponseBody
此外,组合注释可以选择从
元注释以允许自定义。这在以下情况下特别有用
只想公开元注解属性的子集。例如,Spring 的注解将作用域名称硬编码为,但仍允许
自定义
.下面的清单显示了注释的定义:@SessionScope
session
proxyMode
SessionScope
@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
class SessionScopedService {
// ...
}
您还可以覆盖
的值,如以下示例所示:proxyMode
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
有关详细信息,请参阅 Spring Annotation Programming Model wiki 页面。
1.10.3. 自动检测类和注册 Bean 定义
Spring 可以自动检测构造型类,并向
.例如,以下两个类
符合此类自动检测条件:BeanDefinition
ApplicationContext
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的
bean,您需要将
是两个类的公共父包。(或者,您可以指定
逗号、分号或空格分隔的列表,包括每个类的父包。@ComponentScan
@Configuration
basePackages
@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 任务的 files-only 开关。此外,类路径目录可能不是 基于某些环境中的安全策略公开,例如,独立应用 JDK 1.7.0_45 及更高版本(需要在清单中设置“Trusted-Library”,请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在 JDK 9
的模块路径 (Jigsaw) 上,Spring 的类路径扫描通常按预期工作。
但是,请确保在描述符中导出组件类。如果你希望 Spring 调用类的非公共成员,请使
确保它们已“打开”(即,它们在描述符中使用声明而不是声明)。 |
此外,当您使用
component-scan 元素。这意味着这两个组件是自动检测的,并且
连接在一起 — 所有这些都没有在 XML 中提供任何 Bean 配置元数据。AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
您可以通过包含属性来禁用
和 的注册
值为 。AutowiredAnnotationBeanPostProcessor CommonAnnotationBeanPostProcessor annotation-config false
|
1.10.4. 使用过滤器自定义扫描
默认情况下,用 、 、 、 和
注释的类或本身带有注释的自定义注释是
唯一检测到的候选组件。但是,您可以修改和扩展此行为
通过应用自定义过滤器。将它们添加为 或 的属性
注释(或 as 或元素的子元素
XML 配置)。每个筛选器元素都需要 和 属性。
下表介绍了筛选选项:@Component
@Repository
@Service
@Controller
@Configuration
@Component
includeFilters
excludeFilters
@ComponentScan
<context:include-filter
/>
<context:exclude-filter />
<context:component-scan>
type
expression
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
注释(默认) |
|
在目标组件的类型级别存在或元存在的批注。 |
可分配的 |
|
目标组件可分配到(扩展或实现)的类(或接口)。 |
方面j |
|
要由目标组件匹配的 AspectJ 类型表达式。 |
正则表达式 |
|
要与目标组件的类名匹配的正则表达式。 |
习惯 |
|
接口的自定义实现。 |
以下示例显示了忽略所有注释的配置
并改用“存根”存储库:@Repository
@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
class FactoryMethodComponent {
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
fun doWork() {
// Component method implementation omitted
}
}
前面的类是一个 Spring
组件,其方法中包含特定于应用程序的代码。但是,它也贡献了一个具有工厂的 Bean 定义
方法是指方法。注释标识
工厂方法和其他 Bean 定义属性,例如通过
注释。可以指定的其他方法级批注包括 、 和自定义限定符批注。doWork()
publicInstance()
@Bean
@Qualifier
@Scope
@Lazy
除了在元件初始化中的作用外,还可以将注释放在标有
或 的注入点上。在此背景下,
它会导致注入惰性解析代理。然而,这样的代理方法
是相当有限的。适用于复杂的懒惰交互,尤其是组合
对于可选依赖项,我们建议改用。@Lazy @Autowired @Inject ObjectProvider<MyTargetBean>
|
如前所述,支持自动连线字段和方法,并具有其他
支持方法的自动布线。以下示例演示如何执行此操作:@Bean
@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)
}
该示例将 method 参数自动连接到另一个名为
的 Bean 上的属性值。Spring 表达式语言元素
通过表示法定义属性的值。对于注释,表达式解析器被预先配置为在以下情况下查找 Bean 名称
解析表达式文本。String
country
age
privateInstance
#{
<expression> }
@Value
从 Spring Framework 4.3
开始,您还可以将 type (或其更具体的子类: ) 的工厂方法参数声明为
访问触发当前 Bean 创建的请求注入点。
请注意,这仅适用于 Bean 实例的实际创建,而不适用于
注入现有实例。因此,此功能最有意义
原型范围的 bean。对于其他作用域,工厂方法只能看到
触发在给定作用域中创建新 Bean 实例的注入点
(例如,触发创建惰性单例 Bean 的依赖项)。
在这种情况下,可以将提供的注入点元数据与语义一起使用。
以下示例演示如何使用:InjectionPoint
DependencyDescriptor
InjectionPoint
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
常规 Spring 组件中的方法的处理方式与其
Spring 类中的对应项。不同之处在于,类没有使用 CGLIB 进行增强以拦截方法和字段的调用。
CGLIB 代理是调用方法或方法中的字段的方法
在类中创建对协作对象的 Bean 元数据引用。
这些方法不是使用正常的 Java 语义调用的,而是通过
容器,以提供 Spring 通常的生命周期管理和代理
bean,即使通过对方法的编程调用来引用其他 bean。
相比之下,在普通类中调用方法或方法中的字段具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他
应用约束。@Bean
@Configuration
@Component
@Bean
@Configuration
@Bean
@Bean
@Component
您可以将方法声明为
,允许在不
将其包含的配置类创建为实例。这使得特别
在定义后处理器 bean(例如,type 或 )时有意义,因为此类 bean 在容器的早期初始化
生命周期,并应避免在此时触发配置的其他部分。 由于技术原因,对静态方法的调用永远不会被容器截获,即使在类中也不会被截获(如本节前面所述)
限制:CGLIB 子类只能覆盖非静态方法。因此,
对另一个方法的直接调用具有标准的 Java 语义,从而导致
在直接从工厂方法本身返回的独立实例中。 方法的 Java
语言可见性不会对
Spring 容器中生成的 bean 定义。您可以自由声明您的
工厂方法,如您认为适合非类和静态
方法无处不在。但是,类中的常规方法需要
可覆盖 — 也就是说,它们不得声明为 或 。
最后,单个类可以包含多个相同的方法
Bean,作为多种工厂方法的安排,根据可用
运行时的依赖项。这与选择“最贪婪”的算法相同
其他配置方案中的构造函数或工厂方法:带有
在构造时选取最大数量的可满足依赖项,
类似于容器在多个构造函数之间进行选择的方式。 |
1.10.6. 命名自动检测的组件
当组件在扫描过程中被自动检测时,其
Bean 名称为
由该扫描程序已知的策略生成。默认情况下,任何
包含名称的 Spring 构造型注释 (, , 和 ) 从而将该名称提供给
相应的 Bean 定义。BeanNameGenerator
@Component
@Repository
@Service
@Controller
value
如果此类注释不包含名称或任何其他检测到的组件
(例如由自定义过滤器发现的那些),默认的 Bean 名称生成器返回
未大写的非限定类名。例如,如果以下组件
检测到类,名称将是 和 :value
myMovieLister
movieFinderImpl
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果您不想依赖默认的 Bean 命名策略,则可以提供自定义
Bean 命名策略。首先,实现 BeanNameGenerator
接口,并确保包含默认的
no-arg 构造函数。然后,提供完整的
配置扫描程序时限定的类名,如以下示例注解
和 Bean 定义显示。
如果由于多个自动检测到的组件具有
相同的非限定类名(即名称相同但位于
不同的软件包),您可能需要配置一个默认为
生成的 Bean 名称的标准类名。从 Spring Framework 5.2.3
开始,位于包中的可用于此类目的。BeanNameGenerator FullyQualifiedAnnotationBeanNameGenerator org.springframework.context.annotation
|
@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
管理组件一样,默认和最常见的作用域
自动检测的组件是 。但是,有时您需要不同的范围
可以通过注解指定。您可以提供
作用域,如以下示例所示:singleton
@Scope
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 注解仅在具体
Bean 类上进行内省(对于 Annotated
components)或工厂方法(用于方法)。与 XML Bean 相比
定义,没有 bean 定义继承和继承的概念
类级别的层次结构与元数据无关。@Bean |
有关特定于 Web 的作用域的详细信息,例如
Spring 上下文中的“request”或“session”,
请参阅请求、会话、应用程序和 WebSocket 范围。与这些作用域的预构建注释一样,
您还可以使用 Spring 的元注解来编写自己的范围标注
方法:例如,自定义注解 meta-annotated with ,
可能还会声明自定义作用域代理模式。@Scope("prototype")
为范围解析提供自定义策略,而不是依赖于
基于注释的方法,您可以实现 ScopeMetadataResolver
接口。请务必包含默认的 no-arg 构造函数。然后,您可以提供
配置扫描程序时完全限定的类名,如以下示例所示
注解和 Bean 定义显示:
|
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
使用某些非单例作用域时,可能需要为
作用域对象。Scoped Beans as Dependencies 中描述了这种推理。
为此,component-scan 上提供了作用域代理属性
元素。三个可能的值为:、 和 。例如
以下配置将生成标准 JDK 动态代理:no
interfaces
targetClass
@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. 提供带有注解的限定符元数据
使用限定符微调基于注释的自动布线中讨论了注释。
该部分中的示例演示了注释和
自定义限定符注释,用于在解析 AutoWire 时提供细粒度控制
候选人。由于这些示例基于 XML Bean 定义,因此限定符
元数据是在 XML 中使用元素的 or 子元素在候选 Bean 定义上提供的。当依赖类路径扫描时
自动检测组件,可以提供类型级的限定符元数据
候选类的注释。以下三个示例演示了这一点
技术:@Qualifier
@Qualifier
qualifier
meta
bean
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
与大多数基于注释的替代方法一样,请记住,注释元数据是 绑定到类定义本身,而使用 XML 允许多个 Bean 的相同类型,以在其限定符元数据中提供变体,因为 元数据是按实例提供的,而不是按类提供的。 |
1.10.9. 生成候选组件的索引
虽然类路径扫描速度非常快,但可以提高启动性能 通过在编译时创建候选者的静态列表来生成大型应用程序。在这个 模式下,所有作为组件扫描目标的模块都必须使用此机制。
必须保留现有的指令
未更改以请求上下文扫描某些包中的候选项。当检测到这样的索引时,它会自动使用它而不是扫描
类路径。@ComponentScan <context:component-scan/> ApplicationContext
|
若要生成索引,请向包含 作为组件扫描指令目标的组件。以下示例显示 如何使用 Maven 执行此操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.27</version>
<optional>true</optional>
</dependency>
</dependencies>
对于 Gradle 4.5
及更早版本,应在配置中声明依赖项,如以下示例所示:compileOnly
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.3.27"
}
在 Gradle 4.6
及更高版本中,应在配置中声明依赖项,如以下示例所示:annotationProcessor
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.3.27"
}
工件生成一个文件,该文件
包含在 jar
文件中。spring-context-indexer
META-INF/spring.components
在
IDE 中使用此模式时,必须
注册为注释处理器,以确保索引在以下情况下是最新的
候选组件已更新。spring-context-indexer |
找到文件时,将自动启用索引
在类路径上。如果索引部分可用于某些库(或用例)
但无法为整个应用程序构建,您可以回退到常规类路径
通过将设置为 作为 JVM 系统属性或通过 SpringProperties 机制进行排列(好像根本没有索引)。META-INF/spring.components spring.index.ignore true
|
1.11. 使用 JSR 330 标准注解
从 Spring 3.0 开始,Spring 提供了对 JSR-330 标准注解的支持 (依赖注入)。这些注释的扫描方式与 Spring 相同 附注。要使用它们,您需要在类路径中包含相关的 jar。
如果使用
Maven,则工件在标准 Maven 中可用
存储库 ( https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。
您可以将以下依赖项添加到文件 pom.xml:
|
1.11.1. 带有 和
的依赖注入@Inject
@Named
代替
,您可以使用如下方式:@Autowired
@javax.inject.Inject
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
与 一样,您可以在字段级别、方法级别使用
和构造函数参数级别。此外,您可以将注入点声明为 ,允许按需访问较短范围的 bean 或延迟访问
其他 bean 通过调用。以下示例提供了
前面的示例:@Autowired
@Inject
Provider
Provider.get()
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
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) {
// ...
}
}
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
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
在不指定组件名称的情况下使用是很常见的。
可以以类似的方式使用,如以下示例所示:@Component
@Named
import javax.inject.Inject
import javax.inject.Named
@Named
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
使用 或 时,可以在
与使用 Spring 注解时完全相同的方式,如以下示例所示:@Named
@ManagedBean
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
与
相反,JSR-330 和 JSR-250 注解是不可组合的。你应该使用 Spring 的构造型模型来构建
自定义组件注释。@Component @Named @ManagedBean
|
1.11.3. JSR-330 标准注解的局限性
当你使用标准注解时,你应该知道一些重要的 功能不可用,如下表所示:
春天 | javax.inject.* | javax.inject 限制/注释 |
---|---|---|
@Autowired |
@Inject |
|
@Component |
@Named / @ManagedBean |
JSR-330 不提供可组合模型,只提供一种识别命名组件的方法。 |
@Scope(“单例”) |
@Singleton |
JSR-330
的默认作用域类似于 Spring 的 .但是,为了保留它
与 Spring 的一般默认值一致,在 Spring 中声明了一个 JSR-330 bean
默认情况下,容器是 。为了使用 以外的范围 ,
你应该使用 Spring 的注解。 还提供@Scope注释。
然而,这个仅用于创建你自己的注释。 |
@Qualifier |
@Qualifier / @Named |
|
@Value |
- |
无等效项 |
@Required |
- |
无等效项 |
@Lazy |
- |
无等效项 |
对象工厂 |
供应商 |
|
1.12. 基于 Java 的容器配置
本节介绍如何在 Java 代码中使用注解来配置 Spring 容器。它包括以下主题:
1.12.1. 基本概念: 和@Bean
@Configuration
Spring 新的 Java 配置支持中的核心工件是
-annotated 类和 -annotated 方法。@Configuration
@Bean
注释用于指示方法实例化、配置和
初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉的人
在 Spring 的 XML 配置中,注解的作用与
元素。您可以将 -annotated 方法与任何 Spring
一起使用。但是,它们最常与豆类一起使用。@Bean
<beans/>
@Bean
<bean/>
@Bean
@Component
@Configuration
用 注释类表示其主要用途是作为
Bean 定义的来源。此外,类允许 bean 间
依赖项是通过调用同一类中的其他方法来定义的。
最简单的类如下所示:@Configuration
@Configuration
@Bean
@Configuration
@Configuration
class AppConfig {
@Bean
fun myService(): MyServiceImpl {
return MyServiceImpl()
}
}
前面的类等效于以下 Spring
XML:AppConfig
<beans/>
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
以下各节将深入讨论 和 注释。
然而,首先,我们将介绍使用
基于 Java 的配置。@Bean
@Configuration
1.12.2. 使用AnnotationConfigApplicationContext
以下各节记录了 Spring 在
Spring 中引入的
3.0. 这种通用的实现不仅能够接受类作为输入,还能够接受普通类和类
使用 JSR-330 元数据进行注释。AnnotationConfigApplicationContext
ApplicationContext
@Configuration
@Component
当类作为输入提供时,类本身
注册为 Bean 定义和类中所有声明的方法
也被注册为 Bean 定义。@Configuration
@Configuration
@Bean
当提供 和 JSR-330 类时,它们被注册为
bean
定义,并假定 DI 元数据如 或
必要时在这些类中使用。@Component
@Autowired
@Inject
结构简单
就像在实例化 时将 Spring
XML 文件用作输入的方式大致相同,您可以在以下情况下使用类作为输入
实例化 .这允许完全
Spring 容器的无 XML
用法,如以下示例所示:ClassPathXmlApplicationContext
@Configuration
AnnotationConfigApplicationContext
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
如前所述,不仅限于工作
与类。可以提供任何或 JSR-330 注释类
作为构造函数的输入,如以下示例所示:AnnotationConfigApplicationContext
@Configuration
@Component
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()
}
前面的示例假设 , , 并使用
Spring
依赖注入注解,例如 .MyServiceImpl
Dependency1
Dependency2
@Autowired
使用以下方法以编程方式生成容器register(Class<?>…)
您可以使用 no-arg 构造函数实例化
然后使用该方法对其进行配置。这种方法特别有用
以编程方式构建 .以下
示例演示如何执行此操作:AnnotationConfigApplicationContext
register()
AnnotationConfigApplicationContext
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)
class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
有经验的
Spring 用户可能熟悉 XML 声明等效项
Spring 的命名空间,如以下示例所示:
|
在前面的示例中,扫描包以查找任何
-annotated 类,并将这些类注册为 Spring bean
容器中的定义。 公开该方法以允许相同的组件扫描功能,如
以下示例显示:com.acme
@Component
AnnotationConfigApplicationContext
scan(String…)
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean<MyService>()
}
请记住,类是用 进行元注释的,因此它们是组件扫描的候选者。在前面的示例中,
假设它是在包(或任何包)中声明的
下面),它在调用 时被拾取。上,它的所有方法都被处理并注册为容器内的 Bean
定义。@Configuration @Component AppConfig com.acme scan() refresh() @Bean
|
支持 Web 应用程序AnnotationConfigWebApplicationContext
的变体可用
跟。您可以在以下情况下使用此实现
配置 Spring servlet 侦听器、 Spring MVC 等。以下代码片段配置了一个典型的
Spring MVC Web 应用程序(注意 context-param 和
init-param):WebApplicationContext
AnnotationConfigApplicationContext
AnnotationConfigWebApplicationContext
ContextLoaderListener
DispatcherServlet
web.xml
contextClass
<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>
对于编程用例,a
可以用作
替代 。有关详细信息,请参阅 GenericWebApplicationContext
javadoc。GenericWebApplicationContext AnnotationConfigWebApplicationContext
|
1.12.3.
使用注解@Bean
@Bean
是方法级批注,是
XML 元素的直接模拟。
注释支持 提供的一些属性,例如:<bean/>
<bean/>
-
name
.
您可以在 -annotated 或
-annotated
类中使用注释。@Bean
@Configuration
@Component
声明 Bean
要声明
bean,您可以使用注释来注释方法。你用这个
在
指定为方法的返回值。缺省情况下,Bean 名称与
方法名称。下面的示例演示方法声明:@Bean
ApplicationContext
@Bean
@Configuration
class AppConfig {
@Bean
fun transferService() = TransferServiceImpl()
}
前面的配置完全等同于以下 Spring XML:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都使 中命名的
Bean 可用,绑定到 类型的对象实例,作为
以下文字图片显示: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
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
但是,这会将高级类型预测的可见性限制为指定的
接口类型 ()。然后,使用完整类型 ()
只有在实例化受影响的单例 Bean 后,容器才知道。
非惰性单例 Bean 根据其声明顺序进行实例化,
因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间
尝试按未声明的类型(例如 ,
只有在实例化 Bean 后才会解析)。TransferService
TransferServiceImpl
@Autowired
TransferServiceImpl
transferService
如果始终通过声明的服务接口引用类型,则返回类型可以安全地加入该设计决策。但是,对于组件
实现多个接口或用于可能由其引用的组件
实现类型,则声明最具体的返回类型会更安全
(至少与引用 Bean 的注入点所要求的一样具体)。@Bean |
Bean 依赖项
-annotated
方法可以具有任意数量的参数,这些参数描述
构建该 Bean 所需的依赖项。例如,如果我们需要一个 ,我们可以使用一个方法实现该依赖关系
参数,如以下示例所示:@Bean
TransferService
AccountRepository
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
解析机制与基于构造函数的依赖项几乎相同 注射。有关详细信息,请参阅相关部分。
接收生命周期回调
使用注解定义的任何类都支持常规生命周期回调
并且可以使用 JSR-250 中的 and 注解。请参阅 JSR-250 注解以获取更多信息
详。@Bean
@PostConstruct
@PreDestroy
完全支持常规的 Spring 生命周期回调,因为
井。如果 Bean 实现 , 或 ,
容器调用相应的方法。InitializingBean
DisposableBean
Lifecycle
还完全支持标准接口集(如
BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)。*Aware
注解支持指定任意初始化和销毁
回调方法,与Spring XML和属性非常相似
,如以下示例所示:@Bean
init-method
destroy-method
bean
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 会自动登记销毁回调。如果您有公共或方法,并且不希望在容器时调用它
关闭时,您可以添加到 Bean 定义中以禁用
默认模式。 默认情况下,您可能希望对使用
JNDI 获取的资源执行此操作,因为
生命周期在应用程序外部进行管理。特别是,确保始终这样做
,因为已知它在 Java EE
应用程序服务器上是有问题的。 以下示例显示了如何防止自动销毁回调:
此外,对于方法,您通常使用编程
JNDI 查找,或者通过
使用 Spring 或 helpers 或直接使用 JNDI,但不使用 variant(这将强制
将返回类型声明为类型而不是实际目标
类型,使其更难用于其他方法中的交叉引用调用
打算参考此处提供的资源)。 |
在上述示例中,在构造过程中直接调用该方法同样有效,如以下示例所示:BeanOne
init()
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
当你直接在 Java 中工作时,你可以用你的对象做任何你喜欢的事情,并做 并不总是需要依赖容器生命周期。 |
指定 Bean 范围
Spring 包含注解,以便您可以指定
Bean 的范围。@Scope
使用注释@Scope
您可以指定使用注释定义的
Bean 应具有
具体范围。您可以使用“Bean 作用域”部分中指定的任何标准作用域。@Bean
默认范围是
,但您可以使用注释覆盖它,
如以下示例所示:singleton
@Scope
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope
和scoped-proxy
Spring 提供了一种通过作用域代理处理作用域依赖关系的便捷方法。最简单的创建方式
使用 XML 配置时,这样的代理是元素。
在 Java 中使用注解配置 Bean 可提供等效的支持
替换为属性。默认值为 ,即
通常指示不应创建作用域代理,除非使用其他默认值
已在组件扫描指令级别进行配置。可以指定 或
。<aop:scoped-proxy/>
@Scope
proxyMode
ScopedProxyMode.DEFAULT
ScopedProxyMode.TARGET_CLASS
ScopedProxyMode.INTERFACES
ScopedProxyMode.NO
如果将作用域代理示例从
XML 参考文档(请参阅作用域代理)移植到我们使用 Java,
它类似于以下内容:@Bean
// 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
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean 混叠
正如命名 Bean
中所讨论的,有时需要给出单个 Bean
多个名称,也称为 Bean 别名。为此,批注的属性接受一个 String 数组。以下示例演示如何设置
Bean 的多个别名:name
@Bean
@Configuration
class AppConfig {
@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
fun dataSource(): DataSource {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时,提供 Bean 的更详细的文本描述会很有帮助。这可以 当出于监视目的公开 bean(可能通过 JMX)时,它特别有用。
若要向 添加说明,可以使用
@Description
注释,如以下示例所示:@Bean
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}
1.12.4. 使用注解@Configuration
@Configuration
是一个类级注释,指示对象是
Bean 定义。 类通过 -Annotated 声明 Bean
方法。对类方法的调用也可用于定义
Bean 间依赖关系。有关一般介绍,请参阅基本概念:@Bean
和@Configuration
。@Configuration
@Bean
@Bean
@Configuration
注入 Bean 间依赖关系
当 Bean 彼此依赖时,表达这种依赖关系就很简单了 让一个 Bean 方法调用另一个 Bean 方法,如以下示例所示:
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne(beanTwo())
@Bean
fun beanTwo() = BeanTwo()
}
在前面的示例中,接收对
through 构造函数的引用
注射。beanOne
beanTwo
这种声明
bean 间依赖关系的方法仅在以下情况下有效
在类中声明。不能声明 Bean 间依赖关系
通过使用普通类。@Bean @Configuration @Component
|
Lookup 方法注入
如前所述,查找方法注入是一种 您应该很少使用的高级功能。在以下情况下很有用 单例范围的 Bean 依赖于原型范围的 Bean。为此使用 Java 配置类型为实现此模式提供了一种自然的方法。这 以下示例演示如何使用查找方法注入:
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 配置,您可以创建
where 的子类
抽象方法被重写,以便它查找新的
(prototype) 命令对象。以下示例演示如何执行此操作:CommandManager
createCommand()
@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
@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()
被调用了一次,也被调用了一次。
由于此方法创建一个新实例并返回它,因此您将
通常期望有两个实例(每个服务一个)。那肯定是
有问题:在 Spring 中,实例化的 Bean 默认有一个作用域。这是
神奇之处:所有类在启动时都进行了子类化
跟。在子类中,子方法首先检查容器中是否有任何
缓存(作用域)bean,然后再调用父方法并创建新实例。clientService1()
clientService2()
ClientDaoImpl
singleton
@Configuration
CGLIB
根据 Bean 的范围,行为可能会有所不同。我们正在谈论 关于单身人士 这里. |
从 Spring
3.2 开始,不再需要将 CGLIB 添加到类路径中,因为 CGLIB
类已重新打包并直接包含在
在 spring-core JAR 中。 |
由于
CGLIB 在以下位置动态添加功能,因此存在一些限制
启动时间。具体而言,配置类不能是最终的。然而,作为
在 4.3
中,允许在配置类上使用任何构造函数,包括使用或单个非默认构造函数声明进行默认注入。 如果您希望避免任何
CGLIB 施加的限制,请考虑在非类上声明您的方法(例如,改为在普通类上)。
然后,方法之间的跨方法调用不会被截获,因此您有
完全依赖构造函数或方法级别的依赖注入。 |
1.12.5. 编写基于 Java 的配置
Spring 基于 Java 的配置功能允许您编写注释,从而减少 配置的复杂性。
使用注释@Import
就像在Spring
XML文件中使用该元素来帮助模块化一样
配置,注释允许从
另一个配置类,如以下示例所示:<import/>
@Import
@Bean
@Configuration
class ConfigA {
@Bean
fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
@Bean
fun b() = B()
}
现在,不需要同时指定两者和时间
实例化上下文,只需要显式提供,因为
以下示例显示:ConfigA.class
ConfigB.class
ConfigB
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
前面的示例有效,但过于简单。在大多数实际场景中,豆子有
跨配置类的相互依赖关系。使用 XML 时,这不是
问题,因为不涉及编译器,并且您可以在容器初始化期间声明并信任 Spring 来解决这个问题。
使用类时,Java 编译器对
配置模型,因为对其他 Bean 的引用必须是有效的 Java
语法。ref="someBean"
@Configuration
幸运的是,解决这个问题很简单。正如我们已经讨论过的,
一个方法可以具有任意数量的参数来描述 Bean
依赖。请考虑以下更真实的场景,其中包含多个类,每个类都依赖于其他类中声明的 bean:@Bean
@Configuration
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")
}
还有另一种方法可以达到相同的结果。请记住,类是
最终容器中只有另一个 bean:这意味着它们可以利用 和 injection 以及与任何其他 Bean
相同的其他功能。@Configuration
@Autowired
@Value
确保以这种方式注入的依赖项仅是最简单的类型。
类在上下文初始化期间很早就被处理,并强制依赖
以这种方式注入可能会导致意外的早期初始化。只要有可能,求助于
基于参数的注入,如前面的示例所示。 另外,要特别小心和定义
通过。这些通常应该被声明为方法,而不是触发
其包含配置类的实例化。否则,可能不会
处理配置类本身,因为可以将其创建为早于 |
以下示例显示了如何将一个 Bean 自动连接到另一个 Bean:
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")
}
类中的构造函数注入仅从 Spring 开始受支持
框架 4.3.另请注意,如果目标
Bean
只定义了一个构造函数。@Configuration @Autowired
|
在前面的方案中,使用效果很好,并提供所需的
模块化,但确定自动连线 Bean 定义的确切位置是
还是有些模棱两可。例如,作为开发人员,如何
您确切地知道 bean 是在哪里声明的吗?事实并非如此
在代码中显式,这可能很好。请记住,Spring
Tools for Eclipse 提供了以下工具:
可以呈现显示所有内容如何连接的图表,这可能就是您所需要的。也
Java IDE 可以轻松找到该类型的所有声明和用法
并快速显示返回该类型的方法的位置。@Autowired
ServiceConfig
@Autowired
AccountRepository
AccountRepository
@Bean
如果这种歧义是不可接受的,并且您希望直接导航
从 IDE 中的一个类到另一个类,请考虑自动连接
配置类本身。以下示例演示如何执行此操作:@Configuration
@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())
}
}
在前面的情形中,where
is defined 是完全显式的。
但是,现在与 .那就是
权衡。这种紧密耦合可以通过使用基于接口或
基于抽象类的类。请看以下示例:AccountRepository
ServiceConfig
RepositoryConfig
@Configuration
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")
}
现在相对于具体来说是松散耦合的,内置的IDE工具仍然有用:你可以很容易地
获取实现的类型层次结构。在这个
这样,导航类及其依赖关系就没有什么不同了
比导航基于接口的代码的通常过程。ServiceConfig
DefaultRepositoryConfig
RepositoryConfig
@Configuration
如果要影响某些 Bean 的启动创建顺序,请考虑
将其中一些声明为(用于在首次访问时创建,而不是在启动时创建)
或作为某些其他 bean(确保特定的其他 bean 是
在当前 Bean 之前创建,超出了后者的直接依赖关系所暗示的内容)。@Lazy @DependsOn
|
有条件地包含类或方法@Configuration
@Bean
有条件地启用或禁用完整类通常很有用
甚至是基于任意系统状态的单个方法。一个共同点
例如,使用注解仅在特定
配置文件已在 Spring 中启用(有关详细信息,请参见 Bean 定义配置文件)。@Configuration
@Bean
@Profile
Environment
注解实际上是通过使用更灵活的注解来实现的
称为@Conditional
。
注解指示应
在注册 A 之前咨询。@Profile
@Conditional
org.springframework.context.annotation.Condition
@Bean
该接口的实现提供了一个返回
或 的方法。例如,下面的清单显示了用于
:Condition
matches(…)
true
false
Condition
@Profile
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
}
有关更多详细信息,请参阅 javadoc @Conditional
。
结合 Java 和 XML 配置
Spring 的类支持并不是
100% 完全替代
用于 Spring XML。一些工具,如Spring XML命名空间,仍然是
配置容器。在XML方便或必要的情况下,您有一个
选择:例如,通过使用 以“以 XML 为中心”的方式实例化容器,或者通过使用 和 注释导入 XML 以“以
Java 为中心”的方式实例化容器
根据需要。@Configuration
ClassPathXmlApplicationContext
AnnotationConfigApplicationContext
@ImportResource
以 XML
为中心的类使用@Configuration
最好从 XML 引导
Spring 容器并以临时方式包含类。例如,在大型现有代码库中
使用 Spring XML 时,更容易在
根据需要,并将它们包含在现有的 XML 文件中。在本节的后面部分,我们将介绍
在这种“以 XML 为中心”的情况下使用类的选项。@Configuration
@Configuration
@Configuration
请记住,类最终是
容器。在本系列示例中,我们创建了一个名为 和
将其作为定义包含在内。由于已打开,因此容器会识别注释并正确处理其中声明的方法。@Configuration
@Configuration
AppConfig
system-test-config.xml
<bean/>
<context:annotation-config/>
@Configuration
@Bean
AppConfig
以下示例显示了 Java 中的普通配置类:
@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=
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
在 file
中,不声明元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他豆子
ever 引用它,并且不太可能按名称从容器中显式获取它。
同样,Bean 仅按类型自动连接,因此并不严格要求显式 Bean。system-test-config.xml AppConfig <bean/> id DataSource id
|
因为是用 、
-annotated 进行元注释的
类会自动成为组件扫描的候选项。使用与
在前面的示例中,我们可以重新定义以利用组件扫描。
请注意,在这种情况下,我们不需要显式声明 ,因为启用相同的
功能性。@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
在类是主要配置机制的应用程序中
容器,仍然可能需要至少使用一些 XML。在这些
方案中,只能根据需要使用和定义尽可能多的 XML。行为
因此,实现了“以 Java 为中心”的方法来配置容器,并将 XML 保持在
最低 限度。以下示例(包括一个配置类、一个 XML 文件
定义 bean、属性文件和类)显示了如何使用
实现使用 XML 的“以 Java 为中心”配置的注解
根据需要:@Configuration
@ImportResource
main
@ImportResource
@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=
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val transferService = ctx.getBean<TransferService>()
// ...
}
1.13. 环境抽象
Environment
界面
是集成在容器中的抽象,用于对两个键进行建模
应用程序环境的各个方面:配置文件和属性。
概要文件是要注册到
仅当给定配置文件处于活动状态时,容器。可以将 Bean 分配给配置文件
无论是在 XML 中定义还是使用注释定义。对象的作用
与配置文件的关系在于确定哪些配置文件(如果有)当前处于活动状态,
以及默认情况下哪些配置文件(如果有)应处于活动状态。Environment
属性在几乎所有应用中都起着重要作用,可能源于
各种来源:属性文件、JVM 系统属性、系统环境
变量、JNDI、servlet 上下文参数、临时对象、对象等
上。与属性相关的对象的作用是提供
用户具有方便的服务界面,用于配置属性源和解析
属性。Properties
Map
Environment
1.13.1. Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,该机制允许 在不同环境中注册不同的 bean。“环境”这个词, 对不同的用户可能意味着不同的事情,此功能可以帮助解决许多问题 用例,包括:
-
在开发中处理内存中数据源与查找内存中数据源 在 QA 或生产环境中来自 JNDI 的数据源。
-
仅当将应用程序部署到 性能环境。
-
为客户 A 与客户注册 Bean 的定制实现 B 部署。
考虑实际应用中的第一个用例,该用例需要
.在测试环境中,配置可能如下所示:DataSource
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
现在考虑如何将此应用程序部署到
QA 或生产环境中
环境,假设应用程序的数据源已注册
替换为生产应用程序服务器的 JNDI 目录。我们的豆子
现在如以下列表所示:dataSource
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
问题在于如何在使用
当前环境。随着时间的流逝,Spring 用户已经设计了许多方法来
完成此操作,通常依赖于系统环境变量的组合
以及包含可解析的令牌的 XML 语句
根据环境的值添加到正确的配置文件路径
变量。Bean 定义配置文件是一个核心容器功能,它提供了
这个问题的解决方案。<import/>
${placeholder}
如果我们概括前面特定于环境的 Bean 示例中所示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中则不然。你可以说你想注册一个 情况 A 中 Bean 定义的某些配置文件和 情况B.我们首先更新配置以反映这一需求。
用@Profile
@Profile
注释允许您指示组件符合注册条件
当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们
可以按如下方式重写配置:dataSource
@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")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
如前所述,对于方法,通常选择使用编程方式
JNDI 查找,通过使用 Spring 的 / helpers 或
前面显示了直接的 JNDI
用法,但未显示变体,这将强制您将返回类型声明为类型。@Bean JndiTemplate JndiLocatorDelegate InitialContext JndiObjectFactoryBean FactoryBean
|
配置文件字符串可以包含简单的配置文件名称(例如,)
或
配置文件表达式。配置文件表达式允许更复杂的配置文件逻辑
表示(例如,)。以下运算符在
配置文件表达式:production
production & us-east
-
!
:配置文件的逻辑“非” -
&
:配置文件的逻辑“和” -
|
:配置文件的逻辑“或”
如果不使用括号,则不能混合使用
and 运算符。例如,不是有效的表达式。它必须表示为
。& | production & us-east |
eu-central production & (us-east | eu-central) |
您可以用作元注释
创建自定义组合注释。以下示例定义了一个自定义注释,您可以将其用作
:@Profile
@Production
@Profile("production")
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果一个类标有 ,则将绕过与该类关联的所有方法和注释,除非一个或多个
指定的配置文件处于活动状态。如果标记了 或 类
with ,除非
配置文件“P1”或“P2”已激活。如果给定配置文件以
NOT 运算符 (),仅当配置文件未注册时,才会注册带注释的元素
积极。例如,给定 ,如果配置文件
“p1”处于活动状态,或者配置文件“p2”未处于活动状态。@Configuration @Profile @Bean @Import @Component @Configuration @Profile({"p1",
"p2"}) ! @Profile({"p1", "!p2"}) |
@Profile
也可以在方法级别声明为仅包含一个特定的
Bean
的配置类(例如,对于特定 Bean 的替代变体),作为
以下示例显示:
@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
|
使用 on
方法时,可能会适用一种特殊情况:在
具有相同 Java 方法名称的重载方法(类似于构造函数
重载),需要在所有
重载方法。如果条件不一致,则只有
重载方法中的第一个声明很重要。因此,可以
不用于选择具有特定参数签名的重载方法
另一个。同一 Bean 的所有工厂方法之间的解析遵循 Spring 的
创建时的构造函数解析算法。 如果要定义具有不同配置文件条件的替代
Bean,
使用不同的 Java 方法名称,这些名称通过使用名称指向相同的 Bean 名称
属性,如前面的示例所示。如果参数签名全部
相同(例如,所有变体都有 no-arg 工厂方法),这是唯一的
首先在有效的 Java 类中表示这种安排的方法
(因为只能有一个特定名称和参数签名的方法)。 |
XML Bean 定义配置文件
XML 对应项是元素的属性。前面的示例
可以在两个 XML
文件中重写配置,如下所示:profile
<beans>
<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>
已被限制为仅允许以下元素:
文件中的最后几个。这应该有助于在不产生
XML 文件中的混乱。spring-bean.xsd
XML
对应项不支持前面所述的配置文件表达式。有可能,
但是,要使用运算符否定配置文件。也可以应用逻辑
“and”,如以下示例所示:
在前面的示例中,如果
和 配置文件都处于活动状态,则会公开
bean。 |
激活配置文件
现在我们已经更新了配置,我们仍然需要指示
Spring 哪个
配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到
一个扔了,因为容器找不到
春豆名叫.NoSuchBeanDefinitionException
dataSource
激活配置文件可以通过多种方式完成,但最直接的方式是执行
它以编程方式针对 API,该 API 可通过
.以下示例演示如何执行此操作:Environment
ApplicationContext
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
此外,您还可以通过属性以声明方式激活配置文件,该属性可以通过系统环境指定
变量、JVM 系统属性、servlet 上下文参数,甚至作为
JNDI 中的条目(请参阅 PropertySource
抽象)。在集成测试中,活动
可以使用模块中的注释来声明配置文件(请参阅使用环境配置文件进行上下文配置)。spring.profiles.active
web.xml
@ActiveProfiles
spring-test
请注意,配置文件不是一个“非此即彼”的命题。您可以激活多个
一次配置文件。以编程方式,可以向接受 varargs 的方法提供多个配置文件名称。以下示例
激活多个配置文件:setActiveProfiles()
String…
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
以声明方式,可以接受以逗号分隔的配置文件名称列表,
如以下示例所示:spring.profiles.active
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件表示默认启用的配置文件。考虑 以下示例:
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
如果没有配置文件处于活动状态,则创建配置文件。你可以看到这个
作为为一个或多个 Bean 提供缺省定义的一种方式。如果有的话
配置文件已启用,则默认配置文件不适用。dataSource
您可以使用
或者,以声明方式,通过使用属性。setDefaultProfiles()
Environment
spring.profiles.default
1.13.2. 抽象PropertySource
Spring 的抽象提供了基于可配置的搜索操作
属性源的层次结构。请考虑以下列表:Environment
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
在前面的代码片段中,我们看到了一种高级方法,用于询问
Spring 属性是否是
为当前环境定义。为了回答这个问题,对象执行
对一组 PropertySource
对象的搜索。A 是对任何键值对源的简单抽象,并且
Spring 的 StandardEnvironment
配置了两个 PropertySource 对象,一个表示
JVM 系统属性集
() 和一个表示系统环境变量集
().my-property
Environment
PropertySource
System.getProperties()
System.getenv()
这些默认属性源用于
,用于独立
应用。StandardServletEnvironment
填充了其他默认属性源,包括 servlet config、servlet
context 参数和 JndiPropertySource (如果
JNDI 可用)。StandardEnvironment |
具体来说,当您使用
时,如果系统属性或环境变量存在于
运行。StandardEnvironment
env.containsProperty("my-property")
my-property
my-property
执行的搜索是分层的。默认情况下,系统属性优先于
环境变量。因此,如果该属性恰好在两个地方都设置了
调用 ,系统属性值“wins”并返回。
请注意,属性值不会合并
而是完全被前面的条目覆盖。 对于一个普通的
,完整的层次结构如下,其中
顶部的最高优先级条目:
|
最重要的是,整个机制是可配置的。也许您有自定义来源
要集成到此搜索中的属性。为此,请实现
并实例化您自己的 并将其添加到 for 的集合中
当前。以下示例演示如何执行此操作:PropertySource
PropertySources
Environment
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
在上面的代码中,已在
搜索。如果它包含属性,则检测并返回该属性,以支持
任何其他 .MutablePropertySources
API 公开了许多方法,这些方法允许对
属性源。MyPropertySource
my-property
my-property
PropertySource
1.13.3. 使用@PropertySource
@PropertySource
注解提供了一种方便的声明性机制,用于向 Spring 的 .PropertySource
Environment
给定一个包含键值对的文件,
以下类以这样的方式使用
调用返回:app.properties
testbean.name=myTestBean
@Configuration
@PropertySource
testBean.getName()
myTestBean
@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")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
假设它已存在于其中一个属性源中
已注册(例如,系统属性或环境变量),占位符为
解析为相应的值。如果不是,则使用
作为默认值。如果未指定默认值且无法解析属性,则抛出 。my.placeholder
default/path
IllegalArgumentException
根据
Java 8 约定,注解是可重复的。
但是,所有此类注释都需要同时声明
级别,要么直接在配置类上,要么作为
相同的自定义注释。混合直接注释和元注释不是
推荐,因为直接注释有效地覆盖了元注释。@PropertySource @PropertySource
|
1.13.4. 语句中的占位符解析
从历史上看,元素中占位符的值只能针对
JVM 系统属性或环境变量。现在情况已不再如此。因为
抽象集成在整个容器中,很容易
通过它路由占位符的解析。这意味着您可以配置
以您喜欢的任何方式解决问题。您可以更改搜索的优先级
系统属性和环境变量,或完全删除它们。您还可以添加您的
自有财产来源(视情况而定)。Environment
具体而言,无论在何处定义属性,以下语句都有效,只要它在
:customer
Environment
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14. 注册一个LoadTimeWeaver
Spring 使用 Spring 来动态转换类的本来面目
加载到 Java 虚拟机 (JVM) 中。LoadTimeWeaver
若要启用加载时编织,可以将
添加到其中一个类中,如以下示例所示:@EnableLoadTimeWeaving
@Configuration
@Configuration
@EnableLoadTimeWeaving
class AppConfig
或者,对于 XML
配置,可以使用以下元素:context:load-time-weaver
<beans>
<context:load-time-weaver/>
</beans>
一旦为 ,其中的任何 bean 都可以实现
,从而接收对 load-time 的引用
Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能
对于 JPA 类转换是必需的。
有关详细信息,请参阅 LocalContainerEntityManagerFactoryBean
javadoc。有关 AspectJ 加载时编织的更多信息,请参阅在 Spring Framework 中使用 AspectJ 进行加载时编织。ApplicationContext
ApplicationContext
LoadTimeWeaverAware
1.15. 附加功能ApplicationContext
如本章引言中所讨论的,该软件包提供了管理和操作 Bean
的基本功能,包括
程序化方式。该包添加了 ApplicationContext
接口,该接口扩展了接口,此外还扩展了其他
在更多应用程序中提供附加功能的接口
面向框架的风格。许多人使用在一个完全
声明式时尚,甚至不是以编程方式创建它,而是依赖于
支持类,例如在 Java EE Web 应用程序的正常启动过程中自动实例化 。org.springframework.beans.factory
org.springframework.context
BeanFactory
ApplicationContext
ContextLoader
ApplicationContext
为了以更面向框架的风格增强功能,上下文
软件包还提供以下功能:BeanFactory
-
通过界面访问 i18n 样式的消息。
MessageSource
-
通过界面访问资源,例如 URL 和文件。
ResourceLoader
-
事件发布,即对实现接口的 bean, 通过使用界面。
ApplicationListener
ApplicationEventPublisher
-
加载多个(分层)上下文,让每个上下文都专注于一个 特定层,例如应用程序的 Web 层,通过接口。
HierarchicalBeanFactory
1.15.1. 国际化使用MessageSource
该接口扩展了一个名为 and
的接口,
因此,提供国际化 (“i18n”) 功能。Spring 还提供了接口,可以分层解析消息。
这些接口共同构成了 Spring 效果消息的基础
分辨率。在这些接口上定义的方法包括:ApplicationContext
MessageSource
HierarchicalMessageSource
-
String getMessage(String code, Object[] args, String default, Locale loc)
:基本 用于从 中检索消息的方法。未找到消息时 对于指定的区域设置,使用默认消息。传入的任何参数都将成为 替换值,使用标准提供的功能 图书馆。MessageSource
MessageFormat
-
String getMessage(String code, Object[] args, Locale loc)
:基本相同 前面的方法,但有一个区别:不能指定默认消息。如果 找不到消息,抛出 A。NoSuchMessageException
-
String getMessage(MessageSourceResolvable resolvable, Locale locale)
: 所有属性 在上述方法中使用的方法也包装在一个名为 的类中,您可以将其与此方法一起使用。MessageSourceResolvable
加载 an 时,它会自动搜索上下文中定义的
bean。Bean 必须具有名称 。如果这样的豆子
,则对上述方法的所有调用都将委托给消息源。如果没有
找到消息源,尝试查找包含
同名的 bean。如果是这样,它将使用该 Bean 作为 .如果 找不到任何消息源,则实例化一个空消息,以便能够接受对
上面定义的方法。ApplicationContext
MessageSource
messageSource
ApplicationContext
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>
该示例假定您有三个名为
的资源包,并在类路径中定义。任何解析消息的请求都是
以通过对象解析消息的 JDK 标准方式进行处理。对于
出于该示例的目的,假定上述两个资源包文件的内容
具体如下:format
exceptions
windows
ResourceBundle
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
下一个示例显示了运行该功能的程序。
请记住,所有实现也是实现,因此可以强制转换为接口。MessageSource
ApplicationContext
MessageSource
MessageSource
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
println(message)
}
上述程序的结果输出如下:
Alligators rock!
总而言之,在一个名为
的文件中定义,该文件
存在于类路径的根目录中。Bean 定义是指
通过其属性的资源包数。这三个文件
在列表中传递给属性的属性作为文件存在于
classpath 和分别称为 、 和 。MessageSource
beans.xml
messageSource
basenames
basenames
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>
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的各种实现遵循与标准JDK相同的语言环境解析和回退规则。简而言之,并继续定义示例
以前,如果要针对 British () 区域设置解析消息,则
将分别创建名为 、 和
的文件。MessageSource
ResourceBundle
messageSource
en-GB
format_en_GB.properties
exceptions_en_GB.properties
windows_en_GB.properties
通常,区域设置解析由 应用。在以下示例中,(英国)消息所针对的区域设置 solved是手动指定的:
# in exceptions_en_GB.properties argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
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.
还可以使用该接口获取对已定义的任何内容的引用。在实现接口的
中定义的任何 Bean 都会注入
应用程序上下文是创建和配置 Bean 的时间。MessageSourceAware
MessageSource
ApplicationContext
MessageSourceAware
MessageSource
因为
Spring 是基于 Java 的,所以它不会合并
具有相同基本名称的捆绑包,但将仅使用找到的第一个捆绑包。
具有相同基本名称的后续消息包将被忽略。MessageSource ResourceBundle
|
作为
的替代方案,Spring 提供了一个类。此变体支持相同的捆绑包
文件格式,但比基于 JDK 的标准实现更灵活。特别是,它允许阅读
来自任何 Spring 资源位置的文件(不仅来自类路径)并支持热
重新加载捆绑包属性文件(同时在两者之间有效地缓存它们)。
有关详细信息,请参阅 ReloadableResourceBundleMessageSource
javadoc。ResourceBundleMessageSource ReloadableResourceBundleMessageSource ResourceBundleMessageSource
|
1.15.2. 标准和自定义事件
中的事件处理是通过类和接口提供的。如果将实现该接口的
Bean 部署到上下文中,则每次将 发布到 时,都会通知该 Bean。
从本质上讲,这是标准的 Observer 设计模式。ApplicationContext
ApplicationEvent
ApplicationListener
ApplicationListener
ApplicationEvent
ApplicationContext
从
Spring 4.2 开始,活动基础设施得到了显着改进,并提供了
基于注释的模型以及
能够发布任何任意事件(即,不一定
扩展自 )。当这样的对象被发布时,我们将其包装在一个
活动。ApplicationEvent |
下表描述了 Spring 提供的标准事件:
事件 | 解释 |
---|---|
|
在初始化或刷新时发布(例如,由
使用接口上的方法)。
在这里,“初始化”意味着加载所有 bean,检测后处理器 beans
并激活,单例是预先实例化的,对象是
准备使用。只要上下文尚未关闭,就可以触发刷新
多次,前提是被选中的人实际上支持这样的
“热”刷新。例如,支持热刷新,但不支持。 |
|
使用接口上的方法启动时发布。在这里,“started”意味着所有
bean 都接收到一个明确的开始信号。通常,此信号用于重新启动 Bean
在显式停止之后,但它也可用于启动尚未停止的组件
配置为自动启动(例如,尚未启动的组件
初始化)。 |
|
在使用接口上的方法停止时发布。在这里,“stopped”表示所有
bean 都接收到一个明确的停止信号。停止的上下文可以通过调用重新启动。 |
|
在使用
方法关闭 时发布
或通过 JVM 关闭钩子。这里
“closed”表示所有单例 Bean 都将被销毁。关闭上下文后,
它已达到其生命周期的终点,无法刷新或重新启动。 |
|
一个特定于
Web 的事件,告知所有 Bean 已为 HTTP 请求提供服务。这
事件在请求完成后发布。此活动仅适用于
使用 Spring 的 . |
|
它的子类添加特定于
Servlet 的上下文信息。 |
您还可以创建和发布自己的自定义事件。以下示例显示了一个
扩展 Spring 基类的简单类:ApplicationEvent
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
要发布自定义项,请在
.通常,这是通过创建一个实现的类并将其注册为 Spring Bean 来完成的。以下
示例显示了这样的类:ApplicationEvent
publishEvent()
ApplicationEventPublisher
ApplicationEventPublisherAware
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 容器会检测到实现并自动调用
。实际上,传入的参数是 Spring
容器本身。您正在通过其界面与应用程序上下文进行交互。EmailService
ApplicationEventPublisherAware
setApplicationEventPublisher()
ApplicationEventPublisher
要接收自定义,您可以创建一个实现并将其注册为
Spring bean 的类。以下示例
显示这样的类:ApplicationEvent
ApplicationListener
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {
lateinit var notificationAddress: String
override fun onApplicationEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
请注意,它通常使用 your
的类型进行参数化
自定义事件(在前面的示例中)。这意味着该方法可以保持类型安全,避免任何向下转换的需要。
您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,事件
侦听器同步接收事件。这意味着该方法
阻止,直到所有侦听器都完成对事件的处理。这样做的一个优点
同步和单线程方法是,当侦听器接收到事件时,它
如果事务上下文是
可用。如果需要其他事件发布策略,请参阅 javadoc
用于 Spring 的 ApplicationEventMulticaster
接口
以及用于配置选项的 SimpleApplicationEventMulticaster
实现。ApplicationListener
BlockedListEvent
onApplicationEvent()
publishEvent()
以下示例显示了用于注册和配置每个 以上课程:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
把它们放在一起,当 bean 的方法
调用时,如果有任何应阻止的电子邮件,则会发布类型的自定义事件。Bean 被注册为 并接收 ,此时它可以
通知有关各方。sendEmail()
emailService
BlockedListEvent
blockedListNotifier
ApplicationListener
BlockedListEvent
Spring 的事件机制旨在实现 Spring Bean 之间的简单通信 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring Integration 项目提供 全面支持构建轻量级、面向模式、事件驱动的 建立在众所周知的 Spring 编程模型之上的架构。 |
基于注释的事件侦听器
您可以使用注释在受管 Bean
的任何方法上注册事件侦听器。可以按如下方式重写:@EventListener
BlockedListNotifier
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明它侦听的事件类型, 但是,这一次,它使用灵活的名称,并且没有实现特定的侦听器接口。 事件类型也可以通过泛型缩小范围,只要是实际事件类型即可 解析泛型参数的实现层次结构。
如果你的方法应该侦听多个事件,或者你想用没有 参数,事件类型也可以在注解本身上指定。这 以下示例演示如何执行此操作:
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
还可以使用属性添加其他运行时筛选
定义
SpEL
表达式的注释,该表达式应匹配
实际调用特定事件的方法。condition
以下示例显示了如何重写我们的通知程序,使其仅在事件的属性等于
:content
my-event
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
每个表达式都根据专用上下文进行计算。下表列出了
提供给上下文的项,以便您可以使用它们进行条件事件处理:SpEL
名字 | 位置 | 描述 | 例 |
---|---|---|---|
事件 |
根对象 |
实际的 . |
|
Arguments 数组 |
根对象 |
用于调用该方法的参数(作为对象数组)。 |
|
参数名称 |
评估上下文 |
任何方法参数的名称。如果由于某种原因,名称不可用
(例如,因为编译后的字节码中没有调试信息),单个
参数也可使用语法 where 代表
参数索引(从 0 开始)。 |
|
请注意,这使您可以访问基础事件,即使您的方法也是如此
签名实际上是指已发布的任意对象。#root.event
如果需要发布一个事件作为处理另一个事件的结果,则可以将 方法签名,以返回应发布的事件,如以下示例所示:
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。 |
该方法会为它处理的每个方法发布一个新的。如果需要发布多个事件,可以返回
或事件数组。handleBlockedListEvent()
ListUpdateEvent
BlockedListEvent
Collection
异步侦听器
如果希望特定侦听器异步处理事件,则可以重用常规@Async
支持。
以下示例演示如何执行此操作:
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时,请注意以下限制:
-
如果异步事件侦听器抛出 ,它不会传播到 访客。有关详细信息,请参阅
AsyncUncaughtExceptionHandler
。Exception
-
异步事件侦听器方法无法通过返回 价值。如果需要发布另一个事件作为处理的结果,请注入
ApplicationEventPublisher
以手动发布事件。
对侦听器进行排序
如果需要先调用一个侦听器,然后再调用另一个侦听器,则可以将注释添加到方法声明中,如以下示例所示:@Order
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
通用事件
您还可以使用泛型来进一步定义事件的结构。请考虑使用
where 是创建的实际实体的类型。例如,您
可以创建以下侦听器定义,仅接收:EntityCreatedEvent<T>
T
EntityCreatedEvent
Person
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
// ...
}
由于类型擦除,仅当触发的事件解析泛型时,这才有效
事件侦听器过滤的参数(即,类似 )。class PersonCreatedEvent extends
EntityCreatedEvent<Person> { … }
在某些情况下,如果所有事件都遵循相同的内容,这可能会变得非常乏味
结构(与前面示例中的事件一样)。在这种情况下,
您可以实现以指导框架超越运行时的范围
环境提供。以下事件演示如何执行此操作:ResolvableTypeProvider
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
应用程序上下文是 ,可用于加载对象。
A 本质上是 JDK 类的一个功能更丰富的版本。
事实上,该实现包装了一个实例,其中
适当。A 可以从 a 中的几乎任何位置获取低级资源
透明方式,包括从类路径、文件系统位置、任何位置
可使用标准 URL 和其他一些变体进行描述。如果资源位置
string 是一个没有任何特殊前缀的简单路径,这些资源的来源是
特定于实际应用程序上下文类型。ResourceLoader
Resource
Resource
java.net.URL
Resource
java.net.URL
Resource
您可以配置部署到应用程序上下文中的
Bean 来实现特殊的
回调接口,自动回调
应用程序上下文本身作为 .
还可以公开 类型的属性,以用于访问静态资源。
它们像任何其他属性一样被注入其中。您可以将这些属性指定为简单路径,并依赖于这些文本的自动转换
部署 Bean
时实际对象的字符串。ResourceLoaderAware
ResourceLoader
Resource
Resource
String
Resource
提供给构造函数的一个或多个位置路径实际上是
资源字符串,以简单的形式,根据特定的
上下文实现。例如,将简单的
location path 作为类路径位置。还可以使用位置路径(资源字符串)
使用特殊前缀强制从类路径或 URL 加载定义,
无论实际上下文类型如何。ApplicationContext
ClassPathXmlApplicationContext
1.15.4. 应用程序启动跟踪
管理 Spring 应用程序的生命周期,并提供丰富的
围绕组件的编程模型。因此,复杂的应用程序可以具有同等的
复杂的组件图和启动阶段。ApplicationContext
使用特定指标跟踪应用程序启动步骤有助于了解位置 在启动阶段花费了时间,但它也可以用作改进的一种方式 将上下文生命周期作为一个整体进行了解。
(及其子类)使用
,它收集有关各个启动阶段的数据:AbstractApplicationContext
ApplicationStartup
StartupStep
-
应用程序上下文生命周期(基础包扫描、配置类管理)
-
Bean 生命周期(实例化、智能初始化、后处理)
-
应用程序事件处理
下面是
中的检测示例:AnnotationConfigApplicationContext
// create a startup step and start recording
val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages)
// end the current step
scanPackages.end()
应用程序上下文已通过多个步骤进行检测。 一旦记录下来,这些启动步骤就可以被收集、显示和使用特定的工具进行分析。 有关现有启动步骤的完整列表,您可以查看专用的附录部分。
默认实现是无操作变体,以将开销降至最低。
这意味着默认情况下,在应用程序启动期间不会收集任何指标。
Spring Framework 附带了一个使用 Java Flight Recorder 跟踪启动步骤的实现: 。要使用此变体,您必须配置它的实例
到创建后立即添加到。ApplicationStartup
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>
侦听器检查参数。如果参数没有
exist,侦听器用作默认值。当
参数确实存在,侦听器会使用预定义的
分隔符(逗号、分号和空格),并使用值作为位置
搜索应用程序上下文。还支持 Ant 样式的路径模式。
例如(对于名称以 结尾且位于目录中的所有文件)和(对于
的任何子目录中的所有此类文件)。contextConfigLocation
/WEB-INF/applicationContext.xml
String
/WEB-INF/*Context.xml
Context.xml
WEB-INF
/WEB-INF/**/*Context.xml
WEB-INF
1.15.6. 将 Spring 部署为 Java EE
RAR 文件ApplicationContext
可以将 Spring 部署为 RAR
文件,封装
上下文及其在 Java EE RAR 部署中所需的所有 Bean 类和库 JAR
单位。这相当于引导一个独立的(仅托管的
在 Java EE 环境中)能够访问 Java EE 服务器设施。RAR 部署
是部署无头 WAR 文件方案的更自然的替代方案 — 实际上,
没有任何 HTTP 入口点的 WAR 文件,仅用于在 Java EE 环境中引导 Spring。ApplicationContext
ApplicationContext
ApplicationContext
RAR 部署非常适合不需要 HTTP
入口点但
而是仅由消息终结点和计划作业组成。在这样的语境下,豆子可以
使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBC 实例和 JMS 实例,还可以向
该平台的 JMX 服务器 — 全部通过 Spring 的标准事务管理和 JNDI 实现
和 JMX 支持设施。应用程序组件还可以与应用程序交互
服务器的 JCA 通过 Spring 的抽象。DataSource
ConnectionFactory
WorkManager
TaskExecutor
有关RAR部署中涉及的配置详细信息,请参阅SpringContextResourceAdapter
类的javadoc。
对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:
-
包 将所有应用程序类放入一个 RAR 文件(这是一个标准 JAR 文件,具有不同 文件扩展名)。
-
将所有必需的库 JAR 添加到 RAR 存档的根目录中。
-
添加部署描述符(如
SpringContextResourceAdapter
的 javadoc 中所示) 以及相应的 Spring XML Bean 定义文件(通常为 )。META-INF/ra.xml
META-INF/applicationContext.xml
-
将生成的 RAR 文件拖放到您的 应用程序服务器的部署目录。
这种RAR部署单元通常是独立的。它们不公开组件
对外界,甚至对同一应用程序的其他模块也不行。与
基于 RAR 的通常通过与其共享的 JMS 目标发生
其他模块。例如,基于 RAR 的还可以安排一些作业
或对文件系统中的新文件做出反应(或类似)。如果需要允许同步
从外部访问,它可以(例如)导出 RMI 端点,这些端点可用于
由同一台机器上的其他应用程序模块。ApplicationContext ApplicationContext
|
1.16. APIBeanFactory
API 为 Spring 的 IoC 功能提供了底层基础。
它的特定约定主要用于与 Spring 的其他部分集成,并且
相关第三方框架及其实施
是更高级别容器中的键委托。BeanFactory
DefaultListableBeanFactory
GenericApplicationContext
BeanFactory
相关接口(如
、、)是其他框架组件的重要集成点。
由于不需要任何注释甚至反射,它们允许非常高效
容器与其组件之间的交互。应用程序级 Bean 可能
使用相同的回调接口,但通常更喜欢声明式依赖关系
而是通过注释或编程配置进行注入。BeanFactoryAware
InitializingBean
DisposableBean
请注意,核心 API 级别及其实现不会对配置格式或任何
要使用的组件注释。所有这些风格都是通过扩展而来的
(例如 和 ) 和
对共享对象进行操作,将其作为核心元数据表示形式。
这就是 Spring 容器如此灵活和可扩展的本质。BeanFactory
DefaultListableBeanFactory
XmlBeanDefinitionReader
AutowiredAnnotationBeanPostProcessor
BeanDefinition
1.16.1. 或者 ?BeanFactory
ApplicationContext
本部分介绍容器级别和容器级别之间的差异以及对引导的影响。BeanFactory
ApplicationContext
除非你有充分的理由不这样做,否则你应该使用
和 它的子类作为自定义引导的常见实现。这些是主要条目
指向 Spring 的核心容器,用于所有常见目的:加载配置
文件, 触发类路径扫描, 以编程方式注册 Bean 定义
和带注释的类,以及(从 5.0 开始)注册函数 Bean
定义。ApplicationContext
GenericApplicationContext
AnnotationConfigApplicationContext
因为 an 包含 的所有功能,所以它是
一般建议在普通 上,除非满
需要对 Bean 处理进行控制。在(例如实现)中,检测到几种 Bean
按约定(即按 Bean 名称或按 Bean 类型 — 特别是后处理器),
而平原对任何特殊的豆子都是不可知的。ApplicationContext
BeanFactory
BeanFactory
ApplicationContext
GenericApplicationContext
DefaultListableBeanFactory
对于许多扩展容器功能,例如注释处理和
AOP 代理,
BeanPostProcessor
扩展点是必不可少的。
如果仅使用普通 ,则此类后处理器不会
默认情况下被检测并激活。这种情况可能会令人困惑,因为
您的 Bean 配置实际上没有任何问题。相反,在这种情况下,
容器需要通过其他设置完全引导。DefaultListableBeanFactory
下表列出了 和
接口和实现提供的功能。BeanFactory
ApplicationContext
特征 | BeanFactory |
ApplicationContext |
---|---|---|
Bean 实例化/布线 |
是的 |
是的 |
集成式生命周期管理 |
不 |
是的 |
自动注册 |
不 |
是的 |
自动注册 |
不 |
是的 |
方便的访问(国际化) |
不 |
是的 |
内置发布机制 |
不 |
是的 |
要显式地向 ,
您需要以编程方式调用 ,如以下示例所示:DefaultListableBeanFactory
addBeanPostProcessor
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
要将 a 应用于普通 ,
您需要调用其方法,如以下示例所示:BeanFactoryPostProcessor
DefaultListableBeanFactory
postProcessBeanFactory
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)
在这两种情况下,显式注册步骤都很不方便,这是
为什么在 Spring 支持的应用程序中,各种变体比普通变体更受欢迎,尤其是在
依赖 和 实例进行扩展
典型企业设置中的容器功能。ApplicationContext
DefaultListableBeanFactory
BeanFactoryPostProcessor
BeanPostProcessor
具有所有常用的注释后处理器
已注册,并可能在
通过配置注释(如 .
在 Spring 基于注解的配置模型的抽象级别,
Bean 后处理器的概念变成了一个内部容器细节。 |
2. 资源
本章介绍了 Spring 如何处理资源,以及如何在 春天。它包括以下主题:
2.1. 简介
Java 的标准类和各种 URL
前缀的标准处理程序,
不幸的是,对于所有获得低级资源来说,还不够充分。为
例如,没有可用于访问
需要从 类路径 或相对于 .虽然可以为专用前缀注册新的处理程序(类似于前缀的现有处理程序,例如 ),但这通常是
相当复杂,界面仍然缺乏一些理想的功能,
例如,检查所指向的资源是否存在的方法。java.net.URL
URL
ServletContext
URL
http:
URL
2.2. 接口Resource
Spring的接口位于软件包中,是
旨在成为一个功能更强大的接口,用于抽象对低级资源的访问。这
以下列表提供了接口的概述。有关更多详细信息,请参阅资源
javadoc。Resource
org.springframework.core.io.
Resource
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()
:查找并打开资源,返回 for 从资源中读取。预计每次调用都会返回一个新的 .调用方负责关闭流。InputStream
InputStream
-
exists()
:返回一个指示此资源是否实际存在于 物理形式。boolean
-
isOpen()
:返回一个,指示此资源是否表示句柄 与开放流。如果 ,则不能多次读取 和 必须只读取一次,然后关闭,以避免资源泄漏。返回 所有常用的资源实现,但 除外。boolean
true
InputStream
false
InputStreamResource
-
getDescription()
:返回此资源的说明,用于错误 使用资源时的输出。这通常是完全限定的文件名或 资源的实际 URL。
其他方法允许您获取表示
资源(如果底层实现兼容并支持它
功能)。URL
File
该接口的某些实现还实现了扩展的 WritableResource
接口
对于支持写入它的资源。Resource
Spring 本身广泛使用抽象,作为
需要资源时的许多方法签名。某些 Spring API 中的其他方法
(例如各种实现的构造函数)采用一种朴素或简单的形式来创建适当的
该上下文实现,或者通过路径上的特殊前缀,让
调用方指定必须创建和使用特定的实现。Resource
ApplicationContext
String
Resource
String
Resource
虽然该接口在 Spring 和 Spring
中被大量使用,但实际上它是
非常方便地在您自己的代码中单独用作通用实用程序类,以便访问
到资源,即使你的代码不知道或不关心 Spring 的任何其他部分。
虽然这会将您的代码耦合到 Spring,但它实际上只是将其耦合到这一小群
实用程序类,它可以作为
被认为等同于您将用于此目的的任何其他库。Resource
URL
抽象不会取代功能。它把它包裹在什么地方
可能。例如,a 包装一个 URL,并使用包装的 URL 来执行其
工作。Resource UrlResource URL |
2.3.
内置实现Resource
Spring
包含几个内置实现:Resource
有关 Spring 中可用的实现的完整列表,请参阅
资源
javadoc
的“所有已知实现类”部分。Resource
2.3.1.UrlResource
UrlResource
包装
并可用于访问任何对象
通常可通过 URL 访问,例如文件、HTTPS 目标、FTP 目标和
别人。所有 URL 都具有标准化的表示形式,以便适当的
标准化前缀用于指示一种 URL 类型与另一种 URL 类型。这包括访问文件系统路径,通过
HTTPS协议,用于通过FTP等访问资源。java.net.URL
String
file:
https:
ftp:
A 是由 Java 代码通过显式使用构造函数创建的
但通常是在调用 API 方法时隐式创建的,该方法采用旨在表示路径的参数。对于后一种情况,JavaBeans
最终决定创建哪种类型。如果路径字符串包含
well-known (对于属性编辑器来说,即) 前缀(例如 ),它会创建一个
适用于该前缀。但是,如果它无法识别
prefix,它假定该字符串是标准 URL 字符串,并创建一个 .UrlResource
UrlResource
String
PropertyEditor
Resource
classpath:
Resource
UrlResource
2.3.2.ClassPathResource
此类表示应从类路径获取的资源。它使用 线程上下文类装入器、给定类装入器或 的给定类 加载资源。
此实现支持解析为 if 类路径
资源驻留在文件系统中,但对于驻留在
jar 并且尚未扩展(由 servlet 引擎或任何环境扩展)
添加到文件系统。为了解决这个问题,各种实现始终支持
分辨率为 .Resource
java.io.File
Resource
java.net.URL
A 是由 Java
代码通过使用构造函数显式创建的,但通常是在调用 API 方法时隐式创建的,该方法采用旨在表示路径的参数。对于后一种情况,JavaBeans
识别字符串路径上的特殊前缀 , 和
在这种情况下创建一个。ClassPathResource
ClassPathResource
String
PropertyEditor
classpath:
ClassPathResource
2.3.3.FileSystemResource
这是句柄的实现。它还支持句柄,应用
Spring 的标准基于字符串的路径
转换,但通过 API 执行所有操作。对于纯基础支持,请改用 a。 支持分辨率为 a 和 .Resource
java.io.File
java.nio.file.Path
java.nio.file.Files
java.nio.path.Path
PathResource
FileSystemResource
File
URL
2.3.4.PathResource
这是句柄的实现,执行所有
通过 API 进行操作和转换。它支持解析为 和
作为 并且还实现了扩展接口。 是有效的纯基替代品
不同的行为。Resource
java.nio.file.Path
Path
File
URL
WritableResource
PathResource
java.nio.path.Path
FileSystemResource
createRelative
2.3.5.ServletContextResource
这是解释
相关 Web 应用程序根目录中的相对路径。Resource
ServletContext
它始终支持流访问和 URL
访问,但只允许访问
当 Web 应用程序存档被扩展并且资源在物理上位于
文件系统。无论它是否被扩展、是否在文件系统上或被访问
直接从 JAR 或其他其他地方(如数据库)(这是可以想象的)实际上是
依赖于 Servlet 容器。java.io.File
2.4.
界面ResourceLoader
该接口旨在由可以返回的对象实现
(即加载)实例。以下清单显示了接口定义:ResourceLoader
Resource
ResourceLoader
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
所有应用程序上下文都实现该接口。因此,所有
应用程序上下文可用于获取实例。ResourceLoader
Resource
调用特定应用程序上下文和位置路径时
specified 没有特定的前缀,则返回的类型是
适用于该特定应用程序上下文。例如,假设以下内容
针对实例运行的代码片段:getResource()
Resource
ClassPathXmlApplicationContext
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
针对 ,该代码返回一个 .如果
对实例运行相同的方法,它会
返回 .对于 ,它将返回一个 .同样,它将为每个上下文返回适当的对象。ClassPathXmlApplicationContext
ClassPathResource
FileSystemXmlApplicationContext
FileSystemResource
WebApplicationContext
ServletContextResource
因此,您可以以适合特定应用程序的方式加载资源 上下文。
另一方面,您也可以强制使用,无论
应用程序上下文类型,通过指定特殊前缀,如下所示
示例显示:ClassPathResource
classpath:
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,您可以通过指定任何标准前缀来强制使用
a。以下示例使用 和 前缀:UrlResource
java.net.URL
file
https
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
下表总结了将对象转换为对象的策略:String
Resource
前缀 | 例 | 解释 |
---|---|---|
类路径: |
|
从类路径加载。 |
文件: |
|
从文件系统加载。另请参阅
|
网址: |
|
加载为
. |
(无) |
|
取决于基础
. |
2.5. 接口ResourcePatternResolver
接口是接口的扩展
它定义了解析位置模式的策略(例如,Ant 样式路径
pattern)
添加到对象中。ResourcePatternResolver
ResourceLoader
Resource
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
从上面可以看出,这个接口还定义了一个特殊的资源前缀
对于类路径中的所有匹配资源。请注意,资源位置是
在本例中,应为不带占位符的路径,例如 .JAR 文件或类路径中的不同目录可以
包含具有相同路径和相同名称的多个文件。有关详细信息,请参阅应用程序上下文构造函数资源路径及其子部分中的通配符
在带有资源前缀的通配符支持上。classpath*:
classpath*:/config/beans.xml
classpath*:
传入的传入(例如,通过 ResourceLoaderAware
语义提供的传入)可以检查是否
它也实现了这个扩展接口。ResourceLoader
PathMatchingResourcePatternResolver
是可用的独立实现
在 和 之外,也被 用于
填充 Bean 属性。 能够
将指定的资源位置路径解析为一个或多个匹配对象。
源路径可以是一个简单的路径,它与目标具有一对一的映射关系,或者可以包含特殊前缀和/或内部
Ant 样式的正则表达式(使用 Spring 的实用程序进行匹配)。后者都是有效的
通配符。ApplicationContext
ResourceArrayPropertyEditor
Resource[]
PathMatchingResourcePatternResolver
Resource
Resource
classpath*:
org.springframework.util.AntPathMatcher
任何标准中的默认值实际上都是一个实例
其中实现了接口。实例本身也是如此,它也
实现接口并委托给默认的 . |
2.6. 界面ResourceLoaderAware
该接口是一个特殊的回调接口,用于标识
希望提供参考的组件。以下列表
显示了接口的定义:ResourceLoaderAware
ResourceLoader
ResourceLoaderAware
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
当类实现并部署到应用程序上下文中时
(作为 Spring 管理的 bean),它被应用程序识别为
上下文。然后,应用程序上下文调用 ,
提供自身作为参数(请记住,Spring 中的所有应用程序上下文都实现了
接口)。ResourceLoaderAware
ResourceLoaderAware
setResourceLoader(ResourceLoader)
ResourceLoader
由于 an 是 ,因此 bean
也可以实现接口,并直接使用提供的应用程序上下文来
加载资源。但是,一般来说,如果您只需要专用界面,最好使用专用界面。代码将仅与资源加载耦合
接口(可以将其视为实用程序接口),而不是整个 Spring
接口。ApplicationContext
ResourceLoader
ApplicationContextAware
ResourceLoader
ApplicationContext
在应用程序组件中,您还可以依赖 as
的自动接线
实现接口的替代方法。传统模式和自动布线模式(如自动布线协作器中所述)
能够为 构造函数参数或
setter 方法参数。为了获得更大的灵活性(包括能够
AutoWire 字段和多参数方法),考虑使用基于注释的
自动布线功能。在这种情况下,会自动连接到一个字段,
构造函数参数,或需要类型长度的方法参数
因为所讨论的字段、构造函数或方法带有注释。
有关更多信息,请参阅使用 @Autowired
。ResourceLoader
ResourceLoaderAware
constructor
byType
ResourceLoader
ResourceLoader
ResourceLoader
@Autowired
为包含通配符的资源路径加载一个或多个对象
或者使用特殊的资源前缀,请考虑将 ResourcePatternResolver
的实例自动连接到
应用程序组件,而不是 .Resource classpath*: ResourceLoader
|
2.7. 资源作为依赖项
如果 Bean 本身将通过某种排序来确定和提供资源路径
对于动态进程,Bean 使用 or 接口加载资源可能是有意义的。例如,考虑加载
某种模板,其中所需的特定资源取决于
用户的角色。如果资源是静态的,那么完全消除接口(或接口)的使用是有意义的,具有
Bean 公开了它需要的属性,并期望将它们注入其中。ResourceLoader
ResourcePatternResolver
ResourceLoader
ResourcePatternResolver
Resource
然后注入这些属性变得微不足道的是,所有应用程序上下文
注册并使用特殊的 JavaBeans ,它可以转换路径
到对象。例如,以下类具有 类型的属性。PropertyEditor
String
Resource
MyBean
template
Resource
class MyBean(var template: Resource)
在 XML 配置文件中,可以使用简单的
该资源的字符串,如以下示例所示:template
<bean id="myBean" class="example.MyBean">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
请注意,资源路径没有前缀。因此,由于应用程序上下文
本身将用作 ,资源通过 、 或 加载 ,具体取决于
应用程序上下文的确切类型。ResourceLoader
ClassPathResource
FileSystemResource
ServletContextResource
如果需要强制使用特定类型,可以使用前缀。这
以下两个示例显示了如何强制 A 和 A (
后者用于访问文件系统中的文件):Resource
ClassPathResource
UrlResource
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
如果重构该类以用于注解驱动的配置,则
路径可以存储在名为
在提供给 Spring 的属性文件中(参见环境抽象)。然后,可以使用属性占位符通过注释引用模板路径(请参阅使用@Value
)。春天会
以字符串形式检索模板路径的值,并获取特殊遗嘱
将字符串转换为要注入到构造函数中的对象。
下面的示例演示如何实现此目的。MyBean
myTemplate.txt
template.path
Environment
@Value
PropertyEditor
Resource
MyBean
@Component
class MyBean(@Value("\${template.path}") private val template: Resource)
如果我们想支持在多个路径下在同一路径下发现的多个模板
类路径中的位置(例如,类路径中的多个 jar 中)我们可以
使用特殊前缀和通配符将键定义为 。如果我们按如下方式重新定义类,
Spring 会将模板路径模式转换为对象数组,这些对象
可以注入到构造函数中。classpath*:
templates.path
classpath*:/config/templates/*.txt
MyBean
Resource
MyBean
@Component
class MyBean(@Value("\${templates.path}") private val templates: Resource[])
2.8. 应用程序上下文和资源路径
本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 使用 XML、如何使用通配符以及其他详细信息。
2.8.1. 构建应用程序上下文
应用程序上下文构造函数(用于特定的应用程序上下文类型) 将字符串或字符串数组作为资源的位置路径,例如 构成上下文定义的 XML 文件。
当这样的位置路径没有前缀时,特定类型是从
该路径和用于加载 Bean 定义的路径依赖于并适用于
特定的应用程序上下文。例如,请考虑以下示例,该示例创建一个:Resource
ClassPathXmlApplicationContext
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
Bean 定义是从类路径加载的,因为
使用。但是,请考虑以下示例,该示例会创建一个:ClassPathResource
FileSystemXmlApplicationContext
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
现在,Bean 定义是从文件系统位置加载的(在本例中,相对于 当前工作目录)。
请注意,在
位置路径覆盖默认的 Created 类型以加载 Bean
定义。请看以下示例:classpath
Resource
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
using 从类路径中装入 Bean 定义。
但是,它仍然是一个.如果随后将其用作 ,则任何无前缀的路径仍被视为文件系统路径。FileSystemXmlApplicationContext
FileSystemXmlApplicationContext
ResourceLoader
构建实例 —
快捷方式ClassPathXmlApplicationContext
公开了许多构造函数以启用
方便的实例化。基本思想是,您只能提供一个字符串数组
仅包含 XML 文件本身的文件名(不包含前导路径)
信息),并提供 .然后派生
所提供类的路径信息。ClassPathXmlApplicationContext
Class
ClassPathXmlApplicationContext
请考虑以下目录布局:
com/ example/ services.xml repositories.xml MessengerService.class
以下示例演示了由
在名为 和 的文件中定义的 bean(这些
classpath) 可以实例化:ClassPathXmlApplicationContext
services.xml
repositories.xml
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)
有关各种构造函数的详细信息,请参阅 ClassPathXmlApplicationContext
javadoc。
2.8.2. Application Context Constructor资源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径(如
如前所示),每个目标都有一个一对一的映射,或者,
或者,可以包含特殊前缀或内部 Ant 样式模式
(通过使用 Spring 的实用程序进行匹配)。后者都是有效的
通配符。Resource
classpath*:
PathMatcher
此机制的一个用途是当您需要执行组件样式的应用程序组装时。都
组件可以将上下文定义片段发布到已知的位置路径,并且
当使用前缀为 的相同路径创建最终应用程序上下文时,将自动拾取所有组件片段。classpath*:
请注意,此通配符特定于在应用程序上下文中使用资源路径
构造函数(或直接使用实用程序类层次结构时)和
在施工时解决。它与类型本身无关。
不能使用前缀来构造实际的 ,因为
一个资源一次只指向一个资源。PathMatcher
Resource
classpath*:
Resource
蚂蚁样式
路径位置可以包含 Ant 样式模式,如以下示例所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径位置包含 Ant
样式模式时,解析器遵循更复杂的模式
尝试解决通配符的过程。它为通向
最后一个非通配符段,并从中获取 URL。如果此 URL 不是 URL 或
特定于容器的变体(例如在 WebLogic、WebSphere 等中),
a 从中获取,用于通过遍历
文件系统。对于 jar URL,解析器要么从中获取 a 要么手动解析 jar URL,然后遍历
jar
文件的内容来解析通配符。Resource
jar:
zip:
wsjar
java.io.File
java.net.JarURLConnection
对可移植性的影响
如果指定的路径已经是
URL(无论是隐式的,因为基是文件系统的 URL),也可以是显式的),则通配符可以保证
以完全便携的方式工作。file
ResourceLoader
如果指定的路径是一个位置,则解析程序必须获取最后一个
非通配符路径 通过调用来段 URL。由于这个
只是路径的一个节点(不是末尾的文件),它实际上是未定义的(在 javadoc 中),在这种情况下返回的是哪种
URL。在实践中,
它始终表示目录(其中 classpath 资源
解析为文件系统位置)或某种 jar URL(其中 classpath 资源
解析为 jar
位置)。尽管如此,此操作仍存在可移植性问题。classpath
Classloader.getResource()
ClassLoader
java.io.File
如果获取了最后一个非通配符段的
jar URL,则解析器必须能够
从中获取或手动解析 jar URL,以便能够
遍历 jar 的内容并解析通配符。这在大多数环境中都有效
但在其他方面失败,我们强烈建议对资源进行通配符解析
在依赖罐子之前,请在您的特定环境中进行全面测试。java.net.JarURLConnection
前缀classpath*:
在构造基于 XML
的应用程序上下文时,位置字符串可以使用
特殊前缀,如以下示例所示:classpath*:
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
此特殊前缀指定与给定名称匹配的所有类路径资源
必须获取(在内部,这基本上是通过调用 来实现的),然后合并以形成最终应用程序
上下文定义。ClassLoader.getResources(…)
通配符类路径依赖于底层
.由于现在大多数应用程序服务器都提供自己的实现,因此行为可能会有所不同,尤其是在处理
jar 文件时。一个
检查是否有效的简单测试是使用 从
在类路径上的 jar 中:。尝试此测试
具有相同名称但位于两个不同位置的文件,例如,文件
具有相同的名称和相同的路径,但在类路径上的不同 jar 中。万一
返回不适当的结果,请查看应用程序服务器文档中的设置。
这可能会影响行为。getResources() ClassLoader ClassLoader classpath* ClassLoader getClass().getClassLoader().getResources("<someFileInsideTheJar>") ClassLoader
|
您还可以将前缀与
位置路径的其余部分(例如,)。在这个
案例中,解决策略相当简单:调用是
用于最后一个非通配符路径段,以获取
类装入器层次结构,然后,在每个资源之外,相同的分辨率
前面描述的策略用于通配符子路径。classpath*:
PathMatcher
classpath*:META-INF/*-beans.xml
ClassLoader.getResources()
PathMatcher
与通配符有关的其他说明
请注意,当与 Ant
样式模式结合使用时,只能工作
在模式启动之前至少有一个根目录可靠地存在,除非实际
目标文件驻留在文件系统中。这意味着,诸如 这样的模式可能不会从 jar 文件的根目录检索文件,而只是从
jar 文件的根目录中检索文件
从扩展目录的根目录。classpath*:
classpath*:*.xml
Spring 检索类路径条目的能力源自
JDK 的方法,该方法仅返回
空字符串(指示要搜索的潜在根)。Spring 评估运行时配置和 jar 文件中的清单
同样,但这并不能保证会导致可移植行为。ClassLoader.getResources()
URLClassLoader
java.class.path
扫描类路径包需要存在相应的目录
类路径中的条目。使用 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 的类路径扫描通常按预期工作。 在这里也强烈建议将资源放入专用目录, 避免了搜索 JAR 文件根级别的上述可移植性问题。 |
不能保证具有资源的 Ant
样式模式能找到匹配项
资源(如果要搜索的根包在多个类路径位置中可用)。
请考虑以下资源位置示例:classpath:
com/mycompany/package1/service-context.xml
现在考虑一个 Ant 样式的路径,有人可能会用它来尝试查找该文件:
classpath:com/mycompany/**/service-context.xml
这样的资源可能只存在于类路径中的一个位置,但是当路径如
前面的示例用于尝试解决它,解析器在(第一个)
返回的 URL。如果此基础包节点存在于
多个位置,所需的资源可能不存在于第一个位置
找到位置。因此,在这种情况下,您应该更喜欢使用
相同的 Ant 样式模式,该模式搜索包含基本包的所有类路径位置:。getResource("com/mycompany");
ClassLoader
classpath*:
com.mycompany
classpath*:com/mycompany/**/service-context.xml
2.8.3. 注意事项FileSystemResource
未附加到 a 的 A (那
是,当 a 不是实际的 ) 对待
绝对路径和相对路径,如您所料。相对路径是相对于
当前工作目录,而绝对路径相对于
文件系统。FileSystemResource
FileSystemApplicationContext
FileSystemApplicationContext
ResourceLoader
但是,出于向后兼容性(历史)原因,当
是 .强制所有附加实例
将所有位置路径视为相对路径,无论它们是否以前导斜杠开头。
在实践中,这意味着以下示例是等效的:FileSystemApplicationContext
ResourceLoader
FileSystemApplicationContext
FileSystemResource
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
以下示例也是等效的(尽管将它们作为一个不同的示例是有意义的 大小写是相对的,另一个是绝对的):
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
在实践中,如果需要真正的绝对文件系统路径,则应避免使用
带 或 和 的绝对路径
通过使用 URL 前缀强制使用 a。以下示例
演示如何执行此操作:FileSystemResource
FileSystemXmlApplicationContext
UrlResource
file:
// 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
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
3. 验证、数据绑定和类型转换
将验证视为业务逻辑有利有弊,Spring
提供了
一种用于验证(和数据绑定)的设计,不排除其中任何一个。
具体来说,验证不应绑定到 Web 层,并且应该易于本地化。
并且应该可以插入任何可用的验证器。考虑到这些关切,
Spring 提供了一个既基本又实用的契约
在应用程序的每一层中。Validator
数据绑定可用于将用户输入动态绑定到域
应用程序的模型(或用于处理用户输入的任何对象)。春天
提供了恰如其分的名称来做到这一点。和组成包,主要用于但不使用
仅限于 Web 图层。DataBinder
Validator
DataBinder
validation
这是 Spring Framework
中的一个基本概念,并且被大量使用
的地方。但是,您可能不需要直接使用 。但是,由于这是参考文档,因此我们感到了一些解释
可能是有序的。我们在本章中解释,因为,如果你是
要使用它,您很可能在尝试将数据绑定到对象时这样做。BeanWrapper
BeanWrapper
BeanWrapper
Spring 和较低级别的都使用实现来解析和格式化属性值。和
类型是 JavaBeans 规范的一部分,也是
在本章中解释。Spring 3 引入了一个包,它提供了
通用的类型转换工具,以及更高级别的“格式”包
设置 UI 字段值的格式。您可以将这些包用作实现的更简单的替代方法。本章还将讨论它们。DataBinder
BeanWrapper
PropertyEditorSupport
PropertyEditor
PropertyEditorSupport
core.convert
PropertyEditorSupport
Spring 通过设置基础结构和适配器支持
Java Bean 验证
Spring 自己的合同。应用程序可以全局启用一次 Bean 验证,
如
Java Bean 验证中所述,并将其专门用于所有验证
需要。在 Web 层中,应用程序可以进一步注册控制器本地 Spring 实例,如配置 DataBinder
中所述,它可以
对于插入自定义验证逻辑很有用。Validator
Validator
DataBinder
3.1. 使用 Spring 的 Validator 接口进行验证
Spring
具有一个可用于验证对象的接口。该接口通过使用对象来工作,以便在验证时,
验证程序可以向对象报告验证失败。Validator
Validator
Errors
Errors
请考虑以下小型数据对象示例:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
下一个示例通过实现
接口的以下两种方法:Person
org.springframework.validation.Validator
-
supports(Class)
: 这可以验证所提供的实例吗?Validator
Class
-
validate(Object, org.springframework.validation.Errors)
:验证给定对象 并且,如果出现验证错误,则将这些错误注册到给定对象。Errors
实现一个相当简单,特别是当你知道
Spring Framework 也提供的帮助程序类时。以下
实例的示例实现:Validator
ValidationUtils
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");
}
}
}
类上的方法用于
如果属性为 或空字符串,则拒绝该属性。查看 ValidationUtils
javadoc
以查看除前面所示的示例之外,它还提供哪些功能。static
rejectIfEmpty(..)
ValidationUtils
name
null
虽然当然可以实现单个类来验证每个类
对于富对象中的嵌套对象,最好封装验证
每个嵌套类对象在其自己的实现中的逻辑。一个简单的
“丰富”对象的示例是由两个属性(第一个和第二个名称)和一个复杂对象组成的。 对象
可以独立于对象使用,因此已经实现了 distinct。如果您希望重用所包含的逻辑
在课堂上,无需复制粘贴,您可以
dependency-inject 或实例化 ,
如以下示例所示:Validator
Validator
Customer
String
Address
Address
Customer
AddressValidator
CustomerValidator
AddressValidator
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();
}
}
}
验证错误将报告给传递给验证器的对象。在这种情况下
Spring Web MVC 中,您可以使用标记来检查错误消息,但是
您也可以自己检查对象。有关
它提供的方法可以在 JavaDoc 中找到。Errors
<spring:bind/>
Errors
3.2. 将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍如何输出对应的消息
验证错误。在上一节所示的示例中,
我们拒绝了 AND 字段。如果我们想使用 输出错误消息,我们可以使用拒绝字段时提供的错误代码来实现
(在本例中为“姓名”和“年龄”)。当您调用(直接或间接使用,
例如,class) 或其他方法之一
从接口上看,底层实现不仅注册了代码
传入,但也注册了一些额外的错误代码。确定接口注册的错误代码。默认情况下,使用 ,它(例如)不仅注册消息
使用您提供的代码,但也会注册包含您传递的字段名称的消息
到 reject 方法。因此,如果使用 拒绝字段
除了代码之外,Spring 还注册了 and(第一个包含字段名称,第二个包含类型
的字段)。这样做是为了方便开发人员在定位错误消息时提供帮助。name
age
MessageSource
ValidationUtils
rejectValue
reject
Errors
MessageCodesResolver
Errors
DefaultMessageCodesResolver
rejectValue("age",
"too.darn.old")
too.darn.old
too.darn.old.age
too.darn.old.age.int
可以找到有关和默认策略的更多信息
在 MessageCodesResolver 和 DefaultMessageCodesResolver
的 javadoc 中,
分别。
MessageCodesResolver
3.3. Bean 操作和BeanWrapper
该包遵循 JavaBeans 标准。
JavaBean 是一个具有默认无参数构造函数的类,其后
一种命名约定,其中(例如)名为
有一个 setter 方法和一个 getter 方法。为
有关 JavaBeans 和规范的更多信息,请参阅 javabeans。org.springframework.beans
bingoMadness
setBingoMadness(..)
getBingoMadness()
bean 包中一个非常重要的类是接口及其
相应的实现 ()。正如 javadoc 所引用的,它提供了设置和获取属性值的功能(单独或
bulk)、获取属性描述符,并查询属性以确定它们是否
可读或可写。此外,它还提供对嵌套属性的支持,
允许将子属性的属性设置为无限深度。还支持添加标准 JavaBeans 和 的功能,而无需在目标类中支持代码。
最后但并非最不重要的一点是,它提供了对设置索引属性的支持。
通常不由应用程序代码直接使用,但由 和
.BeanWrapper
BeanWrapperImpl
BeanWrapper
BeanWrapper
BeanWrapper
PropertyChangeListeners
VetoableChangeListeners
BeanWrapper
BeanWrapper
DataBinder
BeanFactory
它的名字部分表明了这种工作方式:它将一颗豆包裹到
对该 Bean 执行操作,例如设置和检索属性。BeanWrapper
3.3.1. 设置和获取基本属性和嵌套属性
设置和获取属性是通过 的 和
重载方法变体完成的。请参阅他们的 Javadoc
详。下表显示了这些约定的一些示例:setPropertyValue
getPropertyValue
BeanWrapper
表达 | 解释 |
---|---|
|
指示与
or 和 方法对应的属性。 |
|
指示对应于
(例如)或方法。 |
|
指示索引属性的第三个元素。索引属性
可以是 、
或其他自然有序集合类型。 |
|
指示由属性的键编制索引的映射条目的值。 |
(如果您不打算使用
直接。如果仅使用 和 和 及其默认实现,则应跳到 PropertyEditors
部分。BeanWrapper
DataBinder
BeanFactory
以下两个示例类使用 获取 和 设置
性能:BeanWrapper
class Company {
var name: String? = null
var managingDirector: Employee? = null
}
class Employee {
var name: String? = null
var salary: Float? = null
}
以下代码片段显示了如何检索和操作一些
实例化的 S 和 S 的属性:Company
Employee
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 使用 a 的概念来实现
an 和 a 之间的转换。它可以很方便
以与对象本身不同的方式表示属性。例如,a 可以用人类可读的方式表示(如 : ),而
我们仍然可以将人类可读的形式转换回原始日期(或者,甚至
更好的是,将以人类可读形式输入的任何日期转换回对象)。这
行为可以通过注册 类型的自定义编辑器来实现。在 or 上注册自定义编辑器,
或者,在特定的 IoC 容器中(如上一章所述),给出它
了解如何将属性转换为所需类型。有关更多信息,请参阅 Oracle 的 java.beans
包的
javadoc。PropertyEditor
Object
String
Date
String
'2007-14-09'
Date
java.beans.PropertyEditor
BeanWrapper
PropertyEditor
在 Spring 中使用属性编辑的几个示例:
-
在 Bean 上设置属性是通过使用实现完成的。 当您用作您声明的某个 Bean 的属性的值时 在XML文件中,Spring(如果相应属性的setter具有参数)用于尝试将参数解析为对象。
PropertyEditor
String
Class
ClassEditor
Class
-
在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种方式完成的 可以在 的所有子类中手动绑定的实现。
PropertyEditor
CommandController
Spring 有许多内置的实现,使生活变得轻松。
它们都位于包中。默认情况下,大多数(但不是全部,如下表所示)由 注册。如果属性编辑器可以以某种方式进行配置,则可以
仍然注册您自己的变体以覆盖默认变体。下表描述了
Spring 提供的各种实现:PropertyEditor
org.springframework.beans.propertyeditors
BeanWrapperImpl
PropertyEditor
类 | 解释 |
---|---|
|
字节数组的编辑器。将字符串转换为相应的字节
交涉。默认由 注册。 |
|
将表示类的字符串分析为实际类,反之亦然。当
找不到类,则抛出 an。缺省情况下,由 注册。 |
|
属性的可自定义属性编辑器。默认情况下,注册者
但可以通过将其自定义实例注册为
自定义编辑器。 |
|
集合的属性编辑器,将任何源转换为给定的目标类型。 |
|
可自定义的属性编辑器,支持自定义
.不
默认注册。必须根据需要使用适当的格式进行用户注册。 |
|
任何子类(如
、 、 或 )的可自定义属性编辑器。默认情况下,注册者 但可以被覆盖
将其的自定义实例注册为自定义编辑器。 |
|
将字符串解析为对象。缺省情况下,由
注册。 |
|
单向属性编辑器,可以获取字符串并生成(通过
intermediate 和 ) ,以便可以直接将属性设置为字符串。请注意,默认用法不会关闭
为你。缺省情况下,由 注册。 |
|
可以将字符串解析为对象,反之亦然(字符串格式为
,与 的方法相同)。也接受空格作为分隔符,作为下划线的替代方法。
缺省情况下,由 注册。 |
|
可以将字符串解析为对象,反之亦然。 |
|
可以将字符串(使用类的
javadoc 中定义的格式进行格式化)转换为对象。默认情况下,已注册
由。 |
|
用于修剪字符串的属性编辑器。(可选)允许转换空字符串
转换为值。默认情况下未注册 — 必须是用户注册的。 |
|
可以将
URL 的字符串表示形式解析为实际对象。
缺省情况下,由 注册。 |
Spring 使用 来设置属性的搜索路径
可能需要的编辑器。搜索路径还包括 ,其中
包括 、 和 大多数
基元类型。另请注意,标准的 JavaBeans 基础结构
自动发现类(无需注册它们
显式地),如果它们与它们处理的类位于同一包中,并且具有相同的
name 作为该类,并附加。例如,可以有以下内容
类和包结构,这足以使类
识别并用作 for 类型化属性。java.beans.PropertyEditorManager
sun.bean.editors
PropertyEditor
Font
Color
PropertyEditor
Editor
SomethingEditor
PropertyEditor
Something
com chank pop Something SomethingEditor // the PropertyEditor for the Something class
请注意,您也可以在此处使用标准的
JavaBeans 机制
(这里在某种程度上进行了描述)。这
下面的示例使用该机制将一个或多个实例显式注册到关联类的属性:BeanInfo
BeanInfo
PropertyEditor
com chank pop Something SomethingBeanInfo // the BeanInfo for the Something class
引用类的以下 Java 源代码
将 A
与类的属性相关联:SomethingBeanInfo
CustomNumberEditor
age
Something
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 容器最终使用
标准 JavaBeans 实现,将这些字符串转换为
财产。Spring 预注册了许多自定义实现(例如,到
将表示为字符串的类名转换为对象)。此外
Java 的标准 JavaBeans 查找机制允许对类进行适当的命名,并将其放置在与该类相同的包中
它提供支持,以便可以自动找到它。PropertyEditor
PropertyEditor
Class
PropertyEditor
PropertyEditor
如果需要注册其他自定义,则有几种机制
可用。最手动的方法,通常不方便或
推荐,就是使用接口的方法,假设你有一个参考。
另一种(稍微方便一点)的机制是使用特殊的豆厂
后处理器称为 。虽然你可以使用 Bean 工厂的后处理器
在实现中,有一个
嵌套属性设置,因此我们强烈建议您将其与 一起使用,您可以在其中以与任何其他 Bean 和
可以自动检测和应用的地方。PropertyEditors
registerCustomEditor()
ConfigurableBeanFactory
BeanFactory
CustomEditorConfigurer
BeanFactory
CustomEditorConfigurer
ApplicationContext
请注意,所有 Bean
工厂和应用程序上下文都自动使用许多
内置属性编辑器,通过使用 a to
处理属性转换。寄存器的标准属性编辑器在上一节中列出。
此外,s 还会覆盖或添加其他编辑器来处理
以适合特定应用程序上下文类型的方式进行资源查找。BeanWrapper
BeanWrapper
ApplicationContext
标准 JavaBeans 实例用于转换属性值
表示为属性的实际复杂类型的字符串。您可以使用 ,一个 Bean 工厂后处理器,方便地添加
支持对 .PropertyEditor
CustomEditorConfigurer
PropertyEditor
ApplicationContext
请考虑以下示例,该示例定义了一个名为
和 的用户类
另一个名为
的类,需要设置为属性:ExoticType
DependsOnExoticType
ExoticType
package example
class ExoticType(val name: String)
class DependsOnExoticType {
var type: ExoticType? = null
}
正确设置后,我们希望能够将
type 属性分配为
字符串,A 将其转换为实际实例。以下 Bean 定义显示了如何设置此关系:PropertyEditor
ExoticType
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
实现可能类似于以下内容:PropertyEditor
// converts string representation to ExoticType object
package example
import java.beans.PropertyEditorSupport
class ExoticTypeEditor : PropertyEditorSupport() {
override fun setAsText(text: String) {
value = ExoticType(text.toUpperCase())
}
}
最后,以下示例显示了如何使用
将 new 注册到 ,然后
将能够根据需要使用它: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
容器注册属性编辑器的另一种机制是
创建并使用 .此接口在以下情况下特别有用
您需要在几种不同情况下使用同一组属性编辑器。
您可以编写相应的注册器,并在每种情况下重复使用它。 实例与一个名为 的接口结合使用,该接口由
Spring(和 )实现。 实例特别方便
当与(此处所述)结合使用时,它公开了属性
叫。 已添加实例
以这种方式可以很容易地与和
Spring MVC 控制器。此外,它避免了自定义同步的需要
editors:A 应为每次 Bean
创建尝试创建新实例。PropertyEditorRegistrar
PropertyEditorRegistrar
PropertyEditorRegistry
BeanWrapper
DataBinder
PropertyEditorRegistrar
CustomEditorConfigurer
setPropertyEditorRegistrars(..)
PropertyEditorRegistrar
CustomEditorConfigurer
DataBinder
PropertyEditorRegistrar
PropertyEditor
以下示例演示如何创建自己的实现:PropertyEditorRegistrar
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(..)
下一个示例演示如何配置和注入实例
我们的 into it: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 框架,使用
与数据绑定 Web 控制器结合使用非常方便。以下
示例在方法的实现中使用 a:PropertyEditorRegistrar
PropertyEditorRegistrar
@InitBinder
@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 3 引入了一个提供通用类型转换的包
系统。系统定义一个 SPI 来实现类型转换逻辑和一个 API
在运行时执行类型转换。在 Spring 容器中,您可以使用此系统
作为转换外部化 Bean 属性值的实现的替代方法
字符串设置为所需的属性类型。您还可以在
需要类型转换的应用程序。core.convert
PropertyEditor
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
对于每次调用 ,保证 source
参数不为 null。如果转换失败,您可能会抛出任何未经检查的异常。具体来说,它应该抛出一个 报告无效的源值。
请注意确保您的实现是线程安全的。convert(S)
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
当您需要集中整个类层次结构的转换逻辑时
(例如,从 TO 对象转换时),您可以实现
,如以下示例所示: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 参数化为定义的基本类型
可以转换为的类范围。然后实现,
其中 T 是 R 的子类。getConverter(Class<T>)
以 为例: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
当您需要复杂的实现时,请考虑使用该接口。具有更灵活但类型强度较低的签名
than ,a 支持在多个源和
目标类型。此外,还提供源字段和目标字段
实现转换逻辑时可以使用的上下文。这样的上下文让
类型转换由字段注释或在
字段签名。下面的清单显示了
的接口定义:Converter
GenericConverter
Converter
GenericConverter
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()
convert(Object,
TypeDescriptor,
TypeDescriptor)
TypeDescriptor
TypeDescriptor
一个很好的例子是在 Java
数组之间转换的转换器
和一个集合。这样的内省是对声明
目标集合类型,用于解析集合的元素类型。这让每个
元素,在
集合设置在目标字段上。GenericConverter
ArrayToCollectionConverter
因为是一个更复杂的SPI接口,所以应该使用
它只在你需要的时候。青睐或基本型
转换需求。GenericConverter 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. APIConversionService
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);
}
大多数实现还实现了 ,其中
提供用于注册转换器的 SPI。在内部,实现委托给其注册的转换器来执行类型转换逻辑。ConversionService
ConverterRegistry
ConversionService
该软件包中提供了一个强大的实现。
通用实现是否适用于
在大多数环境中使用。 提供便利的工厂
创建通用配置。ConversionService
core.convert.support
GenericConversionService
ConversionServiceFactory
ConversionService
3.4.5. 配置ConversionService
A 是一个无状态对象,设计为在应用程序实例化
启动,然后在多个线程之间共享。在 Spring 应用程序中,通常
为每个 Spring 容器(或 )配置一个实例。
Spring 会拾取它,并在任何类型
转换需要由框架执行。您也可以将其注入到任何 Bean
中并直接调用它。ConversionService
ConversionService
ApplicationContext
ConversionService
ConversionService
如果
no 在 Spring 中注册,则原始的 -基于
系统。ConversionService PropertyEditor |
要向 Spring 注册默认值,请添加以下
bean 定义
替换为 :ConversionService
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>
在 Spring MVC 应用程序中使用
a 也很常见。请参阅 Spring MVC 一章中的转换和格式化。ConversionService
在某些情况下,您可能希望在转换过程中应用格式。有关使用
的详细信息,请参阅 FormatterRegistry
SPI。FormattingConversionServiceFactoryBean
3.4.6. 以编程方式使用ConversionService
若要以编程方式使用实例,可以注入对
就像你对任何其他豆子一样。以下示例演示如何执行此操作:ConversionService
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
对于大多数用例,您可以使用指定
的方法,但它
不适用于更复杂的类型,例如参数化元素的集合。
例如,如果要以编程方式将 a of 转换为 a,
您需要提供源和目标类型的正式定义。convert
targetType
List
Integer
List
String
幸运的是,它提供了各种选项来使操作变得简单,
如以下示例所示:TypeDescriptor
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)))
请注意,自动注册的转换器是
适用于大多数环境。这包括集合转换器、标量
转换器和基本转换器。您可以注册相同的转换器
通过对类使用 static
方法。DefaultConversionService
Object
String
ConverterRegistry
addDefaultConverters
DefaultConversionService
值类型的转换器被重用于数组和集合,因此有
无需创建特定的转换器来从 A OF 转换为 OF
,假设标准集合处理是合适的。Collection
S
Collection
T
3.5. Spring 字段格式化
如上一节所述,core.convert
是一个
通用型转换系统。它提供了一个统一的 API 作为
以及强类型 SPI,用于实现一种类型的转换逻辑
到另一个。Spring 容器使用此系统来绑定 Bean 属性值。在
此外,Spring 表达式语言 (SpEL) 和使用此系统来
绑定字段值。例如,当 SpEL 需要强制 a 到 a to
完成一次尝试,系统执行强制。ConversionService
Converter
DataBinder
Short
Long
expression.setValue(Object
bean, Object value)
core.convert
现在考虑典型客户端环境的类型转换要求,例如
Web 或桌面应用程序。在此类环境中,通常从 转换为支持客户端回发过程,以及转换回
查看渲染过程。此外,您经常需要本地化值。越多
一般 SPI 无法满足此类格式要求
径直。为了直接解决这些问题,Spring 3 引入了一个方便的 SPI,它
为客户端环境的实现提供了一种简单而可靠的替代方法。String
String
String
core.convert
Converter
Formatter
PropertyEditor
通常,当您需要实现通用类型时,可以使用SPI
转换逻辑 — 例如,用于在 A 和 A 之间进行转换。
当您在客户端环境(例如 Web
application),并且需要解析和打印本地化的字段值。为两个 SPI 提供统一的类型转换 API。Converter
java.util.Date
Long
Formatter
ConversionService
3.5.1.
SPIFormatter
用于实现字段格式化逻辑的 SPI
简单且类型化。这
以下清单显示了接口定义:Formatter
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;
}
要创建自己的接口,请实现前面所示的接口。
参数化为要格式化的对象类型,例如 .实现打印 for 实例的操作
显示在客户端区域设置中。实现从客户端区域设置返回的格式化表示形式的实例分析实例的操作。如果解析尝试失败,则应抛出
a 或 an。拿
注意确保您的实现是线程安全的。Formatter
Formatter
T
java.util.Date
print()
T
parse()
T
Formatter
ParseException
IllegalArgumentException
Formatter
为方便起见,子包提供了多种实现。
该软件包提供 、 和 来格式化使用 .
该软件包提供了一个格式化对象
一个。format
Formatter
number
NumberStyleFormatter
CurrencyStyleFormatter
PercentStyleFormatter
Number
java.text.NumberFormat
datetime
DateFormatter
java.util.Date
java.text.DateFormat
下面是一个示例实现:DateFormatter
Formatter
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 团队欢迎社区驱动的贡献。请参阅
GitHub 问题以做出贡献。Formatter
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
-
Have 返回可以使用注释的字段类型。
getFieldTypes()
-
返回 a 以打印带注释字段的值。
getPrinter()
Printer
-
返回 a 来解析带注释的字段的 a。
getParser()
Parser
clientValue
以下示例实现将注释绑定到格式化程序,以使数字样式或模式成为
指定:AnnotationFormatterFactory
@NumberFormat
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对字段进行注释,如下所示 示例显示:
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
格式注释 API
包中存在可移植格式注释
API。您可以使用 和 等字段的格式,以及 、 、 (用于毫秒时间戳)以及 JSR-310
的格式。org.springframework.format.annotation
@NumberFormat
Number
Double
Long
@DateTimeFormat
java.util.Date
java.util.Calendar
Long
java.time
以下示例用于将 a 格式化为
ISO 日期
(yyyy-MM-dd):@DateTimeFormat
java.util.Date
class MyModel(
@DateTimeFormat(iso=ISO.DATE) private val date: Date
)
3.5.3. SPIFormatterRegistry
是用于注册格式化程序和转换器的
SPI。 是适合
大多数环境。您可以通过编程方式或声明方式配置此变体
作为 Spring bean,例如通过使用 .因为这个
实现也实现,可以直接配置它
用于 Spring 和 Spring 表达式语言 (SpEL)。FormatterRegistry
FormattingConversionService
FormatterRegistry
FormattingConversionServiceFactoryBean
ConversionService
DataBinder
以下列表显示了
SPI:FormatterRegistry
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);
}
如前面的清单所示,您可以按字段类型或批注注册格式化程序。
SPI 允许您集中配置格式规则,而不是
在控制器之间复制此类配置。例如,您可能希望
强制所有日期字段都以某种方式设置格式,或者使用特定
注释以某种方式进行格式化。使用共享的 ,您可以定义
这些规则只需一次,每当需要格式化时都会应用它们。FormatterRegistry
FormatterRegistry
3.5.4. SPIFormatterRegistrar
FormatterRegistrar
是一个 SPI,用于通过
格式化程序注册表。下面的清单显示了它的接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
A 在注册多个相关转换器时很有用,并且
给定格式类别(如日期格式)的格式化程序。它也可以是
在声明性注册不足的情况下很有用,例如,当格式化程序
需要在与其自身不同的特定字段类型下编制索引,或者当
注册一个/对。下一节将提供有关
转换器和格式化程序注册。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
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 验证 API 的支持。
3.7.1. Bean 验证概述
Bean Validation 提供了一种通过约束声明和 Java 应用程序的元数据。要使用它,请使用 声明性验证约束,然后由运行时强制执行。有 内置约束,您还可以定义自己的自定义约束。
请考虑以下示例,该示例显示了具有两个属性的简单模型:PersonForm
class PersonForm(
private val name: String,
private val age: Int
)
Bean Validation 允许您声明约束,如以下示例所示:
class PersonForm(
@get:NotNull @get:Size(max=64)
private val name: String,
@get:Min(0)
private val age: Int
)
然后,Bean Validation 验证器根据声明的 约束。有关以下内容的一般信息,请参阅 Bean Validation API。请参阅 Hibernate Validator 文档 特定约束。了解如何将 Bean 验证提供程序设置为 Spring 豆子,继续阅读。
3.7.2. 配置 Bean 验证提供程序
Spring 提供了对 Bean 验证 API
的完全支持,包括引导
Bean 验证提供程序作为 Spring bean。这使您可以注入验证或任何验证
在您的应用程序中需要。javax.validation.ValidatorFactory
javax.validation.Validator
您可以使用 将默认的 Validator
配置为 Spring
bean,如以下示例所示:LocalValidatorFactoryBean
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
前面示例中的基本配置触发 Bean 验证以初始化 使用其默认的引导机制。Bean Validation 提供程序,例如 Hibernate Validator,应存在于类路径中,并被自动检测。
注入验证者
LocalValidatorFactoryBean
实现了 和 ,以及 Spring 的 .
您可以将对这些接口中任一的引用注入到需要调用的 Bean 中
验证逻辑。javax.validation.ValidatorFactory
javax.validation.Validator
org.springframework.validation.Validator
如果您更喜欢使用
Bean,可以注入对
验证 API,如以下示例所示:javax.validation.Validator
import javax.validation.Validator;
@Service
class MyService(@Autowired private val validator: Validator)
你可以注入一个引用,如果你的
bean
需要 Spring Validation API,如以下示例所示:org.springframework.validation.Validator
import org.springframework.validation.Validator
@Service
class MyService(@Autowired private val validator: Validator)
配置自定义约束
每个 Bean 验证约束都由两部分组成:
-
声明约束及其可配置属性的批注。
@Constraint
-
实现 约束的行为。
javax.validation.ConstraintValidator
要将声明与实现相关联,每个注释
引用相应的实现类。在运行时,当
域模型中遇到约束注释。@Constraint
ConstraintValidator
ConstraintValidatorFactory
默认情况下,配置使用
Spring 创建实例的 。这让您的自定义从依赖注入中受益,就像任何其他 Spring Bean
一样。LocalValidatorFactoryBean
SpringConstraintValidatorFactory
ConstraintValidator
ConstraintValidators
以下示例显示了一个自定义声明,后跟一个使用
Spring
进行依赖注入的关联实现:@Constraint
ConstraintValidator
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
import javax.validation.ConstraintValidator
class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {
// ...
}
如前面的示例所示,实现可以像任何其他
Spring Bean
一样具有其依赖项。ConstraintValidator
@Autowired
弹簧驱动方法验证
您可以集成 Bean
Validation 1.1 支持的方法验证功能(并且,作为
一个自定义扩展,也由 Hibernate Validator 4.3 提供)通过 bean 定义进入 Spring
上下文:MethodValidationPostProcessor
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
为了有资格进行 Spring
驱动的方法验证,所有目标类都需要被注释
替换为 Spring 的注解,也可以选择声明验证
要使用的组。请参阅 MethodValidationPostProcessor
,了解
Hibernate Validator 和 Bean Validation 1.1
提供程序的设置细节。@Validated
其他配置选项
默认配置足以满足大多数人的需求
例。有许多配置选项可用于各种 Bean 验证
构造,从消息插值到遍历解析。有关这些选项的更多信息,请参阅 LocalValidatorFactoryBean
javadoc。LocalValidatorFactoryBean
3.7.3.
配置DataBinder
从 Spring 3 开始,您可以使用
.一次
配置后,可以通过调用 .任何验证都会自动添加到活页夹的 .DataBinder
Validator
Validator
binder.validate()
Errors
BindingResult
下面的示例演示如何以编程方式使用验证来调用验证
绑定到目标对象后的逻辑:DataBinder
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
您还可以通过 和 配置具有多个实例的
。这在以下情况下很有用
将全局配置的 Bean 验证与 Spring 配置的
本地位于 DataBinder 实例上。请参阅 Spring MVC
验证配置。DataBinder
Validator
dataBinder.addValidators
dataBinder.replaceValidators
Validator
3.7.4. Spring MVC 3 验证
请参阅 Spring MVC 章节中的验证。
4. Spring 表达式语言 (SpEL)
Spring 表达式语言(简称“SpEL”)是一种强大的表达式语言,它 支持在运行时查询和操作对象图。语言语法是 与 Unified EL 类似,但提供了额外的功能,最明显的是方法调用和 基本字符串模板功能。
虽然还有其他几种可用的 Java 表达式语言——OGNL、MVEL 和 JBoss EL,仅举几例——Spring 表达式语言的创建是为了提供 Spring 社区,具有单一支持良好的表达语言,可用于所有 Spring 产品组合中的产品。它的语言功能由 Spring 产品组合中项目的要求,包括工具要求 用于 Spring Tools for Eclipse 中的代码完成支持。 也就是说,SpEL 基于与技术无关的 API,该 API 允许其他表达式语言 如果需要,可以集成实现。
而 SpEL 是 Spring 中表达评估的基础 portfolio,它不直接绑定 Spring,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是 独立的表达语言。这需要创建一些引导 基础结构类,例如解析器。大多数 Spring 用户不需要处理 相反,此基础结构只能创作表达式字符串以进行计算。 这种典型用途的一个例子是将 SpEL 集成到创建 XML 或 基于注释的 Bean 定义,如对定义 Bean 定义的表达式支持中所示。
本章介绍表达式语言、其 API 及其语言的功能
语法。在几个地方,类被用作目标
用于表达式求值的对象。这些类声明和用于
填充它们列在本章末尾。Inventor
Society
表达式语言支持以下功能:
-
文本表达式
-
布尔运算符和关系运算符
-
正则表达式
-
类表达式
-
访问属性、数组、列表和映射
-
方法调用
-
关系运算符
-
分配
-
调用构造函数
-
Bean 引用
-
阵列结构
-
内联列表
-
内联映射
-
三元运算符
-
变量
-
用户定义的函数
-
集合投影
-
馆藏选择
-
模板化表达式
4.1. 评估
本节介绍SpEL接口的简单用法及其表达式语言。 完整的语言参考可以在语言参考中找到。
以下代码介绍了用于计算文本字符串表达式
的 SpEL API。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
spel.support
该接口负责分析表达式字符串。在
在前面的示例中,表达式字符串是由周围的单个表示的字符串文本
引号。该接口负责评估先前定义的
表达式字符串。调用 和 时可以抛出的两个异常 和 ,
分别。ExpressionParser
Expression
ParseException
EvaluationException
parser.parseExpression
exp.getValue
SpEL 支持多种功能,例如调用方法、访问属性、 和调用构造函数。
在下面的方法调用示例中,我们对字符串文字调用该方法:concat
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | 的值现在是“Hello World!”。message |
以下调用 JavaBean
属性的示例调用该属性:String
Bytes
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | 此行将文本转换为字节数组。 |
SpEL 还通过使用标准点表示法(如
)以及属性值的相应设置来支持嵌套属性。
也可以访问公共字段。prop1.prop2.prop3
下面的示例演示如何使用点表示法获取文本的长度:
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 的构造函数,而不是使用字符串文本,如下所示 示例显示:
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
// 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
该接口在计算要解析的表达式时使用
属性、方法或字段,并帮助执行类型转换。Spring 提供了两个
实现。EvaluationContext
-
SimpleEvaluationContext
:公开基本 SpEL 语言功能的子集,以及 配置选项,适用于不需要全图范围的表达式类别 的 SpEL 语言语法,并应进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的筛选器。 -
StandardEvaluationContext
:公开全套 SpEL 语言功能和 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 Bean 引用。它还需要
可以显式选择对表达式中的属性和方法的支持级别。
默认情况下,静态工厂方法仅允许对属性进行读取访问。
您还可以获得一个构建器来配置所需的确切支持级别,定位
以下一项或某种组合:create()
-
仅自定义(无反射)
PropertyAccessor
-
只读访问的数据绑定属性
-
读取和写入的数据绑定属性
类型转换
默认情况下,SpEL 使用
Spring core 中提供的转换服务
().此转换服务来了
具有许多用于常见转换的内置转换器,但也是完全可扩展的,因此
您可以在类型之间添加自定义转化。此外,它是
泛型感知。这意味着,当您在
表达式,SpEL 会尝试转换以保持任何对象的类型正确性
它遇到。org.springframework.core.convert.ConversionService
这在实践中意味着什么?假设正在使用
使用 的赋值
设置属性。属性的类型实际上是 。SPEL系列
认识到列表的元素需要转换为之前
被安置在其中。以下示例演示如何执行此操作:setValue()
List
List<Boolean>
Boolean
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 表达式解析器
对象 ()。配置
对象控制某些表达式组件的行为。例如,如果你
索引到数组或集合中,并且指定索引处的元素为 ,SpEL
可以自动创建元素。这在使用由
属性引用链。如果对数组或列表进行索引并指定索引
即超出当前数组或列表的末尾,SpEL 可以自动
增大数组或列表以容纳该索引。为了在
指定的索引,SpEL 将尝试使用元素类型的默认值创建元素
构造函数。如果元素类型没有
默认构造函数,将被添加到数组或列表中。如果没有内置
或知道如何设置值的自定义转换器,将保留在数组中,或者
在指定索引处列出。以下示例演示了如何自动增长
列表:org.springframework.expression.spel.SpelParserConfiguration
null
null
null
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 包括一个基本的表达式编译器。表达式通常 解释,这在评估过程中提供了很大的动态灵活性,但 不能提供最佳性能。对于偶尔的表达式用法, 这很好,但是,当被其他组件(如 Spring Integration)使用时, 性能可能非常重要,并且不需要真正的动态性。
SpEL 编译器旨在满足这一需求。在计算期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为,并使用该 类以实现更快的表达式计算。由于缺乏打字 表达式,编译器使用在解释计算期间收集的信息 执行编译时的表达式。例如,它不知道类型 的属性引用纯粹来自表达式,但在第一次解释期间 评估,它会找出它是什么。当然,基于这种衍生的编译 如果各种表达式元素的类型,信息可能会在以后引起麻烦 随时间变化。因此,编译最适合于 类型信息不会在重复计算时更改。
请考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
由于前面的表达式涉及数组访问,因此某些属性取消引用, 和数值运算,性能提升可能非常明显。在示例中 Micro Benchmark 运行了 50000 次迭代,使用 解释器,使用表达式的编译版本只需 3ms。
编译器配置
默认情况下,编译器未打开,但您可以在以下两种方法之一中打开它 不同的方式。您可以使用分析器配置过程将其打开 (前面讨论过)或使用 Spring 属性 当 SpEL 使用嵌入到另一个组件中时。本节讨论以下两个 这些选项。
编译器可以在枚举中捕获的三种模式之一中运行。模式如下:org.springframework.expression.spel.SpelCompilerMode
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 评估收到异常。 -
MIXED
:在混合模式下,表达式在解释和编译之间静默切换 模式随时间变化。经过一定次数的解释运行后,它们切换到编译的 形式,如果编译后的表格出现问题(例如类型更改,则为 前面所述),表达式会自动切换回解释形式 再。一段时间后,它可能会生成另一个编译的表单并切换到它。基本上 用户在模式下获取的异常则在内部处理。IMMEDIATE
IMMEDIATE
mode
之所以存在,是因为 mode 可能会导致以下表达式出现问题
有副作用。如果编译的表达式在部分成功后爆炸,则
可能已经做了一些影响系统状态的事情。如果这
发生时,调用方可能不希望它在解释模式下以静默方式重新运行,
因为表达式的一部分可能运行了两次。MIXED
选择模式后,使用 配置解析器。这
以下示例演示如何执行此操作:SpelParserConfiguration
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)。 编译的表达式在提供的 any 下创建的子类装入器中定义。 重要的是要确保,如果指定了类加载器,它可以看到 表达式计算过程。如果未指定类装入器,那么将使用缺省类装入器 (通常是在表达式求值期间运行的线程的上下文类装入器)。
配置编译器的第二种方法是在
SpEL 嵌入到某些编译器中时使用
其他组件,可能无法通过配置进行配置
对象。在这些情况下,可以通过JVM系统属性(或通过SpringProperties
机制)将属性设置为枚举值(,或)之一。spring.expression.compiler.mode
SpelCompilerMode
off
immediate
mixed
4.2. Bean 定义中的表达式
您可以将 SpEL 表达式与基于 XML
或基于注释的配置元数据一起使用,以便
定义实例。在这两种情况下,定义表达式的语法都是
形式。BeanDefinition
#{ <expression string> }
4.2.1.XML 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 名称。这包括用于访问运行时环境的标准上下文 bean,例如 (of type ) 以及 和 (of type
)。environment
org.springframework.core.env.Environment
systemProperties
systemEnvironment
Map<String,
Object>
以下示例显示了对 Bean 作为
SpEL 变量的访问:systemProperties
<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
以下示例设置字段的默认值:
class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
下面的示例演示了等效的,但在属性 setter 方法上:
class PropertyValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
自动连线方法和构造函数也可以使用注释,如下所示
示例显示:@Value
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
}
// ...
}
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()
下面的清单显示了文本的简单用法。通常,它们不用于 像这样的隔离,而是作为更复杂表达式的一部分——例如, 在逻辑比较运算符的一侧使用文字或作为 方法。
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
// evaluates to 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
属性名称的首字母允许不区分大小写。因此,
上述示例中的表达式可以分别写为 和 。此外,还可以选择通过以下方式访问属性
方法调用 — 例如,而不是 . |
数组和列表的内容是使用方括号表示法获得的,因为 以下示例显示:
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
// 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
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
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 语法来构建数组,也可以选择提供初始值设定项 在构造时填充数组。以下示例演示如何执行此操作:
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 编程语法调用方法。还可以调用方法 在文字上。还支持变量参数。以下示例演示如何 invoke 方法:
// 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
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
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
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
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
赋值运算符
若要设置属性,请使用赋值运算符
()。这通常在
调用 to 但也可以在 的调用中完成。以下
列表显示了使用赋值运算符的两种方法:=
setValue
getValue
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. 类型
您可以使用特殊运算符来指定 (
类型)。静态方法也使用此运算符调用。使用 a
来查找类型,并且(可以替换)是在了解包的情况下构建的。这意味着对包中类型的引用不需要完全限定,但所有其他类型引用都必须是完全限定的。这
以下示例演示如何使用运算符:T
java.lang.Class
StandardEvaluationContext
TypeLocator
StandardTypeLocator
java.lang
T()
java.lang
T
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
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
有效的变量名称必须由以下一项或多项支持的内容组成 字符。
|
下面的示例演示如何使用变量。
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
变量始终是定义的,并引用当前评估对象
(根据该引用解决不合格的引用)。变量始终是
定义并引用根上下文对象。虽然可能因
表达式的计算,始终引用根。以下示例
展示如何使用 AND
变量:#this
#root
#this
#root
#this
#root
// 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
val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)
例如,请考虑以下反转字符串的实用工具方法:
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()
}
然后,您可以注册并使用上述方法,如以下示例所示:
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。以下示例演示如何
为此,请执行以下操作:@
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 名称前面加上一个符号。
以下示例演示如何执行此操作:&
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 条件逻辑 表达式。以下清单显示了一个最小示例:
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'"
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 运算符:
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'
下面的清单显示了一个更复杂的示例:
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 运算符在表达式中应用默认值。以下
示例演示如何在表达式中使用 Elvis 运算符:
如果已定义,这将注入系统属性,如果未定义,则注入
25。 |
4.3.15. 安全导航操作员
安全导航运算符用于避免 和 来自
Groovy 语言。通常,当您具有对某个对象的引用时,可能需要验证
在访问对象的方法或属性之前,它不是 null。为了避免这种情况,
安全导航运算符返回 null 而不是引发异常。以下
示例演示如何使用安全导航运算符: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. 集合选择
选择是一种强大的表达式语言功能,可让您将 通过从其条目中进行选择,将集合源到另一个集合中。
Selection 使用的语法为
。它筛选集合和
返回包含原始元素子集的新集合。例如
选择让我们可以轻松获得塞尔维亚发明人列表,如以下示例所示:.?[selectionExpression]
val list = parser.parseExpression(
"members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
数组和任何实现 或
.对于列表或数组,将根据每个选项评估选择条件
单个元素。针对地图,根据每个地图评估选择标准
entry(Java 类型的对象)。每个地图条目都有其 和 可作为属性在选择中使用。java.lang.Iterable
java.util.Map
Map.Entry
key
value
以下表达式返回一个新映射,该映射由 条目值小于 27 的原始地图:
val newMap = parser.parseExpression("map.?[value<27]").getValue()
除了返回所有选定的元素外,还可以仅检索第一个或
最后一个元素。要获取与所选内容匹配的第一个元素,语法为 。要获取最后一个匹配的选择,语法为
。.^[selectionExpression]
.$[selectionExpression]
4.3.17. 集合投影
投影允许集合驱动子表达式的计算,结果是
一个新系列。projection 的语法是 。例如
假设我们有一个发明家名单,但想要他们出生的城市名单。
实际上,我们希望评估发明人中每个条目的“placeOfBirth.city”
列表。以下示例使用投影来执行此操作:.![projectionExpression]
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]") as List<*>
数组和任何实现 或
.使用地图驱动投影时,投影表达式为
根据映射中的每个条目(表示为 Java )进行评估。结果
地图上的投影是一个列表,其中包含对投影的评估
表达式。java.lang.Iterable
java.util.Map
Map.Entry
4.3.18. 表达式模板
表达式模板允许将文本与一个或多个计算块混合。
每个评估块都用前缀和后缀字符分隔,您可以
定义。常见的选择是用作分隔符,如以下示例所示
显示:#{ }
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
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;
}
}
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;
}
}
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;
}
}
5. 使用 Spring 进行面向方面的编程
面向方面的编程 (AOP) 通过以下方式补充面向对象编程 (OOP) 提供另一种思考程序结构的方式。模块化的关键单元 在 OOP 中是类,而在 AOP 中,模块化的单位是方面。方面 实现跨关注点(如事务管理)的模块化 多种类型和对象。(这种担忧通常被称为“跨领域”关注 在 AOP 文献中。
Spring 的关键组件之一是 AOP 框架。而春季 IoC 容器不依赖于 AOP(这意味着如果您不想,则不需要使用 AOP to),AOP 补充了 Spring IoC,提供了一个非常强大的中间件解决方案。
AOP 在 Spring Framework 中用于:
-
提供声明式企业服务。此类服务中最重要的服务是声明式事务管理。
-
让用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。
如果您只对通用声明性服务或其他预打包服务感兴趣 声明式中间件服务,例如池化,您不需要直接使用 Spring AOP,可以跳过本章的大部分内容。 |
5.1. AOP 概念
让我们从定义一些核心的 AOP 概念和术语开始。这些条款不是 特定于弹簧。不幸的是,AOP 术语并不是特别直观。 但是,如果 Spring 使用自己的术语,那就更令人困惑了。
-
方面:跨多个类的关注点的模块化。 事务管理是企业 Java 中横切关注的一个很好的例子 应用。在 Spring AOP 中,各个方面是通过使用常规类实现的 (基于模式的方法)或使用注释注释的常规类(@AspectJ样式)。
@Aspect
-
连接点:程序执行过程中的一个点,例如执行 方法或异常的处理。在 Spring AOP 中,连接点总是 表示方法执行。
-
建议:特定连接点处的某个方面所采取的操作。不同类型的 建议包括“周围”、“之前”和“之后”的建议。(建议类型已讨论 后来。许多 AOP 框架,包括 Spring,将建议建模为拦截器和 在加入点周围维护一连串拦截器。
-
切入点:与连接点匹配的谓词。建议与 pointcut 表达式,并在与切入点匹配的任何连接点(例如, 执行具有特定名称的方法)。连接点作为匹配的概念 by pointcut 表达式是 AOP 的核心,Spring 使用 AspectJ 切入点 默认的表达式语言。
-
简介:代表类型声明其他方法或字段。春天 AOP 允许您将新接口(和相应的实现)引入任何 建议的对象。例如,您可以使用简介使 Bean 实现接口,以简化缓存。(介绍称为 AspectJ 社区中的 inter-type 声明。
IsModified
-
目标对象:由一个或多个方面建议的对象。也称为 “建议对象”。由于 Spring AOP 是使用运行时代理实现的,因此 object 始终是代理对象。
-
AOP 代理:由 AOP 框架创建的对象,用于实现 aspect 合约(建议方法执行等)。在 Spring Framework 中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。
-
编织:将方面与其他应用程序类型或对象链接以创建一个 建议的对象。这可以在编译时完成(使用 AspectJ 编译器,用于 example)、加载时或运行时。Spring AOP 与其他纯 Java AOP 框架一样, 在运行时执行编织。
Spring AOP 包括以下类型的建议:
-
建议之前:在连接点之前运行但未包含的建议 阻止执行流继续到连接点的能力(除非它抛出 一个例外)。
-
返回建议后:在加入点完成后运行的建议 通常(例如,如果方法返回而不引发异常)。
-
抛出建议后:如果方法退出时,通过抛出 例外。
-
在(最终)建议之后:无论通过何种方式运行 连接点退出(正常或异常返回)。
-
围绕建议:围绕连接点的建议,例如方法调用。 这是最有力的建议。围绕建议可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续到连接点,或者通过返回其 自己的返回值或抛出异常。
围绕建议是最普遍的建议。从 Spring
AOP 开始,就像 AspectJ 一样,
提供全方位的建议类型,我们建议您使用功能最弱的
可以实现所需行为的建议类型。例如,如果您只需要
使用方法的返回值更新缓存,最好实现
在返回建议后,比周围的建议,尽管周围的建议可以完成
同样的事情。使用最具体的建议类型可提供更简单的编程模型
出错的可能性较小。例如,您不需要在 used for around advice 上调用该方法,因此,您不能不调用它。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 Framework 的 AOP 功能通常用于 与 Spring IoC 容器结合使用。使用普通 Bean 配置方面 定义语法(尽管这允许强大的“自动代理”功能)。这是一个 与其他 AOP 实现的关键区别。你不能做一些事情 使用 Spring AOP 轻松或高效地,例如建议非常细粒度的对象(通常, 域对象)。在这种情况下,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 Framework 确实为您提供了将特定于 Spring Framework 的依赖项引入 代码库。为您提供此类选项的基本原理是因为,在某些情况下,它 可能只是更容易阅读或编码某些特定功能,例如 一种方式。但是,Spring Framework(几乎)总是为您提供选择:您拥有 自由决定哪个选项最适合您的特定用途 案例或场景。 与本章相关的一个选择是哪个 AOP 框架(和 选择哪种 AOP 样式。您可以选择 AspectJ、Spring AOP 或两者兼而有之。你 还可以选择@AspectJ注释样式的方法或 Spring XML 配置样式方法。事实上,本章选择介绍 首先,不应将 @AspectJ 风格的方法视为 Spring 团队的指示 与Spring XML配置样式相比,偏爱@AspectJ注释样式的方法。 请参阅选择要使用的 AOP 声明样式,以更完整地讨论 每种风格。 |
5.3. AOP 代理
Spring AOP 默认对 AOP 代理使用标准 JDK 动态代理。这 允许对任何接口(或接口集)进行代理。
Spring AOP 也可以使用 CGLIB 代理。这是代理类所必需的,而不是 接口。默认情况下,如果业务对象没有实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见)的情况下,可以强制使用 CGLIB 需要建议一个未在接口上声明的方法,或者你需要 将代理对象作为具体类型传递给方法。
重要的是要掌握Spring AOP是基于代理的事实。请参阅了解 AOP 代理,以全面了解其具体内容 实施细节实际上意味着。
5.4. @AspectJ支持
@AspectJ 指的是一种将方面声明为常规 Java 类的样式,并带有 附注。@AspectJ风格是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。春天 使用 AspectJ 提供的库解释与 AspectJ 5 相同的注释 用于切入点解析和匹配。不过,AOP 运行时仍然是纯 Spring AOP,并且 不依赖于 AspectJ 编译器或 weaver。
使用 AspectJ 编译器和 weaver 可以使用完整的 AspectJ 语言和 在将 AspectJ 与 Spring 应用程序一起使用中进行了讨论。 |
5.4.1. 启用@AspectJ支持
要在 Spring 配置中使用@AspectJ方面,您需要启用 Spring 支持 基于@AspectJ方面配置 Spring AOP,并基于 他们是否从这些方面得到建议。通过自动代理,我们的意思是,如果 Spring 确定 Bean 由一个或多个方面提供建议,它会自动生成 该 Bean 的代理,用于拦截方法调用并确保运行建议 根据需要。
可以通过 XML 或 Java
样式的配置启用@AspectJ支持。在任一
情况下,还需要确保 AspectJ 的库位于
应用程序的类路径(版本 1.8 或更高版本)。此库位于 AspectJ 发行版的目录中,也可从 Maven Central
存储库获得。aspectjweaver.jar
lib
5.4.2. 声明一个方面
启用 @AspectJ
支持后,在应用程序上下文中定义的任何 Bean 都带有
作为@AspectJ方面(具有注释)的类会自动
由 Spring 检测到并用于配置 Spring AOP。接下来的两个示例显示了
不太有用的方面所需的最低限度定义。@Aspect
这两个示例中的第一个示例显示了应用程序中的常规
Bean 定义
指向具有注解的 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
class NotVeryUsefulAspect
方面(注解的类)可以具有方法和字段,与任何
其他类。它们还可以包含切入点、建议和介绍(类型间)
声明。@Aspect
通过组件扫描自动检测方面
您可以在 Spring XML
配置中将 aspect 类注册为常规 bean,
通过类中的方法,或者让 Spring 通过
类路径扫描 — 与任何其他 Spring 管理的 Bean 相同。但是,请注意,注释不足以在类路径中进行自动检测。为此
目的,您需要添加一个单独的注释(或者,或者,一个自定义
根据 Spring
组件扫描器的规则,符合条件的构造型注解)。@Bean @Configuration @Aspect @Component
|
用其他方面提供建议?
在 Spring AOP
中,方面本身不能成为其他方面建议的目标
方面。类上的注释将其标记为一个方面,因此排除
它来自自动代理。@Aspect |
5.4.3. 声明切入点
切入点确定连接兴趣点,从而使我们能够控制
当建议运行时。Spring AOP 仅支持 Spring 的方法执行连接点
bean,因此您可以将切入点视为与 Spring 上方法的执行相匹配
豆。切入点声明由两部分组成:包含名称和任何
参数和切入点表达式,用于准确确定哪种方法
我们感兴趣的处决。在 AOP @AspectJ注释样式中,切入点
签名由正则方法定义提供,切入点表达式为
使用注释(用作切入点签名的方法)指示
必须具有返回类型)。@Pointcut
void
一个示例可能有助于区分切入点签名和切入点
表达清晰。以下示例定义了一个名为
匹配任何名为 :anyOldTransfer
transfer
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
构成批注值的切入点表达式是常规表达式
AspectJ 切入点表达式。有关 AspectJ 切入点语言的完整讨论,请参阅
方面J
编程指南(以及扩展的 AspectJ 5
Developer's Notebook)或一本关于 AspectJ 的书籍(例如 Colyer 的
Eclipse AspectJ
等人,或 AspectJ in Action,作者:Ramnivas Laddad)。@Pointcut
支持的切入点指示符
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
:限制匹配到具有给定 annotation(在具有给定注解的类型中声明的方法的执行,当 使用 Spring AOP)。 -
@annotation
:将匹配限制为连接点的主体的连接点 (在 Spring AOP 中运行的方法)具有给定的注解。
由于 Spring AOP
将匹配限制为仅方法执行连接点,因此前面的讨论
的切入点指示符给出的定义比您在
AspectJ 编程指南。此外,AspectJ 本身具有基于类型的语义,并且
执行连接点,和 引用同一个对象:
执行该方法的对象。Spring AOP 是一个基于代理的系统,具有差异化
在代理对象本身(绑定到 )和 后面的目标对象之间
proxy(绑定到
)。this
target
this
target
由于 Spring 的 AOP 框架基于代理的性质,目标对象中的调用 根据定义,不会被拦截。对于 JDK 代理,只有公共接口方法 可以截获对代理的调用。使用 CGLIB,public 和 protected 方法调用 代理被截获(如有必要,甚至是包可见的方法)。然而 通过代理进行的常见交互应始终通过公共签名进行设计。 请注意,切入点定义通常与任何截获的方法匹配。 如果切入点严格来说是仅限公共的,即使在 CGLIB 代理场景中 通过代理进行的潜在非公开互动,需要相应地定义。 如果您的拦截需求包括目标中的方法调用甚至构造函数 类,请考虑改用 Spring 驱动的原生 AspectJ 编织 Spring 基于代理的 AOP 框架。这构成了 AOP 使用的不同模式 具有不同的特点,所以一定要让自己熟悉编织 在做出决定之前。 |
Spring AOP 还支持一个名为
.此 PCD 允许您限制
连接点与特定命名的 Spring Bean 或一组命名的
Spring bean(使用通配符时)。PCD 具有以下形式:bean
bean
bean(idOrNameOfBean)
令牌可以是任何 Spring
Bean 的名称。有限的通配符
提供了使用该字符的支持,因此,如果您建立一些命名
Spring bean 的约定,您可以编写一个 PCD 表达式
以选择它们。与其他切入点指示符一样,PCD 可以
也可以与 (and)、(or) 和 (negation)
运算符一起使用。idOrNameOfBean
*
bean
bean
&&
||
!
PCD 仅在
Spring AOP 中受支持,而在
原生 AspectJ 编织。它是 Spring 对标准 PCD 的扩展,它
AspectJ 定义了模型中声明的方面,因此不可用。 PCD
在实例级别运行(基于 Spring Bean 名称构建
concept),而不是仅在类型级别(基于编织的 AOP 仅限于此级别)。
基于实例的切入点指示符是 Spring 的一项特殊功能
基于代理的 AOP 框架及其与 Spring Bean 工厂的紧密集成,其中
通过名称识别特定豆类是自然而直接的。 |
组合切入点表达式
可以使用 和
组合切入点表达式。您也可以参考
按名称划分的切入点表达式。以下示例显示了三个切入点表达式:&&,
||
!
@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
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() {
}
}
您可以在任何需要的地方参考在此类方面定义的切入点 切入点表达式。例如,要使服务层具有事务性,您可以 写下以下内容:
<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 支持中进行了讨论。这
事务元素在事务管理中讨论。<aop:config>
<aop:advisor>
例子
Spring AOP
用户可能最常使用切入点指示符。
执行表达式的格式如下:execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除返回类型模式(在前面的代码片段中)之外的所有部分,
名称模式和参数模式是可选的。返回类型模式确定
方法的返回类型必须是什么,才能匹配联接点。 最常用作返回类型模式。它与任何回报相匹配
类型。仅当方法返回给定的
类型。名称模式与方法名称匹配。您可以使用通配符作为 all 或
名称模式的一部分。如果指定声明类型模式,
包括尾随以将其连接到名称模式组件。
参数模式稍微复杂一些:匹配
不带参数的方法,而匹配任意数量(零个或多个)参数。
该模式与采用任何类型的一个参数的方法匹配。 匹配采用两个参数的方法。第一个可以是任何类型,而
第二个必须是 .咨询语言
AspectJ
编程指南的语义部分了解更多信息。ret-type-pattern
*
*
.
()
(..)
(*)
(*,String)
String
以下示例显示了一些常见的切入点表达式:
-
任何公共方法的执行:
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..*)
-
代理实现接口的任何连接点(仅在 Spring AOP 中执行方法):
AccountService
this(com.xyz.service.AccountService)
this
更常以装订形式使用。有关如何使代理对象在建议正文中可用,请参阅声明建议部分。 -
目标对象所在的任何连接点(仅在 Spring AOP 中执行方法) 实现以下接口:
AccountService
target(com.xyz.service.AccountService)
target
更常以装订形式使用。请参阅“声明建议”部分 了解如何使目标对象在建议正文中可用。 -
任何采用单个参数的连接点(仅在 Spring AOP 中执行方法) 其中在运行时传递的参数是:
Serializable
args(java.io.Serializable)
args
更常以装订形式使用。请参阅“声明建议”部分 了解如何使方法参数在建议正文中可用。请注意,此示例中给出的切入点与 不同。如果在运行时传递的参数是 ,则 args 版本匹配,如果方法签名声明单个 类型的参数。
execution(* *(java.io.Serializable))
Serializable
Serializable
-
目标对象具有注释的任何连接点(仅在 Spring AOP 中执行方法):
@Transactional
@target(org.springframework.transaction.annotation.Transactional)
您也可以以绑定形式使用。请参阅“声明建议”部分,了解 如何使注释对象在建议正文中可用。 @target
-
任何连接点(仅在 Spring AOP 中执行方法),其中声明的 目标对象有一个注释:
@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 上名为
tradeService
bean(tradeService)
-
Spring Bean 上的任何连接点(仅在 Spring AOP 中执行方法)的名称 匹配通配符表达式:
*Service
bean(*Service)
写出好的切入点
在编译过程中,AspectJ 会处理切入点以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态)给定的切入点是一个成本高昂的过程。(动态匹配是指匹配 无法从静态分析中完全确定,并且测试被放置在代码中 确定代码运行时是否存在实际匹配项)。初次遇到 切入点声明,AspectJ 将其重写为匹配的最佳形式 过程。这是什么意思?基本上,切入点是用 DNF 重写的(析取 Normal Form) 和对切入点的分量进行排序,以便这些分量 首先检查评估成本较低的产品。这意味着您不必担心 关于了解各种切入点指示符的性能,并可能提供它们 在切入点声明中以任何顺序。
但是,AspectJ 只能使用它所告诉的内容。为了实现最佳性能 匹配时,您应该考虑他们想要实现的目标并缩小搜索范围 在定义中尽可能多地留出匹配空间。现有指示符 自然属于以下三组之一:kinded、scopeping 和 contextual:
-
种类指示符选择特定类型的连接点:、、、和 。
execution
get
set
call
handler
-
范围指示符选择一组联接兴趣点 (可能有很多种):和
within
withincode
-
上下文指示符根据上下文匹配(并选择性地绑定):、 和
this
target
@annotation
一个写得好的切入点应该至少包括前两种类型(kinded 和 范围界定)。您可以包含要根据以下条件进行匹配的上下文指示符 加入点上下文或绑定该上下文以在建议中使用。仅提供 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),由于额外的处理和分析。范围 指示符的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速地匹配 忽略不应进一步处理的联接点组。一个好的 如果可能,Pointcut 应始终包含一个。
5.4.4. 声明建议
建议与切入点表达式相关联,并在之前、之后或前后运行 与切入点匹配的方法执行。切入点表达式可以是 对已命名的切入点或就地声明的切入点表达式的简单引用。
咨询前
您可以使用注释在某个方面的建议之前声明:@Before
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
class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
fun doAccessCheck() {
// ...
}
}
回馈建议后
返回后,当匹配的方法执行正常返回时,建议运行。
您可以使用注解来声明它:@AfterReturning
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
您可以有多个建议声明(以及其他成员), 都在同一个方面。我们在这些中只显示了一个建议声明 示例来集中每个效果。 |
有时,您需要在建议正文中访问返回的实际值。
您可以使用绑定返回值的形式来获取该
access,如以下示例所示:@AfterReturning
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
returning
Object
请注意,在以下情况下,无法返回完全不同的引用 返回建议后使用。
抛出建议后
抛出后,当匹配的方法执行通过抛出
例外。您可以使用注解来声明它,因为
以下示例显示:@AfterThrowing
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doRecoveryActions() {
// ...
}
}
通常,您希望建议仅在引发给定类型的异常时运行,
而且,您还经常需要访问建议正文中引发的异常。您可以
使用该属性既可以限制匹配(如果需要,则用作异常类型),也可以将引发的异常绑定到 advice 参数。
以下示例演示如何执行此操作:throwing
Throwable
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) {
// ...
}
}
属性中使用的名称必须与
建议方法。当方法执行通过引发异常而退出时,异常
作为相应的参数值传递给 advice 方法。子句
还将匹配限制为仅那些引发
指定的类型(在本例中为)。throwing
throwing
DataAccessException
请注意,这并不表示常规异常处理回调。
具体来说,建议方法应该只接收异常
从连接点(用户声明的目标方法)本身,而不是从随附的 / 方法。 |
之后(最后)建议
之后(最终)建议在匹配的方法执行退出时运行。它是由
使用注释。建议后必须准备好处理正常和
异常返回条件。它通常用于释放资源和类似
目的。以下示例显示了如何使用 after finally 建议:@After
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@Aspect
class AfterFinallyExample {
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doReleaseLock() {
// ...
}
}
请注意,AspectJ
中的建议被定义为“在最终建议之后”,类似于
添加到 try-catch 语句中的 finally 块。它将被调用用于任何结果,
从连接点抛出的正常返回或异常(用户声明的目标方法),
相比之下,它仅适用于成功的正常回报。 |
周边建议
最后一种建议是关于建议的。围绕建议“围绕”匹配 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定方法何时、如何运行,甚至是否实际运行。 如果您需要在方法之前和之后共享状态,则经常使用围绕建议 以线程安全的方式执行,例如,启动和停止计时器。
始终使用符合您要求的最小形式的建议。 例如,如果之前的建议足以满足您的需求,请不要使用周围建议。 |
围绕建议是通过用注释注释方法来声明的。这
方法应声明为其返回类型,以及该方法的第一个参数
必须为 类型。在建议方法的正文中,您必须
调用,以便基础方法
跑。不带参数的调用将导致调用方的原始
在调用基础方法时提供给基础方法的参数。用于高级用途
情况下,该方法有一个重载变体,它接受一个数组
参数 ()。数组中的值将用作
基础方法。@Around
Object
ProceedingJoinPoint
proceed()
ProceedingJoinPoint
proceed()
proceed()
Object[]
使用 an
调用时的行为与
由 AspectJ 编译器编译的 for around 建议的行为。对于周围
使用传统的 AspectJ 语言编写的建议,传递给的参数数量必须与传递给周围建议的参数数量相匹配(而不是数量
底层连接点所采用的参数),以及传递的值以在
给定参数位置取代了实体连接点处的原始值,
值被绑定到(如果现在没有意义,请不要担心)。 Spring
采用的方法更简单,并且与其基于代理的方法更匹配,
仅执行语义。只有当你编译为 Spring 编写的方面并与 AspectJ
一起使用参数时,你才需要意识到这种差异
编译器和编织器。有一种方法可以编写 100% 兼容的
Spring AOP 和 AspectJ,这将在下一节中讨论建议参数。 |
around advice 返回的值是
方法。例如,一个简单的缓存方面可以从缓存中返回一个值(如果有)
一个或调用(并返回该值),如果没有。请注意,可以调用一次、多次,或者根本不在建议的正文中调用。都
这些都是合法的。proceed()
proceed
如果将 around advice 方法的返回类型声明为
,将始终返回给调用方,从而有效地忽略任何调用的结果
之。因此,建议使用一种围绕建议的方法声明退货
的类型。advice 方法通常应返回从
调用 ,即使基础方法具有返回类型。
但是,建议可以选择返回缓存值、包装值或其他一些值
值取决于用例。void null proceed() Object proceed() void
|
以下示例演示如何使用 around 建议:
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
提供了完全类型化的建议,这意味着您可以在
建议签名(正如我们之前在返回和抛出示例中看到的那样)而不是
始终使用数组。我们看到如何使论证和其他上下文相关
本节后面部分可供建议机构使用的值。首先,我们来看看如何
撰写通用建议,了解该建议当前建议的方法。Object[]
访问当前JoinPoint
任何建议方法都可以声明一个
类型的参数作为其第一个参数。请注意,声明第一个需要周围的建议
类型的参数,它是 的子类。org.aspectj.lang.JoinPoint
ProceedingJoinPoint
JoinPoint
该接口提供了许多有用的方法:JoinPoint
-
getArgs()
:返回方法参数。 -
getThis()
:返回代理对象。 -
getTarget()
:返回目标对象。 -
getSignature()
:返回建议的方法的说明。 -
toString()
:打印建议方法的有用说明。
有关更多详细信息,请参阅 javadoc。
将参数传递给建议
我们已经了解了如何绑定返回值或异常值(使用
after
返回并在提出建议后)。使参数值可用于建议
body,则可以使用 的绑定形式。如果使用参数名称代替
type name,则相应参数的值将作为
调用建议时的参数值。一个例子应该更清楚地说明这一点。
假设您要建议执行以对象为第一个参数的 DAO 操作,并且您需要访问建议正文中的帐户。
你可以写以下内容:args
args
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 fun accountDataAccessOperation(account: Account) {
}
@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
// ...
}
有关更多详细信息,请参阅 AspectJ 编程指南。
代理对象 ()、目标对象
() 和注释 (、、 和 ) 都可以以类似的方式绑定。下一个
两个示例演示如何匹配带有注释的方法的执行并提取审计代码:this
target
@within
@target
@annotation
@args
@Auditable
两个示例中的第一个示例显示了注释的定义:@Auditable
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)
两个示例中的第二个示例显示了与方法执行相匹配的建议:@Auditable
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
val code = auditable.value()
// ...
}
建议参数和泛型
Spring AOP 可以处理类声明和方法参数中使用的泛型。假设 您有一个泛型类型,如下所示:
interface Sample<T> {
fun sampleGenericMethod(param: T)
fun sampleGenericCollectionMethod(param: Collection<T>)
}
您可以通过以下方式将方法类型的截获限制为某些参数类型 将 advice 参数绑定到要截获方法的参数类型:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
// Advice implementation
}
此方法不适用于泛型集合。所以你不能定义一个 切入点如下:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
// Advice implementation
}
为了完成这项工作,我们必须检查集合的每个元素,但事实并非如此
合理,因为我们也无法决定如何对待一般的价值观。实现
与此类似,您必须手动键入参数
检查元素的类型。null
Collection<?>
确定参数名称
建议调用中的参数绑定依赖于匹配切入点中使用的名称 在 advice 和 pointcut 方法签名中声明的参数名称的表达式。
本节可以互换使用术语 argument 和 parameter,因为 AspectJ API 将参数名称称为参数名称。 |
Spring AOP 使用以下实现来确定
参数名称。每个发现者都有机会发现参数名称,并且
第一个成功的发现者获胜。如果所有已注册的发现者都无法
确定参数名称时,将引发异常。ParameterNameDiscoverer
AspectJAnnotationParameterNameDiscoverer
-
使用已显式命名的参数名称 由用户通过相应建议中的属性指定,或 切入点注释。有关详细信息,请参阅显式参数名称。
argNames
KotlinReflectionParameterNameDiscoverer
-
使用 Kotlin 反射 API 确定 参数名称。仅当类路径上存在此类 API 时,才使用此发现器。 在 GraalVM 本机映像中不受支持。
StandardReflectionParameterNameDiscoverer
-
使用标准 API 确定参数名称。要求使用 的标志编译代码。Java 8+ 上的推荐方法。
java.lang.reflect.Parameter
-parameters
javac
LocalVariableTableParameterNameDiscoverer
-
分析可用的局部变量表 在 advice 类的字节码中,根据调试信息确定参数名称。 要求使用调试符号(至少)编译代码。荒废的 从 Spring Framework 6.0 开始,在 Spring Framework 6.1 中删除以支持编译 使用 的代码。在 GraalVM 本机映像中不受支持,除非相应的 类文件作为资源存在于映像中。
-g:vars
-parameters
AspectJAdviceParameterNameDiscoverer
-
从切入点推断参数名称 表达式、 和子句。有关所用算法的详细信息,请参阅 javadoc。
returning
throwing
显式参数名称
@AspectJ建议和切入点注释具有可选属性,即
可用于指定带批注方法的参数名称。argNames
如果
AspectJ 编译器 () 编译了 @AspectJ 方面,即使没有
调试信息,则无需添加属性,因为编译器
保留所需的信息。 同样,如果已使用该标志编译了@AspectJ方面,则无需添加该属性,因为编译器会保留
需要的信息。 |
以下示例演示如何使用该属性:argNames
@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
}
如果第一个参数的类型为
、 或 ,则可以从属性值中省略参数的名称。例如,如果修改上述建议以接收联接
point
对象,属性不需要包含它:JoinPoint
ProceedingJoinPoint
JoinPoint.StaticPart
argNames
argNames
@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()")
fun audit(jp: JoinPoint) {
// ... use jp
}
继续参数
我们之前说过,我们将描述如何用
参数,在 Spring AOP 和 AspectJ 中始终如一地工作。解决方案是
以确保 Advice 签名按顺序绑定每个方法参数。
以下示例演示如何执行此操作:proceed
@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 相同的优先级规则来确定建议的顺序 执行。最高优先级的建议首先运行“在进入的途中”(因此,给定两个部分 在建议之前,优先级最高的建议首先运行)。“在出门的路上” 加入点,最高优先级建议最后运行(因此,给定两条之后 建议,优先级最高的建议将排在第二位)。
当在不同方面定义的两条建议需要同时运行时
联接点,除非另有指定,否则执行顺序未定义。您可以
通过指定优先级来控制执行顺序。这是在正常情况下完成的
Spring 方式,通过实现
Aspect 类或使用注解对其进行注释。给定两个方面,
从(或注释值)返回较低值的方面具有
优先级越高。org.springframework.core.Ordered
@Order
Ordered.getOrder()
从概念上讲,特定方面的每种不同建议类型都应适用
直接连接到连接点。因此,建议方法不是
应该从随附的 / 方法接收异常。 从
Spring Framework 5.2.7 开始,建议方法在同一个类中定义
需要在同一加入点运行的优先级会根据其建议类型分配优先级
按从高到低的顺序排列:、、、、。但是请注意,建议方法将
在任何或建议方法之后有效调用
在同一方面,遵循 AspectJ 的 “after finally advice” 语义。 当两条相同类型的建议(例如,两种建议方法)时
在同一个类中定义的都需要在同一个连接点上运行,排序
未定义(因为无法通过
javac 编译类的反射)。考虑将此类建议方法合并为一种
每个类中每个连接点的建议方法或将建议片段重构为
您可以通过 或
在宽高比级别对单独的类进行排序。 |
5.4.5. 简介
引入(在 AspectJ 中称为类型间声明)使一个方面能够声明 建议对象实现给定的接口,并提供 该接口代表这些对象。
您可以使用注释进行介绍。此注解
用于声明匹配类型具有新的父级(因此得名)。例如
给定一个名为 的接口和该接口的实现,以下方面声明 服务的所有实现者
接口也实现了该接口(例如,通过 JMX 进行统计):@DeclareParents
UsageTracked
DefaultUsageTracked
UsageTracked
@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()
}
}
要实现的接口由带注释的字段的类型决定。批注的属性是
AspectJ 类型模式。任何
匹配类型的 Bean 实现接口。请注意,在
在前面的示例建议之前,服务 Bean 可以直接用作
接口的实现。如果以编程方式访问 bean,
你会写以下内容:value
@DeclareParents
UsageTracked
UsageTracked
val usageTracked = context.getBean("myService") as UsageTracked
5.4.6. Aspect 实例化模型
这是一个高级主题。如果您刚刚开始使用 AOP,您可以安全地跳过 直到以后。 |
默认情况下,应用程序中每个方面都有一个实例
上下文。AspectJ 将其称为单例实例化模型。可以定义
具有交替生命周期的方面。Spring 支持 AspectJ 和实例化模型;、 和 目前没有
支持。perthis
pertarget
percflow
percflowbelow
pertypewithin
您可以通过在注释中指定子句来声明一个方面。请看以下示例:perthis
perthis
@Aspect
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {
private val someState: Int = 0
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
fun recordServiceUsage() {
// ...
}
}
在前面的示例中,子句的效果是
one aspect 实例
是为执行业务服务的每个唯一服务对象创建的(每个唯一的
绑定到与切入点表达式匹配的连接点的对象)。方面
首次在服务对象上调用方法时,将创建实例。这
当服务对象超出范围时,方面超出范围。在方面之前
实例已创建,但其中的任何建议都不会运行。一旦 aspect 实例
已创建,其中声明的建议在匹配的连接点运行,但仅
当服务对象是与此方面关联的对象时。查看 AspectJ
编程指南以获取有关子句的更多信息。perthis
this
per
实例化模型的工作方式与
完全相同,但它
在匹配的连接点处为每个唯一目标对象创建一个 Aspect 实例。pertarget
perthis
5.4.7. AOP 示例
现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起来做 有用的东西。
业务服务的执行有时可能会由于并发问题而失败(对于
例如,死锁失败者)。如果重试该操作,则很可能会成功
在下一次尝试时。对于适合重试的业务服务,在此类服务中重试
条件(幂等操作,无需返回给用户进行冲突
resolution),我们希望透明地重试该操作,以避免客户端看到 .这是一项明确跨越的要求
服务层中的多个服务,因此非常适合通过
方面。PessimisticLockingFailureException
因为我们想重试操作,所以我们需要使用around建议,以便我们可以
多次通话。以下清单显示了基本方面的实现:proceed
@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
}
}
请注意,该方面实现了接口,以便我们可以设置
高于交易建议的方面(我们每次都想要一笔新的交易
重试)。和属性都是由 Spring 配置的。这
主要行动发生在周围的建议中。请注意,对于
时刻,我们将重试逻辑应用于每个 .我们试着继续,
如果我们失败了,我们会再试一次,除非
我们已用尽所有重试尝试。Ordered
maxRetries
order
doConcurrentOperation
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(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation
然后,我们可以使用注解来注解服务操作的实现。变化
对于仅重试幂等操作的方面,涉及优化切入点
表达式,以便只有操作匹配,如下所示:@Idempotent
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
// ...
}
5.5. 基于模式的 AOP 支持
如果您更喜欢基于 XML 的格式,Spring
还支持定义方面
使用命名空间标记。完全相同的切入点表达式和建议类型
与使用@AspectJ样式时一样。因此,在本节中,我们将重点关注
该语法,并让读者参考上一节中的讨论
(@AspectJ支持)用于理解编写切入点表达式和绑定
建议参数。aop
若要使用本节中描述的 aop
命名空间标记,您需要导入架构,如基于 XML 架构的配置中所述。请参阅 AOP
架构,了解如何在命名空间中导入标签。spring-aop
aop
在 Spring 配置中,所有 aspect 和
advisor 元素都必须放在
一个元素(您可以在
应用程序上下文配置)。元素可以包含切入点、
advisor 和 aspect
元素(请注意,这些元素必须按该顺序声明)。<aop:config>
<aop:config>
<aop:config>
配置风格大量使用了
Spring 的自动代理机制。这可能会导致问题(例如建议
not being woven),如果您已经通过使用或类似的东西使用了显式自动代理。推荐的使用模式是
仅使用样式或仅使用样式和
切勿将它们混合使用。<aop:config> BeanNameAutoProxyCreator <aop:config> AutoProxyCreator
|
5.5.1. 声明一个方面
当您使用模式支持时,一个方面是定义为 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 对象的方法,切入点和建议信息被捕获在 XML 中。
您可以使用元素声明一个方面,并引用后备
Bean
通过使用该属性,如以下示例所示:<aop:aspect>
ref
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
当然,支持方面(在本例中)的
bean 可以配置为
依赖注入就像任何其他 Spring Bean 一样。aBean
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方面大致相同,使用基于架构的模式声明切入点
定义样式可以收集连接点上下文。例如,以下切入点
将对象收集为连接点上下文,并将其传递给 Advice:this
<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>
必须声明建议以接收收集的联接点上下文,方法是包括 匹配名称的参数,如下所示:
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 引用,不能
用作命名切入点以形成复合切入点。命名的切入点支持
因此,基于架构的定义样式比@AspectJ提供的定义样式更受限制
风格。id
5.5.3. 声明建议
基于模式的 AOP 支持使用与 @AspectJ 样式相同的五种建议,并且它们具有 完全相同的语义。
咨询前
在匹配方法执行之前运行建议之前。它是使用元素在
an 中声明的,如以下示例所示:<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样式时所指出的,使用命名的切入点可以 显著提高代码的可读性。
该属性标识一个方法
(),该方法提供
建议。必须为 aspect 元素引用的 bean 定义此方法
包含建议。在执行数据访问操作之前(方法执行
连接点与切入点表达式匹配),方面的方法
Bean
被调用。method
doAccessCheck
doAccessCheck
回馈建议后
返回后,当匹配的方法执行正常完成时,建议运行。是的
以与建议之前相同的方式在内部声明。以下示例
演示如何声明它:<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
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
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
之后(最后)建议
在(最终)建议运行之后,无论匹配的方法执行如何退出。
可以使用该元素声明它,如以下示例所示:after
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是关于建议的。围绕建议“围绕”匹配 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定方法何时、如何运行,甚至是否实际运行。 如果您需要在方法之前和之后共享状态,则经常使用围绕建议 以线程安全的方式执行,例如,启动和停止计时器。
始终使用符合您要求的最小形式的建议。 例如,如果之前的建议足以满足您的需求,请不要使用周围建议。 |
您可以使用该元素声明
around advice。建议方法应
declare 作为其返回类型,并且该方法的第一个参数必须为
类型。在 advice 方法的主体中,必须调用 on 才能运行基础方法。
不带参数的调用将导致调用方的原始参数
在调用基础方法时提供给基础方法。对于高级用例,有
是接受参数数组的方法的重载变体
().数组中的值将用作基础的参数
方法。有关使用
.aop:around
Object
ProceedingJoinPoint
proceed()
ProceedingJoinPoint
proceed()
proceed()
Object[]
proceed
Object[]
下面的示例演示如何在 XML 中声明 around 建议:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
建议的实施可以与
@AspectJ示例(当然,减去注释),如以下示例所示:doBasicProfiling
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return pjp.proceed()
}
建议参数
基于架构的声明样式支持完全类型化建议的方式与
针对 @AspectJ 支持进行描述 — 通过按名称匹配切入点参数
建议方法参数。有关详细信息,请参阅建议参数。如果你愿意
显式指定 advice 方法的参数名称(不依赖于
检测策略),可以使用 advice 元素的属性来执行此操作,该属性的处理方式与 advice 注释中的属性相同(如确定参数名称中所述)。
下面的示例演示如何在 XML
中指定参数名称:arg-names
argNames
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
该属性接受以逗号分隔的参数名称列表。arg-names
下面基于 XSD 的方法的稍微复杂一些的示例显示 一些建议与一些强类型参数结合使用:
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
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>
请考虑以下驱动程序脚本:
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)
建议订购
当多条建议需要在同一连接点运行时(执行方法)
排序规则如建议排序中所述。优先顺序
方面之间通过元素中的属性或
通过将注释添加到支持该方面的 Bean 中,或者将
Bean
实现接口。order
<aop:aspect>
@Order
Ordered
与在同一类中定义的建议方法的优先规则相反,当在同一元素中定义的两条建议都需要
在同一连接点上运行,优先级由建议的顺序决定
元素在封闭元素中声明,从最高到最低
优先。 例如,给定一个建议和在同一元素中定义的适用于同一连接点的建议,为确保该建议的优先级高于建议,该元素必须
在元素之前声明。 作为一般经验法则,如果您发现您定义了多条建议
在应用于同一连接点的同一元素中,请考虑折叠
此类建议方法在每个元素中的每个连接点中都变成一个建议方法
或者将建议重构为可以订购的单独元素
在纵横比级别。 |
5.5.4. 简介
引入(在 AspectJ 中称为类型间声明)让一个方面声明 建议对象实现给定的接口并提供 该接口代表这些对象。
您可以使用 .
可以使用该元素声明匹配类型具有新的父级(因此得名)。
例如,给定一个名为 的接口和一个名为 的接口的实现,以下方面声明服务的所有实现者
接口也实现该接口。(为了公开统计信息
例如,通过 JMX。aop:declare-parents
aop:aspect
aop:declare-parents
UsageTracked
DefaultUsageTracked
UsageTracked
<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>
然后,支持 Bean
的类将包含以下方法:usageTracking
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
要实现的接口由属性确定。这
属性的值是 AspectJ 类型模式。任何 Bean 的
匹配类型实现接口。请注意,在之前
在前面的例子中,服务 Bean 可以直接用作
界面。要以编程方式访问 bean,您可以编写
以后:implement-interface
types-matching
UsageTracked
UsageTracked
val usageTracked = context.getBean("myService") as UsageTracked
5.5.6. 顾问
“advisors”的概念来源于 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接等价物。顾问就像一个小 自成一体的方面,有一条建议。建议本身是 由 Bean 表示,并且必须实现 Spring 中的建议类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。
Spring 通过元素支持 advisor
概念。你最
通常将其与交易建议结合使用,交易建议也有自己的
Spring 中的命名空间支持。以下示例显示了一个顾问程序:<aop:advisor>
<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 模式示例
本部分介绍使用 schema 支持重写 AOP 示例中的并发锁定失败重试示例时的外观。
业务服务的执行有时可能会由于并发问题而失败(对于
例如,死锁失败者)。如果重试该操作,则很可能会成功
在下一次尝试时。对于适合重试的业务服务,在此类服务中重试
条件(幂等操作,无需返回给用户进行冲突
resolution),我们希望透明地重试该操作,以避免客户端看到 .这是一项明确跨越的要求
服务层中的多个服务,因此非常适合通过
方面。PessimisticLockingFailureException
因为我们想重试操作,所以我们需要使用around建议,以便我们可以
多次通话。下面的清单显示了基本方面实现
(这是一个使用模式支持的常规 Java 类):proceed
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
}
}
请注意,该方面实现了接口,以便我们可以设置
高于交易建议的方面(我们每次都想要一笔新的交易
重试)。和属性都是由 Spring 配置的。这
主要操作发生在 Around Advice 方法中。我们尝试
进行。如果我们失败了,我们再试一次,
除非我们已经用尽了所有的重试尝试。Ordered
maxRetries
order
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(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 编译器/weaver 引入您的开发中 并构建流程。如果你只需要建议在 Spring 上执行操作 豆,Spring AOP是正确的选择。如果您需要建议不受 Spring 容器(例如域对象,通常),您需要使用 方面J。如果您希望建议加入点以外的连接点,您还需要使用 AspectJ 简单的方法执行(例如,字段获取或设置连接点等)。
使用 AspectJ 时,可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果您不使用 Java 5+,已经为你做出了选择:使用代码样式。如果方面发挥大作用 角色,并且您可以使用 AspectJ Eclipse 的开发工具 (AJDT) 插件,AspectJ 语言语法是 首选选项。它更干净、更简单,因为语言是有目的的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中不起主要作用,您可能需要考虑使用 @AspectJ风格,在 IDE 中坚持常规 Java 编译,并添加 构建脚本的 Aspect Weaving 阶段。
5.6.2. Spring AOP 的 @AspectJ 或 XML?
如果您选择使用 Spring AOP,则可以选择@AspectJ或 XML 样式。 需要考虑各种权衡。
XML风格对于现有的Spring用户来说可能是最熟悉的,它是由真正的 POJO的。当使用 AOP 作为配置企业服务的工具时,XML 可能是一个很好的选择 选择(一个很好的测试是你是否认为切入点表达式是你的 您可能希望单独更改的配置)。使用 XML 样式时,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。
XML 样式有两个缺点。首先,它没有完全封装 在一个地方实现它所解决的需求。DRY原则说 任何一块都应该有一个单一的、明确的、权威的表示 系统内的知识。当使用XML样式时,如何了解需求 在后备 Bean 类的声明和 XML 中实现 配置文件。使用@AspectJ样式时,此信息将被封装 在一个模块中:方面。其次,XML样式在什么方面略有限制 它可以表达比@AspectJ风格:只有“单例”方面实例化模型 ,并且无法组合在 XML 中声明的命名切入点。 例如,在@AspectJ样式中,您可以编写如下内容:
@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. 混合 Aspect 类型
通过使用自动代理支持,完全可以混合@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 代理,请设置属性的值
元素设置为 true,如下所示:proxy-target-class
<aop:config>
<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"/>
多个部分被折叠成一个统一的自动代理创建器
在运行时,它应用任何部分(通常来自不同的 XML Bean 定义文件)指定的最强代理设置。
这也适用于 and 元素。 需要明确的是,使用
on 、 或 元素会强制使用 CGLIB
这三个人的代理。 |
5.8.1. 理解 AOP 代理
Spring AOP 是基于代理的。掌握 在你写你自己的方面或使用任何一个之前,最后这句话的实际含义是什么 Spring Framework 提供的基于 Spring AOP 的方面。
首先考虑这样一个场景:你有一个普通的、无代理的、 没什么特别的,直接的对象引用,如下 代码片段显示:
class SimplePojo : Pojo {
fun foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar()
}
fun bar() {
// some logic...
}
}
如果在对象引用上调用方法,则该方法将直接在 该对象引用,如下图和列表所示:

fun main() {
val pojo = SimplePojo()
// this is a direct method call on the 'pojo' reference
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,如以下示例所示:
class SimplePojo : Pojo {
fun foo() {
// this works, but... gah!
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
这将你的代码完全耦合到 Spring AOP,并使类本身意识到 事实上,它被用于 AOP 上下文,这与 AOP 背道而驰。它 在创建代理时还需要一些额外的配置,因为 以下示例显示:
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代理
除了使用 或
声明配置中的方面之外,还可以以编程方式创建代理
建议目标对象。有关 Spring 的 AOP API 的完整详细信息,请参阅下一章。在这里,我们要重点介绍自动
使用@AspectJ方面创建代理。<aop:config>
<aop:aspectj-autoproxy>
您可以使用该类
为一个或多个@AspectJ方面建议的目标对象创建代理。
此类的基本用法非常简单,如以下示例所示:org.springframework.aop.aspectj.annotation.AspectJProxyFactory
// 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. 在 Spring 应用程序中使用 AspectJ
到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。在本节中, 我们将探讨如何使用 AspectJ 编译器或 weaver 来代替 或 如果您的需求超出了 Spring AOP 提供的设施,则除了 Spring AOP 之外 独自。
Spring 附带了一个小型的 AspectJ
方面库,该库可在
分布为 。您需要按顺序将其添加到您的类路径中
以使用其中的方面。使用 AspectJ 将 Domain 对象与 Spring 和 AspectJ 的其他 Spring 方面联系起来,讨论
此库的内容以及如何使用它。使用 Spring IoC 配置 AspectJ 方面讨论了如何
依赖项注入使用 AspectJ 编译器编织的 AspectJ 方面。最后,Spring Framework 中的 Load-time Weaving with AspectJ 介绍了
Spring 应用程序的加载时编织
使用 AspectJ。spring-aspects.jar
5.10.1. 使用 AspectJ 依赖 Spring 注入域对象
Spring 容器实例化和配置应用程序中定义的
bean
上下文。也可以要求 Bean 工厂配置预先存在的
对象,给定包含要应用的配置的 Bean 定义的名称。 包含一个注释驱动的方面,它利用了这一点
允许任何对象的依赖关系注入的能力。该支持旨在
用于在任何容器控制之外创建的对象。域对象
通常属于这一类,因为它们通常是使用运算符或由 ORM
工具作为数据库查询的结果以编程方式创建的。spring-aspects.jar
new
该注释将类标记为符合 Spring
驱动的条件
配置。在最简单的情况下,您可以纯粹将其用作标记注释,因为
以下示例显示:@Configurable
package com.xyz.myapp.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable
class Account {
// ...
}
当以这种方式用作标记接口时,Spring
会配置
注释类型(在本例中为)通过使用 Bean 定义(通常
prototype-scoped),与完全限定的类型名称同名
().由于 Bean 的默认名称是
其类型的完全限定名称,一种声明原型定义的便捷方式
是省略该属性,如以下示例所示:Account
com.xyz.myapp.domain.Account
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")
class Account {
// ...
}
Spring 现在寻找一个名为 Bean
定义并将其用作
定义来配置新实例。account
Account
您还可以使用自动布线来避免在
都。要让 Spring 应用自动布线,请使用注解的属性。您可以按类型或名称指定自动接线的 或,
分别。作为替代方法,最好指定显式的、注释驱动的
通过字段或方法级别或在字段或方法级别注入 Bean 的依赖关系(有关详细信息,请参阅基于注释的容器配置)。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
请注意,单独使用注释不会执行任何操作。正是
in 作用于
注释。从本质上讲,该方面说,“从初始化返回后
用 批注了 的新对象,配置新创建的对象
根据注解的属性使用 Spring”。在此背景下,
“初始化”是指新实例化的对象(例如,实例化的对象
与运算符)以及正在经历的对象
反序列化(例如,通过 readResolve())。AnnotationBeanConfigurerAspect
spring-aspects.jar
@Configurable
new
Serializable
上一段中的关键词之一是“实质上”。在大多数情况下,
“after returned from the initialization of a new object”的确切语义是
好。在此上下文中,“初始化后”意味着依赖项是
在对象构造完成后注入。这意味着依赖项
不能在类的构造函数体中使用。如果您想要
在构造函数体运行之前注入的依赖项,因此
可在构造函数的主体中使用,您需要在声明中定义它,如下所示:
您可以找到有关各种切入点的语言语义的更多信息 AspectJ 中的类型 AspectJ 的附录 编程指南。 |
为此,必须使用 AspectJ
织布机编织带注释的类型。您可以
使用构建时的 Ant 或 Maven 任务来执行此操作(例如,参见 AspectJ 开发
环境指南)或加载时编织(参见 Spring Framework 中使用 AspectJ
进行加载时编织)。本身需要通过 Spring 进行配置(以便获取
对用于配置新对象的 Bean 工厂的引用)。如果你
使用基于 Java
的配置,可以添加到任何类中,如下所示:AnnotationBeanConfigurerAspect
@EnableSpringConfigured
@Configuration
@Configuration
@EnableSpringConfigured
class AppConfig {
}
如果您更喜欢基于 XML
的配置,Spring 上下文
命名空间定义了一个方便的元素,您可以按如下方式使用该元素:context:spring-configured
<context:spring-configured/>
在配置方面之前创建的对象实例
导致向调试日志发出一条消息,并且没有配置
正在发生的对象。一个示例可能是 Spring 配置中的一个 bean,它创建
domain 对象,当它被 Spring 初始化时。在这种情况下,您可以使用 bean 属性手动指定 bean 依赖于
配置方面。以下示例演示如何使用该属性:@Configurable
depends-on
depends-on
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
不要通过
Bean 配置器方面激活处理,除非您
实际上意味着在运行时依赖其语义。特别是,请确保您这样做
不用于注册为常规 Spring Bean 的 Bean 类
与容器。这样做会导致双重初始化,一次通过
容器,一次通过方面。@Configurable @Configurable |
单元测试对象@Configurable
支持的目标之一是启用独立的单元测试
的域对象,而没有与硬编码查找相关的困难。
如果 AspectJ 未编织类型,则注解没有影响
在单元测试期间。您可以在
测试并照常进行。如果类型是由 AspectJ 编织的,
您仍然可以像往常一样在容器外部进行单元测试,但您会看到一条警告
每次构造一个对象时的消息,指示它具有
未由 Spring 配置。@Configurable
@Configurable
@Configurable
@Configurable
使用多个应用程序上下文
用于实现支持的
是 AspectJ 单例方面。单例方面的作用域与作用域相同
of members:每个类加载器都有一个定义类型的 aspect 实例。
这意味着,如果在同一类装入器中定义多个应用程序上下文
层次结构,您需要考虑在哪里定义 bean 和
在类路径上的位置。AnnotationBeanConfigurerAspect
@Configurable
static
@EnableSpringConfigured
spring-aspects.jar
考虑具有共享父应用程序的典型
Spring Web 应用程序配置
定义公共业务服务的上下文,以及支持这些服务所需的一切,
以及每个 Servlet 的一个子应用程序上下文(其中包含特定定义
到该 servlet)。所有这些上下文都共存于同一个类加载器层次结构中,
因此,只能引用其中之一。
在这种情况下,我们建议在共享中定义 Bean
(父)应用程序上下文。这定义了您可能想要的服务
注入到域对象中。结果是无法配置域对象
引用在子(特定于 servlet)上下文中定义的 bean,方法是使用
@Configurable机制(无论如何,这可能不是你想做的事情)。AnnotationBeanConfigurerAspect
@EnableSpringConfigured
在同一容器中部署多个 Web
应用程序时,请确保每个
Web 应用程序使用自己的类加载器加载类型
(例如,通过将 )。如果仅添加到容器范围的类路径(因此由共享父级加载)
类加载器),所有 Web 应用程序共享相同的 aspect 实例(可能是
不是你想要的)。spring-aspects.jar
spring-aspects.jar
WEB-INF/lib
spring-aspects.jar
5.10.2. AspectJ 的其他 Spring 方面
除了 aspect 之外,还包含一个
AspectJ
可用于驱动 Spring 的类型和方法事务管理的方面
使用注释进行注释。这主要适用于以下用户
希望在 Spring 容器之外使用 Spring Framework
的事务支持。@Configurable
spring-aspects.jar
@Transactional
解释注释的方面是
.当您使用此功能时,您必须注释
实现类(或该类中的方法或两者),而不是接口(如果
any) 实现。AspectJ 遵循 Java 的规则,即注解
接口不是继承的。@Transactional
AnnotationTransactionAspect
类的注释指定了
在类中执行任何公共操作。@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 创建的方法
通过 Spring 的方面依赖于 AspectJ 实例化模型(子句)
由方面使用。per-xxx
大多数 AspectJ
方面都是单例方面。这些的配置
方面很容易。您可以创建一个 Bean 定义,该定义将 aspect 类型引用为
normal 并包含 bean 属性。这确保了
Spring 通过向 AspectJ 请求 aspect 实例来获取它,而不是尝试创建
实例本身。以下示例演示如何使用该属性:factory-method="aspectOf"
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 定义并使用 Support from 来配置方面实例,一旦它们由
AspectJ 运行时。@Configurable
spring-aspects.jar
如果您有一些@AspectJ方面想要与
AspectJ 一起编织(例如,
对域模型类型使用加载时编织)和其他所需的@AspectJ方面
与 Spring AOP 一起使用,并且这些方面都在 Spring 中配置,您
需要告诉 Spring AOP @AspectJ自动代理支持哪个确切的子集
配置中定义@AspectJ方面都应用于自动代理。您可以
为此,请在声明中使用一个或多个元素。每个元素都指定一个名称模式,并且只有
与至少一种模式匹配的名称用于 Spring AOP 自动代理
配置。以下示例演示如何使用元素:<include/>
<aop:aspectj-autoproxy/>
<include/>
<include/>
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被元素的名称所误导。使用它
导致创建Spring AOP代理。方面@AspectJ风格
此处使用了 declaration,但不涉及 AspectJ 运行时。<aop:aspectj-autoproxy/>
|
5.10.4. Spring Framework 中使用 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 允许您在
per-basis,哪个更细粒度,哪个可以赚得更多
在“单 JVM-多应用程序”环境中(例如在典型的
应用程序服务器环境)。ClassLoader
此外,在某些环境中,此支持可实现
加载时编织,无需对应用程序服务器的启动进行任何修改
需要添加或(如我们所描述的
在本节的后面部分)。开发人员配置
应用程序上下文,用于启用加载时编织,而不是依赖管理员
通常负责部署配置(例如启动脚本)的人员。-javaagent:path/to/aspectjweaver.jar
-javaagent:path/to/spring-instrument.jar
现在销售宣传已经结束,让我们首先看一下 AspectJ 的快速示例 使用 Spring 的 LTW,然后是 例。有关完整示例,请参阅 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
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() {
}
}
我们还需要创建一个文件,以通知
AspectJ 织工
我们想把我们的融入到我们的课堂中。此文件约定,即
Java 类路径上存在一个或多个文件,称为
标准 AspectJ.以下示例显示了该文件:META-INF/aop.xml
ProfilingAspect
META-INF/aop.xml
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>
现在所有必需的工件(方面、文件和
Spring 配置)都已到位,我们可以创建以下内容
驱动程序类,其中包含用于演示 LTW 运行的方法:META-INF/aop.xml
main(..)
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()
}
我们还有最后一件事要做。本节的引言确实说可以
使用 Spring 有选择地打开 LTW,这是真的。
但是,在此示例中,我们使用 Java 代理(随 Spring 提供)来打开 LTW。
我们使用以下命令来运行前面所示的类:ClassLoader
Main
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
是用于指定和启用代理的标志
来检测在 JVM 上运行的程序。Spring Framework 附带了这样一个
agent,它被打包在 中作为参数的值提供
前面的示例。-javaagent
InstrumentationSavingAgent
spring-instrument.jar
-javaagent
程序执行的输出类似于下一个示例。
(我在实现中引入了一个语句,以便分析器实际上捕获 0 以外的内容
毫秒(毫秒不是 AOP 引入的开销)。
下面的清单显示了我们在运行探查器时得到的输出:Main
Thread.sleep(..)
calculateEntitlement()
01234
Calculating entitlement StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
由于此 LTW 是通过使用成熟的
AspectJ 实现的,因此我们不仅限于提供建议
春豆。程序的以下细微变化会产生相同的结果
结果:Main
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 中 LTW 支持的基础知识 在前面的示例中都介绍了这些内容,本节的其余部分将进行说明 详细描述每一点配置和使用背后的“原因”。
此示例中使用的可能是基本的,但它非常有用。这是一个
开发人员在开发过程中可以使用的开发时方面的一个很好的例子
然后轻松地从正在部署的应用程序的构建中排除
进入UAT或生产。ProfilingAspect |
方面
您在 LTW 中使用的方面必须是 AspectJ 方面。你可以把它们写在 要么是 AspectJ 语言本身,要么你可以用 @AspectJ 风格编写你的方面。 然后,您的方面都是有效的 AspectJ 和 Spring AOP 方面。 此外,编译后的方面类需要在类路径上可用。
“元-INF/aop.xml”
AspectJ LTW 基础结构通过使用
Java 类路径上的一个或多个文件(直接或更典型的 jar 文件)进行配置。META-INF/aop.xml
此文件的结构和内容在 AspectJ 参考的 LTW 部分中有详细说明
文档。由于该文件是 100%
AspectJ,因此我们在这里不再进一步描述它。aop.xml
必需的库 (JARS)
至少需要以下库才能使用 Spring Framework 的支持 对于 AspectJ LTW:
-
spring-aop.jar
-
aspectjweaver.jar
如果您使用 Spring 提供的代理来启用 仪器,您还需要:
-
spring-instrument.jar
Spring 配置
Spring 的 LTW
支持中的关键组件是接口(在包中)和众多实现
它与 Spring 发行版一起发布。A负责
将一个或多个添加到 AT
运行时,它为各种有趣的应用程序打开了大门,其中之一
恰好是方面的LTW。LoadTimeWeaver
org.springframework.instrument.classloading
LoadTimeWeaver
java.lang.instrument.ClassFileTransformers
ClassLoader
如果您不熟悉运行时类文件转换的概念,请参阅
javadoc API 文档,然后再继续。
虽然该文档并不全面,但至少您可以看到关键接口
和类(供您在阅读本节时参考)。java.lang.instrument |
为特定对象配置 a 可以像
添加一行。(请注意,您几乎肯定需要使用 a 作为 Spring 容器——通常,a 不是
足够了,因为 LTW 支持使用
.)LoadTimeWeaver
ApplicationContext
ApplicationContext
BeanFactory
BeanFactoryPostProcessors
要启用 Spring Framework 的
LTW 支持,您需要配置一个 ,
这通常是通过使用注释来完成的,如下所示:LoadTimeWeaver
@EnableLoadTimeWeaving
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}
或者,如果您更喜欢基于 XML
的配置,请使用该元素。请注意,该元素是在命名空间中定义的。以下示例演示如何使用:<context:load-time-weaver/>
context
<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,例如 a 和 an 。
默认值是类,它尝试
来装饰自动检测到的 .“自动检测”的确切类型取决于您的运行时环境。
下表总结了各种实现:LoadTimeWeaver
AspectJWeavingEnabler
LoadTimeWeaver
DefaultContextLoadTimeWeaver
LoadTimeWeaver
LoadTimeWeaver
LoadTimeWeaver
运行环境 | LoadTimeWeaver 实现 |
---|---|
在 Apache Tomcat 中运行 |
|
在 GlassFish 中运行(仅限于 EAR 部署) |
|
|
|
在 IBM 的 WebSphere 中运行 |
|
在 Oracle WebLogic 中运行 |
|
JVM 从 Spring
开始 |
|
回退,期望底层
ClassLoader 遵循通用约定
(即和可选的方法) |
|
请注意,该表仅列出了在以下情况下自动检测到的
使用 .您可以准确指定要使用的实现。LoadTimeWeavers
DefaultContextLoadTimeWeaver
LoadTimeWeaver
要指定特定的 Java
配置,请实现接口并重写该方法。
以下示例指定:LoadTimeWeaver
LoadTimeWeavingConfigurer
getLoadTimeWeaver()
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>
配置定义和注册的可以稍后
使用众所周知的名称 从 Spring 容器中检索。
请记住,它仅作为 Spring LTW 的机制存在
基础结构来添加一个或多个 .执行 LTW 的实际是 (从
package) 类。有关更多详细信息,请参阅该类的类级 javadoc,因为具体操作方法
实际影响的编织超出了本文件的范围。LoadTimeWeaver
loadTimeWeaver
LoadTimeWeaver
ClassFileTransformers
ClassFileTransformer
ClassPreProcessorAgentAdapter
org.aspectj.weaver.loadtime
ClassPreProcessorAgentAdapter
配置的最后一个属性需要讨论:属性(或者如果您使用
XML)。此属性控制 LTW 是否
是否启用。它接受三个可能值之一,默认值为该属性不存在时。下表总结了这三点
可能的值:aspectjWeaving
aspectj-weaving
autodetect
注释值 | XML 值 | 解释 |
---|---|---|
|
|
AspectJ 编织已开启,并酌情在加载时编织方面。 |
|
|
LTW 已关闭。在加载时,没有任何方面是编织的。 |
|
|
如果 Spring LTW
基础结构可以找到至少一个文件,
然后 AspectJ 编织开启。否则,它将关闭。这是默认值。 |
特定于环境的配置
最后一部分包含您需要的任何其他设置和配置 当您在应用程序服务器和 Web 等环境中使用 Spring 的 LTW 支持时 器皿。
Tomcat、JBoss、WebSphere、WebLogic
Tomcat、JBoss/WildFly、IBM
WebSphere Application Server 和 Oracle WebLogic Server
提供能够进行本地检测的常规应用。春天的
原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。
如前所述,您可以简单地启用加载时编织。
具体来说,您无需修改 JVM 启动脚本即可添加 .ClassLoader
-javaagent:path/to/spring-instrument.jar
请注意,在 JBoss
上,你可能需要禁用应用服务器扫描,以防止它
在应用程序实际启动之前加载类。一个快速的解决方法是添加
添加到您的项目中,以以下内容命名的文件:WEB-INF/jboss-scanning.xml
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 应用程序
当在不支持的环境中需要类检测时
具体实现,JVM 代理是通用解决方案。
对于这种情况,Spring 提供了需要
特定于 Spring(但非常通用)的 JVM 代理,自动检测
通过通用和设置。LoadTimeWeaver
InstrumentationLoadTimeWeaver
spring-instrument.jar
@EnableLoadTimeWeaving
<context:load-time-weaver/>
要使用它,您必须通过提供 以下 JVM 选项:
-javaagent:/path/to/spring-instrument.jar
请注意,这需要修改 JVM 启动脚本,这可能会阻止您 在应用程序服务器环境中使用它(取决于您的服务器和 操作策略)。也就是说,对于每个 JVM 一个应用程序的部署,例如独立部署 Spring Boot 应用程序,您通常在任何情况下都可以控制整个 JVM 设置。
5.11. 更多资源
有关 AspectJ 的更多信息,请访问 AspectJ 网站。
Adrian Colyer 等人的 Eclipse AspectJ。(Addison-Wesley,2005)提供了一个 AspectJ语言的综合介绍和参考。
Ramnivas Laddad(Manning,2009 年)的 AspectJ in Action, Second Edition 非常受欢迎 推荐。本书的重点是 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
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
将接口拆分为两部分允许重用类和方法
匹配部件和细粒度组合操作(例如执行“联合”
使用另一个方法匹配器)。Pointcut
该接口用于将切入点限制为一组给定的目标
类。如果该方法始终返回 true,则所有目标类都
匹配。以下清单显示了接口定义:ClassFilter
matches()
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);
}
该方法用于测试此切入点是否曾经
匹配目标类上的给定方法。当 AOP
创建代理是为了避免对每个方法调用进行测试的需要。如果
双参数方法返回给定方法,MethodMatcher 的方法返回,三参数匹配方法为
在每次方法调用时调用。这样一来,切入点即可查看传递的参数
在目标建议开始之前立即调用方法。matches(Method,
Class)
matches
true
isRuntime()
true
大多数实现都是静态的,这意味着它们的方法
返回。在这种情况下,从不调用三参数方法。MethodMatcher
isRuntime()
false
matches
如果可能的话,尝试将切入点设置为静态,允许 AOP 框架缓存 创建 AOP 代理时切入点评估的结果。 |
6.1.2. 切入点操作
Spring 支持切入点操作(特别是并集和交集)。
Union 表示任一切入点匹配的方法。
交集表示两个切入点匹配的方法。
联合通常更有用。
可以通过使用类中的静态方法或使用同一包中的类来编写切入点。但是,使用 AspectJ 切入点
表达式通常是一种更简单的方法。org.springframework.aop.support.Pointcuts
ComposablePointcut
6.1.3. AspectJ表达式切入
从 2.0 开始,Spring
使用的最重要的切入点类型是 。这是一个切入点
使用 AspectJ 提供的库来分析 AspectJ 切入点表达式字符串。org.springframework.aop.aspectj.AspectJExpressionPointcut
有关受支持的 AspectJ 切入点基元的讨论,请参阅上一章。
6.1.4. 便利的切入点实现
Spring 提供了几种方便的切入点实现。您可以使用其中的一些 径直;其他的则旨在在特定于应用程序的切入点中进行子类化。
静态切入点
静态切入点基于方法和目标类,无法考虑 方法的参数。对于大多数用途,静态切入点就足够了,而且是最好的。 Spring 在首次调用方法时只能计算一次静态切入点。 之后,无需在每次方法调用时再次计算切入点。
本节的其余部分将介绍一些静态切入点实现,这些实现是 包含在 Spring 中。
正则表达式切入点
指定静态切入点的一种明显方法是正则表达式。几个AOP
除了 Spring 之外,框架使这成为可能。 是通用常规
表达式切入点,使用 JDK 中的正则表达式支持。org.springframework.aop.support.JdkRegexpMethodPointcut
使用该类,可以提供模式字符串列表。
如果其中任何一个匹配,则切入点的计算结果为 。(因此,
生成的切入点实际上是指定图案的并集。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 提供了一个名为
的便利类,它让我们
也引用 an(请记住,an 可以是拦截器,在建议之前,
抛出建议等)。在幕后,Spring 使用 .
使用简化了布线,因为一个 Bean 封装了两者
pointcut 和
advice,如以下示例所示:RegexpMethodPointcutAdvisor
Advice
Advice
JdkRegexpMethodPointcut
RegexpMethodPointcutAdvisor
<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
StaticMethodMatcherPointcut
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. 建议生命周期
每个建议都是一颗春豆。建议实例可以在所有建议之间共享 对象或对于每个建议的对象是唯一的。这对应于每个类或 按实例提供建议。
最常使用每类建议。它适用于一般性建议,例如 交易顾问。这些不依赖于代理对象的状态,也不添加新的 州。他们只是根据方法和论点行事。
每个实例的建议适用于介绍,以支持 mixin。在本例中, 该建议将状态添加到代理对象。
您可以在同一 AOP 代理中混合使用共享建议和每个实例的建议。
6.2.2. Spring 中的建议类型
Spring 提供了多种建议类型,并可扩展以支持 任意建议类型。本节介绍基本概念和标准建议类型。
拦截建议
Spring 中最基本的建议类型是围绕建议的拦截。
Spring 符合 AOP
接口,用于使用方法的 around advice
拦截。实现和围绕建议实现的类也应该实现
以下接口:Alliance
MethodInterceptor
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
该方法的参数公开了该方法
调用、目标加入点、AOP 代理和方法的参数。该方法应返回调用的结果:联接的返回值
点。MethodInvocation
invoke()
invoke()
以下示例演示了一个简单的实现:MethodInterceptor
class DebugInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation): Any {
println("Before: invocation=[$invocation]")
val rval = invocation.proceed()
println("Invocation returned")
return rval
}
}
请注意对 的方法的调用。这样一来,就
通往连接点的拦截器链。大多数拦截器调用此方法,并且
返回其返回值。但是,与周围的任何建议一样,可以
返回不同的值或引发异常,而不是调用 proceed 方法。
但是,您不想在没有充分理由的情况下这样做。proceed()
MethodInvocation
MethodInterceptor
MethodInterceptor 实现提供与其他符合 AOP 联盟标准的
AOP 的互操作性
实现。本节其余部分讨论的其他建议类型
实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有一个优势
在使用最具体的建议类型时,如果出现以下情况,请坚持使用周围的建议
您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点
目前在框架之间无法互操作,AOP 联盟也无法互操作
当前定义切入点接口。MethodInterceptor |
咨询前
更简单的建议类型是建议前。这不需要对象,因为它仅在输入方法之前被调用。MethodInvocation
之前建议的主要优点是不需要调用该方法,因此,不可能无意中无法继续
拦截器链。proceed()
以下列表显示了该接口:MethodBeforeAdvice
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring 的 API 设计将允许 字段在建议之前,尽管通常的对象适用于字段拦截,并且是 Spring 不太可能实现它。
请注意,返回类型为
。在建议之前可以在连接之前插入自定义行为
Point 运行,但无法更改返回值。如果之前的建议抛出
异常,它会停止拦截器链的进一步执行。例外
向上传播拦截器链。如果未选中或签名
调用的方法,它将直接传递给客户端。否则,它是
被 AOP 代理包装在未经检查的异常中。void
以下示例显示了 Spring 中的 before 建议,它计算了所有方法调用:
class CountingBeforeAdvice : MethodBeforeAdvice {
var count: Int = 0
override fun before(m: Method, args: Array<Any>, target: Any?) {
++count
}
}
在建议可以与任何切入点一起使用之前。 |
投掷建议
如果连接点抛出,则在连接点返回后调用抛出建议
异常。Spring 提供打字投掷建议。请注意,这意味着该接口不包含任何方法。这是一个
标记接口,标识给定对象实现一个或多个类型化抛出
建议方法。这些应采用以下形式:org.springframework.aop.ThrowsAdvice
afterThrowing([Method, args, target], subclassOfThrowable)
只有最后一个参数是必需的。方法签名可以有一个或四个 参数,取决于建议方法是否对该方法感兴趣,以及 参数。接下来的两个列表显示了作为抛出建议示例的类。
如果抛出
a(包括来自子类),则调用以下建议:RemoteException
class RemoteThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with remote exception
}
}
与前面不同
建议,下一个示例声明了四个参数,以便它可以访问调用的方法 method
参数和目标对象。如果抛出 a,则调用以下建议:ServletException
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
// Do something with all arguments
}
}
最后一个示例演示了如何在单个类中使用这两种方法
同时处理 和 。任意数量的投掷建议
方法可以组合在单个类中。下面的清单显示了最终示例:RemoteException
ServletException
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 中的 after return
建议必须实现该接口,如下表所示:org.springframework.aop.AfterReturningAdvice
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
返回后的建议可以访问返回值(它不能修改), 调用的方法、方法的参数和目标。
返回建议后的以下代码将计算所有成功的方法调用,这些方法调用具有 未引发异常:
class CountingAfterReturningAdvice : AfterReturningAdvice {
var count: Int = 0
private set
override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
++count
}
}
此建议不会更改执行路径。如果它抛出异常,则为 抛出拦截器链而不是返回值。
返回后,建议可以与任何切入点一起使用。 |
介绍建议
Spring将介绍建议视为一种特殊的拦截建议。
简介需要 an 和 an that
实现以下接口:IntroductionAdvisor
IntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
继承自 AOP Alliance
接口的方法必须
实施简介。也就是说,如果调用的方法位于
接口,引入拦截器负责处理方法调用 — IT
无法调用
。invoke()
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 测试套件中的一个例子,假设我们想要 将以下接口引入一个或多个对象:
interface Lockable {
fun lock()
fun unlock()
fun locked(): Boolean
}
这说明了一个混合。我们希望能够将建议的对象转换为
,
无论其类型和调用锁定和解锁方法如何。如果我们调用该方法,我们
希望所有 setter 方法都抛出一个 .因此,我们可以添加一个方面
提供了使对象不可变的能力,而对象对此一无所知:
AOP的一个很好的例子。Lockable
lock()
LockedException
首先,我们需要一个可以完成繁重工作的人。在这个
案例中,我们扩展了便利类。我们可以直接实现,但在大多数情况下使用是最好的。IntroductionInterceptor
org.springframework.aop.support.DelegatingIntroductionInterceptor
IntroductionInterceptor
DelegatingIntroductionInterceptor
旨在将介绍委托给
实际实现引入的接口,隐蔽使用拦截
来做到这一点。您可以使用构造函数参数将委托设置为任何对象。这
默认委托(使用无参数构造函数时)为 。因此,在下一个示例中,
委托是 的子类。
给定一个委托(默认情况下,它本身),一个实例
查找委托实现的所有接口(除 之外),并支持针对其中任何一个接口的引入。
子类(如 )可以调用该方法来禁止显示不应公开的接口。但是,无论多少
接口 an 准备支持,所使用的控件实际公开哪些接口。一
引入的接口隐藏了目标对同一接口的任何实现。DelegatingIntroductionInterceptor
this
LockMixin
DelegatingIntroductionInterceptor
DelegatingIntroductionInterceptor
IntroductionInterceptor
LockMixin
suppressInterface(Class
intf)
IntroductionInterceptor
IntroductionAdvisor
因此,扩展和实现自身。超类会自动拾取可以支持的
引言,所以我们不需要指定。我们可以介绍任意数量的
以这种方式接口。LockMixin
DelegatingIntroductionInterceptor
Lockable
Lockable
请注意实例变量的使用。这有效地增加了额外的状态
到目标对象中保存的。locked
以下示例显示了示例类:LockMixin
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)
}
}
通常,您不需要重写该方法。实现(调用方法
if
引入该方法,否则继续向连接点前进)通常
够。在本例中,我们需要添加一个检查:不能调用 setter 方法
如果处于锁定模式。invoke()
DelegatingIntroductionInterceptor
delegate
所需的引入只需要保存一个不同的实例并指定引入的接口(在本例中,仅
)。一个更复杂的例子可能会引用引言
拦截器(将被定义为原型)。在这种情况下,没有
与 相关的配置,因此我们使用 .
以下示例显示了我们的类:LockMixin
Lockable
LockMixin
new
LockMixinAdvisor
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
我们可以非常简单地应用此顾问,因为它不需要配置。(但是,它
如果没有 .) 就无法使用与通常的介绍一样,顾问必须是每个实例,
因为它是有状态的。对于每个建议的对象,我们需要一个不同的实例,因此 和 。顾问包括建议对象的一部分
州。IntroductionInterceptor
IntroductionAdvisor
LockMixinAdvisor
LockMixin
我们可以通过以下方法以编程方式应用此顾问程序,或者
(推荐的方式)在 XML 配置中,就像任何其他顾问一样。所有代理创建
下面讨论的选项(包括“自动代理创建者”)可以正确处理介绍
和有状态的混合。Advised.addAdvisor()
6.3. Spring 中的 Advisor API
在 Spring 中,Advisor 是一个仅包含关联到的单个 advice 对象的方面 带有切入点表达式。
除了介绍的特殊情况外,任何顾问都可以与任何建议一起使用。
是最常用的
顾问类。它可以与 、 或 一起使用。org.springframework.aop.support.DefaultPointcutAdvisor
MethodInterceptor
BeforeAdvice
ThrowsAdvice
在同一个 AOP 代理中,可以在 Spring 中混合使用 advisor 和 advice 类型。为 例如,您可以在 advice 周围、抛出建议和建议之前使用拦截 一个代理配置。Spring 会自动创建必要的拦截器 链。
6.4. 使用 创建 AOP 代理ProxyFactoryBean
如果您将 Spring IoC 容器 (an or
) 用于
业务对象(你应该是!),你想使用Spring的AOP实现之一。(请记住,工厂 Bean 引入了一层间接层,让
它创建不同类型的对象。ApplicationContext
BeanFactory
FactoryBean
Spring AOP 支持还在后台使用工厂 bean。 |
在 Spring 中创建 AOP 代理的基本方法是使用
.这样可以完全控制
切入点、适用的任何建议及其顺序。但是,有更简单的
如果您不需要此类控制,则首选选项。org.springframework.aop.framework.ProxyFactoryBean
6.4.1. 基础
与其他 Spring 实现一样,引入了
间接级别。如果定义一个命名的 对象
引用看不到实例本身,而是一个对象
由 中的方法实现创建。这
方法创建一个包装目标对象的 AOP
代理。ProxyFactoryBean
FactoryBean
ProxyFactoryBean
foo
foo
ProxyFactoryBean
getObject()
ProxyFactoryBean
使用一个或另一个 IoC
感知的最重要好处之一
创建 AOP 代理的类是建议和切入点也可以
由 IoC 管理。这是一项强大的功能,支持某些难以实现的方法
与其他 AOP 框架一起实现。例如,建议本身可以参考
应用程序对象(除了目标,它应该在任何 AOP 中都可用
framework),受益于依赖注入提供的所有可插拔性。ProxyFactoryBean
6.4.2. JavaBean 属性
与 Spring 提供的大多数实现一样,该类本身就是一个
JavaBean。其属性用于:FactoryBean
ProxyFactoryBean
-
指定要代理的目标。
-
指定是否使用 CGLIB(稍后将介绍,另请参阅基于 JDK 和 CGLIB 的代理)。
一些关键属性继承自(Spring 中所有
AOP 代理工厂的超类)。这些关键属性包括
以下内容:org.springframework.aop.framework.ProxyConfig
-
proxyTargetClass
:如果要代理目标类,而不是 目标类的接口。如果此属性值设置为 ,则 CGLIB 代理 (但也请参阅基于 JDK 和 CGLIB 的代理)。true
true
-
optimize
:控制是否对代理应用主动优化 通过 CGLIB 创建。除非您完全 了解相关 AOP 代理如何处理优化。这是当前使用的 仅适用于 CGLIB 代理。它对 JDK 动态代理没有影响。 -
frozen
:如果代理配置为 ,则对配置的更改是 不再允许。这既可以作为轻微的优化,也可以用于这些情况 当您不希望调用方在创建代理后能够操作代理(通过接口)时。此属性的默认值为 ,因此允许进行更改(例如添加其他建议)。frozen
Advised
false
-
exposeProxy
:确定是否应在 中公开当前代理,以便目标可以访问它。如果目标需要获取 代理和属性设置为 ,目标可以使用该方法。ThreadLocal
exposeProxy
true
AopContext.currentProxy()
特定于的其他属性包括:ProxyFactoryBean
-
proxyInterfaces
:接口名称数组。如果未提供,则 CGLIB 使用目标类的代理(但也请参阅基于 JDK 和 CGLIB 的代理)。String
-
interceptorNames
:、拦截器或其他建议名称的数组 应用。订购很重要,先到先得。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。String
Advisor
这些名称是当前工厂中的 Bean 名称,包括来自祖先的 Bean 名称 工厂。您不能在此处提及 Bean 引用,因为这样做会导致忽略建议的单例设置。
ProxyFactoryBean
您可以在拦截器名称后附加星号 ()。这样做会导致 应用名称以星号前的部件开头的所有顾问 Bean 要应用。您可以在使用“全局”顾问中找到使用此功能的示例。
*
-
singleton:工厂是否应该返回单个对象,无论如何 通常调用该方法。多种实现提供 这样的方法。缺省值为 。如果要使用有状态建议 - for 例如,对于有状态的 mixin - 使用原型建议以及单例值 .
getObject()
FactoryBean
true
false
6.4.3. 基于 JDK 和 CGLIB 的代理
本节是关于如何选择为特定目标创建基于
JDK 的代理或基于 CGLIB 的代理的权威文档
对象(要代理)。ProxyFactoryBean
创建基于
JDK 或 CGLIB 的行为
代理在 Spring 的 1.2.x 和 2.0 版本之间发生了变化。现在
在自动检测接口方面表现出与类相似的语义。ProxyFactoryBean ProxyFactoryBean TransactionProxyFactoryBean
|
如果要代理的目标对象的类(以下简称为
目标类)不实现任何接口,基于 CGLIB 的代理是
创建。这是最简单的方案,因为 JDK 代理是基于接口的,并且没有
接口意味着 JDK 代理甚至是不可能的。您可以插入目标 bean
并通过设置属性来指定侦听器列表。请注意,一个
即使 的属性已设置为 ,也会创建基于 CGLIB 的代理。(这样做是没有意义的,最好是
从 Bean 定义中删除,因为它充其量是多余的,最坏的情况是多余的
令人困惑。interceptorNames
proxyTargetClass
ProxyFactoryBean
false
如果目标类实现一个(或多个)接口,则代理的类型为
创建取决于 的配置。ProxyFactoryBean
如果 的属性已设置为 ,
创建基于 CGLIB 的代理。这是有道理的,并且符合
最小意外原则。即使 的属性已设置为一个或多个完全限定的接口名称,事实
该属性设置为导致基于 CGLIB
代理生效。proxyTargetClass
ProxyFactoryBean
true
proxyInterfaces
ProxyFactoryBean
proxyTargetClass
true
如果 的属性已设置为一个或多个
完全限定的接口名称,则创建基于 JDK 的代理。创建的
Proxy 实现属性中指定的所有接口。如果目标类碰巧实现了比
物业中指定的那些,一切都很好,但那些
返回的代理不会实现其他接口。proxyInterfaces
ProxyFactoryBean
proxyInterfaces
proxyInterfaces
如果 的属性尚未设置,但
目标类确实实现了一个(或多个)接口,自动检测目标类实际上确实实现了这一事实
实现至少一个接口,并创建一个基于 JDK 的代理。接口
实际代理的是目标类的所有接口
实现。实际上,这与提供每个列表的列表相同
目标类实现到属性的接口。然而
它的工作量大大减少,并且不易出现印刷错误。proxyInterfaces
ProxyFactoryBean
ProxyFactoryBean
proxyInterfaces
6.4.4. 代理接口
考虑一个简单的实际示例。此示例涉及:ProxyFactoryBean
-
代理的目标 Bean。这是 bean 的定义 例子。
personTarget
-
An 和 an 用于提供建议。
Advisor
Interceptor
-
用于指定目标对象(Bean)的 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 引用。原因是
如果 的 singleton 属性设置为 ,它必须能够
返回独立的代理实例。如果任何顾问本身就是原型,则
需要返回独立实例,因此必须能够获取
工厂原型的实例。仅持有引用是不够的。ProxyFactoryBean false
|
前面所示的 Bean 定义可以用来代替实现,因为
遵循:person
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>
此示例中的类公开 类型的属性。就
就此而言,AOP代理可以透明地代替“真实”的人使用
实现。但是,它的类将是一个动态代理类。这是可能的
将其强制转换为接口(稍后讨论)。PersonUser
Person
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 的优点是只有一个
类型的对象。如果我们愿意,这很有用
防止应用程序上下文的用户获取对未建议的引用
对象或需要避免 Spring IoC 自动接线的任何歧义。还有,
可以说,一个优点是定义是独立的。
但是,有时能够从
工厂实际上可能是一个优势(例如,在某些测试场景中)。Person
ProxyFactoryBean
6.4.5. 代理类
如果需要代理一个类,而不是一个或多个接口,该怎么办?
想象一下,在我们前面的示例中,没有接口。我们需要提供建议
一个名为 它未实现任何业务接口的类。在这种情况下,您
可以将 Spring 配置为使用 CGLIB 代理而不是动态代理。为此,请将前面所示的属性设置为 。虽然最好
编程到接口而不是类,能够建议不这样做的类
在使用遗留代码时,实现接口可能很有用。(一般来说,春季
不是规定性的。虽然它使应用良好做法变得容易,但它避免了强制
特定方法。Person
Person
proxyTargetClass
ProxyFactoryBean
true
如果你愿意,你可以在任何情况下强制使用CGLIB,即使你有 接口。
CGLIB 代理的工作原理是在运行时生成目标类的子类。春天 配置此生成的子类,以将方法调用委托给原始目标。这 subclass 用于实现 Decorator 模式,在建议中编织。
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 定义标记为
如前所述,通过将属性设置为 来抽象,因此它实际上可能永远不会
实例。默认情况下,应用程序上下文(但不是简单的 Bean 工厂),
预实例化所有单例。因此,这很重要(至少对于单例 bean)
如果您有一个(父)Bean 定义,并且您打算仅将其用作模板,
并且此定义指定了一个类,您必须确保将属性设置为 .否则,应用程序上下文实际上会尝试
预先实例化它。abstract
true
abstract
true
6.6.
使用ProxyFactory
使用 Spring 以编程方式创建 AOP 代理很容易。这使您可以使用 不依赖于 Spring IoC 的 Spring AOP。
目标对象实现的接口有 自动代理。下面的清单显示了为目标对象创建代理,其中一个 拦截器和一名顾问:
val factory = ProxyFactory(myBusinessInterfaceImpl)
factory.addAdvice(myMethodInterceptor)
factory.addAdvisor(myAdvisor)
val tb = factory.proxy as MyBusinessInterface
第一步是构造一个
类型的对象。您可以使用目标创建此内容
对象,如前面的示例所示,或指定要在备用接口中代理的接口
构造 函数。org.springframework.aop.framework.ProxyFactory
您可以添加建议(使用拦截器作为专门类型的建议)、顾问或两者兼而有之
并在 .如果添加 ,则可以使代理实现其他
接口。ProxyFactory
IntroductionInterceptionAroundAdvisor
上也有方便的方法(继承自 )
允许您添加其他建议类型,例如 before 和 throws 建议。 是 和 的超类。ProxyFactory
AdvisedSupport
AdvisedSupport
ProxyFactory
ProxyFactoryBean
将 AOP 代理创建与 IoC 框架集成是大多数情况下的最佳实践 应用。我们建议您使用 AOP 将 Java 代码中的配置外部化, 一般来说,你应该这样做。 |
6.7. 操作建议对象
无论您如何创建 AOP
代理,您都可以使用界面来操作它们。任何 AOP 代理都可以强制转换为此
接口,无论它实现哪些其他接口。此接口包括
以下方法:org.springframework.aop.framework.Advised
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
该方法为每个顾问、拦截器或
已添加到工厂的其他建议类型。如果添加了 、
此索引处返回的顾问是您添加的对象。如果您添加了
拦截器或其他建议类型,Spring 将其包装在顾问中,并带有
始终返回 .因此,如果您添加了 ,则顾问
为此索引返回的是返回 your 和匹配所有类和方法的切入点。getAdvisors()
Advisor
Advisor
true
MethodInterceptor
DefaultPointcutAdvisor
MethodInterceptor
这些方法可用于添加任何 .通常,顾问持有
pointcut 和 advice 是通用的,您可以将其与
任何建议或切入点(但不适用于介绍)。addAdvisor()
Advisor
DefaultPointcutAdvisor
默认情况下,即使代理一次,也可以添加或删除顾问或拦截器 已创建。唯一的限制是无法添加或删除 Introduction Advisor,因为出厂时的现有代理不显示界面 改变。(您可以从工厂获取新的代理以避免此问题。
以下示例演示如何将 AOP
代理强制转换为接口,并检查和
操纵其建议:Advised
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 来检查这一点 在将事务标记为回滚之前,数据库已正确更新。 |
根据您创建代理的方式,您通常可以设置一个标志。在那
case 时,该方法将返回 ,以及任何修改尝试
通过添加或删除的建议会导致 .能力
冻结建议对象的状态在某些情况下很有用(例如,要
防止调用代码删除安全拦截器)。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
该类是自动创建的
名称与文本值或通配符匹配的 Bean 的 AOP 代理。以下
示例演示如何创建 bean:BeanNameAutoProxyCreator
BeanPostProcessor
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 定义,例如前面的
和
例如,是带有目标类的普通旧 Bean 定义。AOP 代理是
由 自动创建。同样的建议也适用
到所有匹配的 bean。请注意,如果使用 advisors(而不是
前面的示例),切入点可能以不同的方式应用于不同的 bean。jdkMyBean
onlyJdk
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
一个更通用且功能极其强大的自动代理创建器是
。这会自动将符合条件的顾问应用于
当前上下文,无需在自动代理中包含特定的 Bean 名称
顾问的 Bean 定义。它提供了一致的配置和
避免重复,因为 .DefaultAdvisorAutoProxyCreator
BeanNameAutoProxyCreator
使用此机制涉及:
-
指定 Bean 定义。
DefaultAdvisorAutoProxyCreator
-
在相同或相关的上下文中指定任意数量的顾问。请注意,这些 必须是顾问,而不是拦截者或其他建议。这是必要的, 因为必须有一个切入点来评估,以检查每个建议的资格 候选 Bean 定义。
自动计算包含的切入点
在每个顾问中,查看它应该应用于每个业务对象的建议(如果有的话)
(例如示例中的 和)。DefaultAdvisorAutoProxyCreator
businessObject1
businessObject2
这意味着可以自动将任意数量的顾问应用于每个业务 对象。如果任何顾问程序中的任何切入点都与业务对象中的任何方法匹配, 对象未被代理。为新业务对象添加 Bean 定义时, 如有必要,它们会自动代理。
一般来说,自动代理的优点是使调用者或
依赖项来获取不建议的对象。调用此函数将返回 AOP 代理,而不是目标业务对象。(“内部
前面显示的
bean“成语也提供了这个好处。getBean("businessObject1")
ApplicationContext
以下示例创建一个 Bean
和另一个
本节中讨论的元素:DefaultAdvisorAutoProxyCreator
<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
提供对筛选的支持(通过使用命名
约定,以便只评估某些顾问,这允许使用多个,
配置不同,AdvisorAutoProxyCreators 在同一工厂)和排序。
顾问可以实现该接口,以确保
如果这是一个问题,请正确排序。用于
前面的示例具有可配置的订单值。默认设置为无序。DefaultAdvisorAutoProxyCreator
org.springframework.core.Ordered
TransactionAttributeSourceAdvisor
6.9.
使用实现TargetSource
Spring 提供了 ,的概念在接口中表示。此接口负责
返回实现连接点的“目标对象”。每次 AOP 代理处理方法时,都会要求实现提供目标实例
调用。TargetSource
org.springframework.aop.TargetSource
TargetSource
使用 Spring AOP
的开发人员通常不需要直接使用实现,但
这为支持池化、热插拔和其他
复杂的目标。例如,池化可以返回不同的目标
实例,通过使用池来管理实例。TargetSource
TargetSource
如果未指定 ,则使用默认实现来包装
本地对象。每次调用都会返回相同的目标(如您所料)。TargetSource
本节的其余部分将介绍 Spring 提供的标准目标源以及如何使用它们。
使用自定义目标源时,目标通常需要是原型 而不是单例 Bean 定义。这允许 Spring 创建一个新目标 实例(需要时)。 |
6.9.1. 热插拔目标源
的存在让目标
切换 AOP 代理,同时让调用方保留对它的引用。org.springframework.aop.target.HotSwappableTargetSource
更改目标源的目标会立即生效。是线程安全的。HotSwappableTargetSource
可以使用
HotSwappableTargetSource 上的方法更改目标,如以下示例所示:swap()
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>
前面的调用更改了可交换 Bean
的目标。持有
引用该 bean 不知道更改,但立即开始命中
新目标。swap()
尽管此示例未添加任何建议(但无需将建议添加到
使用 a ),任何都可以与
武断的建议。TargetSource
TargetSource
6.9.2. 池化目标源
使用池化目标源提供与无状态会话类似的编程模型 EJB,其中使用方法调用维护相同实例池 转到释放池中的对象。
Spring 池化和 SLSB 池化之间的一个关键区别是 Spring 池化可以 应用于任何 POJO。与一般的 Spring 一样,此服务可以应用于 非侵入性方式。
Spring 提供了对 Commons Pool
2.2 的支持,它提供了
相当有效的池化实现。你需要 Jar 在你的
应用程序的类路径来使用此功能。您还可以子类以支持任何其他
池化 API。commons-pool
org.springframework.aop.target.AbstractPoolingTargetSource
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>
请注意,目标对象(在前面的示例中)必须是
原型。这允许实现创建新实例
目标,以根据需要增加池。有关信息,请参阅 AbstractPoolingTargetSource
的 javadoc 和您希望使用的具体子类
关于它的属性。 是最基本的,并且始终保证存在。businessObjectTarget
PoolingTargetSource
maxSize
在本例中,是拦截器的名称,需要
在同一 IoC 上下文中定义。但是,您无需将拦截器指定为
使用池化。如果您只想池化而没有其他建议,请不要设置该属性。myInterceptor
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>
此顾问是通过在类上调用便捷方法获得的,因此使用
.这
顾问的名称 (, here) 必须位于
公开池化对象。AbstractPoolingTargetSource
MethodInvokingFactoryBean
poolConfigAdvisor
ProxyFactoryBean
演员表定义如下:
val conf = beanFactory.getBean("businessObject") as PoolingConfig
println("Max pool size is " + conf.maxSize)
池化无状态服务对象通常不是必需的。我们认为不应该这样做 是默认选择,因为大多数无状态对象自然是线程安全的,并且实例 如果缓存了资源,则池化是有问题的。 |
使用自动代理可以更简单地池化。您可以设置实现
由任何自动代理创建者使用。TargetSource
6.9.3. 原型目标源
设置“原型”目标源类似于设置池。在这个
情况下,每次方法调用都会创建一个新的目标实例。虽然
在现代 JVM 中,创建新对象的成本并不高,连接
新对象(满足其 IoC 依赖项)可能更昂贵。因此,你不应该
在没有充分理由的情况下使用这种方法。TargetSource
为此,您可以修改前面显示的定义,如下所示
(为了清楚起见,我们还更改了名称):poolTargetSource
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标 Bean
的名称。在实现中使用继承来确保命名的一致性。与池化目标一样
source,则目标 Bean 必须是原型 Bean 定义。TargetSource
6.9.4. 目标源ThreadLocal
ThreadLocal
如果需要为每个对象创建一个对象,则目标源非常有用
传入请求(即每个线程)。a 的概念提供了 JDK 范围
工具,以透明的方式将资源与线程一起存储。设置与其他类型的解释几乎相同
的目标源,如以下示例所示:ThreadLocal
ThreadLocalTargetSource
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal 在以下情况下,实例会出现严重问题(可能导致内存泄漏)
在多线程和多类加载器环境中错误地使用它们。你
应该始终考虑将 threadlocal 包装在其他类中,并且永远不要直接使用
本身(包装类除外)。另外,你应该
永远记住正确设置和取消设置(后者只涉及调用 )线程本地资源。取消设置应在
无论如何,因为不取消设置可能会导致有问题的行为。Spring
的支持可以为您完成此操作,并且应始终考虑使用没有其他正确处理代码的实例。ThreadLocal ThreadLocal.set(null) ThreadLocal ThreadLocal
|
6.10. 定义新的建议类型
Spring AOP 被设计为可扩展的。而拦截实施策略 目前在内部使用,可以支持 除了拦截周围的建议,之前,抛出建议,以及 在返回建议后。
该软件包是一个 SPI 封装,它允许
在不更改核心框架的情况下添加对新的自定义建议类型的支持。
自定义类型的唯一约束是它必须实现标记器接口。org.springframework.aop.framework.adapter
Advice
org.aopalliance.aop.Advice
有关更多信息,请参阅 org.springframework.aop.framework.adapter
javadoc。
7. 零安全
尽管 Java 不允许你用它的类型系统来表达
null-safety,但 Spring Framework
现在在包中提供以下注释,以便您
声明 API 和字段的可空性:org.springframework.lang
-
@Nullable
:注解,表示 特定参数、返回值或字段可以是 。null
-
@NonNull
:注解,表示特定 参数、返回值或字段不能(参数/返回值不需要) 和字段 where 和 apply)。null
@NonNullApi
@NonNullFields
-
@NonNullApi
:包级别的注释 将 non-null 声明为参数和返回值的默认语义。 -
@NonNullFields
:包装上的注释 将非 null 声明为字段默认语义的级别。
Spring Framework 本身利用了这些注解,但它们也可以用于任何 基于 Spring 的 Java 项目,用于声明 null 安全 API 和可选的 null 安全字段。 尚不支持泛型类型参数、varargs 和数组元素可为 null,但 应该在即将发布的版本中,请参阅 SPR-15942 以获取最新信息。可为 null 性声明应在 Spring Framework 版本,包括次要版本。方法中使用的类型的可空性 正文不在此功能的范围之内。
其他常见的库(如 Reactor 和 Spring Data)提供了 null 安全的 API,这些 API 使用类似的可空性安排,为 Spring 应用程序开发人员。 |
7.1. 用例
除了为 Spring Framework API
可空性提供显式声明外,
IDE(如 IDEA 或 Eclipse)可以使用这些注释来提供有用的
与 null 安全相关的警告,以便在运行时避免。NullPointerException
它们还用于使 Spring API 在 Kotlin 项目中为 null 安全,因为 Kotlin 本身是 支持零安全。更多详情 可在 Kotlin 支持文档中找到。
7.2. JSR-305 元注解
Spring 注解是用 JSR 305 注解(一个休眠但广泛传播的 JSR)进行元注解的。JSR-305 元注解让工具供应商 像 IDEA 或 Kotlin 一样,以通用方式提供零安全支持,而不必 对 Spring 注解的硬编码支持。
没有必要也不建议将 JSR-305
依赖项添加到项目类路径中
利用 Spring null 安全 API。仅使用
其代码库中的 null-safety 注释应添加 Gradle 配置或 Maven 范围,以避免编译警告。com.google.code.findbugs:jsr305:3.0.2
compileOnly
provided
8. 数据缓冲区和编解码器
Java NIO 提供了,但许多库在此基础上构建了自己的字节缓冲区
API,
特别是对于重复使用缓冲区和/或使用直接缓冲区的网络操作
有利于性能。例如,Netty 具有层次结构,Undertow 使用
XNIO,Jetty 使用池字节缓冲区和要释放的回调,依此类推。
该模块提供了一组抽象来处理各种字节缓冲区
API接口如下:ByteBuffer
ByteBuf
spring-core
-
DataBufferFactory
抽象化了数据缓冲区的创建。 -
DataBuffer
表示一个字节缓冲区,该缓冲区可以池化。 -
DataBufferUtils
为数据缓冲区提供实用工具方法。 -
编解码器将数据缓冲区流解码或编码为更高级别的对象。
8.1.DataBufferFactory
DataBufferFactory
用于通过以下两种方式之一创建数据缓冲区:
-
分配新的数据缓冲区,可以选择预先指定容量(如果已知),即 即使实现可以按需增长和收缩,效率也更高。
DataBuffer
-
包装现有的 或 ,它用 一个不涉及分配的实现。
byte[]
java.nio.ByteBuffer
DataBuffer
请注意,WebFlux
应用程序不会直接创建一个,而是创建一个
通过 或 在客户端访问它。
工厂的类型取决于底层客户端或服务器,例如 对于 Reactor Netty,对于其他人。DataBufferFactory
ServerHttpResponse
ClientHttpRequest
NettyDataBufferFactory
DefaultDataBufferFactory
8.2.DataBuffer
该界面提供与 - 但
带来了一些额外的好处,其中一些是受到 Netty 的启发。
以下是部分好处列表:DataBuffer
java.nio.ByteBuffer
ByteBuf
-
使用独立位置进行读写,即不需要调用 在读取和写入之间交替。
flip()
-
容量随需求扩展,如 。
java.lang.StringBuilder
-
通过
PooledDataBuffer
进行池化缓冲区和引用计数。 -
以 、 或 的形式查看缓冲区。
java.nio.ByteBuffer
InputStream
OutputStream
-
确定给定字节的索引或最后一个索引。
8.3.PooledDataBuffer
正如 ByteBuffer 的 Javadoc 中所解释的, 字节缓冲区可以是直接的,也可以是非直接的。直接缓冲区可能位于 Java 堆之外 这样就无需复制本机 I/O 操作。这使得直接缓冲 对于通过套接字接收和发送数据特别有用,但它们也更多 创建和发布成本高昂,这导致了池化缓冲区的想法。
PooledDataBuffer
是有助于参考计数的扩展,其中
对于字节缓冲区池至关重要。它是如何工作的?当
分配的引用计数为 1。调用以递增计数,而
调用以减少它。只要计数大于 0,缓冲区
保证不被释放。当计数减少到 0 时,池缓冲区可以是
release,这实际上可能意味着缓冲区的保留内存将返回到
内存池。DataBuffer
PooledDataBuffer
retain()
release()
请注意,在大多数情况下,与其直接操作,不如直接操作
使用其中的便捷方法,仅当 是 的实例时,才将 release 或 retain 应用于
。PooledDataBuffer
DataBufferUtils
DataBuffer
PooledDataBuffer
8.4.DataBufferUtils
DataBufferUtils
提供了许多实用程序方法来对数据缓冲区进行操作:
-
将数据缓冲区流加入到单个缓冲区中,可能具有零拷贝,例如通过 复合缓冲区(如果基础字节缓冲区 API 支持)。
-
将 或 NIO 变成 ,反之亦然 a into 或 NIO 。
InputStream
Channel
Flux<DataBuffer>
Publisher<DataBuffer>
OutputStream
Channel
-
如果缓冲区是 的实例,则释放或保留 的方法。
DataBuffer
PooledDataBuffer
-
跳过或从字节流中获取,直到达到特定的字节计数。
8.5. 编解码器
该软件包提供以下策略接口:org.springframework.core.codec
-
Encoder
编码为数据缓冲区流。Publisher<T>
-
Decoder
解码为更高级别的对象流。Publisher<DataBuffer>
该模块提供 、 、 、
以及编码器和解码器实现。该模块添加了 Jackson JSON、
Jackson Smile、JAXB2、Protocol Buffers 和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器。spring-core
byte[]
ByteBuffer
DataBuffer
Resource
String
spring-web
8.6. 使用DataBuffer
使用数据缓冲区时,必须特别小心,以确保释放缓冲区 因为它们可能被汇集在一起。我们将使用编解码器来说明 这是如何工作的,但这些概念更普遍地适用。让我们看看编解码器必须做什么 在内部管理数据缓冲区。
A 是创建更高级别之前最后一个读取输入数据缓冲区的
对象,因此它必须按如下方式释放它们:Decoder
-
如果 a 只是读取每个输入缓冲区并准备好 立即释放它,它可以通过 .
Decoder
DataBufferUtils.release(dataBuffer)
-
如果 a 使用 或 运算符,例如 、 和 其他在内部预取和缓存数据项,或者使用运算符(如 、)和其他遗漏项的运算符,则必须添加到 成分链,以确保此类缓冲液在被丢弃之前被释放,可能 也是由于错误或取消信号的结果。
Decoder
Flux
Mono
flatMap
reduce
filter
skip
doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
-
如果以任何其他方式保留一个或多个数据缓冲区,则必须 确保它们在完全阅读时被释放,或者在出现错误或取消信号时被释放 在读取和释放缓存的数据缓冲区之前发生。
Decoder
请注意,这提供了一种安全有效的方法来聚合数据
将流缓冲到单个数据缓冲区中。同样,并且是解码器使用的其他安全方法。DataBufferUtils#join
skipUntilByteCount
takeUntilByteCount
分配其他人必须读取(和释放)的数据缓冲区。所以安没有太多事情可做。但是,如果出现以下情况,必须注意释放数据缓冲区
使用数据填充缓冲区时发生序列化错误。例如:Encoder
Encoder
Encoder
val buffer = factory.allocateBuffer()
var release = true
try {
// serialize and populate buffer..
release = false
} finally {
if (release) {
DataBufferUtils.release(buffer)
}
}
return buffer
an 的使用者负责释放它收到的数据缓冲区。
在 WebFlux 应用程序中,输出用于写入 HTTP 服务器
响应,或客户端 HTTP 请求,在这种情况下,释放数据缓冲区是
代码写入服务器响应或客户端请求的责任。Encoder
Encoder
请注意,在Netty上运行时,有一些调试选项可以对缓冲区泄漏进行故障排除。
9. 日志记录
从 Spring Framework 5.0 开始,Spring
实现了自己的 Commons Logging 桥
在模块中。该实现检查是否存在 Log4j 2.x
API 和 SLF4J 1.7 API,并使用找到的第一个
日志记录实现,回退到 Java 平台的核心日志记录工具(同时
如果 Log4j 2.x 和 SLF4J 都不可用,则称为 JUL 或 )。spring-jcl
java.util.logging
将 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());
// ...
}
10. 附录
10.1.XML 模式
附录的这一部分列出了与核心容器相关的 XML 架构。
10.1.1. 模式util
顾名思义,这些标签处理常见的实用程序配置
问题,例如配置集合、引用常量等。
若要在架构中使用标记,需要在顶部具有以下前导码
的 Spring XML 配置文件(代码片段中的文本引用了
更正架构,以便命名空间中的标签可供您使用):util
util
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>
前面的配置使用 Spring 实现
() 来设置 Bean 上的属性值
设置为常量的值。这是
一切都很好,但它很冗长,并且(不必要地)暴露了 Spring 的内部
向最终用户输送管道。FactoryBean
FieldRetrievingFactoryBean
isolation
java.sql.Connection.TRANSACTION_SERIALIZABLE
以下基于 XML 模式的版本更简洁,清楚地表达了 开发人员的意图(“注入这个常量值”),它读起来更好:
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
从字段值设置 Bean 属性或构造函数参数
FieldRetrievingFactoryBean
是一个检索或非静态字段值的字段。它通常是
用于检索常量,然后可以使用常量设置
另一个 Bean
的属性值或构造函数参数。FactoryBean
static
public
static
final
下面的示例演示如何使用
staticField
属性公开字段:static
<bean id="myField"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>
还有一个方便的使用表单,其中字段被指定为
bean
名称,如以下示例所示:static
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
这确实意味着 bean
是什么不再有任何选择(所以任何其他
引用它的 bean 也必须使用这个更长的名称),但这种形式非常
定义简洁,用作内部 Bean 非常方便,因为没有
为 Bean 引用指定,如以下示例所示:id
id
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
您还可以访问另一个 Bean 的非静态(实例)字段,如
在
FieldRetrievingFactoryBean
类的 API 文档中进行了描述。
将枚举值作为属性或构造函数参数注入
bean 是
在春天很容易做到。您实际上不需要做任何事情或知道任何事情
Spring 内部结构(甚至关于诸如 .
以下示例枚举显示了注入枚举值是多么容易:FieldRetrievingFactoryBean
package javax.persistence;
public enum PersistenceContextType {
TRANSACTION,
EXTENDED
}
现在考虑以下类型的
setter 和相应的 bean 定义:PersistenceContextType
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"/>
前面的配置使用 Spring 实现
(的 ) 来创建一个名为 的 bean(类型 )
的值等于 Bean 的属性。FactoryBean
PropertyPathFactoryBean
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"/>
元素的属性值遵循
的形式。在本例中,它选取名为 的 Bean 的属性。该属性的值为 。path
<property-path/>
beanName.beanProperty
age
testBean
age
10
用于设置 Bean
属性或构造函数参数<util:property-path/>
PropertyPathFactoryBean
是计算给定属性路径的
目标对象。目标对象可以直接指定,也可以通过 Bean 名称指定。然后,您可以使用它
另一个 Bean 定义中的值作为属性值或构造函数
论点。FactoryBean
以下示例显示了按名称对另一个 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"/>
这种形式确实意味着在豆子的名称中没有选择。对它的任何引用
也要用同样的,也就是路径。如果用作内部
bean,完全没有必要引用它,如下例所示:id
<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>
前面的配置使用 Spring 实现
() 来实例化具有值的实例
从提供的资源
位置加载)。FactoryBean
PropertiesFactoryBean
java.util.Properties
下面的示例使用元素进行更简洁的表示: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>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</list>
</property>
</bean>
前面的配置使用 Spring 实现
() 来创建一个实例,并使用所取的值对其进行初始化
从提供的 .FactoryBean
ListFactoryBean
java.util.List
sourceList
下面的示例使用元素进行更简洁的表示:<util:list/>
<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:list>
You can also explicitly
control the exact type of that is instantiated and
populated by using the attribute on the element. For
example, if we really need a to be instantiated, we could use the
following configuration:List
list-class
<util:list/>
java.util.LinkedList
<util:list id="emails" list-class="java.util.LinkedList">
<value>jackshaftoe@vagabond.org</value>
<value>eliza@thinkingmanscrumpet.org</value>
<value>vanhoek@pirate.org</value>
<value>d'Arcachon@nemesis.org</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="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</map>
</property>
</bean>
前面的配置使用 Spring 实现
() 来创建使用键值对初始化的实例
取自提供的 .FactoryBean
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="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</util:map>
您还可以显式控制实例化的确切类型,并且
通过使用元素上的属性进行填充。为
例如,如果我们真的需要实例化,我们可以使用
以下配置:Map
'map-class'
<util:map/>
java.util.TreeMap
<util:map id="emails" map-class="java.util.TreeMap">
<entry key="pechorin" value="pechorin@hero.org"/>
<entry key="raskolnikov" value="raskolnikov@slums.org"/>
<entry key="stavrogin" value="stavrogin@gov.org"/>
<entry key="porfiry" value="porfiry@gov.org"/>
</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>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</set>
</property>
</bean>
前面的配置使用 Spring 实现
() 来创建使用所取值初始化的实例
从提供的
.FactoryBean
SetFactoryBean
java.util.Set
sourceSet
下面的示例使用元素进行更简洁的表示:<util:set/>
<!-- creates a java.util.Set instance with the supplied values -->
<util:set id="emails">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:set>
您还可以显式控制实例化的确切类型,并且
通过使用元素上的属性进行填充。为
例如,如果我们真的需要实例化,我们可以使用
以下配置:Set
set-class
<util:set/>
java.util.TreeSet
<util:set id="emails" set-class="java.util.TreeSet">
<value>pechorin@hero.org</value>
<value>raskolnikov@slums.org</value>
<value>stavrogin@gov.org</value>
<value>porfiry@gov.org</value>
</util:set>
如果未提供任何属性,则容器将选择实现。set-class
Set
10.1.2. 模式aop
这些标签处理在 Spring 中配置
AOP 的所有内容,包括 Spring 的
拥有基于代理的 AOP 框架以及 Spring 与 AspectJ AOP 框架的集成。
这些标签在标题为“Aspect Oriented Programming with Spring”一章中得到了全面的介绍。aop
为了完整起见,要在架构中使用标签,您需要
Spring XML 配置文件顶部的以下前导码(
代码段引用正确的架构,以便命名空间中的标签
可供您使用):aop
aop
<?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
这些标记处理与管道相关的配置,也就是说,通常不是对最终用户很重要的
bean,而是对最终用户很重要的 bean
Spring 中的很多“咕噜咕噜”工作,比如 .以下
代码段引用正确的架构,以便命名空间中的元素是
可供您使用:context
ApplicationContext
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
。如果您需要对特定设置进行更多控制,您可以自己将其显式定义为
bean。${…}
PropertySourcesPlaceholderConfigurer
用<annotation-config/>
此元素激活 Spring 基础结构以检测 bean 类中的注解:
-
Spring 的
@Configuration
模型 -
@Autowired
/@Inject
、 和@Value
@Lookup
-
JSR-250 的 、 和(如果可用)
@Resource
@PostConstruct
@PreDestroy
-
JAX-WS 和 EJB 3(如果可用)
@WebServiceRef
@EJB
-
JPA 和(如果有)
@PersistenceContext
@PersistenceUnit
或者,您可以选择为这些注释显式激活个人。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 模式
最后但并非最不重要的一点是,我们在架构中有元素。这些元素
从框架诞生之初就一直在 Spring 中。各种元素的示例
在架构中,此处未显示,因为它们已非常全面地涵盖
在依赖关系和配置中详细介绍了(实际上,在整个章节中)。beans
beans
请注意,您可以向 XML
定义添加零个或多个键值对。
如果有的话,用这个额外的元数据做什么完全取决于你自己的习惯
逻辑(因此,通常仅在您按照所述编写自己的自定义元素时才有用
在标题为“XML 架构创作”的附录中)。<bean/>
以下示例在周围环境的上下文中显示元素(请注意,在没有任何逻辑来解释它的情况下,元数据实际上是无用的
就目前而言)。<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 模式的内容。
要创建新的 XML 配置扩展,请执行以下操作:
对于一个统一的示例,我们创建了一个
XML 扩展(自定义 XML 元素),它允许我们配置(来自包)类型的对象。当我们完成时,
我们将能够定义类型的 Bean
定义,如下所示: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 | 指示的行包含所有可识别标签的扩展基础
(这意味着它们有一个属性,我们可以将其用作
容器)。我们可以使用这个属性,因为我们导入了 Spring
提供的命名空间。id beans |
前面的架构允许我们直接在
XML 应用程序上下文文件,如下所示
示例显示:SimpleDateFormat
<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(由 type
的名称标识),并设置了几个属性。dateFormat
SimpleDateFormat
基于架构的配置格式创建方法允许紧密集成 替换为具有架构感知 XML 编辑器的 IDE。通过使用正确创作的架构,您可以 可以使用自动完成功能让用户在多个配置选项之间进行选择 在枚举中定义。 |
10.2.2. 编写NamespaceHandler
除了模式之外,我们还需要一个来解析
Spring 在解析配置文件时遇到的这个特定命名空间。对于此示例,应负责元素的解析。NamespaceHandler
NamespaceHandler
myns:dateformat
该接口具有三种方法:NamespaceHandler
-
init()
:允许初始化 和 由 在使用处理程序之前使用弹簧。NamespaceHandler
-
BeanDefinition parse(Element, ParserContext)
:当 Spring 遇到 顶级元素(不嵌套在 Bean 定义或其他命名空间中)。 此方法本身可以注册 Bean 定义和/或返回 Bean 定义。 -
BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext)
:叫 当 Spring 遇到不同命名空间的属性或嵌套元素时。 一个或多个 Bean 定义的装饰(例如)与 Spring 支持的作用域一起使用。 我们首先强调一个简单的例子,不使用装饰,然后 我们在一个更高级的例子中展示了装饰。
虽然你可以为整个
命名空间(因此提供解析命名空间中每个元素的代码),
通常情况下,Spring XML配置文件中的每个顶级XML元素
生成单个 Bean 定义(在我们的示例中,单个元素生成单个 Bean 定义)。Spring 具有
支持此方案的便利类数。在以下示例中,我们
使用类:NamespaceHandler
<myns:dateformat/>
SimpleDateFormat
NamespaceHandlerSupport
package org.springframework.samples.xml
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class MyNamespaceHandler : NamespaceHandlerSupport {
override fun init() {
registerBeanDefinitionParser("dateformat", SimpleDateFormatBeanDefinitionParser())
}
}
你可能会注意到,实际上并没有很多解析逻辑
在这门课上。事实上,该类有一个内置的概念
代表团。它支持注册任意数量的实例,当它需要解析其中的元素时,它会委托给这些实例
命名空间。这种干净的关注点分离可以处理
编排其命名空间中所有自定义元素的解析,而
委托执行 XML 解析的繁重工作。这
表示每个仅包含用于解析单个
自定义元素,正如我们在下一步中看到的那样。NamespaceHandlerSupport
BeanDefinitionParser
NamespaceHandler
BeanDefinitionParsers
BeanDefinitionParser
10.2.3. 使用BeanDefinitionParser
如果遇到 XML
元素,该元素已映射到特定的 Bean 定义解析器
(在本例中)。换言之,是
负责分析架构中定义的一个不同的顶级 XML 元素。在
解析器,我们可以访问 XML 元素(因此也可以访问其子元素),以便
我们可以解析自定义 XML 内容,如以下示例所示:BeanDefinitionParser
NamespaceHandler
dateformat
BeanDefinitionParser
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 提供的
Spring 来处理很多
创建单个 .AbstractSingleBeanDefinitionParser BeanDefinition |
2 | 我们为超类提供
单身代表。AbstractSingleBeanDefinitionParser BeanDefinition |
在这个简单的情况下,这就是我们需要做的。我们单曲的创建由超类处理,因为
是 Bean 定义的唯一标识符的提取和设置。BeanDefinition
AbstractSingleBeanDefinitionParser
10.2.4. 注册处理程序和模式
编码完成。剩下要做的就是制作
Spring XML
解析基础结构,了解我们的自定义元素。为此,我们将自定义和自定义 XSD 文件注册到两个专用属性文件中。这些
属性文件都位于应用程序的目录中,并且
例如,可以与二进制类一起分发到 JAR 文件中。春天
XML 分析基础结构通过使用
这些特殊的属性文件,其格式将在接下来的两节中详细介绍。namespaceHandler
META-INF
写作META-INF/spring.handlers
调用的属性文件包含 XML 模式
URI 到
命名空间处理程序类。对于我们的示例,我们需要编写以下内容:spring.handlers
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(该字符是 Java
属性格式中的有效分隔符,因此 URI
中的字符需要使用反斜杠进行转义。:
:
键值对的第一部分(键)是与自定义关联的
URI
命名空间扩展,并且需要与自定义 XSD 架构中指定的属性值完全匹配。targetNamespace
编写“META-INF/spring.schemas”
调用的属性文件包含 XML
模式位置的映射
(在使用架构作为一部分的 XML 文件中与架构声明一起引用)
的属性)添加到类路径资源。此文件是必需的
以防止 Spring 绝对必须使用需要
用于检索架构文件的 Internet 访问。如果在此中指定映射
属性文件,Spring 在类路径上搜索模式(在本例中为包中)。
以下代码片段显示了我们需要为自定义架构添加的行:spring.schemas
xsi:schemaLocation
EntityResolver
myns.xsd
org.springframework.samples.xml
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(请记住,必须对字符进行转义。:
建议将 XSD 文件(或多个文件)部署在旁边
类路径上的 and 类。NamespaceHandler
BeanDefinitionParser
10.2.5. 在Spring XML配置中使用自定义扩展
使用您自己实现的自定义扩展与使用
Spring 提供的“自定义”扩展之一。以下
示例使用在前面步骤中开发的自定义元素
在Spring XML配置文件中:<dateformat/>
<?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>
上述配置将自定义扩展相互嵌套。班级
实际上由元素配置的是类(在下一个示例中显示)。请注意,该类不会公开
属性的 setter 方法。这使得它变得困难(或者更确切地说是不可能的)
使用 setter 注入为类配置 Bean 定义。
下面的清单显示了该类:<foo:component/>
Component
Component
components
Component
Component
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
}
}
此问题的典型解决方案是创建一个自定义,该自定义公开
setter
属性。下面的清单显示了这样的自定义:FactoryBean
components
FactoryBean
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
class ComponentNamespaceHandler : NamespaceHandlerSupport() {
override fun init() {
registerBeanDefinitionParser("component", ComponentBeanDefinitionParser())
}
}
接下来是自定义。请记住,我们正在创造
a 表示 .以下
清单显示了我们的自定义实现: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
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
基础结构注册各种工件,
通过修改 AND 文件,如下所示: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,并且您希望确保 命名的 JCache 实例在周围的集群中急切地启动。 下面的清单显示了这样的定义:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
jcache:cache-name="checking.account">
<!-- other dependencies here... -->
</bean>
然后,我们可以在解析属性时创建另一个属性。然后初始化
为我们命名的 JCache。我们还可以修改现有的 ,以便它依赖于这个新的
JCache 初始化 。以下列表显示了我们的:BeanDefinition
'jcache:cache-name'
BeanDefinition
BeanDefinition
'checkingAccountService'
BeanDefinition
JCacheInitializer
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
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
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 基础结构注册各种工件
通过修改 AND 文件,如下所示: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 及其依赖项的实例化。 |
|
|
Bean
的初始化。 |
|
|
创建
. |
|
|
扫描基础包。 |
|
|
豆类后处理阶段。 |
|
|
调用
bean。 |
|
|
调用
bean。 |
|
|
通过
注册组件类。 |
|
|
使用 CGLIB 代理增强配置类。 |
|
|
配置类使用
解析阶段。 |
|
|
应用程序上下文刷新阶段。 |