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

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

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

(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不持有 任何对话状态。我们更容易重用 单例图。
下面的示例将 Bean 定义为 XML 中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他范围相比, Spring 不管理 prototype bean 的 bean 中。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 生命周期回调。客户端代码必须清理 prototype-scoped 对象,并释放原型 bean 所持有的昂贵资源。要获取 Spring 容器来释放原型范围的 bean 持有的资源,请尝试使用 自定义 Bean 后处理器,它包含对 需要清理的豆子。
在某些方面, Spring 容器在原型范围的 bean 中的作用是
Java 的替代品new
算子。超过该点的所有生命周期管理都必须
由客户端处理。(有关 Spring 中 bean 生命周期的详细信息
容器,请参阅 生命周期回调。
1.5.3. 具有原型 bean 依赖项的单例 bean
当您使用依赖于原型 bean 的单例作用域 bean 时,请注意 依赖项在实例化时解析。因此,如果你依赖注入一个 原型范围的 bean 转换为单例范围的 bean,则会实例化一个新的原型 bean 然后 dependency-injection 到单例 bean 中。prototype 实例是唯一的 实例。
但是,假设您希望单例范围的 bean 获取 prototype 范围的 bean。您不能依赖注入 prototype 作用域的 bean 添加到你的单例 bean 中,因为该注入只发生 一次,当 Spring 容器实例化单例 bean 并解析 并注入其依赖项。如果您需要原型 bean 的新实例,网址为 运行时,请参阅 Method Injection。
1.5.4. 请求、会话、应用程序和 WebSocket 作用域
这request
,session
,application
和websocket
范围仅可用
如果使用 Web 感知 SpringApplicationContext
implementation (例如XmlWebApplicationContext
).如果你将这些作用域与常规的 Spring IoC 容器一起使用,
例如ClassPathXmlApplicationContext
一IllegalStateException
那个抱怨
关于未知的 bean 范围。
初始 Web 配置
为了在request
,session
,application
和websocket
levels(Web 范围的 bean)中,一些次要的初始配置是
在定义 bean 之前是必需的。(此初始设置不是必需的
对于标准范围:singleton
和prototype
.)
如何完成此初始设置取决于特定的 Servlet 环境。
如果您在 Spring Web MVC 中访问范围限定的 bean,则实际上,在
由 Spring 处理DispatcherServlet
,无需特殊设置。DispatcherServlet
already 公开所有相关状态。
如果您使用 Servlet 2.5 Web 容器,并且请求在 Spring 的DispatcherServlet
(例如,在使用 JSF 或 Struts 时),您需要注册org.springframework.web.context.request.RequestContextListener
ServletRequestListener
.
对于 Servlet 3.0+,这可以通过使用WebApplicationInitializer
接口。或者,或者对于较旧的容器,将以下声明添加到
Web 应用程序的web.xml
文件:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果您的侦听器设置存在问题,请考虑使用 Spring 的RequestContextFilter
.过滤器映射取决于周围的 Web
application configuration 的 app 配置,因此您必须根据需要对其进行更改。以下清单
显示了 Web 应用程序的 Filter 部分:
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet
,RequestContextListener
和RequestContextFilter
都完全正确
同样的事情,即将 HTTP 请求对象绑定到Thread
那就是服务
那个请求。这使得请求和会话范围的 bean 进一步可用
沿着调用链向下。
请求范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器会创建一个LoginAction
bean 使用loginAction
bean 定义。也就是说,loginAction
bean 的范围限定在 HTTP 请求级别。您可以将内部的
state (状态),因为其他实例
从同一loginAction
Bean 定义在 state 中看不到这些更改。
它们特定于单个请求。当请求完成处理时,
范围限定为请求的 bean 将被丢弃。
当使用注解驱动的组件或 Java 配置时,@RequestScope
注解
可用于将组件分配给request
范围。以下示例显示了如何作
为此,请执行以下作:
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
会话范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器会创建一个UserPreferences
bean 使用userPreferences
单个 HTTP 生命周期的 bean 定义Session
.在其他
words、userPreferences
bean 的作用域实际上限定在 HTTPSession
水平。如
使用请求范围的 bean,您可以更改实例的内部状态,即
根据需要创建尽可能多的 HTTPSession
实例也是
使用从同一userPreferences
bean 定义看不到这些
状态更改,因为它们特定于单个 HTTPSession
.当
HTTP 协议Session
最终被丢弃,则范围限定为该特定 HTTP 的 BeanSession
也会被丢弃。
当使用注释驱动的组件或 Java 配置时,您可以使用@SessionScope
注解将组件分配给session
范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
应用范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器会创建一个AppPreferences
bean 使用appPreferences
Bean 定义。也就是说,appPreferences
bean 的作用域为ServletContext
级别并存储为常规ServletContext
属性。这有点类似于 Spring 单例 bean,但
在两个重要方面有所不同:它是ServletContext
,而不是每个 SpringApplicationContext
(任何给定的 Web 应用程序中都可能有多个),
它实际上是公开的,因此显示为ServletContext
属性。
当使用注释驱动的组件或 Java 配置时,您可以使用@ApplicationScope
注解将组件分配给application
范围。这
以下示例显示了如何执行此作:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
作为依赖项的作用域 Bean
Spring IoC 容器不仅管理对象 (bean) 的实例化, 但也包括合作者(或依赖项)的联系。如果要注入 (对于 example) 将一个 HTTP 请求范围的 bean 转换为另一个寿命较长的 bean 中,您可以 选择注入 AOP 代理来代替作用域 Bean。也就是说,您需要注入 一个代理对象,它公开与 Scoped 对象相同的公共接口,但可以 还可以从相关范围(如 HTTP 请求)中检索真正的目标对象 并将方法调用委托到真实对象上。
您也可以使用 声明 此外,作用域代理并不是从较短的作用域访问 bean 的唯一方法
生命周期安全时尚。您还可以声明注入点(即
constructor 或 setter 参数或 autowired 字段)设置为 作为扩展变体,您可以声明 它的 JSR-330 变体称为 |
以下示例中的配置只有一行,但对于 了解其背后的 “为什么” 和 “如何”:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/> (1)
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
1 | 定义代理的行。 |
要创建这样的代理,请插入一个子级<aop:scoped-proxy/>
元素放入 Scoped
Bean 定义(请参见选择要创建的代理类型和基于 XML 架构的配置)。
为什么 bean 的定义范围限定在request
,session
和 custom-scope
级别需要<aop:scoped-proxy/>
元素?
考虑以下单例 bean 定义,并将其与
您需要为上述范围定义什么(请注意,以下内容userPreferences
bean 定义是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例 Bean (userManager
) 中注入了一个引用
到 HTTPSession
-作用域的 bean (userPreferences
).这里的要点是,userManager
Bean 是一个单例:它每
container 及其依赖项(在本例中只有一个userPreferences
bean) 为
也只注射了一次。这意味着userManager
bean 仅在
完全相同userPreferences
object(即最初注入它的那个)。
当将生存期较短的作用域 bean 注入
生存期较长的作用域 Bean(例如,注入 HTTPSession
-范围的协作
bean 作为单例 bean 的依赖项)。相反,您需要一个userManager
对象,并且对于 HTTP 的生命周期Session
,您需要一个userPreferences
对象
特定于 HTTPSession
.因此,容器会创建一个对象,该对象
公开与UserPreferences
类(理想情况下是
对象,它是一个UserPreferences
实例),该实例可以获取真实的UserPreferences
对象(HTTP 请求、Session
,等等
forth)。容器将此代理对象注入到userManager
bean,即
不知道这个UserPreferences
reference 是代理。在此示例中,当UserManager
实例在 Dependency-InjectedUserPreferences
对象,它实际上是在代理上调用一个方法。然后,代理会获取真实的UserPreferences
对象Session
并委派
方法调用到检索到的 realUserPreferences
对象。
因此,在注入时,您需要以下(正确且完整)的配置request-
和session-scoped
bean 转换为协作对象,如下例所示
显示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型
默认情况下,当 Spring 容器为标记为
这<aop:scoped-proxy/>
元素中,将创建一个基于 CGLIB 的类代理。
CGLIB 代理仅拦截公共方法调用!不要调用非公共方法 在这样的代理上。它们不会委托给实际的作用域目标对象。 |
或者,您可以将 Spring 容器配置为创建标准 JDK
此类作用域 bean 的基于接口的代理,通过指定false
对于
这proxy-target-class
属性的<aop:scoped-proxy/>
元素。使用 JDK
基于接口的代理意味着您的
application classpath 来影响此类代理。但是,这也意味着
作用域 Bean 必须至少实现一个接口,并且所有协作者
作用域 bean 注入到其中,必须通过其
接口。以下示例显示了基于接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
有关选择基于类或基于接口的代理的更多详细信息, 请参见代理机制。
1.5.5. 自定义范围
Bean 范围机制是可扩展的。您可以定义自己的
范围,甚至重新定义现有范围,尽管后者被认为是不好的做法
并且您无法覆盖内置的singleton
和prototype
范围。
创建自定义范围
要将自定义范围集成到 Spring 容器中,您需要实现org.springframework.beans.factory.config.Scope
接口,具体如下
部分。有关如何实现自己的范围的想法,请参阅Scope
Spring Framework 本身提供的实现以及Scope
javadoc 中,
其中更详细地解释了您需要实现的方法。
这Scope
interface 有四种方法可以从 scope 中获取对象,从
范围,然后销毁它们。
例如,会话范围的实现返回会话范围的 Bean(如果它 不存在,则该方法在将 Bean 绑定到 会话以供将来参考)。以下方法从 底层范围:
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
例如,会话范围实现从
基础会话。应该返回对象,但您可以返回null
如果
找不到具有指定名称的对象。以下方法从
底层范围:
Object remove(String name)
fun remove(name: String): Any
以下方法注册一个回调,范围应在 destroyed 或当 scope 中的指定对象被销毁时:
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
有关销毁回调的更多信息,请参见 javadoc 或 Spring 范围实现。
以下方法获取基础范围的聊天标识符:
String getConversationId()
fun getConversationId(): String
此标识符对于每个范围都不同。对于会话范围的实现,此 identifier 可以是会话标识符。
使用自定义范围
编写和测试一个或多个自定义Scope
implementations,您需要进行
Spring 容器知道您的新范围。以下方法是中心
方法注册新的Scope
使用 Spring 容器:
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
此方法在ConfigurableBeanFactory
接口,该接口可用
通过BeanFactory
大多数混凝土上的特性ApplicationContext
Spring 附带的实现。
第一个参数registerScope(..)
method 是与
一个范围。Spring 容器本身中此类名称的示例包括singleton
和prototype
.的第二个参数registerScope(..)
method 是一个实际实例
的定制Scope
实现。
假设您编写了自定义Scope
implementation 的 Implementation,然后按如下所示注册它
在下一个示例中。
下一个示例使用SimpleThreadScope ,它包含在 Spring 中,但不是
registered (默认)。对于您自己的自定义,说明是相同的Scope 实现。 |
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
然后,您可以创建符合自定义范围规则的 Bean 定义Scope
如下:
<bean id="..." class="..." scope="thread">
使用自定义Scope
implementation 中,您不仅限于编程注册
的范围。您还可以执行Scope
注册,通过使用CustomScopeConfigurer
类,如下例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
当您将<aop:scoped-proxy/> 在<bean> 声明FactoryBean implementation 时,是工厂 Bean 本身被限定为 Scope,而不是对象
返回自getObject() . |
1.6. 自定义 bean 的性质
Spring Framework 提供了许多接口,您可以使用这些接口来自定义 豆子。本节将它们分组如下:
1.6.1. 生命周期回调
要与容器对 bean 生命周期的 Management 进行交互,您可以实现
SpringInitializingBean
和DisposableBean
接口。容器调用afterPropertiesSet()
对于前者和destroy()
对于后者,让 bean
在初始化和销毁 bean 时执行某些作。
The JSR-250 如果您不想使用 JSR-250 注解,但仍希望删除
耦合,请考虑 |
在内部,Spring 框架使用BeanPostProcessor
implementations 来处理任何
callback 接口,它可以找到并调用适当的方法。如果您需要自定义
功能或其他生命周期行为 Spring 默认不提供,您可以
实现BeanPostProcessor
你自己。有关更多信息,请参阅容器扩展点。
除了初始化和销毁回调之外, Spring Management 的对象还可以
此外,实现Lifecycle
接口,以便这些对象可以参与
启动和关闭过程,由容器自身的生命周期驱动。
生命周期回调接口将在 此部分 中介绍。
初始化回调
这org.springframework.beans.factory.InitializingBean
接口允许 bean
在容器在
豆。这InitializingBean
interface 指定单个方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用InitializingBean
接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
这@PostConstruct
annotation 或
指定 POJO 初始化方法。对于基于 XML 的配置元数据,
您可以使用init-method
属性来指定具有 void 的方法的名称
no-argument 签名。通过 Java 配置,您可以使用initMethod
属性@Bean
.请参阅 接收生命周期回调。请考虑以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
class ExampleBean {
fun init() {
// do some initialization work
}
}
前面的示例与以下示例的效果几乎完全相同 (由两个列表组成):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// do some initialization work
}
}
但是,前面两个示例中的第一个示例并没有将代码耦合到 Spring。
销毁回调
实施org.springframework.beans.factory.DisposableBean
接口允许
bean 在包含它的容器被销毁时获取回调。这DisposableBean
interface 指定单个方法:
void destroy() throws Exception;
我们建议您不要使用DisposableBean
callback 接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
这@PreDestroy
annotation 或
指定 Bean 定义支持的泛型方法。使用基于 XML 的
configuration 元数据,您可以使用destroy-method
属性<bean/>
.
通过 Java 配置,您可以使用destroyMethod
属性@Bean
.请参阅 接收生命周期回调。请考虑以下定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
class ExampleBean {
fun cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
前面的定义与下面的定义几乎完全相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
class AnotherExampleBean : DisposableBean {
override fun destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,上述两个定义中的第一个并没有将代码耦合到 Spring。
您可以分配destroy-method 属性<bean> 元素为特殊(inferred) 值,它指示 Spring 自动检测close 或shutdown 方法。(任何实现java.lang.AutoCloseable 或java.io.Closeable 因此会匹配。您还可以设置
这个特别的(inferred) 值default-destroy-method 属性<beans> 元素将此行为应用于整个 bean 集(请参见默认初始化和销毁方法)。请注意,这是
default behavior 与 Java 配置一起使用。 |
默认 Initialization 和 Destroy 方法
当您编写不使用
特定于 SpringInitializingBean
和DisposableBean
回调接口,则
通常编写名称为init()
,initialize()
,dispose()
,等等
上。理想情况下,此类生命周期回调方法的名称在
project 中,以便所有开发人员都使用相同的方法名称并确保一致性。
你可以将 Spring 容器配置为“查找”命名初始化和销毁
回调方法名称。这意味着您作为应用程序
开发人员可以编写您的应用程序类并使用名为init()
,而无需配置init-method="init"
属性
定义。Spring IoC 容器在创建 Bean 时调用该方法(在
根据前面描述的标准生命周期回调协定)。此功能还对
initialization 和 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.");
}
}
}
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// this is (unsurprisingly) the initialization callback method
fun init() {
if (blogDao == null) {
throw IllegalStateException("The [blogDao] property must be set.")
}
}
}
然后,您可以在类似于以下内容的 bean 中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
存在default-init-method
属性<beans/>
元素
属性使 Spring IoC 容器识别一个名为init
在 Bean 上
class 作为初始化方法回调。在创建和组装 bean 时,如果
Bean 类具有这样一个方法,则会在适当的时间调用它。
您可以类似地(即在 XML 中)通过使用default-destroy-method
属性<beans/>
元素。
现有 Bean 类已经具有以 Variance 命名的回调方法
使用约定,您可以通过指定(在 XML 中,即)来覆盖默认值
方法名称init-method
和destroy-method
的属性<bean/>
本身。
Spring 容器保证调用配置的初始化回调
紧接着为 bean 提供所有依赖项。因此,初始化
callback 在原始 bean 引用上调用,这意味着 AOP 拦截器等
forth 尚未应用于 Bean。首先完全创建目标 Bean,然后
然后应用 AOP 代理(例如)及其拦截器链。如果目标
bean 和 proxy 是单独定义的,你的代码甚至可以与原始的
target bean,绕过代理。因此,应用
拦截器添加到init
方法,因为这样做会耦合
将 bean 绑定到其代理或拦截器,并在您的代码
直接与原始目标 Bean 交互。
组合生命周期机制
从 Spring 2.5 开始,您有三个选项来控制 bean 生命周期行为:
-
习惯
init()
和destroy()
方法 -
这
@PostConstruct
和@PreDestroy
附注.您可以组合这些机制来控制给定的 bean。
如果为 Bean 配置了多个生命周期机制,并且每个机制都是
配置了不同的方法名称,则每个配置的方法都会在
在此说明之后列出的顺序。但是,如果配置了相同的方法名称 — 例如,init() 对于初始化方法 — 对于这些生命周期机制中的多个,
该方法运行一次,如上一节所述。 |
为同一 bean 配置了多个生命周期机制,具有不同的 初始化方法的调用方式如下:
-
注释有
@PostConstruct
-
afterPropertiesSet()
由InitializingBean
回调接口 -
自定义配置
init()
方法
Destroy 方法的调用顺序相同:
-
注释有
@PreDestroy
-
destroy()
由DisposableBean
回调接口 -
自定义配置
destroy()
方法
启动和关闭回调
这Lifecycle
interface 为任何具有自己的
生命周期要求(例如启动和停止某些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring 管理的对象都可以实现Lifecycle
接口。然后,当ApplicationContext
本身接收开始和停止信号(例如,对于 STOP/RESTART
场景),它会将这些调用级联到所有Lifecycle
实现
在该上下文中定义。它通过委托给LifecycleProcessor
显示
在下面的清单中:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor
本身是Lifecycle
接口。它还添加了另外两种方法来响应正在刷新的上下文
并关闭。
请注意,常规的 另外,请注意,不保证在销毁之前收到停止通知。
在常规关闭时,所有 |
启动和关闭调用的顺序可能很重要。如果 “depends-on”
关系存在于任意两个对象之间,则依赖端在其
依赖项,并且它在依赖项之前停止。然而,有时,直接的
依赖项是未知的。您可能只知道特定类型的对象应该启动
在其他类型的对象之前。在这些情况下,SmartLifecycle
interface 定义
另一个选项,即getPhase()
方法,即Phased
.下面的清单显示了Phased
接口:
public interface Phased {
int getPhase();
}
下面的清单显示了SmartLifecycle
接口:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
开始时,阶段最低的对象首先启动。停止时,
遵循 Reverse Order。因此,实现SmartLifecycle
和
谁的getPhase()
method 返回Integer.MIN_VALUE
将是第一批开始的人
也是最后一个停下来的。在频谱的另一端,相位值为Integer.MAX_VALUE
将指示对象应最后启动并停止
first (可能是因为它依赖于其他进程运行)。在考虑
phase 值,同样重要的是要知道任何 “normal” 的默认 phaseLifecycle
未实现SmartLifecycle
是0
.因此,任何
负相位值表示对象应在这些标准之前开始
组件(并在它们之后停止)。对于任何正相位值,情况正好相反。
由SmartLifecycle
接受回调。任何
实现必须调用该回调的run()
方法,在该实现的
shutdown 过程完成。这将在必要时启用异步关闭,因为
默认实现的LifecycleProcessor
接口DefaultLifecycleProcessor
等待对象组的超时值
在每个阶段中调用该回调。默认的每阶段超时为 30 秒。
您可以通过定义一个名为lifecycleProcessor
在上下文中。如果只想修改超时,
定义以下内容就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor
interface 定义
刷新和关闭上下文。后者驱动关闭
process as ifstop()
被显式调用,但当上下文为
关闭。另一方面,'refresh' 回调启用了SmartLifecycle
豆。刷新上下文时(在所有对象都已
instantiated 和 initialized),则会调用该回调。此时,
默认生命周期处理器会检查每个SmartLifecycle
对象的isAutoStartup()
方法。如果true
,则该对象为
启动,而不是等待显式调用上下文的 OR
自己start()
方法(与上下文刷新不同,上下文启动不会发生
automatically 用于标准上下文实现)。这phase
value 和任何
“depends-on” 关系确定 Startup Sequence,如前所述。
在非 Web 应用程序中正常关闭 Spring IoC 容器
本节仅适用于非 Web 应用程序。Spring 的基于 Web 的 |
如果你在非 Web 应用程序环境中使用 Spring 的 IoC 容器(对于 例如,在富客户端桌面环境中),使用 JVM 的 JVM 中。这样做可以确保正常关闭,并在 singleton bean 的实例,以便释放所有资源。您仍必须配置 并正确实现这些 destroy 回调。
要注册 shutdown 钩子,请调用registerShutdownHook()
方法,即
在ConfigurableApplicationContext
接口,如下例所示:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
// add a shutdown hook for the above context...
ctx.registerShutdownHook()
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
1.6.2.ApplicationContextAware
和BeanNameAware
当ApplicationContext
创建一个实现org.springframework.context.ApplicationContextAware
interface 中,实例会提供
并引用ApplicationContext
.下面的清单显示了定义
的ApplicationContextAware
接口:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean 可以通过编程方式作ApplicationContext
创造了他们,
通过ApplicationContext
接口,或者将引用强制转换为已知的
子类(例如ConfigurableApplicationContext
,它公开了
附加功能)。一种用途是以编程方式检索其他 bean。
有时此功能很有用。但是,一般来说,您应该避免使用它,因为
它将代码耦合到 Spring,并且不遵循 Inversion of Control 风格,
其中,协作者作为属性提供给 bean。其他方法ApplicationContext
提供对文件资源的访问、发布应用程序事件、
并访问MessageSource
.这些附加功能在的附加功能ApplicationContext
.
自动装配是获取对ApplicationContext
.传统的 constructor
和byType
自动装配模式
(如 Autowiring Collaborators 中所述)可以提供ApplicationContext
对于 constructor 参数或 setter 方法参数,
分别。为了提高灵活性,包括自动装配字段和
多个参数方法,使用基于注释的自动装配功能。如果这样做,
这ApplicationContext
自动连接到字段、构造函数参数或方法
参数,该参数需要ApplicationContext
如果字段、构造函数或
有问题的方法带有@Autowired
注解。有关更多信息,请参阅用@Autowired
.
当ApplicationContext
创建一个实现org.springframework.beans.factory.BeanNameAware
接口,该类由
对其关联对象定义中定义的名称的引用。以下清单
显示了 BeanNameAware 接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
该回调在填充普通 bean 属性之后但在
初始化回调,例如InitializingBean.afterPropertiesSet()
或自定义
init-method 的
1.6.3. 其他Aware
接口
此外ApplicationContextAware
和BeanNameAware
(前面讨论过)、
Spring 提供广泛的Aware
让 bean 向容器指示的回调接口
它们需要一定的基础设施依赖性。作为一般规则,该名称表示
dependency 类型。下表总结了最重要的Aware
接口:
名字 | 注入的依赖项 | 解释于... |
---|---|---|
|
声明 |
|
|
封闭事件发布者 |
|
|
用于加载 bean 类的类加载器。 |
|
|
声明 |
|
|
声明 Bean 的名称。 |
|
|
资源适配器 |
|
|
定义了 weaver,用于在加载时处理类定义。 |
|
|
配置的消息解析策略(支持参数化和 国际化)。 |
|
|
Spring JMX 通知发布者。 |
|
|
配置了 loader ,用于对资源的低级访问。 |
|
|
当前 |
|
|
当前 |
再次注意,使用这些接口会将您的代码绑定到 Spring API,并且不会 遵循 Inversion of Control 样式。因此,我们建议将它们用于基础设施 需要以编程方式访问容器的 bean。
1.7. Bean 定义继承
一个 bean 定义可以包含很多配置信息,包括构造函数 参数、属性值和特定于容器的信息,例如初始化 method、静态工厂方法名称等。子 Bean 定义继承 来自父定义的 configuration 数据。子定义可以覆盖一些 值或根据需要添加其他值。使用父 Bean 定义和子 Bean 定义可以节省很多 的打字。实际上,这是一种模板形式。
如果您使用ApplicationContext
以编程方式接口,子 Bean
定义由ChildBeanDefinition
类。大多数用户不工作
与他们在这个层面上。相反,它们在类中以声明方式配置 bean 定义
例如ClassPathXmlApplicationContext
.使用基于 XML 的配置时
元数据,您可以使用parent
属性
指定父 Bean 作为此属性的值。以下示例显示了如何作
为此,请执行以下作:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize"> (1)
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
1 | 请注意parent 属性。 |
子 Bean 定义使用父定义中的 Bean 类(如果没有 指定,但也可以覆盖它。在后一种情况下,子 Bean 类必须是 与 Parent 兼容(即,它必须接受 Parent的 Property 值)。
子 Bean 定义继承 scope、constructor argument 值、property 值和
method 覆盖父级,并可选择添加新值。任何范围、初始化
method、destroy method 或static
您指定的出厂设置方法
覆盖相应的父设置。
其余设置始终取自子定义:depends on, autowire 模式、依赖关系检查、Singleton 和 Lazy init。
前面的示例通过使用
这abstract
属性。如果父定义未指定类,则显式
将父 Bean 定义标记为abstract
是必需的,如下例所示
显示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父 Bean 不能单独实例化,因为它不完整,并且是
也显式标记为abstract
.当定义为abstract
是的
只能用作纯模板 bean 定义,用作
子定义。尝试使用这样的abstract
父 Bean 本身,通过引用
作为另一个 bean 的 ref 属性或执行显式getBean()
call 替换为
父 Bean ID 返回错误。同样,容器的内部preInstantiateSingletons()
method 忽略定义为
抽象。
ApplicationContext 默认情况下,预实例化所有单例。因此,它是
重要的是(至少对于单例 bean),如果你有一个(父)bean 定义
它仅打算用作模板,并且此定义指定了一个类,则
必须确保将 abstract 属性设置为 true,否则应用程序
context 实际上会(尝试)预先实例化abstract 豆。 |
1.8. 容器扩展点
通常,应用程序开发人员不需要子类化ApplicationContext
implementation 类。相反,Spring IoC 容器可以通过插入来扩展
特殊集成接口的实现。接下来的几节将介绍这些
集成接口。
1.8.1. 使用BeanPostProcessor
这BeanPostProcessor
interface 定义您可以实现的回调方法
提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项
resolution logic,依此类推。如果你想在
Spring 容器完成实例化、配置和初始化 bean 后,您可以
插入一个或多个自定义BeanPostProcessor
实现。
您可以配置多个BeanPostProcessor
实例,您可以控制 Order
其中这些BeanPostProcessor
实例运行,方法是将order
财产。
只有在BeanPostProcessor
实现Ordered
接口。如果您编写自己的BeanPostProcessor
,您应该考虑实现
这Ordered
界面也是如此。有关更多详细信息,请参阅BeanPostProcessor
和Ordered
接口。另见注释
上编程
注册BeanPostProcessor
实例.
要更改实际的 Bean 定义(即定义 Bean 的 Blueprint),请执行以下作:
您需要改用 |
这org.springframework.beans.factory.config.BeanPostProcessor
接口包括
恰好是两个回调方法。当此类注册为后处理器时,使用
容器中,对于容器创建的每个 bean 实例,
后处理器在容器之前从容器获取回调
初始化方法(如InitializingBean.afterPropertiesSet()
或任何
宣布init
方法)调用,并在任何 bean 初始化回调之后调用。
后处理器可以对 bean 实例执行任何作,包括忽略
callback 的 Quin 函数。bean 后处理器通常会检查回调接口,
或者它可能用代理包装 bean。一些 Spring AOP 基础设施类是
作为 Bean 后处理器实现,以提供代理包装逻辑。
一ApplicationContext
自动检测在
配置元数据,这些元数据实现BeanPostProcessor
接口。这ApplicationContext
将这些 bean 注册为后处理器,以便可以调用它们
稍后,在 bean 创建时。Bean 后处理器可以部署在容器中的
与任何其他Beans一样时尚。
请注意,在声明BeanPostProcessor
通过使用@Bean
factory 方法在
configuration 类,则工厂方法的返回类型应为 implementation
类本身或至少org.springframework.beans.factory.config.BeanPostProcessor
接口,清楚地指示该 Bean 的后处理器性质。否则,ApplicationContext
在完全创建之前无法按类型自动检测它。
由于BeanPostProcessor
需要提前实例化,以便应用于
初始化上下文中的其他 bean,这种早期类型检测至关重要。
以编程方式注册 虽然BeanPostProcessor 实例BeanPostProcessor 注册通过ApplicationContext auto-detection (如前所述),您可以注册它们
以编程方式针对ConfigurableBeanFactory 通过使用addBeanPostProcessor 方法。当您需要在
注册,甚至用于跨层次结构中的上下文复制 Bean 后处理器。
但请注意,BeanPostProcessor 以编程方式添加的实例不遵循
这Ordered 接口。在这里,是注册顺序决定了顺序
的执行。另请注意,BeanPostProcessor 以编程方式注册的实例
始终在通过自动检测注册的 URL 之前处理,无论
显式排序。 |
BeanPostProcessor 实例和 AOP 自动代理实现 对于任何此类 bean,您应该会看到一条信息性日志消息: 如果你有 bean 连接到你的 |
以下示例演示如何编写、注册和使用BeanPostProcessor
实例
在ApplicationContext
.
示例:Hello World、BeanPostProcessor
-风格
第一个示例说明了基本用法。该示例显示了自定义BeanPostProcessor
调用toString()
方法作为
它由容器创建,并将生成的字符串打印到系统控制台。
以下清单显示了自定义BeanPostProcessor
implementation 类定义:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
import org.springframework.beans.factory.config.BeanPostProcessor
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
// simply return the instantiated bean as-is
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
return bean // we could potentially return any object reference here...
}
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
println("Bean '$beanName' created : $bean")
return bean
}
}
以下内容beans
元素使用InstantiationTracingBeanPostProcessor
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean (messenger) is instantiated, this custom
BeanPostProcessor implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
请注意InstantiationTracingBeanPostProcessor
只是定义。它没有
甚至有一个名称,并且,因为它是一个 bean,所以它可以像任何 bean 一样被依赖注入
其他豆子。(前面的配置还定义了一个由 Groovy 支持的 bean
脚本。Spring 动态语言支持在标题为“动态语言支持”的章节中进行了详细介绍。
以下 Java 应用程序运行上述代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
val messenger = ctx.getBean<Messenger>("messenger")
println(messenger)
}
上述应用程序的输出类似于以下内容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
1.8.2. 使用BeanFactoryPostProcessor
我们要看的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor
.的语义
此接口类似于BeanPostProcessor
,具有一个 Major
差异:BeanFactoryPostProcessor
对 Bean 配置元数据进行作。
也就是说,Spring IoC 容器允许BeanFactoryPostProcessor
阅读
配置元数据,并可能在容器实例化之前对其进行更改
除BeanFactoryPostProcessor
实例。
您可以配置多个BeanFactoryPostProcessor
实例,您可以在
这些BeanFactoryPostProcessor
实例运行,方法是将order
财产。
但是,只有在BeanFactoryPostProcessor
实现Ordered
接口。如果您编写自己的BeanFactoryPostProcessor
,您应该
考虑实施Ordered
界面也是如此。请参阅BeanFactoryPostProcessor
和Ordered
interfaces 了解更多详情。
如果要更改实际的 Bean 实例(即创建的对象
),那么您需要改用 也 |
当 bean 工厂后处理器在ApplicationContext
,以便将更改应用于
定义容器。Spring 包含许多预定义的 bean factory
后处理器,例如PropertyOverrideConfigurer
和PropertySourcesPlaceholderConfigurer
.您还可以使用自定义BeanFactoryPostProcessor
— 例如,注册自定义属性编辑器。
一ApplicationContext
自动检测部署到其中的任何 bean
实现BeanFactoryPostProcessor
接口。它将这些 bean 用作 bean factory
后处理器。您可以将这些后处理器 bean 部署为
你会喜欢任何其他豆子。
与 一样BeanPostProcessor s ,您通常不想配置BeanFactoryPostProcessor s 进行延迟初始化。如果没有其他 bean 引用Bean(Factory)PostProcessor ,该后处理器根本不会被实例化。
因此,将其标记为延迟初始化将被忽略,并且Bean(Factory)PostProcessor 将预先实例化,即使您将default-lazy-init 属性设置为true 在您的<beans /> 元素。 |
示例:类名替换PropertySourcesPlaceholderConfigurer
您可以使用PropertySourcesPlaceholderConfigurer
外部化属性值
使用标准 Java 从单独文件中的 bean 定义Properties
格式。
这样做使部署应用程序的人员能够自定义特定于环境的
属性(例如数据库 URL 和密码),而不会产生
修改容器的一个或多个主 XML 定义文件。
请考虑以下基于 XML 的配置元数据片段,其中DataSource
with placeholder values 定义:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
该示例显示了从外部Properties
文件。在运行时,
一个PropertySourcesPlaceholderConfigurer
应用于元数据,该元数据将替换某些
属性。要替换的值指定为
形式${property-name}
,它遵循 Ant 和 log4j 以及 JSP EL 样式。
实际值来自标准 Java 中的另一个文件Properties
格式:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
因此,${jdbc.username}
string 在运行时替换为值 'sa' 和
这同样适用于与 Properties 文件中的键匹配的其他占位符值。
这PropertySourcesPlaceholderConfigurer
检查大多数属性中的占位符,以及
bean 定义的属性。此外,您还可以自定义占位符前缀和后缀。
使用context
namespace 中引入的,你可以配置属性占位符
替换为专用的配置元素。您可以将一个或多个位置作为
逗号分隔的列表location
属性,如下例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
这PropertySourcesPlaceholderConfigurer
不仅在Properties
文件。默认情况下,如果在指定的属性文件中找不到属性,则
它检查 SpringEnvironment
properties 和常规 JavaSystem
性能。
您可以使用
如果该类在运行时无法解析为有效类,则 Bean 的解析
在即将创建时失败,即在 |
示例:PropertyOverrideConfigurer
这PropertyOverrideConfigurer
,另一个 Bean Factory 后处理器,类似于PropertySourcesPlaceholderConfigurer
,但与后者不同的是,原始定义
可以为 Bean 属性设置默认值或根本没有值。如果 overoverrideProperties
file 没有某个 Bean 属性的条目,则默认的
上下文定义。
请注意,bean 定义不知道被覆盖,因此它不是
从 XML 定义文件中可以立即明显看出 override configurer 正在
使用。如果有多个PropertyOverrideConfigurer
定义不同
值,由于覆盖机制,最后一个 Bean 属性优先。
Properties 文件配置行采用以下格式:
beanName.property=value
下面的清单显示了格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
此示例文件可以与容器定义一起使用,该容器定义包含名为dataSource
该driver
和url
性能。
还支持复合属性名称,只要路径的每个组件
除了被覆盖的最终属性已经是非 null 的(大概是初始化的
由构造函数)。在以下示例中,sammy
属性的bob
属性的fred
属性的tom
豆
设置为 Scalar 值123
:
tom.fred.bob.sammy=123
指定的覆盖值始终是文本值。它们不会被翻译成 bean 引用。当 XML Bean 中的原始值 definition 指定 bean 引用。 |
使用context
namespace 中引入的,则可以配置
属性替换为专用的 configuration 元素,如下例所示:
<context:property-override location="classpath:override.properties"/>
1.8.3. 使用FactoryBean
您可以实现org.springframework.beans.factory.FactoryBean
对象的接口
本身就是工厂。
这FactoryBean
interface 是 Spring IoC 容器的
实例化逻辑。如果你有复杂的初始化代码,最好用
与 Java 相比,您可以创建自己的 XMLFactoryBean
,在该类中编写复杂的初始化,然后将
习惯FactoryBean
放入容器中。
这FactoryBean<T>
interface 提供了三种方法:
-
T getObject()
:返回此工厂创建的对象的实例。这 实例可以共享,具体取决于此工厂是否返回单例 或原型。 -
boolean isSingleton()
:返回true
如果此FactoryBean
返回单例或false
否则。此方法的默认实现返回true
. -
Class<?> getObjectType()
:返回getObject()
方法 或null
如果事先不知道类型。
这FactoryBean
concept 和 interface 在 Spring 中的许多地方使用
框架。超过 50 种FactoryBean
与 Spring 的接口
本身。
当您需要向容器询问实际FactoryBean
实例本身而不是
它生成的 bean 会作为 bean 的id
当
调用&
getBean()
方法ApplicationContext
.因此,对于给定的FactoryBean
替换为id
之myBean
调用getBean("myBean")
在容器上返回
的产品FactoryBean
,而调用getBean("&myBean")
返回FactoryBean
实例本身。
1.9. 基于注解的容器配置
XML 设置的替代方案由基于 annotation 的配置提供,它依赖于
用于连接组件的字节码元数据,而不是尖括号声明。
开发人员没有使用 XML 来描述 Bean 连接,而是移动了配置
到组件类本身中,方法是在相关的类、方法或
field 声明。如示例:AutowiredAnnotationBeanPostProcessor
用
一个BeanPostProcessor
与 annotation 结合使用是扩展
Spring IoC 容器。例如,Spring 2.0 引入了强制执行
required 属性替换为@Required
注解。Spring
2.5 使得遵循相同的通用方法来驱动 Spring 的依赖项成为可能
注射。从本质上讲,@Autowired
annotation 提供与
在 Autowiring Collaborators 中描述,但具有更精细的控制和更广泛的
适用性。Spring 2.5 还添加了对 JSR-250 注解的支持,例如@PostConstruct
和@PreDestroy
.Spring 3.0 增加了对 JSR-330(依赖性
Injection for Java)注解中包含的javax.inject
软件包,例如@Inject
和@Named
.有关这些注释的详细信息,请参阅相关部分。
注释注入在 XML 注入之前执行。因此,XML 配置 覆盖通过这两种方法连接的属性的注释。 |
与往常一样,您可以将后处理器注册为单独的 bean 定义,但是它们
也可以通过在基于 XML 的 Spring 中包含以下标记来隐式注册
配置(请注意,其中包含了context
namespace) 的 URL:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
这<context:annotation-config/>
元素隐式注册以下后处理器:
|
1.9.1. @Required
这@Required
注释适用于 Bean 属性 setter 方法,如下所示
例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Required
lateinit var movieFinder: MovieFinder
// ...
}
此注释指示受影响的 Bean 属性必须在
配置时间,通过 Bean 定义中的显式属性值或通过
autowiring 的如果受影响的 Bean 属性尚未
填充。这允许预先和显式的失败,避免NullPointerException
实例或类似内容。我们仍然建议您将断言放入
Bean 类本身(例如,放入 init 方法中)。这样做会强制执行那些必需的
引用和值,即使您在容器外部使用类也是如此。
这 |
这 |
1.9.2. 使用@Autowired
JSR 330 的 |
您可以应用@Autowired
注解添加到构造函数中,如下例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao)
从 Spring Framework 4.3 开始, |
您还可以应用@Autowired
对传统 setter 方法的注释,
如下例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Autowired
lateinit var movieFinder: MovieFinder
// ...
}
您还可以将注释应用于具有任意名称和多个 参数,如下例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
您可以申请@Autowired
添加到字段,甚至将其与构造函数混合使用,因为
以下示例显示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao) {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
确保您的目标组件(例如 对于通过 Classpath 扫描找到的 XML 定义的 bean 或组件类,容器
通常预先知道混凝土类型。但是,对于 |
你还可以指示 Spring 从ApplicationContext
通过添加@Autowired
对字段或方法的注释,该字段或方法
需要该类型的数组,如下例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalogs: Array<MovieCatalog>
// ...
}
这同样适用于类型化集合,如下例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Set<MovieCatalog>
// ...
}
您的目标 bean 可以实现 您可以声明 请注意,标准 |
甚至打字Map
只要预期的密钥类型为String
.
map 值包含预期类型的所有 bean,键包含
相应的 bean 名称,如下例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Map<String, MovieCatalog>
// ...
}
默认情况下,当给定的 bean 没有匹配的候选 bean 可用时,自动装配将失败 注射点。对于声明的数组、集合或 map,至少有一个 匹配元素。
默认行为是将带注释的方法和字段视为指示必需的
依赖。您可以更改此行为,如以下示例所示,
使框架能够跳过无法满足的注入点,方法是将其标记为
非必需的(即,通过设置required
属性@Autowired
自false
):
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Autowired(required = false)
var movieFinder: MovieFinder? = null
// ...
}
如果非必需方法的依赖项(或其 dependencies)不可用。非必填字段将 在这种情况下,根本不填充,保留其默认值。
注入的构造函数和工厂方法参数是一种特殊情况,因为required
属性@Autowired
由于 Spring 的构造函数,其含义略有不同
Resolution 算法,该算法可能会处理多个构造函数。构造 函数
和工厂方法参数在默认情况下实际上是必需的,但有一些特殊的
单构造函数方案中的规则,例如多元素注入点(数组、
collections, map) 解析为空实例。这
允许使用通用的实现模式,其中所有依赖项都可以在
unique 多参数构造函数 — 例如,声明为单个公共构造函数
没有@Autowired
注解。
任何给定 bean 类只有一个构造函数可以声明 这 |
或者,您可以表示特定依赖项的非必需性质
通过 Java 8 的java.util.Optional
,如下例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从 Spring Framework 5.0 开始,您还可以使用@Nullable
注释(任何类型的
在任何 package 中 — 例如,javax.annotation.Nullable
来自 JSR-305)或只是利用
Kotlin 内置 null-safety 支持:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
class SimpleMovieLister {
@Autowired
var movieFinder: MovieFinder? = null
// ...
}
您还可以使用@Autowired
对于众所周知的可解析接口
依赖:BeanFactory
,ApplicationContext
,Environment
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
.这些接口及其扩展
接口,例如ConfigurableApplicationContext
或ResourcePatternResolver
是
自动解析,无需特殊设置。以下示例 autowires
一ApplicationContext
对象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var context: ApplicationContext
// ...
}
这 |
1.9.3. 微调基于 Annotation 的自动装配@Primary
因为按类型自动装配可能会导致多个候选者,所以通常需要
对选择过程的更多控制。实现此目的的一种方法是使用 Spring 的@Primary
注解。@Primary
指示应给定一个特定的 bean
当多个 bean 是自动连接到单值的候选者时,首选项
Dependency。如果候选者中正好存在一个主 bean,则它将成为
autowired 值。
考虑以下定义firstMovieCatalog
作为
主要MovieCatalog
:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
@Configuration
class MovieConfiguration {
@Bean
@Primary
fun firstMovieCatalog(): MovieCatalog { ... }
@Bean
fun secondMovieCatalog(): MovieCatalog { ... }
// ...
}
在上述配置中,以下MovieRecommender
与firstMovieCatalog
:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
相应的 bean 定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 使用限定符微调基于 Annotation 的自动装配
@Primary
是在多个实例中使用 autowiring by type 的有效方法,当一个实例
可以确定主要候选人。当您需要对选择过程进行更多控制时,
您可以使用 Spring 的@Qualifier
注解。您可以关联限定符值
使用特定参数时,缩小类型匹配的集合,以便特定的 bean 为
为每个参数选择。在最简单的情况下,这可以是一个简单的描述性值,如
如以下示例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
您还可以指定@Qualifier
annotation 在单个构造函数参数上或
method 参数,如以下示例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
以下示例显示了相应的 Bean 定义。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/> (2)
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1 | 具有main qualifier 值与 constructor 参数连接,该
使用相同的值进行限定。 |
2 | 具有action qualifier 值与 constructor 参数连接,该
使用相同的值进行限定。 |
对于回退匹配,Bean 名称被视为默认限定符值。因此,您
可以使用id
之main
而不是嵌套的限定符元素,而是 leading
更改为相同的匹配结果。但是,尽管您可以使用此约定来引用
按名称指定 bean,@Autowired
从根本上讲,是关于类型驱动注入的
可选的语义限定符。这意味着限定符值,即使 bean 名称为
fallback 在类型匹配集中始终具有缩小语义。他们没有
在语义上表示对唯一 Bean 的引用id
.好的限定符值为main
或EMEA
或persistent
,表示特定组件的特征,这些特征是
独立于 Beanid
,在匿名 bean 的情况下可能会自动生成
定义,例如前面示例中的定义。
限定符也适用于类型化集合,如前所述 — 例如,到Set<MovieCatalog>
.在这种情况下,所有匹配的 bean 都会根据声明的
限定符作为集合注入。这意味着限定符不必是
独特。相反,它们构成了筛选标准。例如,您可以定义
倍数MovieCatalog
具有相同限定符值 “action” 的 bean,所有这些 bean 都是
注入到Set<MovieCatalog>
注解@Qualifier("action")
.
在类型匹配中,让限定符值根据目标 Bean 名称进行选择
应聘者不需要 |
也就是说,如果您打算按名称表示注解驱动的注入,请不要
主要用途@Autowired
,即使它能够在
类型匹配的候选项。相反,请使用 JSR-250@Resource
注解,即
语义上定义以通过其唯一名称标识特定的目标组件,其中
声明的类型与匹配过程无关。@Autowired
有
不同的语义:按类型选择候选 bean 后,指定的String
限定符值仅在类型选定的候选项中考虑(例如,
匹配account
限定符对标有相同限定符标签的 bean)。
对于本身定义为集合的 bean,Map
或数组类型、@Resource
是一个很好的解决方案,通过唯一名称引用特定的集合或数组 bean。
也就是说,从 4.3 开始,您可以匹配 collection、Map
和数组类型通过 Spring 的@Autowired
类型匹配算法,只要元素类型信息
保存在@Bean
返回类型签名或集合继承层次结构。
在这种情况下,你可以使用限定符值在相同类型的集合中进行选择。
如上一段所述。
从 4.3 开始,@Autowired
还会考虑注入的自引用(即引用
返回到当前注入的 bean)。请注意,自注入是一种后备。
对其他组件的常规依赖项始终具有优先权。从这个意义上说,自我
参考文献不参与常规的候选选择,因此位于
特别 从 不 主要。相反,它们总是以最低优先级结束。
在实践中,您应该仅将自引用作为最后的手段(例如,对于
通过 Bean 的事务代理调用同一实例上的其他方法)。
在这种情况下,请考虑将受影响的方法分解为单独的委托 Bean。
或者,您可以使用@Resource
,这可能会获取返回到当前 Bean 的代理
通过其唯一名称。
尝试注入 |
@Autowired
应用于字段、构造函数和多参数方法,允许
通过参数级别的限定符注释缩小范围。相比之下,@Resource
仅支持具有单个参数的字段和 Bean 属性 setter 方法。
因此,如果您的注入目标是
constructor 或多参数方法。
您可以创建自己的自定义限定符注释。为此,请定义一个 annotation 和
提供@Qualifier
注释,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
然后,您可以在自动装配的字段和参数上提供自定义限定符,如 以下示例显示:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
class MovieRecommender {
@Autowired
@Genre("Action")
private lateinit var actionCatalog: MovieCatalog
private lateinit var comedyCatalog: MovieCatalog
@Autowired
fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
this.comedyCatalog = comedyCatalog
}
// ...
}
接下来,您可以提供候选 Bean 定义的信息。您可以添加<qualifier/>
标签作为<bean/>
标签中,然后指定type
和value
以匹配您的自定义限定符注释。该类型与
注解的完全限定类名。或者,为了方便,如果没有
存在冲突的名称,则可以使用短类名。以下示例
演示了这两种方法:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在 Classpath Scanning 和 Managed Components 中,您可以看到基于 Comments 的替代方法 在 XML 中提供限定符元数据。具体而言,请参阅提供带有注释的限定符元数据。
在某些情况下,使用没有值的 Comments 可能就足够了。这可以是 当注释用于更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 目录,当没有 Internet 连接可用时可以搜索。首先,定义 简单注释,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
然后将注释添加到要自动装配的字段或属性中,如 以下示例:
public class MovieRecommender {
@Autowired
@Offline (1)
private MovieCatalog offlineCatalog;
// ...
}
1 | 此行添加@Offline 注解。 |
class MovieRecommender {
@Autowired
@Offline (1)
private lateinit var offlineCatalog: MovieCatalog
// ...
}
1 | 此行添加@Offline 注解。 |
现在 bean 定义只需要一个限定符type
,如以下示例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
1 | 此元素指定限定符。 |
您还可以定义自定义限定符注释,这些注释接受
加法 or 代替简单的value
属性。如果多个属性值为
然后在要自动装配的字段或参数上指定,则 Bean 定义必须匹配
所有此类属性值都被视为 Autowire 候选项。例如,
请考虑以下注释定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
在这种情况下Format
是一个枚举,定义如下:
public enum Format {
VHS, DVD, BLURAY
}
enum class Format {
VHS, DVD, BLURAY
}
要自动装配的字段使用 custom 限定符进行批注,并包含值
对于这两个属性:genre
和format
,如下例所示:
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
class MovieRecommender {
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Action")
private lateinit var actionVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Comedy")
private lateinit var comedyVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.DVD, genre = "Action")
private lateinit var actionDvdCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.BLURAY, genre = "Comedy")
private lateinit var comedyBluRayCatalog: MovieCatalog
// ...
}
最后,bean 定义应包含匹配的限定符值。这个例子
还演示了您可以使用 Bean 元属性而不是<qualifier/>
元素。如果可用,则<qualifier/>
元素及其属性采用
优先级,但自动装配机制会回退到<meta/>
标记(如果不存在此类限定符),如
以下示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>
1.9.5. 使用泛型作为自动装配限定符
除了@Qualifier
注释,您可以使用 Java 泛型类型
作为限定的隐含形式。例如,假设您有以下
配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Configuration
class MyConfiguration {
@Bean
fun stringStore() = StringStore()
@Bean
fun integerStore() = IntegerStore()
}
假设前面的 bean 实现了一个通用接口(即Store<String>
和Store<Integer>
),您可以@Autowire
这Store
interface 的 API 和 generic 是
用作限定符,如下例所示:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean
@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean
泛型限定符也适用于自动装配列表时,Map
实例和数组。这
以下示例自动装配一个泛型List
:
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>
1.9.6. 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
,允许您注册自己的自定义限定符
注解类型,即使它们没有使用 Spring 的@Qualifier
注解。
以下示例演示如何使用CustomAutowireConfigurer
:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
这AutowireCandidateResolver
通过以下方式确定 AutoWire 候选项:
-
这
autowire-candidate
每个 bean 定义的值 -
任何
default-autowire-candidates
模式在<beans/>
元素 -
存在
@Qualifier
注释和已注册的任何自定义注释 使用CustomAutowireConfigurer
当多个 bean 符合自动装配候选者的条件时,“主要”的确定是
如下所示:如果候选者中只有一个 bean 定义具有primary
属性设置为true
,则它将被选中。
1.9.7. 使用@Resource
Spring 还支持使用 JSR-250 进行注入@Resource
注解
(javax.annotation.Resource
) 或 Bean 属性 setter 方法。
这是 Java EE 中的一种常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 中
端点。Spring 也支持 Spring Management 的对象使用这种模式。
@Resource
接受 name 属性。默认情况下,Spring 将该值解释为
要注入的 bean 名称。换句话说,它遵循 by-name 语义,
如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") (1)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
1 | 此行注入@Resource . |
class SimpleMovieLister {
@Resource(name="myMovieFinder") (1)
private lateinit var movieFinder:MovieFinder
}
1 | 此行注入@Resource . |
如果未明确指定名称,则默认名称派生自字段名称或
setter 方法。如果是字段,则采用字段名称。对于 setter 方法,
它采用 Bean 属性 name。以下示例将具有 bean
叫movieFinder
注入到其 setter 方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
class SimpleMovieLister {
@Resource
private lateinit var movieFinder: MovieFinder
}
随 Comments 提供的名称由ApplicationContext 其中,CommonAnnotationBeanPostProcessor 是有意识的。
如果您配置 Spring 的SimpleJndiBeanFactory 明确地。但是,我们建议您依赖默认行为和
使用 Spring 的 JNDI 查找功能来保持间接级别。 |
在 exclusive case 中@Resource
未指定显式名称的用法,以及类似的
自@Autowired
,@Resource
查找主要类型匹配项,而不是特定的命名 Bean
并解析众所周知的可解析依赖项:BeanFactory
,ApplicationContext
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
接口。
因此,在以下示例中,customerPreferenceDao
field 首先查找 bean
命名为 “customerPreferenceDao”,然后回退到该类型的主要类型匹配项CustomerPreferenceDao
:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; (1)
public MovieRecommender() {
}
// ...
}
1 | 这context field 根据已知的 Resolvable 依赖项类型注入:ApplicationContext . |
class MovieRecommender {
@Resource
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Resource
private lateinit var context: ApplicationContext (1)
// ...
}
1 | 这context field 根据已知的 Resolvable 依赖项类型注入:ApplicationContext . |
1.9.8. 使用@Value
@Value
通常用于注入外部化属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig
以及以下内容application.properties
文件:
catalog.name=MovieCatalog
在这种情况下,catalog
parameter 和 field 将等于MovieCatalog
价值。
Spring 提供了默认的宽松嵌入值解析器。它将尝试解析
属性值,如果无法解析,则为属性名称(例如${catalog.name}
)
将作为值注入。如果您想对不存在的
值,您应该声明一个PropertySourcesPlaceholderConfigurer
bean,如下所示
示例显示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Configuration
class AppConfig {
@Bean
fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
配置PropertySourcesPlaceholderConfigurer 使用 JavaConfig 时,@Bean method 必须为static . |
如果无法解析任何占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用${}
setPlaceholderPrefix
,setPlaceholderSuffix
或setValueSeparator
自定义
占位符。
默认情况下,Spring Boot 会配置一个PropertySourcesPlaceholderConfigurer bean 那个
将从application.properties 和application.yml 文件。 |
Spring 提供的内置转换器支持允许简单的类型转换(到Integer
或int
)进行自动处理。多个逗号分隔值可以是
自动转换为String
数组。
可以按如下方式提供默认值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
弹簧BeanPostProcessor
使用ConversionService
在幕后处理
转换String
值@Value
添加到 Target 类型。如果您想
提供自家自定义类型的转换支持,可自家提供ConversionService
bean 实例,如下例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): ConversionService {
return DefaultFormattingConversionService().apply {
addConverter(MyCustomConverter())
}
}
}
什么时候@Value
包含一个SpEL
表达该值将为 dynamic
在运行时计算,如下例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(
@Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)
SPEL 还支持使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
@Component
class MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)
1.9.9. 使用@PostConstruct
和@PreDestroy
这CommonAnnotationBeanPostProcessor
不仅识别@Resource
注解
还有 JSR-250 生命周期注释:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
.在 Spring 2.5 中引入,对这些
annotations 提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方案。前提是CommonAnnotationBeanPostProcessor
在 Spring 中注册ApplicationContext
,
带有这些 Comments 之一的方法在生命周期的同一点被调用
作为相应的 Spring 生命周期接口方法或显式声明的回调
方法。在以下示例中,缓存在初始化时预先填充,并且
销毁时清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
fun clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制。
喜欢 |
1.10. Classpath 扫描和托管组件
本章中的大多数示例都使用 XML 来指定生成
每BeanDefinition
在 Spring 容器中。上一节
(基于注释的容器配置)演示了如何提供大量配置
元数据。然而,即使在这些例子中,“基础”
Bean 定义在 XML 文件中显式定义,而 Comments 仅驱动
依赖项注入。本节介绍用于隐式检测
candidate 组件。候选组件是
匹配过滤条件,并在
容器。这样就不需要使用 XML 来执行 Bean 注册。相反,您
可以使用注解(例如@Component
)、AspectJ 类型表达式或您自己的表达式
自定义过滤条件,用于选择哪些类注册了 Bean 定义
容器。
从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能包括
核心 Spring Framework 的一部分。这允许您使用 Java 而不是 Java 定义 bean
而不是使用传统的 XML 文件。查看 |
1.10.1.@Component
和进一步的 Stereotype Annotations
这@Repository
annotation 是满足该角色的任何类的标记,或者
存储库的构造型(也称为数据访问对象或 DAO)。用途
的标记是异常的自动转换,如 异常转换中所述。
Spring 提供了进一步的构造型 Comments:@Component
,@Service
和@Controller
.@Component
是任何 Spring Management 组件的通用构造型。@Repository
,@Service
和@Controller
是 的特化@Component
为
更具体的使用案例(在 Persistence、Service 和 Presentation 中
层)。因此,您可以使用@Component
,但是,通过使用@Repository
,@Service
或@Controller
相反,您的类更适合通过工具或关联进行处理
与方面。例如,这些原型注释是
切入点。@Repository
,@Service
和@Controller
也可以
在 Spring Framework 的未来版本中携带其他语义。因此,如果你是
选择使用@Component
或@Service
对于您的服务层,@Service
是
显然是更好的选择。同样,如前所述,@Repository
已经
支持作为持久层中自动异常转换的标记。
1.10.2. 使用元注解和组合注解
Spring 提供的许多 Comments 都可以用作
自己的代码。元注释是可以应用于另一个注释的注释。
例如,@Service
前面提到的 annotation 是元注释的@Component
,如下例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
1 | 这@Component 原因@Service 以与@Component . |
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
1 | 这@Component 原因@Service 以与@Component . |
您还可以组合元注释来创建 “组合注释”。例如
这@RestController
来自 Spring MVC 的注解由@Controller
和@ResponseBody
.
此外,组合注释可以选择从
meta-annotations 允许自定义。当您
希望仅公开 meta-annotation 属性的子集。例如, Spring 的@SessionScope
注解将范围名称硬编码为session
但仍允许
自定义proxyMode
.下面的清单显示了SessionScope
注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
@get:AliasFor(annotation = Scope::class)
val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
然后,您可以使用@SessionScope
而不声明proxyMode
如下:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope
class SessionScopedService {
// ...
}
您还可以覆盖proxyMode
,如下例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
有关更多详细信息,请参阅 Spring Annotation Programming Model wiki 页面。
1.10.3. 自动检测类并注册 bean 定义
Spring 可以自动检测构造型 class 并注册相应的BeanDefinition
实例中具有ApplicationContext
.例如,以下两个类
符合此类自动检测的条件:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的 bean,您需要添加@ComponentScan
发送到您的@Configuration
类,其中basePackages
属性
是这两个类的公共父包。(或者,您可以指定
包含每个类的父包的逗号或分号或空格分隔的列表。
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
为简洁起见,前面的示例可以使用value 属性的
注解(即@ComponentScan("org.example") ). |
以下替代方法使用 XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
的使用<context:component-scan> 隐式启用<context:annotation-config> .通常不需要包含<context:annotation-config> 元素<context:component-scan> . |
扫描 classpath 包需要存在相应的目录 条目。使用 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 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。
但是,请确保您的组件类已导出到 |
此外,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
在您使用
component-scan 元素。这意味着这两个组件是自动检测的,并且
连接在一起 — 所有这些都没有以 XML 形式提供任何 bean 配置元数据。
您可以禁用AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor 通过包含annotation-config 属性
的值为false . |
1.10.4. 使用过滤器自定义扫描
默认情况下,用@Component
,@Repository
,@Service
,@Controller
,@Configuration
的 API 中,或者使用自定义注释(本身使用@Component
是
唯一检测到的候选组件。但是,您可以修改和扩展此行为
通过应用自定义筛选器。将它们添加为includeFilters
或excludeFilters
的属性
这@ComponentScan
注解(或 AS<context:include-filter />
或<context:exclude-filter />
的<context:component-scan>
元素
XML 配置)。每个滤芯都需要type
和expression
属性。
下表描述了筛选选项:
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
annotation (默认) |
|
目标组件中类型级别存在或元存在的 Annotation。 |
可分配的 |
|
目标组件可分配给 (扩展或实现) 的类 (或接口) 。 |
AspectJ |
|
要由目标组件匹配的 AspectJ 类型表达式。 |
正则表达式 |
|
要与目标组件的类名称匹配的正则表达式。 |
习惯 |
|
的 |
以下示例显示了忽略所有@Repository
附注
并使用 “stub” 存储库:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
class AppConfig {
// ...
}
下面的清单显示了等效的 XML:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
您还可以通过设置useDefaultFilters=false 在
注解或通过提供use-default-filters="false" 作为<component-scan/> 元素。这实际上禁用了类的自动检测
annotated 或 meta-annotated with@Component ,@Repository ,@Service ,@Controller ,@RestController 或@Configuration . |
1.10.5. 在组件中定义 Bean 元数据
Spring 组件还可以向容器提供 bean 定义元数据。你可以做
this 具有相同的@Bean
用于定义 Bean 元数据的注释@Configuration
带注释的类。以下示例显示了如何执行此作:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@Component
class FactoryMethodComponent {
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
fun doWork() {
// Component method implementation omitted
}
}
前面的类是一个 Spring 组件,其doWork()
方法。但是,它还提供了一个 bean 定义,该定义具有 factory
method 引用 methodpublicInstance()
.这@Bean
annotation 标识
Factory 方法和其他 Bean 定义属性,例如通过
这@Qualifier
注解。可以指定的其他方法级 Comments 包括@Scope
,@Lazy
和自定义限定符注释。
除了组件初始化的角色外,您还可以将@Lazy 在标有@Autowired 或@Inject .在此上下文中,
它会导致注入延迟分辨率代理。但是,这种代理方法
相当有限。用于复杂的惰互,特别是组合
对于可选依赖项,我们建议ObjectProvider<MyTargetBean> 相反。 |
如前所述,支持自动装配的字段和方法,并带有额外的
支持自动装配@Bean
方法。以下示例显示了如何执行此作:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
该示例自动装配String
method 参数country
设置为age
属性privateInstance
.Spring Expression Language 元素
通过表示法定义属性的值#{ <expression> }
.为@Value
annotations,则表达式解析器会预先配置为在
解析表达式文本。
从 Spring Framework 4.3 开始,您还可以声明InjectionPoint
(或其更具体的子类:DependencyDescriptor
) 更改为
访问触发当前 Bean 创建的请求注入点。
请注意,这仅适用于 bean 实例的实际创建,而不适用于
注入现有实例。因此,此功能最适合
prototype 范围的 bean。对于其他作用域,工厂方法只看到
触发在给定范围内创建新 bean 实例的注入点
(例如,触发创建惰性单例 Bean 的依赖项)。
在这种情况下,您可以谨慎使用提供的注入点元数据。
以下示例演示如何使用InjectionPoint
:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
这@Bean
方法的处理方式与它们的
Spring 中的对应项@Configuration
类。区别在于@Component
类没有使用 CGLIB 进行增强以拦截方法和字段的调用。
CGLIB 代理是调用@Bean
方法
在@Configuration
类 创建对协作对象的 Bean 元数据引用。
这些方法不是用普通的 Java 语义调用的,而是通过
container 来提供 Spring 的通常生命周期管理和代理
bean,即使通过对@Bean
方法。
相反,在@Bean
方法中的普通@Component
类具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他
约束适用。
您可以声明 对 static 的调用 的 Java 语言可见性
最后,单个类可以包含多个 |
1.10.6. 命名自动检测到的分量
当组件在扫描过程中被自动检测时,其 Bean 名称为
由BeanNameGenerator
该扫描仪已知的策略。默认情况下,任何
Spring 构造型注释 (@Component
,@Repository
,@Service
和@Controller
) 中包含名称value
从而将该名称提供给
相应的 bean 定义。
如果此类批注不包含名称value
或任何其他检测到的组件
(例如由自定义过滤器发现的那些),默认的 Bean 名称生成器返回
未大写的非限定类名。例如,如果以下组件
类,则名称将为myMovieLister
和movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果您不想依赖默认的 bean 命名策略,则可以提供自定义的
bean 命名策略。首先,实现BeanNameGenerator
接口,并确保包含默认的 no-arg 构造函数。然后,提供完整的
限定的类名,如以下示例注释
和 bean 定义显示。
如果由于多个自动检测到的组件具有
相同的非限定类名(即,具有相同名称但驻留在
不同的软件包),则可能需要配置BeanNameGenerator 默认为
生成的 Bean 名称的完全限定类名。从 Spring Framework 5.2.3 开始,FullyQualifiedAnnotationBeanNameGenerator 位于包装中org.springframework.context.annotation 可用于此类目的。 |
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作为一般规则,请考虑在使用 Comments 指定名称时,每当其他 组件可能会显式引用它。另一方面, 每当容器负责 wiring 时,自动生成的名称就足够了。
1.10.7. 为自动检测的组件提供范围
与一般的 Spring 管理组件一样,默认和最常见的
autodetected components 为singleton
.但是,有时您需要不同的范围
,可由@Scope
注解。您可以提供
范围,如下例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 注解仅在具体的 Bean 类上被内省(对于带注解的
组件)或工厂方法(对于@Bean 方法)。与 XML Bean 相比
定义,没有 bean 定义继承的概念,而继承
类级别的层次结构与元数据目的无关。 |
有关 Spring 上下文中特定于 Web 的范围(例如“request”或“session”)的详细信息,
请参阅 Request、Session、Application 和 WebSocket 范围。与这些范围的预构建注释一样,
您还可以使用 Spring 的元注释编写自己的范围注释
方法:例如,使用@Scope("prototype")
,
也可能声明自定义范围代理模式。
要为范围解析提供自定义策略,而不是依赖
基于注解的方法,您可以实现ScopeMetadataResolver 接口。请务必包含默认的 no-arg 构造函数。然后,您可以提供
完全限定的类名,如以下示例所示
注释和 bean 定义显示: |
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使用某些非单例作用域时,可能需要为
scoped 对象。原因在 Scoped Bean as Dependencies 中进行了描述。
为此,component-scan 上提供了 scoped-proxy 属性
元素。三个可能的值是:no
,interfaces
和targetClass
.例如
以下配置将生成标准 JDK 动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8. 提供带有注解的限定符元数据
这@Qualifier
annotation 在 Fine-tuning Annotation-based Autowiring with Qualifiers中讨论。
该部分中的示例演示了@Qualifier
annotation 和
自定义限定符注释,用于在解析 autowire 时提供精细控制
候选人。因为这些示例是基于 XML Bean 定义的,所以限定符
元数据是使用qualifier
或meta
的bean
元素。当依赖 Classpath 扫描
自动检测组件,则可以提供类型级
Candidate 类的注释。以下三个示例演示了这一点
技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
与大多数基于 Comments 的替代方案一样,请记住,Comments 元数据是 绑定到类定义本身,而 XML 的使用允许多个 bean 的 Bean Package,以便在其限定符元数据中提供变体,因为 元数据是按实例而不是按类提供的。 |
1.10.9. 生成候选组件的索引
虽然 Classpath 扫描非常快,但可以提高启动性能 通过在编译时创建静态候选列表来获取大型应用程序。在这个 模式下,作为组件扫描目标的所有模块都必须使用此机制。
您现有的@ComponentScan 或<context:component-scan/> 指令必须保留
unchanged 请求上下文以扫描某些包中的候选项。当ApplicationContext 检测到此类索引时,它会自动使用它而不是扫描
类路径。 |
要生成索引,请向包含 作为组件 Scan 指令目标的组件。以下示例显示了 如何使用 Maven 执行此作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.25.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
对于 Gradle 4.5 及更早版本,应在compileOnly
配置,如以下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.25.RELEASE"
}
对于 Gradle 4.6 及更高版本,应在annotationProcessor
配置,如以下示例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.2.25.RELEASE"
}
这spring-context-indexer
artifact 会生成一个META-INF/spring.components
文件,该
包含在 jar 文件中。
在 IDE 中使用此模式时,spring-context-indexer 必须是
注册为注释处理器,以确保索引在以下时间是最新的
候选组件已更新。 |
当META-INF/spring.components 找到 file
在 Classpath 上。如果索引部分可用于某些库(或用例)
但无法为整个应用程序构建,则可以回退到常规的 Classpath
排列(就好像根本没有索引一样)通过设置spring.index.ignore 自true 作为 JVM 系统属性或通过SpringProperties 机制。 |
1.11. 使用 JSR 330 标准注解
从 Spring 3.0 开始, Spring 提供对 JSR-330 标准注释的支持 (依赖关系注入)。这些 Comments 的扫描方式与 Spring 相同 附注。要使用它们,您需要在 Classpath 中包含相关的 jar。
如果您使用 Maven,则
|
1.11.1. 依赖注入@Inject
和@Named
而不是@Autowired
,您可以使用@javax.inject.Inject
如下:
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
与 一样@Autowired
,您可以使用@Inject
在字段级别、方法级别
和 constructor-argument 级别。此外,您可以将注入点声明为Provider
,允许按需访问范围较短的 bean 或延迟访问
其他 bean 通过Provider.get()
叫。以下示例提供了
前面的示例:
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider<MovieFinder> movieFinder;
@Inject
public void setMovieFinder(Provider<MovieFinder> movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
如果您想为应该注入的依赖项使用限定名称,
您应该使用@Named
annotation 中,如下例所示:
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
@Inject
fun setMovieFinder(@Named("main") movieFinder: MovieFinder) {
this.movieFinder = movieFinder
}
// ...
}
与 一样@Autowired
,@Inject
也可与java.util.Optional
或@Nullable
.这在这里更加适用,因为@Inject
没有
一个required
属性。以下一对示例展示了如何使用@Inject
和@Nullable
:
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
class SimpleMovieLister {
@Inject
var movieFinder: MovieFinder? = null
}
1.11.2.@Named
和@ManagedBean
:标准等效于@Component
注解
而不是@Component
,您可以使用@javax.inject.Named
或javax.annotation.ManagedBean
,
如下例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
使用@Component
而不指定组件的名称。@Named
可以以类似的方式使用,如下例所示:
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
@Named
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
当您使用@Named
或@ManagedBean
中,您可以在
与使用 Spring 注解时的方式完全相同,如下例所示:
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
与@Component 、JSR-330@Named 和 JSR-250ManagedBean 注释是不可组合的。您应该使用 Spring 的 stereotype 模型来构建
自定义组件注释。 |
1.11.3. JSR-330 标准注解的限制
当您使用标准注释时,您应该知道一些重要的 功能不可用,如下表所示:
Spring | javax.inject.* | javax.inject 限制 / 注释 |
---|---|---|
@Autowired |
@Inject |
|
@Component |
@Named / @ManagedBean |
JSR-330 不提供可组合模型,只提供一种标识命名组件的方法。 |
@Scope(“单例”) |
@Singleton |
JSR-330 的默认范围类似于 Spring 的 |
@Qualifier |
@Qualifier / @Named |
|
@Value |
- |
无等效项 |
@Required |
- |
无等效项 |
@Lazy |
- |
无等效项 |
对象工厂 |
供应商 |
|
1.12. 基于 Java 的容器配置
本节介绍如何在 Java 代码中使用 Comments 来配置 Spring 容器。它包括以下主题:
1.12.1. 基本概念:@Bean
和@Configuration
Spring 的新 Java 配置支持中的核心工件是@Configuration
-annotated 类和@Bean
-annotated 方法。
这@Bean
annotation 用于指示方法 instantiates、configure 和
初始化一个要由 Spring IoC 容器管理的新对象。对于熟悉的人
与 Spring 的<beans/>
XML 配置、@Bean
annotation 的作用与
这<bean/>
元素。您可以使用@Bean
-annotated 方法与任何 Spring@Component
.但是,它们最常与@Configuration
豆。
使用@Configuration
表示其主要用途是作为
bean 定义的来源。此外@Configuration
类 let inter-bean
dependencies 通过调用其他@Bean
方法。
尽可能简单@Configuration
类读取如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun myService(): MyService {
return MyServiceImpl()
}
}
前面的AppConfig
class 等价于下面的 Spring<beans/>
XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
这@Bean
和@Configuration
以下部分将深入讨论 Comments。
但是,首先,我们介绍了使用
基于 Java 的配置。
1.12.2. 使用 实例化 Spring 容器AnnotationConfigApplicationContext
以下部分记录了 Spring 的AnnotationConfigApplicationContext
,在 Spring 中引入
3.0. 这个多才多艺的ApplicationContext
implementation 不仅能够接受@Configuration
类作为输入,但也作为普通类@Component
类和类
使用 JSR-330 元数据进行注释。
什么时候@Configuration
类作为输入提供,@Configuration
类本身
注册为 Bean 定义,并且所有声明@Bean
类中 methods
也注册为 Bean 定义。
什么时候@Component
和 JSR-330 类,它们被注册为 bean
定义,并假设 DI 元数据(如@Autowired
或@Inject
是
在必要时使用这些类。
结构简单
与在实例化ClassPathXmlApplicationContext
,您可以使用@Configuration
类作为输入,当
实例化AnnotationConfigApplicationContext
.这允许完全
Spring 容器的无 XML 用法,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
如前所述,AnnotationConfigApplicationContext
不仅限于工作
跟@Configuration
类。任何@Component
或提供 JSR-330 注释的类
作为构造函数的 input,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
前面的示例假定MyServiceImpl
,Dependency1
和Dependency2
使用 Spring
依赖项注入注释,例如@Autowired
.
使用 以编程方式构建容器register(Class<?>…)
您可以实例化AnnotationConfigApplicationContext
通过使用 no-arg 构造函数
,然后使用register()
方法。这种方法特别有用
当以编程方式构建AnnotationConfigApplicationContext
.以下内容
示例展示了如何做到这一点:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.register(AppConfig::class.java, OtherConfig::class.java)
ctx.register(AdditionalConfig::class.java)
ctx.refresh()
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
启用 Component Scanningscan(String…)
要启用组件扫描,您可以对@Configuration
类,如下所示:
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
有经验的 Spring 用户可能熟悉 XML 声明等价物
Spring的
|
在前面的示例中,com.acme
扫描包以查找任何@Component
-annotated 类,并且这些类被注册为 Spring bean
定义。AnnotationConfigApplicationContext
公开scan(String…)
方法允许相同的组件扫描功能,就像
以下示例显示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean<MyService>()
}
请记住@Configuration 类使用@Component ,因此它们是组件扫描的候选项。在前面的示例中,
假设AppConfig 在com.acme package (或任何 package
在下面),它会在调用scan() .后refresh() ,则其所有@Bean 方法在容器中被处理并注册为 Bean 定义。 |
支持 Web 应用程序AnnotationConfigWebApplicationContext
一个WebApplicationContext
的变体AnnotationConfigApplicationContext
可用
跟AnnotationConfigWebApplicationContext
.在以下情况下,可以使用此实现
配置 SpringContextLoaderListener
servlet 侦听器, Spring MVCDispatcherServlet
等。以下内容web.xml
snippet 配置一个典型的
Spring MVC Web 应用程序(注意contextClass
context-param 和
init-param) 的
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
1.12.3. 使用@Bean
注解
@Bean
是方法级注释,是 XML 的直接模拟<bean/>
元素。
该注释支持<bean/>
如:
-
name
.
您可以使用@Bean
注解@Configuration
-annotated 或@Component
-annotated 类。
声明一个 Bean
要声明一个 Bean,你可以使用@Bean
注解。您使用此
方法在ApplicationContext
的类型
指定为方法的返回值。默认情况下,bean 名称与
方法名称。以下示例显示了@Bean
方法声明:
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService() = TransferServiceImpl()
}
前面的配置与下面的 Spring XML 完全等价:
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
这两个声明都会创建一个名为transferService
在ApplicationContext
绑定到TransferServiceImpl
,作为
以下文本图像显示:
transferService -> com.acme.TransferServiceImpl
您还可以声明@Bean
具有接口(或基类)的方法
return 类型,如下例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
但是,这会将高级类型预测的可见性限制为指定的
接口类型 (TransferService
).然后,使用完整类型 (TransferServiceImpl
)
仅在实例化受影响的 singleton bean 后,容器才知道。
非惰性单例 bean 根据它们的声明顺序进行实例化,
因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间
尝试通过未声明的类型(例如@Autowired TransferServiceImpl
,
它仅在transferService
bean 已被实例化)。
如果您始终通过声明的服务接口引用您的类型,则您的@Bean 返回类型可以安全地加入该设计决策。但是,对于组件
实现多个接口,或者对于可能由其
implementation 类型,则声明最具体的返回类型会更安全
(至少与引用 bean 的注入点所要求一样具体)。 |
Bean 依赖项
一个@Bean
-annotated 方法可以具有任意数量的参数来描述
构建该 bean 所需的依赖项。例如,如果TransferService
需要AccountRepository
中,我们可以使用方法
参数,如下例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
解析机制与基于构造函数的依赖项几乎相同 注射。有关更多详细信息,请参阅相关部分。
接收生命周期回调
任何使用@Bean
annotation 支持常规生命周期回调
,并且可以使用@PostConstruct
和@PreDestroy
来自 JSR-250 的注释。有关详细信息,请参阅 JSR-250 注释
详。
常规的 Spring 生命周期回调完全支持为
井。如果 Bean 实现了InitializingBean
,DisposableBean
或Lifecycle
他们
容器调用相应的方法。
标准的*Aware
接口(例如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContext Aware 等)也完全受支持。
这@Bean
annotation 支持指定任意初始化和析构
回调方法,很像 Spring XML 的init-method
和destroy-method
属性
在bean
元素,如下例所示:
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
class BeanOne {
fun init() {
// initialization logic
}
}
class BeanTwo {
fun cleanup() {
// destruction logic
}
}
@Configuration
class AppConfig {
@Bean(initMethod = "init")
fun beanOne() = BeanOne()
@Bean(destroyMethod = "cleanup")
fun beanTwo() = BeanTwo()
}
默认情况下,使用 Java 配置定义的 bean 具有公共 默认情况下,您可能希望对使用 JNDI 获取的资源执行此作,因为它的
生命周期在应用程序外部进行管理。特别是,确保始终这样做
对于 以下示例显示了如何防止 Java
Kotlin
此外,使用 |
在BeanOne
从前面的示例中,调用init()
方法,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
当您直接在 Java 中工作时,您可以对对象执行任何您喜欢的作,并执行 并不总是需要依赖容器生命周期。 |
指定 Bean 范围
Spring 包括@Scope
注解,以便您可以指定 Bean 的范围。
使用@Scope
注解
您可以指定使用@Bean
注解应该有一个
特定范围。您可以使用 Bean Scopes 部分中指定的任何标准范围。
默认范围为singleton
,但您可以使用@Scope
注解
如下例所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope
和scoped-proxy
Spring 提供了一种通过作用域代理处理作用域依赖项的便捷方法。最简单的创建
这样的代理在使用 XML 配置时是<aop:scoped-proxy/>
元素。
在 Java 中使用@Scope
Annotation 提供等效支持
使用proxyMode
属性。默认值为ScopedProxyMode.DEFAULT
哪
通常表示不应创建作用域代理,除非使用不同的默认值
已在 component-scan 指令级别进行配置。您可以指定ScopedProxyMode.TARGET_CLASS
,ScopedProxyMode.INTERFACES
或ScopedProxyMode.NO
.
如果您将 XML 参考文档(请参阅范围代理)中的范围代理示例移植到我们的@Bean
使用 Java,
它类似于以下内容:
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
return SimpleUserService().apply {
// a reference to the proxied userPreferences bean
setUserPreferences(userPreferences())
}
}
自定义 Bean 命名
默认情况下,配置类使用@Bean
method's name 作为
结果 bean。但是,此功能可以使用name
属性
如下例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean 别名
正如 命名 Bean 中所讨论的,有时需要给出一个 bean
多个名称,也称为 Bean 别名。这name
属性的@Bean
annotation 接受一个 String 数组来实现此目的。以下示例演示如何设置
bean 的多个别名:
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Configuration
class AppConfig {
@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
fun dataSource(): DataSource {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时,提供更详细的 bean 文本描述会很有帮助。这可以 当 bean 公开(可能通过 JMX)以进行监视时,特别有用。
要向@Bean
中,您可以使用@Description
annotation 中,如下例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}
1.12.4. 使用@Configuration
注解
@Configuration
是类级注解,指示对象是
bean 定义。@Configuration
类通过@Bean
-注释
方法。调用@Bean
方法@Configuration
类还可用于定义
bean 间依赖关系。看基本概念:@Bean
和@Configuration
以获取一般介绍。
注入 bean 间依赖关系
当 bean 彼此依赖时,表达这种依赖性就很简单 就像让一个 bean 方法调用另一个 bean 方法一样,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne(beanTwo())
@Bean
fun beanTwo() = BeanTwo()
}
在前面的示例中,beanOne
接收对beanTwo
通过构造函数
注射。
这种声明 bean 间依赖关系的方法仅在@Bean 方法
在@Configuration 类。不能声明 bean 间依赖关系
通过使用 Plain@Component 类。 |
查找方法注入
如前所述,查找方法注入是一个 您应该很少使用的高级功能。它在 singleton 范围的 bean 依赖于原型范围的 bean。使用 Java 实现此目的 type 配置为实现此模式提供了一种自然的方法。这 以下示例显示了如何使用 Lookup 方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.setState(commandState)
return command.execute()
}
// okay... but where is the implementation of this method?
protected abstract fun createCommand(): Command
}
通过使用 Java 配置,您可以创建CommandManager
哪里
摘要createCommand()
方法被覆盖,以便它查找新的
(prototype) 命令对象。以下示例显示了如何执行此作:
@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
AsyncCommand command = new AsyncCommand();
// inject dependencies here as required
return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return new CommandManager() {
protected Command createCommand() {
return asyncCommand();
}
}
}
@Bean
@Scope("prototype")
fun asyncCommand(): AsyncCommand {
val command = AsyncCommand()
// inject dependencies here as required
return command
}
@Bean
fun commandManager(): CommandManager {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object
return object : CommandManager() {
override fun createCommand(): Command {
return asyncCommand()
}
}
}
有关基于 Java 的配置如何在内部工作的更多信息
请考虑以下示例,该示例显示了@Bean
带注释的方法被调用两次:
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl();
clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() {
return new ClientDaoImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun clientService1(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientService2(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientDao(): ClientDao {
return ClientDaoImpl()
}
}
clientDao()
已在clientService1()
和一次clientService2()
.
由于此方法会创建一个新的ClientDaoImpl
并返回它,您将
通常期望有两个实例(每个服务一个)。那肯定是
有问题的:在 Spring 中,实例化的 bean 有一个singleton
范围。这是
魔力所在:全部@Configuration
类在启动时被子类化
跟CGLIB
.在子类中,子方法首先检查容器是否有任何
在调用 Parent 方法并创建新实例之前缓存 (作用域) bean。
根据 Bean 的范围,行为可能会有所不同。我们正在交谈 关于单例 这里. |
从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的类路径中,因为 CGLIB
类已重新打包在 |
由于 CGLIB 在
startup-time 的 Startup-time 中。特别是,配置类不能是 final。但是,由于
在 4.3 中,允许在配置类上使用任何构造函数,包括使用 如果您希望避免任何 CGLIB 施加的限制,请考虑声明您的 |
1.12.5. 编写基于 Java 的配置
Spring 基于 Java 的配置功能允许您编写 Comments,这可以减少 配置的复杂程度。
使用@Import
注解
就像<import/>
元素在 Spring XML 文件中用于帮助模块化
配置、@Import
annotation 允许加载@Bean
定义来自
另一个 Configuration 类,如下例所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@Configuration
class ConfigA {
@Bean
fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
@Bean
fun b() = B()
}
现在,无需同时指定两者ConfigA.class
和ConfigB.class
什么时候
仅实例化上下文ConfigB
需要显式提供,因为
以下示例显示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
// now both beans A and B will be available...
val a = ctx.getBean<A>()
val b = ctx.getBean<B>()
}
这种方法简化了容器实例化,因为只需要处理一个类
替换为 Bean 的 Git,而不是要求您记住可能大量的@Configuration
类。
从 Spring Framework 4.2 开始,@Import 还支持对常规组件的引用
类,类似于AnnotationConfigApplicationContext.register 方法。
如果您想通过使用一些
configuration 类作为入口点来显式定义所有组件。 |
在 Imported 上注入依赖项@Bean
定义
前面的示例有效,但过于简单。在大多数实际场景中,bean 具有
跨配置类彼此依赖。使用 XML 时,这不是
issue,因为不涉及编译器,你可以声明ref="someBean"
并相信 Spring 会在容器初始化期间解决它。
使用@Configuration
类中,Java 编译器对
配置模型,因为对其他 bean 的引用必须是有效的 Java 语法。
幸运的是,解决这个问题很简单。正如我们已经讨论过的,
一个@Bean
method 可以具有任意数量的描述 bean 的参数
依赖。请考虑以下更真实的场景,其中包含多个@Configuration
类,每个类都依赖于其他类中声明的 bean:
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource dataSource) {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig {
@Bean
fun accountRepository(dataSource: DataSource): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
还有另一种方法可以达到相同的结果。请记住@Configuration
类是
最终,容器中只有另一个 bean:这意味着他们可以利用@Autowired
和@Value
injection 和其他功能与任何其他 bean 相同。
确保您以这种方式注入的依赖项只是最简单的类型。 此外,要特别小心 |
下面的示例展示了如何将一个 bean 自动连接到另一个 bean:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
lateinit var accountRepository: AccountRepository
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
构造函数注入@Configuration classes 仅从 Spring 开始受支持
框架 4.3.另请注意,无需指定@Autowired 如果目标
Bean 只定义了一个构造函数。 |
在前面的场景中,使用@Autowired
效果很好,并提供所需的
模块化,但确定自动装配的 bean 定义的确切声明位置是
仍然有点模棱两可。例如,作为查看ServiceConfig
、如何
您确切地知道@Autowired AccountRepository
bean 被声明了吗?事实并非如此
explicit 的 intent 函数,这可能就好了。请记住,Spring Tools for Eclipse 提供了以下工具
可以渲染显示所有内容是如何连接的图表,这可能就是您所需要的。也
您的 Java IDE 可以轻松找到AccountRepository
类型
并快速显示@Bean
返回该类型的方法。
如果这种歧义是不可接受的,并且您希望进行直接导航
从 IDE 中从一个@Configuration
类添加到另一个类中,请考虑自动将
configuration 类本身。以下示例显示了如何执行此作:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
在上述情况下,其中AccountRepository
是完全明确的。
然而ServiceConfig
现在与RepositoryConfig
.那就是
权衡。这种紧密耦合可以通过使用基于接口的 或
抽象类@Configuration
类。请考虑以下示例:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
@Configuration
interface RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository
}
@Configuration
class DefaultRepositoryConfig : RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(...)
}
}
@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class) // import the concrete config!
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
现在ServiceConfig
相对于混凝土松散耦合DefaultRepositoryConfig
,并且内置的 IDE 工具仍然很有用:您可以轻松地
获取RepositoryConfig
实现。在这个
方式, 导航@Configuration
类及其依赖项也不例外
而不是导航基于接口的代码的通常过程。
如果要影响某些 bean 的启动创建顺序,请考虑
将其中一些声明为@Lazy (用于在首次访问时创建,而不是在启动时创建)
或@DependsOn 某些其他 bean (确保特定的其他 bean 是
在当前 bean 之前创建,超出了后者的直接依赖关系所暗示的范围)。 |
有条件地包含@Configuration
Classes 或@Bean
方法
有条件地启用或禁用完整的@Configuration
类
甚至是个人@Bean
方法,基于一些任意的系统状态。一个普通
例如,使用@Profile
注解,仅当特定的
配置文件已在 Spring 中启用Environment
(有关详细信息,请参见 Bean 定义配置文件)。
这@Profile
Annotation 实际上是通过使用更灵活的 Comments 来实现的
叫@Conditional
.
这@Conditional
annotation 表示特定org.springframework.context.annotation.Condition
应该
在@Bean
已注册。
的Condition
interface 提供matches(…)
方法,该方法返回true
或false
.例如,下面的清单显示了实际的Condition
用于@Profile
:
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
// Read the @Profile annotation attributes
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
return true
}
}
return false
}
return true
}
请参阅@Conditional
javadoc 了解更多详情。
组合 Java 和 XML 配置
Spring的@Configuration
Class Support 的目标不是成为 100% 完全的替代品
用于 Spring XML。一些工具(例如 Spring XML 名称空间)仍然是
配置容器。在 XML 方便或必要的情况下,您有一个
choice:以“以 XML 为中心”的方式实例化容器,例如,ClassPathXmlApplicationContext
,或者使用AnnotationConfigApplicationContext
和@ImportResource
用于导入 XML 的注释
根据需要。
以 XML 为中心的使用@Configuration
类
最好从 XML 引导 Spring 容器并包含@Configuration
类。例如,在大型现有代码库中
使用 Spring XML,则更容易创建@Configuration
类
根据需要,并从现有 XML 文件中包含它们。在本节的后面部分,我们将介绍
使用选项@Configuration
类。
请记住@Configuration
类最终是
容器。在本系列示例中,我们将创建一个@Configuration
类名称AppConfig
和
将其包含在system-test-config.xml
作为<bean/>
定义。因为<context:annotation-config/>
处于打开状态时,容器会识别@Configuration
注解并处理@Bean
在AppConfig
适当地。
以下示例显示了 Java 中的普通配置类:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
@Configuration
class AppConfig {
@Autowired
private lateinit var dataSource: DataSource
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService() = TransferService(accountRepository())
}
以下示例显示了示例的一部分system-test-config.xml
文件:
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
以下示例显示了可能的jdbc.properties
文件:
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
在system-test-config.xml 文件中,使用AppConfig <bean/> 不声明id 元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他 bean
Ever 引用它,并且不太可能按名称从容器中显式获取它。
同样,DataSource bean 只按类型自动装配,因此显式 beanid 并非严格要求。 |
因为@Configuration
使用@Component
,@Configuration
-注释
类会自动成为组件扫描的候选对象。使用与
describe 中,我们可以重新定义system-test-config.xml
以利用组件扫描。
请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>
因为<context:component-scan/>
启用相同的
功能性。
以下示例显示了修改后的system-test-config.xml
文件:
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
以类为中心 将 XML 与@ImportResource
在满足以下条件的应用中@Configuration
类是配置
容器,则可能仍然需要至少使用一些 XML。在这些
方案中,您可以使用@ImportResource
并仅定义所需数量的 XML。行为
因此,实现了一种“以 Java 为中心”的容器配置方法,并将 XML 保持为
最低 限度。以下示例(包括一个配置类、一个 XML 文件
,它定义了一个 Bean、一个属性文件和main
类)演示如何使用
这@ImportResource
注解来实现使用 XML 的“以 Java 为中心”的配置
根据需要:
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {
@Value("\${jdbc.url}")
private lateinit var url: String
@Value("\${jdbc.username}")
private lateinit var username: String
@Value("\${jdbc.password}")
private lateinit var password: String
@Bean
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val transferService = ctx.getBean<TransferService>()
// ...
}
1.13. 环境抽象
这Environment
接口
是集成在容器中的抽象,它对两个键进行建模
应用程序环境的各个方面:配置文件和属性。
配置文件是要注册到
容器。可以将 Bean 分配给配置文件
无论是在 XML 中定义还是使用注释定义。的作用Environment
object 替换为
relation to profiles 用于确定当前处于活动状态的用户档案(如果有),
以及默认情况下应处于活动状态的配置文件(如果有)。
属性在几乎所有应用程序中都起着重要作用,可能源自
多种来源:属性文件、JVM 系统属性、系统环境
变量, JNDI, servlet 上下文参数, 临时Properties
对象Map
对象,等等
上。的作用Environment
object 的
具有便捷服务界面的用户,用于配置属性源和解析
属性。
1.13.1. Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,该机制允许 在不同环境中注册不同的 bean。“环境”这个词 对不同的用户可能意味着不同的事情,而此功能可以帮助解决许多问题 使用案例,包括:
-
在开发中处理内存中数据源与查找相同的数据源 datasource 的 JNDI 的 QA 或生产环境。
-
仅在将应用程序部署到 性能环境。
-
为客户 A 与客户注册 bean 的自定义实现 B 部署。
考虑实际应用中的第一个用例,它需要DataSource
.在测试环境中,配置可能类似于以下内容:
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
现在考虑如何将此应用程序部署到 QA 或生产环境中
环境中,假设应用程序的数据源已注册
替换为生产应用程序服务器的 JNDI 目录。我们dataSource
豆
现在如下面的清单所示:
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
问题在于如何根据
当前环境。随着时间的推移,Spring 用户已经设计出了许多方法来
完成此作,通常依赖于系统环境变量的组合
和 XML<import/>
包含${placeholder}
解析
添加到正确的配置文件路径,具体取决于环境的值
变量。Bean 定义配置文件是一个核心容器功能,它提供了一个
解决这个问题。
如果我们概括前面特定于环境的 bean 示例中显示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中没有。您可以说您想要注册一个 情况 A 中 bean 定义的某个配置文件和 情况 B.我们首先更新配置以反映此需求。
用@Profile
这@Profile
annotation 允许您指示组件符合注册条件
当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们
可以重写dataSource
配置如下:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("development")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
如前所述,使用@Bean 方法,您通常选择使用 Programmatic
JNDI 查找,通过使用 Spring 的JndiTemplate /JndiLocatorDelegate helpers 或
直 JNDIInitialContext 用法,但未显示JndiObjectFactoryBean variant 的 Variant 中,这将强制您将返回类型声明为FactoryBean 类型。 |
配置文件字符串可以包含一个简单的配置文件名称(例如production
) 或
profile 表达式。配置文件表达式允许更复杂的配置文件逻辑
表示的(例如,production & us-east
).支持以下运算符
配置文件表达式:
-
!
:配置文件的逻辑 “not” -
&
:配置文件的逻辑 “and” -
|
:配置文件的逻辑 “or”
您不能混合使用 和& | 运算符。例如production & us-east | eu-central 不是有效的表达式。它必须表示为production & (us-east | eu-central) . |
您可以使用@Profile
作为元注释
创建自定义组合注释。以下示例定义了一个自定义@Production
注释,您可以将其用作@Profile("production")
:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果@Configuration 类标有@Profile 、所有@Bean methods 和@Import 与该类关联的注释将被绕过,除非一个或多个
指定的配置文件处于活动状态。如果@Component 或@Configuration 类被标记
跟@Profile({"p1", "p2"}) ,则不会注册或处理该类,除非
配置文件“P1”或“P2”已激活。如果给定配置文件的前缀为
NOT 运算符 (! ),则仅当配置文件未
积极。例如,给定@Profile({"p1", "!p2"}) ,如果配置文件
“p1”处于活动状态,或者配置文件“p2”未处于活动状态。 |
@Profile
也可以在方法级别声明以仅包含一个特定的 bean
配置类中(例如,对于特定 Bean 的替代变体),作为
以下示例显示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | 这standaloneDataSource 方法仅在development 轮廓。 |
2 | 这jndiDataSource 方法仅在production 轮廓。 |
@Configuration
class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
fun standaloneDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
@Bean("dataSource")
@Profile("production") (2)
fun jndiDataSource() =
InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 | 这standaloneDataSource 方法仅在development 轮廓。 |
2 | 这jndiDataSource 方法仅在production 轮廓。 |
跟 如果要定义具有不同性能分析条件的替代 bean,
通过使用 |
XML Bean 定义配置文件
XML 对应项是profile
属性的<beans>
元素。我们前面的示例
配置可以重写为两个 XML 文件,如下所示:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
也可以避免这种 split 和 nest<beans/>
元素,
如下例所示:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
这spring-bean.xsd
已被约束为仅允许
文件中的最后一个。这应该有助于提供灵活性,而不会产生
XML 文件中的混乱。
XML 对应项不支持前面描述的配置文件表达式。有可能,
但是,要使用
在前面的示例中, |
激活配置文件
现在我们已经更新了配置,我们仍然需要指示 Spring 哪个
配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到
一个NoSuchBeanDefinitionException
thrown,因为容器找不到
名为dataSource
.
可以通过多种方式激活配置文件,但最直接的是
它以编程方式针对Environment
API 的 API 可通过ApplicationContext
.以下示例显示了如何执行此作:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
此外,您还可以通过spring.profiles.active
属性,可以通过系统环境指定
中的变量、JVM 系统属性、Servlet 上下文参数web.xml
,甚至作为
条目(参见PropertySource
抽象化).在集成测试中,active
配置文件可以使用@ActiveProfiles
注解中的spring-test
模块(请参阅使用环境配置文件进行上下文配置)。
请注意,用户档案不是“非此即彼”的命题。您可以激活多个
配置文件。以编程方式,您可以向setActiveProfiles()
方法,它接受String…
varargs 的以下示例
激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
声明式地,spring.profiles.active
可以接受以逗号分隔的配置文件名称列表,
如下例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
default 配置文件表示默认启用的配置文件。考虑一下 以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
如果没有配置文件处于活动状态,则dataSource
已创建。你可以看到这个
作为为一个或多个 bean 提供默认定义的一种方式。如果有
profile 时,默认配置文件不适用。
您可以使用setDefaultProfiles()
上
这Environment
或者,以声明方式使用spring.profiles.default
财产。
1.13.2.PropertySource
抽象化
Spring的Environment
abstraction 通过可配置的
属性源的层次结构。请考虑以下清单:
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
在前面的代码片段中,我们看到了一种高级方式,即询问 Spring 的my-property
property 为
为当前环境定义。要回答这个问题,Environment
对象执行
对一组PropertySource
对象。一个PropertySource
是对任何键值对源的简单抽象,并且
Spring的StandardEnvironment
配置了两个 PropertySource 对象 — 一个表示 JVM 系统属性集
(System.getProperties()
) 和一个表示系统环境变量集
(System.getenv()
).
这些默认属性源适用于StandardEnvironment ,用于独立
应用。StandardServletEnvironment 填充了其他默认属性源,包括 Servlet Config 和 Servlet
context 参数。它可以选择启用JndiPropertySource .
有关详细信息,请参阅 javadoc。 |
具体来说,当您使用StandardEnvironment
中,调用env.containsProperty("my-property")
如果my-property
system 属性或my-property
环境变量位于
运行。
执行的搜索是分层的。默认情况下,系统属性优先于
环境变量。因此,如果 对于常见的
|
最重要的是,整个机制是可配置的。也许您有一个自定义源
要集成到此搜索中的属性。为此,请实现
并实例化您自己的PropertySource
并将其添加到PropertySources
对于
当前Environment
.以下示例显示了如何执行此作:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
在上面的代码中,MyPropertySource
已在
搜索。如果它包含my-property
property,则会检测并返回该属性,以支持
任何my-property
property 在任何其他PropertySource
.这MutablePropertySources
API 公开了许多方法,这些方法允许精确作
property 源。
1.13.3. 使用@PropertySource
这@PropertySource
注解提供了一种方便的声明式机制,用于添加PropertySource
到 Spring 的Environment
.
给定一个名为app.properties
,其中包含键值对testbean.name=myTestBean
,
以下@Configuration
类用途@PropertySource
以这种方式,
调用testBean.getName()
返回myTestBean
:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
任何${…}
placeholder 存在于@PropertySource
resource location 为
针对已针对
environment,如下例所示:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
假设my.placeholder
已存在于其中一个属性源中
registered (例如,系统属性或环境变量),则占位符为
resolved 的值。如果不是,则default/path
已使用
作为默认值。如果未指定 default 且无法解析属性,则IllegalArgumentException
被抛出。
这@PropertySource 注解是可重复的,根据 Java 8 约定。
然而,所有这些@PropertySource 注解需要在同一
级别,可以直接在配置类上,也可以作为
相同的自定义注释。混合直接注释和元注释不是
推荐,因为直接注释可以有效地覆盖元注释。 |
1.13.4. 语句中的占位符解析
从历史上看,元素中占位符的值只能针对
JVM 系统属性或环境变量。现在情况已不再如此。因为
这Environment
abstraction 集成在整个容器中,因此很容易
通过它的 route resolution of placeholders 进行路由解析。这意味着您可以配置
解决过程。您可以更改搜索的优先级
系统属性和环境变量,或者完全删除它们。您还可以添加
自己的属性源添加到组合中。
具体来说,以下语句无论customer
属性,只要它在Environment
:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14. 注册LoadTimeWeaver
这LoadTimeWeaver
被 Spring 用于按原样动态转换类
加载到 Java 虚拟机 (JVM) 中。
要启用加载时编织,您可以添加@EnableLoadTimeWeaving
到你的@Configuration
类,如下例所示:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig
或者,对于 XML 配置,您可以使用context:load-time-weaver
元素:
<beans>
<context:load-time-weaver/>
</beans>
配置ApplicationContext
、该ApplicationContext
可以实施LoadTimeWeaverAware
,从而获得对加载时间的引用
Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能是
对于 JPA 类转换是必需的。
查阅LocalContainerEntityManagerFactoryBean
javadoc 了解更多详情。有关 AspectJ 加载时编织的更多信息,请参见 Spring 框架中的使用 AspectJ 进行加载时编织。
1.15. 其他功能ApplicationContext
如本章介绍中所述,org.springframework.beans.factory
package 提供了用于管理和作 bean 的基本功能,包括在
编程方式。这org.springframework.context
软件包会添加ApplicationContext
接口,该接口扩展了BeanFactory
接口,除了扩展其他
接口,以便在更多应用程序中提供额外的功能
面向框架的样式。许多人使用ApplicationContext
在一个完全
声明式方式,甚至不是以编程方式创建它,而是依赖于
支持类,例如ContextLoader
要自动实例化ApplicationContext
作为 Java EE Web 应用程序的正常启动过程的一部分。
增强BeanFactory
功能,上下文
package 还提供以下功能:
-
以 i18n 样式访问消息,通过
MessageSource
接口。 -
通过
ResourceLoader
接口。 -
事件发布,即向实现
ApplicationListener
接口 通过使用ApplicationEventPublisher
接口。 -
加载多个 (分层) 上下文,让每个上下文都专注于一个 特定层(例如应用程序的 Web 层)通过
HierarchicalBeanFactory
接口。
1.15.1. 国际化使用MessageSource
这ApplicationContext
interface 扩展了一个名为MessageSource
和
因此,提供国际化 (“i18n”) 功能。Spring 还提供了HierarchicalMessageSource
接口,该接口可以分层解析消息。
这些接口共同为 Spring effects 消息提供了基础
分辨率。在这些接口上定义的方法包括:
-
String getMessage(String code, Object[] args, String default, Locale loc)
: 基本 用于从MessageSource
.未找到消息时 对于指定的区域设置,将使用 default message。传入的任何参数都将变为 replacement 值,使用MessageFormat
标准提供的功能 图书馆。 -
String getMessage(String code, Object[] args, Locale loc)
:基本相同 前一种方法,但有一个区别:不能指定默认消息。如果 找不到该消息,则NoSuchMessageException
被抛出。 -
String getMessage(MessageSourceResolvable resolvable, Locale locale)
:所有属性 使用的方法也被包装在一个名为MessageSourceResolvable
,您可以将其与此方法一起使用。
当ApplicationContext
加载时,它会自动搜索MessageSource
bean 中定义的 bean。该 bean 必须具有名称messageSource
.如果这样的 bean
,则所有对上述方法的调用都会委托给消息源。如果没有
message 源,则ApplicationContext
尝试查找包含
bean 的 bean 的 intent如果是这样,它将使用该 bean 作为MessageSource
.如果ApplicationContext
找不到任何消息源,空的DelegatingMessageSource
实例化,以便能够接受对
方法。
Spring 提供三种MessageSource
实现ResourceBundleMessageSource
,ReloadableResourceBundleMessageSource
和StaticMessageSource
.他们都实现了HierarchicalMessageSource
为了做嵌套
消息。这StaticMessageSource
很少使用,但提供了编程方式
将消息添加到源。以下示例显示了ResourceBundleMessageSource
:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
该示例假定您有三个名为format
,exceptions
和windows
定义。任何解决消息的请求都是
以 JDK 标准方式处理,通过ResourceBundle
对象。对于
本示例的目的,假设上述两个资源包文件的内容
如下:
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
下一个示例显示了一个运行MessageSource
功能性。
请记住,所有ApplicationContext
implementations 也是MessageSource
实现,因此可以转换为MessageSource
接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
println(message)
}
上述程序的结果输出如下:
Alligators rock!
总而言之,MessageSource
在名为beans.xml
哪
存在于 Classpath 的根目录中。这messageSource
bean 定义是指
通过其basenames
财产。这三个文件是
在列表中传递给basenames
属性作为文件存在于
classpath 并调用format.properties
,exceptions.properties
和windows.properties
分别。
下一个示例显示了传递给消息查找的参数。这些参数是
转换为String
对象并插入到查找消息的占位符中。
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
class Example {
lateinit var messages: MessageSource
fun execute() {
val message = messages.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.ENGLISH)
println(message)
}
}
调用execute()
方法如下:
The userDao argument is required.
关于国际化(“i18n”),Spring 的各种MessageSource
实现遵循与标准 JDK 相同的区域设置解析和回退规则ResourceBundle
.简而言之,继续这个例子messageSource
定义
以前,如果要解决针对英国 (en-GB
) 区域设置中,您
将创建名为format_en_GB.properties
,exceptions_en_GB.properties
和windows_en_GB.properties
分别。
通常,区域设置解析由 应用。在以下示例中,(英国)消息所针对的区域设置 resolved 手动指定:
# in exceptions_en_GB.properties argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.UK)
println(message)
}
运行上述程序的结果输出如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用MessageSourceAware
接口来获取对任何MessageSource
这已经被定义过了。在ApplicationContext
实现MessageSourceAware
interface 注入了
应用程序上下文的MessageSource
创建和配置 Bean 时。
因为 Spring 的MessageSource 基于 Java 的ResourceBundle ,则不会合并
具有相同基本名称的捆绑包,但将仅使用找到的第一个捆绑包。
具有相同基本名称的后续消息包将被忽略。 |
作为ResourceBundleMessageSource 中,Spring 提供了一个ReloadableResourceBundleMessageSource 类。此变体支持相同的捆绑包
文件格式,但比基于 JDK 的标准 JDK 更灵活ResourceBundleMessageSource 实现。特别是,它允许读取
文件(不仅来自 Classpath),并支持热
重新加载 bundle 属性文件(同时在两者之间有效地缓存它们)。
请参阅ReloadableResourceBundleMessageSource javadoc 了解详细信息。 |
1.15.2. 标准事件和自定义事件
事件处理ApplicationContext
通过ApplicationEvent
类和ApplicationListener
接口。如果实现ApplicationListener
interface 部署到上下文中,则每次ApplicationEvent
发布到ApplicationContext
,该 bean 会收到通知。
从本质上讲,这是标准的 Observer 设计模式。
从 Spring 4.2 开始,活动基础设施得到了显著改进,并提供
基于注释的模型以及
能够发布任何任意事件(即,不一定
延伸自ApplicationEvent ).当这样的对象被发布时,我们将其包装在
活动。 |
下表描述了 Spring 提供的标准事件:
事件 | 解释 |
---|---|
|
发布时 |
|
发布时 |
|
发布时 |
|
发布时 |
|
一个特定于 Web 的事件,告诉所有 bean HTTP 请求已得到处理。这
事件在请求完成后发布。此活动仅适用于
使用 Spring 的 |
|
的子类 |
您还可以创建和发布自己的自定义事件。以下示例显示了
simple 类,该类扩展了 Spring 的ApplicationEvent
基类:
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
发布自定义ApplicationEvent
,调用publishEvent()
方法在ApplicationEventPublisher
.通常,这是通过创建一个实现ApplicationEventPublisherAware
并将其注册为 Spring bean。以下内容
example 显示了这样的类:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
class EmailService : ApplicationEventPublisherAware {
private lateinit var blockedList: List<String>
private lateinit var publisher: ApplicationEventPublisher
fun setBlockedList(blockedList: List<String>) {
this.blockedList = blockedList
}
override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
this.publisher = publisher
}
fun sendEmail(address: String, content: String) {
if (blockedList!!.contains(address)) {
publisher!!.publishEvent(BlockedListEvent(this, address, content))
return
}
// send email...
}
}
在配置时, Spring 容器检测到EmailService
实现ApplicationEventPublisherAware
并自动调用setApplicationEventPublisher()
.实际上,传入的参数是 Spring
容器本身。您正在通过其ApplicationEventPublisher
接口。
要接收自定义ApplicationEvent
中,您可以创建一个实现ApplicationListener
并将其注册为 Spring bean。以下示例
显示了这样的类:
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
class BlockedListNotifier : ApplicationListener<BlockedListEvent> {
lateinit var notificationAddres: String
override fun onApplicationEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
请注意,ApplicationListener
通常使用 your
自定义事件 (BlockedListEvent
在前面的示例中)。这意味着onApplicationEvent()
method 可以保持类型安全,无需进行 downcast。
您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,event
侦听器同步接收事件。这意味着publishEvent()
方法
块,直到所有侦听器都处理完事件。这样做的一个优势
同步和单线程方法是,当侦听器接收到事件时,它会
在发布者的事务上下文中运行(如果事务上下文为
可用。如果需要其他事件发布策略,请参阅 javadoc
用于 Spring 的ApplicationEventMulticaster
接口
和SimpleApplicationEventMulticaster
implementation 以获取配置选项。
以下示例显示了用于注册和配置每个 上面的类:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="[email protected]"/>
</bean>
把它们放在一起,当sendEmail()
方法emailService
bean 是
如果有任何应阻止的电子邮件消息,则调用一个BlockedListEvent
发布。这blockedListNotifier
bean 注册为ApplicationListener
并接收BlockedListEvent
,此时它可以
通知相关方。
Spring 的事件机制是为 Spring bean 之间的简单通信而设计的 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring 集成项目提供了 完全支持构建轻量级、面向模式、事件驱动的 构建在众所周知的 Spring 编程模型之上的架构。 |
基于注释的事件侦听器
您可以使用@EventListener
注解。这BlockedListNotifier
可以重写如下:
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明它监听的事件类型, 但是,这一次,使用灵活的名称,并且没有实现特定的侦听器接口。 事件类型也可以通过泛型缩小范围,只要实际的事件类型 在其 implementation hierarchy 中解析泛型参数。
如果你的方法应该监听多个事件,或者你想用 no 参数,也可以在 Annotation 本身上指定事件类型。这 以下示例显示了如何执行此作:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
还可以使用condition
属性
的注释中定义SpEL
表达,它应该匹配
以实际调用特定事件的方法。
下面的示例展示了如何重写我们的通知器,以便仅在content
属性等于my-event
:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
每SpEL
expression 根据专用上下文进行计算。下表列出了
项,以便您可以将它们用于条件事件处理:
名字 | 位置 | 描述 | 例 |
---|---|---|---|
事件 |
root 对象 |
实际的 |
|
Arguments 数组 |
root 对象 |
用于调用方法的参数(作为对象数组)。 |
|
参数名称 |
评估上下文 |
任何方法参数的名称。如果由于某种原因,名称不可用
(例如,因为编译后的字节码中没有调试信息),单个
参数也可以使用 |
|
请注意,#root.event
允许您访问底层事件,即使您的方法
signature 实际上是指已发布的任意对象。
如果您需要发布事件作为处理其他事件的结果,则可以更改 method signature 返回应发布的事件,如下例所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。 |
这handleBlockedListEvent()
方法发布一个新的ListUpdateEvent
对于每个BlockedListEvent
它处理。如果需要发布多个事件,可以返回
一个Collection
或事件数组。
异步侦听器
如果您希望特定侦听器异步处理事件,则可以重用定期@Async
支持.
以下示例显示了如何执行此作:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时,请注意以下限制:
-
如果异步事件侦听器抛出
Exception
,它不会传播到 访客。看AsyncUncaughtExceptionHandler
了解更多详情。 -
异步事件侦听器方法无法通过返回 价值。如果您需要发布另一个事件作为处理的结果,请注入
ApplicationEventPublisher
以手动发布事件。
对侦听器进行排序
如果需要先调用一个侦听器,然后再调用另一个侦听器,则可以添加@Order
注解添加到方法声明中,如下例所示:
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
泛型事件
您还可以使用泛型来进一步定义事件的结构。考虑使用EntityCreatedEvent<T>
哪里T
是已创建的实际实体的类型。例如,您
可以创建以下侦听器定义以仅接收EntityCreatedEvent
对于Person
:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
// ...
}
由于类型擦除,仅当触发的事件解析泛型
事件侦听器过滤的参数(即,类似于class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
).
在某些情况下,如果所有事件都遵循相同的
结构(如上例中的事件所示)。在这种情况下,
您可以实施ResolvableTypeProvider
引导框架超越运行时
环境提供。以下事件演示如何执行此作:
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {
override fun getResolvableType(): ResolvableType? {
return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
}
}
这不仅适用于ApplicationEvent 但是您作为
一个事件。 |
1.15.3. 方便地访问低级资源
为了最佳地使用和理解应用程序上下文,您应该熟悉
您自己与 Spring 的Resource
abstraction 的 API 方法,如 参考资料 中所述。
应用程序上下文是一个ResourceLoader
,可用于加载Resource
对象。
一个Resource
本质上是 JDK 的一个功能更丰富的版本java.net.URL
类。
事实上,Resource
包装java.net.URL
哪里
适当。一个Resource
可以从
透明方式,包括从 Classpath、文件系统位置、任何位置
decpreable 替换为标准 URL 和其他一些变体。如果资源位置
string 是一个没有任何特殊前缀的简单路径,这些资源的来源是
特定于实际的应用程序上下文类型。
您可以配置部署到应用程序上下文中的 Bean 来实现特殊的
callback 接口,ResourceLoaderAware
,以自动回调
初始化时间,应用程序上下文本身作为ResourceLoader
.
您还可以公开Resource
,用于访问静态资源。
它们像任何其他属性一样被注入其中。您可以指定这些Resource
properties as simpleString
paths 并依赖于这些文本的自动转换
strings 到 actualResource
对象。
提供给ApplicationContext
constructor 实际上是
资源字符串,并且以简单形式,根据特定的
context 实现。例如ClassPathXmlApplicationContext
将简单的
location path 作为 Classpath 位置。您还可以使用位置路径(资源字符串)
替换为特殊前缀来强制从 Classpath 或 URL 加载定义,
无论实际的上下文类型如何。
1.15.4. Web 应用程序的便捷 ApplicationContext 实例化
您可以创建ApplicationContext
实例,例如,使用ContextLoader
.当然,您也可以创建ApplicationContext
实例
以编程方式使用ApplicationContext
实现。
您可以注册一个ApplicationContext
通过使用ContextLoaderListener
,作为
以下示例显示:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
侦听器检查contextConfigLocation
参数。如果参数不
存在,则侦听器使用/WEB-INF/applicationContext.xml
作为默认值。当
参数确实存在,则侦听器会分隔String
通过使用预定义的
分隔符(逗号、分号和空格),并将值用作其中
搜索应用程序上下文。还支持 Ant 样式的路径模式。
例如:/WEB-INF/*Context.xml
(对于名称以Context.xml
,它们位于WEB-INF
目录)和/WEB-INF/**/*Context.xml
(对于WEB-INF
).
1.15.5. 部署 SpringApplicationContext
作为 Java EE RAR 文件
可以部署 SpringApplicationContext
作为 RAR 文件,将
context 及其所有必需的 bean 类和库 JAR 在 Java EE RAR 部署中
单位。这相当于引导一个独立的ApplicationContext
(仅托管
在 Java EE 环境中)能够访问 Java EE 服务器工具。RAR 部署
是部署无头 WAR 文件方案的更自然的替代方案 — 实际上,
一个没有任何 HTTP 入口点的 WAR 文件,仅用于引导 SpringApplicationContext
在 Java EE 环境中。
RAR 部署非常适合不需要 HTTP 入口点但
而是由消息终端节点和计划作业组成。在这种情况下,bean 可以
使用应用程序服务器资源,比如 JTA 事务管理器和 JNDI 绑定的 JDBCDataSource
实例和 JMSConnectionFactory
实例,也可以注册
平台的 JMX 服务器 — 全部通过 Spring 的标准事务管理和 JNDI
和 JMX 支持工具。应用程序组件还可以与应用程序交互
服务器的 JCAWorkManager
通过 Spring 的TaskExecutor
抽象化。
请参阅SpringContextResourceAdapter
类,了解 RAR 部署中涉及的配置详细信息。
要将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:
-
包 所有应用程序类都合并到一个 RAR 文件(这是一个标准、JAR 文件,具有不同的 文件扩展名)。
-
将所有必需的库 JAR 添加到 RAR 存档的根目录中。
-
添加
META-INF/ra.xml
部署描述符(如javadoc 的SpringContextResourceAdapter
) 和相应的 Spring XML bean 定义文件(通常为META-INF/applicationContext.xml
). -
将生成的 RAR 文件拖放到 Application Server 的部署目录。
此类 RAR 部署单元通常是独立的。它们不暴露组件
对外界,甚至对同一应用程序的其他模块也不例外。与
基于 RARApplicationContext 通常通过与之共享的 JMS 目标发生
其他模块。基于 RAR 的ApplicationContext 例如,还可以安排一些作业
或对文件系统中的新文件(或类似文件)做出反应。如果需要允许同步
从外部访问,它可以(例如)导出 RMI 端点,这些端点可以使用
通过同一台计算机上的其他应用程序模块。 |
1.16. 使用BeanFactory
这BeanFactory
API 为 Spring 的 IoC 功能提供了基础。
它的特定 Contract 主要用于与 Spring 和
相关的第三方框架及其DefaultListableBeanFactory
实现
是更高级别中的关键委托GenericApplicationContext
容器。
BeanFactory
和相关接口(如BeanFactoryAware
,InitializingBean
,DisposableBean
) 是其他框架组件的重要集成点。
通过不需要任何注释甚至反射,它们允许非常高效
容器与其组件之间的交互。应用程序级 bean 可以
使用相同的回调接口,但通常更喜欢声明性依赖项
注入,而是通过 Comments 或编程配置。
请注意,核心BeanFactory
API 级别及其DefaultListableBeanFactory
implementation 不要对配置格式或任何
要使用的组件注释。所有这些风格都来自扩展
(例如XmlBeanDefinitionReader
和AutowiredAnnotationBeanPostProcessor
) 和
对共享作BeanDefinition
对象作为核心元数据表示形式。
这就是 Spring 的容器如此灵活和可扩展的本质。
1.16.1.BeanFactory
或ApplicationContext
?
本节介绍了BeanFactory
和ApplicationContext
容器级别及其对引导程序的影响。
您应该使用ApplicationContext
除非您有充分的理由不这样做,否则使用GenericApplicationContext
及其子类AnnotationConfigApplicationContext
作为自定义引导的常见实现。这些是主要条目
指向 Spring 的核心容器,用于所有常见目的:加载配置
文件, 触发类路径扫描, 以编程方式注册 Bean 定义
和带 Comments 的类,以及(从 5.0 开始)注册函数式 bean 定义。
因为ApplicationContext
包括BeanFactory
是的
通常推荐于平原BeanFactory
,但 full
需要控制 Bean 处理。在ApplicationContext
(例如GenericApplicationContext
implementation 中),会检测到几种 bean
按约定(即按 Bean 名称或 Bean 类型 — 特别是后处理器),
虽然普通的DefaultListableBeanFactory
对任何特殊 bean 都不可知。
对于许多扩展容器功能,例如注释处理和 AOP 代理,
这BeanPostProcessor
扩展点是必不可少的。
如果您只使用普通DefaultListableBeanFactory
,此类后处理器不会
默认情况下被检测并激活。这种情况可能会令人困惑,因为
您的 bean 配置实际上没有任何问题。相反,在这种情况下,
容器需要通过其他设置完全引导。
下表列出了BeanFactory
和ApplicationContext
接口和实现。
特征 | BeanFactory |
ApplicationContext |
---|---|---|
Bean 实例化/连接 |
是的 |
是的 |
集成的生命周期管理 |
不 |
是的 |
自动 |
不 |
是的 |
自动 |
不 |
是的 |
方便 |
不 |
是的 |
内置 |
不 |
是的 |
要使用DefaultListableBeanFactory
,
您需要以编程方式调用addBeanPostProcessor
,如下例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(MyBeanPostProcessor())
// now start using the factory
要应用BeanFactoryPostProcessor
到平原DefaultListableBeanFactory
,
你需要调用它的postProcessBeanFactory
方法,如下例所示:
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))
// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))
// now actually do the replacement
cfg.postProcessBeanFactory(factory)
在这两种情况下,显式注册步骤都很不方便,即
为什么各种ApplicationContext
变体优于普通DefaultListableBeanFactory
在 Spring 支持的应用程序中,尤其是当
依靠BeanFactoryPostProcessor
和BeanPostProcessor
extended 的实例
典型企业设置中的容器功能。
一 |
2. 资源
本章介绍了 Spring 如何处理资源以及如何使用 Spring。它包括以下主题:
2.1. 简介
Java 的标准java.net.URL
类和标准处理程序,用于各种 URL 前缀,
不幸的是,对于所有对低级资源的访问来说,这还不够。为
示例,则没有标准化的URL
可用于访问
需要从 Classpath 获取的 Resource 或相对于ServletContext
.虽然可以为 specialized 注册新的处理程序URL
前缀(类似于http:
),这通常是
相当复杂,而URL
界面仍然缺乏一些理想的功能,
例如,用于检查所指向的资源是否存在的方法。
2.2. 资源接口
Spring的Resource
interface 旨在成为一个功能更强大的抽象接口
访问低级资源。下面的清单显示了Resource
接口
定义:
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
interface Resource : InputStreamSource {
fun exists(): Boolean
val isOpen: Boolean
val url: URL
val file: File
@Throws(IOException::class)
fun createRelative(relativePath: String): Resource
val filename: String
val description: String
}
作为Resource
interface 显示,它扩展了InputStreamSource
接口。下面的清单显示了InputStreamSource
接口:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
interface InputStreamSource {
val inputStream: InputStream
}
中Resource
接口是:
-
getInputStream()
:查找并打开资源,返回InputStream
为 从资源中读取。预计每次调用都会返回一个全新的InputStream
.调用方负责关闭流。 -
exists()
:返回boolean
指示此资源是否实际存在于 物理形式。 -
isOpen()
:返回boolean
指示此资源是否表示句柄 与开放的流。如果true
这InputStream
无法多次读取,并且 必须只读取一次,然后关闭以避免资源泄漏。返回false
为 所有常用的资源实现,除了InputStreamResource
. -
getDescription()
:返回此资源的说明,用于错误 output 来执行。这通常是完全限定的文件名或 资源的实际 URL。
其他方法允许您获取实际的URL
或File
对象表示
资源(如果底层实现兼容并支持
功能)。
Spring 本身使用Resource
abstraction 中,作为
需要资源时有许多方法签名。某些 Spring API 中的其他方法
(例如各种ApplicationContext
implementations) 采用String
它以未经修饰或简单的形式用于创建Resource
适合于
该上下文实现,或者通过String
path 中,让
caller 指定特定的Resource
必须创建和使用 implementation。
虽然Resource
interface 在 Spring 中被大量使用,而 Spring 实际上是
在您自己的代码中单独用作通用工具类非常有用,用于访问
资源,即使您的代码不知道或不关心 Spring 的任何其他部分。
虽然这会将您的代码耦合到 Spring,但它实际上只将其耦合到这一小群
Utility 类,可以作为URL
并且可以是
被认为等同于您用于此目的的任何其他库。
这Resource abstraction 不会取代 functionality。
它会尽可能地包装它。例如,UrlResource 包装 URL 并使用
包裹URL 来完成它的工作。 |
2.3. 内置资源实现
Spring 包括以下内容Resource
实现:
2.3.1.UrlResource
UrlResource
将java.net.URL
,并可用于访问任何对象
通常可通过 URL 访问,例如文件、HTTP 目标、FTP 目标等。都
URL 具有标准化的String
表示,以便适当的标准化
前缀用于指示一种 URL 类型与另一种 URL 类型。这包括file:
为
访问文件系统路径,http:
用于通过 HTTP 协议访问资源,ftp:
用于通过 FTP 访问资源等。
一个UrlResource
由 Java 代码显式使用UrlResource
构造 函数
但通常在调用采用String
参数来表示路径。对于后一种情况,JavaBeansPropertyEditor
最终决定哪种类型的Resource
以创建。如果路径
string 包含众所周知的(对它来说,即)前缀(例如classpath:
)、它
创建适当的 specializedResource
对于该前缀。但是,如果它没有
识别前缀,则假定该字符串是标准 URL 字符串,并且
创建一个UrlResource
.
2.3.2.ClassPathResource
此类表示应从 Classpath 获取的资源。它使用 线程上下文类加载器、给定类加载器或 loading resources.
这Resource
implementation 支持解析为java.io.File
如果类路径
资源驻留在文件系统中,但不适用于驻留在
jar 中,并且尚未扩展(通过 servlet 引擎或任何环境)
添加到文件系统中。为了解决这个问题,各种Resource
implementations 始终支持
resolution 作为java.net.URL
.
一个ClassPathResource
由 Java 代码显式使用ClassPathResource
构造函数,但通常在调用采用String
参数来表示路径。对于后一种情况,JavaBeansPropertyEditor
识别特殊前缀classpath:
、字符串路径和
创建一个ClassPathResource
在那种情况下。
2.3.3.FileSystemResource
这是一个Resource
implementation forjava.io.File
和java.nio.file.Path
处理。
它支持将分辨率解析为File
和URL
.
2.3.4.ServletContextResource
这是一个Resource
implementation forServletContext
解释
相关 Web 应用程序根目录中的相对路径。
它始终支持流访问和 URL 访问,但允许java.io.File
仅限访问
当 Web 应用程序存档扩展并且资源物理位于
文件系统。无论它是否被扩展、在文件系统上或被访问
直接从 JAR 或其他位置(如数据库)实际上是
依赖于 Servlet 容器。
2.3.5.InputStreamResource
一InputStreamResource
是一个Resource
implementation for a givenInputStream
.只有在没有
特定Resource
implementation 是适用的。特别是,首选ByteArrayResource
或任何基于文件的Resource
implementation 的 Implementations 中。
与其他Resource
implementations,这是已打开的
资源。因此,它返回true
从isOpen()
.如果需要,请勿使用
将资源描述符保留在某个位置,或者如果您需要读取多个流
次。
2.4. 使用ResourceLoader
这ResourceLoader
interface 的 API 是由可以返回
(即 load)Resource
实例。下面的清单显示了ResourceLoader
接口定义:
public interface ResourceLoader {
Resource getResource(String location);
}
interface ResourceLoader {
fun getResource(location: String): Resource
}
所有应用程序上下文都实现ResourceLoader
接口。因此,所有
应用程序上下文可用于获取Resource
实例。
当您调用getResource()
在特定的应用程序上下文中,以及位置路径
specified 没有特定的前缀,则会返回一个Resource
type (即
适合该特定应用程序上下文。例如,假设以下内容
代码段针对ClassPathXmlApplicationContext
实例:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
val template = ctx.getResource("some/resource/path/myTemplate.txt")
针对ClassPathXmlApplicationContext
,该代码会返回一个ClassPathResource
.如果运行相同的方法
针对FileSystemXmlApplicationContext
实例,它将返回一个FileSystemResource
.对于WebApplicationContext
,它将返回一个ServletContextResource
.它同样会为每个上下文返回适当的对象。
因此,您可以以适合特定应用程序的方式加载资源 上下文。
另一方面,您也可以强制ClassPathResource
,无论
application context 类型,通过指定特殊的classpath:
前缀,如下所示
示例显示:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,您可以强制UrlResource
通过指定任何标准java.net.URL
前缀。以下一对示例使用file
和http
前缀:
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
下表总结了转换策略String
对象设置为Resource
对象:
前缀 | 例 | 解释 |
---|---|---|
类路径: |
|
从 Classpath 加载。 |
文件: |
加载为 |
|
http: |
加载为 |
|
(无) |
|
取决于底层 |
2.5. 使用ResourceLoaderAware
接口
这ResourceLoaderAware
interface 是一个特殊的回调接口,用于标识
组件,这些组件希望提供ResourceLoader
参考。以下内容
清单显示了ResourceLoaderAware
接口:
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
interface ResourceLoaderAware {
fun setResourceLoader(resourceLoader: ResourceLoader)
}
当类实现ResourceLoaderAware
并部署到应用程序上下文中
(作为 Spring 管理的 bean),它被识别为ResourceLoaderAware
按应用程序
上下文。然后,应用程序上下文调用setResourceLoader(ResourceLoader)
,
将自身作为参数提供(请记住,Spring 中的所有应用程序上下文都实现了
这ResourceLoader
接口)。
由于ApplicationContext
是一个ResourceLoader
,该 bean 还可以实现ApplicationContextAware
接口,并使用提供的 Application Context 直接
load 资源。但是,一般来说,最好使用专用的ResourceLoader
界面。该代码将仅与资源加载耦合
interface(可以认为是一个 Util 接口),而不是整个 SpringApplicationContext
接口。
在应用程序组件中,您还可以依赖ResourceLoader
如
实现ResourceLoaderAware
接口。“传统”constructor
和byType
自动装配模式(如 自动装配协作者中所述)
能够提供ResourceLoader
对于构造函数参数或
setter 方法参数。为了获得更大的灵活性(包括
autowire fields 和多个参数方法),请考虑使用基于注释的
自动装配功能。在这种情况下,ResourceLoader
自动连接到字段,
constructor 参数或方法参数,该参数需要ResourceLoader
type 作为 long
由于有问题的字段、构造函数或方法带有@Autowired
注解。
有关更多信息,请参阅用@Autowired
.
2.6. 资源作为依赖项
如果 Bean 本身将通过某种排序来确定和提供资源路径
的 Bean 使用ResourceLoader
interface 加载资源。例如,考虑加载一些
sort,其中所需的特定资源取决于用户的角色。如果
资源是静态的,因此消除ResourceLoader
接口中,让 bean 公开Resource
属性,
并期望他们被注入其中。
然后注入这些属性变得微不足道的是,所有应用程序上下文
注册并使用特殊的 JavaBeansPropertyEditor
,它可以将String
路径
自Resource
对象。因此,如果myBean
具有 Template 类型的属性Resource
,它可以
为该资源配置一个简单的字符串,如下例所示:
<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
请注意,资源路径没有前缀。因此,因为应用程序上下文本身是
将用作ResourceLoader
,则资源本身是通过ClassPathResource
一个FileSystemResource
或ServletContextResource
,
取决于上下文的确切类型。
如果您需要强制使用特定的Resource
type,可以使用前缀。
以下两个示例展示了如何强制执行ClassPathResource
以及UrlResource
(后者用于访问文件系统文件):
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
2.7. 应用程序上下文和资源路径
本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 ,以及如何使用 XML、如何使用通配符和其他详细信息。
2.7.1. 构造应用程序上下文
应用程序上下文构造函数(针对特定的应用程序上下文类型) 将字符串或字符串数组作为资源的位置路径,例如 构成上下文定义的 XML 文件。
当此类位置路径没有前缀时,特定的Resource
类型构建自
该路径 和 用于加载 bean 定义的路径取决于 并且适合于
特定的应用程序上下文。例如,请考虑以下示例,该示例会创建一个ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
bean 定义是从 classpath 加载的,因为ClassPathResource
是
使用。但是,请考虑以下示例,该示例会创建一个FileSystemXmlApplicationContext
:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
现在,bean 定义是从文件系统位置加载的(在本例中,相对于 当前工作目录)。
请注意,在
location path 会覆盖默认类型Resource
created 来加载
定义。请考虑以下示例:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
用FileSystemXmlApplicationContext
从 Classpath 中加载 bean 定义。但是,它仍然是一个FileSystemXmlApplicationContext
.如果它随后被用作ResourceLoader
任何
无前缀的路径仍被视为文件系统路径。
构建ClassPathXmlApplicationContext
实例 — 快捷方式
这ClassPathXmlApplicationContext
公开了许多构造函数以启用
方便的实例化。基本思想是,您可以只提供一个字符串数组
,仅包含 XML 文件本身的文件名(没有前导路径
信息),并且还提供了一个Class
.这ClassPathXmlApplicationContext
然后从提供的类中派生路径信息。
请考虑以下目录布局:
com/ foo/ services.xml daos.xml MessengerService.class
以下示例显示了ClassPathXmlApplicationContext
实例由
名为services.xml
和daos.xml
(在 Classpath 上)可以实例化:
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "daos.xml"}, MessengerService.class);
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "daos.xml"), MessengerService::class.java)
请参阅ClassPathXmlApplicationContext
javadoc 了解有关各种构造函数的详细信息。
2.7.2. 应用程序上下文构造函数资源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径(如
前面所示),每个 API 都有一个到目标的一对一映射Resource
或者,也可以选择
包含特殊的 “classpath*:” 前缀或内部 Ant 样式的正则表达式
(使用 Spring 的PathMatcher
实用程序)。后者两者都是有效的
通配符。
此机制的一个用途是当您需要执行组件样式的应用程序组装时。都
组件可以将上下文定义片段“发布”到已知的位置路径,并且
当使用前缀为classpath*:
,则会自动选取所有组件片段。
请注意,此通配符特定于应用程序上下文中资源路径的使用
构造函数(或者当您使用PathMatcher
Utility 类层次结构),并且是
在构建时解决。它与Resource
键入自身。
您不能使用classpath*:
前缀来构造实际的Resource
如
一个资源一次只指向一个资源。
Ant-style 模式
路径位置可以包含 Ant 样式模式,如下例所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径位置包含 Ant 样式模式时,解析程序会遵循更复杂的过程来尝试解析
通配符。它生成一个Resource
对于直到最后一个非通配符分段的路径,以及
从中获取 URL。如果此 URL 不是jar:
特定于 URL 或容器的变体
(例如zip:
在 WebLogic 中,wsjar
在 WebSphere 中,依此类推)、java.io.File
是
从中获取,并用于通过遍历文件系统来解析通配符。在
如果是 jar URL,解析器要么获取java.net.JarURLConnection
从 it 或
手动解析 jar URL,然后遍历 jar 文件的内容以解析
通配符。
对可移植性的影响
如果指定的路径已经是一个文件 URL(要么隐式,因为基ResourceLoader
是文件系统 One 或显式的),则通配符保证为
以完全便携的方式工作。
如果指定的路径是 Classpath 位置,则解析程序必须获取最后一个
非通配符路径段 URL,方法是将Classloader.getResource()
叫。由于这个
只是路径的一个节点(不是末尾的文件),它实际上是 undefined 的(在ClassLoader
javadoc) 中返回的 URL 类型。在实践中,
它始终是一个java.io.File
表示目录(其中 Classpath 资源
解析为文件系统位置)或某种类型的 jar URL(其中 Classpath 资源
解析为 jar 位置)。尽管如此,此作仍然存在可移植性问题。
如果获取了最后一个非通配符段的 jar URL,则解析程序必须能够
获取java.net.JarURLConnection
或手动解析 jar URL,以便能够
遍历 jar 的内容并解析通配符。这在大多数环境中都有效
但在其他 API 中失败,我们强烈建议 resources 的通配符解析
来自 jars 在您依赖它之前,请在您的特定环境中对其进行全面测试。
这classpath*:
前缀
在构建基于 XML 的应用程序上下文时,位置字符串可以使用
特殊classpath*:
前缀,如下例所示:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
此特殊前缀指定与给定名称匹配的所有 Classpath 资源
必须获取(在内部,这基本上是通过调用ClassLoader.getResources(…)
),然后合并形成最终应用程序
context 定义。
通配符类路径依赖于getResources() 底层
classloader 中。由于现在大多数应用程序服务器都提供自己的类加载器
实现时,行为可能会有所不同,尤其是在处理 JAR 文件时。一个
简单测试以检查classpath* works 是使用 classloader 从
在 Classpath 上的 jar 中:getClass().getClassLoader().getResources("<someFileInsideTheJar>") .尝试此测试
具有相同名称但放置在两个不同位置的文件。如果
不适当的结果,请查看 Application Server 文档以获取
可能影响 ClassLoader 行为的设置。 |
您还可以将classpath*:
前缀替换为PathMatcher
pattern 中
位置路径的其余部分(例如classpath*:META-INF/*-beans.xml
).在这个
的情况下,解决策略相当简单:AClassLoader.getResources()
call 为
用于获取
类加载器层次结构,然后从每个资源中,相同的PathMatcher
分辨率
前面描述的策略用于通配符子路径。
与通配符相关的其他说明
请注意,classpath*:
,当与 Ant 样式模式结合使用时,仅有效
可靠地使用至少一个根目录,除非实际的
目标文件驻留在文件系统中。这意味着classpath*:*.xml
可能不会从 jar 文件的根目录中检索文件,而只能检索
从扩展目录的根目录。
Spring 检索 Classpath 条目的能力源自 JDK 的ClassLoader.getResources()
方法,该方法仅返回
空字符串(指示要搜索的潜在根)。Spring 计算URLClassLoader
运行时配置和java.class.path
在 jar 文件中的清单
,但不能保证这会导致可移植行为。
扫描 classpath 包需要存在相应的目录 条目。使用 Ant 构建 JAR 时,不要仅激活文件 switch 的 jar 任务。此外,Classpath 目录可能不会根据安全性公开 某些环境中的策略 — 例如,JDK 1.7.0_45 上的独立应用程序 和更高级别(这需要在您的清单中设置 'Trusted-Library')。请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。 在这里,强烈建议将资源放入专用目录, 避免了上述搜索 jar 文件根级别的可移植性问题。 |
Ant 样式的模式,其中classpath:
不保证 resources 能找到匹配项
resources(如果要搜索的根包在多个类路径位置中可用)。
请考虑以下资源位置示例:
com/mycompany/package1/service-context.xml
现在考虑一个 Ant 样式的路径,有人可能会使用它来尝试查找该文件:
classpath:com/mycompany/**/service-context.xml
此类资源可能只位于一个位置,但是当路径(如前面的示例)
用于尝试解析它,则解析器会处理由getResource("com/mycompany");
.如果此基础包节点存在于多个
ClassLoader 位置,则实际的最终资源可能不存在。因此,在这种情况下
您应该更喜欢使用classpath*:
具有相同的 Ant 样式模式,其中
搜索包含根包的所有类路径位置。
2.7.3.FileSystemResource
警告
一个FileSystemResource
未附加到FileSystemApplicationContext
(那个
是,当FileSystemApplicationContext
不是实际的ResourceLoader
) 治疗
绝对路径和相对路径。相对路径是相对于
当前工作目录,而绝对路径是相对于
文件系统。
但是,出于向后兼容性(历史)原因,当FileSystemApplicationContext
是ResourceLoader
.这FileSystemApplicationContext
强制所有附加FileSystemResource
实例
将所有位置路径视为相对路径,无论它们是否以前导斜杠开头。
在实践中,这意味着以下示例是等效的:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
以下示例也是等效的(即使它们不同是有意义的,但作为一个 case 是相对的,另一个是绝对的):
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
在实践中,如果你需要真正的绝对文件系统路径,你应该避免使用
绝对路径FileSystemResource
或FileSystemXmlApplicationContext
和
强制使用UrlResource
通过使用file:
URL 前缀。以下示例
演示如何执行此作:
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
3. 验证、数据绑定和类型转换
将验证视为业务逻辑有利有弊,Spring 提供了
不排除其中任何一个的验证 (和数据绑定) 设计。
具体来说,验证不应该与 Web 层相关联,并且应该易于本地化。
并且应该可以插入任何可用的验证器。考虑到这些担忧,
Spring 提供了一个Validator
既基本又非常有用的合约
在应用程序的每一层中。
数据绑定对于将用户输入动态绑定到域非常有用
应用程序模型(或用于处理用户输入的任何对象)的Spring
提供了恰当命名的DataBinder
正是这样做的。这Validator
和DataBinder
组成validation
package,它主要用于
仅限于 Web 层。
这BeanWrapper
是 Spring Framework 中的一个基本概念,被广泛使用
的地方。但是,您可能不需要使用BeanWrapper
径直。因为这是参考文档,所以我们觉得有一些解释
可能是有序的。我们解释了BeanWrapper
在本章中,因为您是
打算使用它,你很可能在尝试将数据绑定到对象时这样做。
Spring的DataBinder
和较低级别的BeanWrapper
两者都使用PropertyEditorSupport
实现来解析属性值并设置其格式。这PropertyEditor
和PropertyEditorSupport
types 是 JavaBeans 规范的一部分,也是
在本章中解释。Spring 3 引入了一个core.convert
软件包,该软件包提供
通用类型转换工具,以及用于
格式化 UI 字段值。您可以将这些软件包用作PropertyEditorSupport
实现。本章还将讨论它们。
Spring 通过设置基础设施和适配器支持 Java Bean 验证
Spring 自己的Validator
合同。应用程序可以全局启用 Bean 验证一次,
如 Java Bean 验证中所述,并将其专门用于所有验证
需要。在 Web 层中,应用程序可以进一步注册控制器本地的 SpringValidator
实例数DataBinder
,如配置DataBinder
,它可以
对于插入自定义验证逻辑很有用。
3.1. 使用 Spring 的 Validator 接口进行验证
Spring 具有Validator
可用于验证对象的接口。这Validator
interface 通过使用Errors
对象,以便在验证时,
验证者可以向Errors
对象。
请考虑以下小型数据对象示例:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
class Person(val name: String, val age: Int)
下一个示例提供了Person
类,方法是实现
以下两种方法org.springframework.validation.Validator
接口:
-
supports(Class)
: 这个可以吗Validator
验证提供的Class
? -
validate(Object, org.springframework.validation.Errors)
:验证给定的对象 并且,如果出现验证错误,则使用给定的Errors
对象。
实施Validator
相当简单,尤其是当您知道ValidationUtils
helper 类。以下内容
示例 implementsValidator
为Person
实例:
public class PersonValidator implements Validator {
/**
* This Validator validates only Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
class PersonValidator : Validator {
/*
* This Validator validates only Person instances
*/
override fun supports(clazz: Class<>): Boolean {
return Person::class.java == clazz
}
override fun validate(obj: Any, e: Errors) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
val p = obj as Person
if (p.age < 0) {
e.rejectValue("age", "negativevalue")
} else if (p.age > 110) {
e.rejectValue("age", "too.darn.old")
}
}
}
这static
rejectIfEmpty(..)
方法上的ValidationUtils
class 用于
reject 的name
property (如果是null
或空字符串。看看ValidationUtils
Javadoc
以查看除了前面显示的示例之外,它还提供了哪些功能。
虽然当然可以实现单个Validator
类来验证每个
的嵌套对象中,最好将验证
每个嵌套类的对象在其自己的 logicValidator
实现。一个简单的
“rich” 对象的示例是Customer
它由两个String
属性(名字和第二个名称)和复杂Address
对象。Address
对象
可以独立使用Customer
对象,因此AddressValidator
已实施。如果您希望您的CustomerValidator
以重用包含的 logic
在AddressValidator
类,而无需使用复制和粘贴,则可以
dependency-inject 或实例化AddressValidator
在您的CustomerValidator
,
如下例所示:
public class CustomerValidator implements Validator {
private final Validator addressValidator;
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException("The supplied [Validator] is " +
"required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException("The supplied [Validator] must " +
"support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
class CustomerValidator(private val addressValidator: Validator) : Validator {
init {
if (addressValidator == null) {
throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
}
if (!addressValidator.supports(Address::class.java)) {
throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
}
}
/*
* This Validator validates Customer instances, and any subclasses of Customer too
*/
override fun supports(clazz: Class<>): Boolean {
return Customer::class.java.isAssignableFrom(clazz)
}
override fun validate(target: Any, errors: Errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
val customer = target as Customer
try {
errors.pushNestedPath("address")
ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
} finally {
errors.popNestedPath()
}
}
}
验证错误将报告给Errors
对象传递给验证器。在这种情况下
的 Spring Web MVC 中,您可以使用<spring:bind/>
标签来检查错误消息,但
您还可以检查Errors
反对自己。有关
它提供的方法可以在 Javadoc 中找到。
3.2. 将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍如何输出对应的消息
验证错误。在上一节所示的示例中,
我们拒绝了name
和age
领域。如果我们想使用MessageSource
,我们可以使用在拒绝字段时提供的错误代码来执行此作
(在本例中为 'name' 和 'age')。当您调用 (直接或间接, 通过使用
例如,ValidationUtils
类)rejectValue
或其他reject
方法
从Errors
接口,底层实现不仅会注册你
传入,但还会注册许多其他错误代码。这MessageCodesResolver
确定Errors
interface 寄存器。默认情况下,DefaultMessageCodesResolver
,它(例如)不仅注册消息
使用您提供的代码,但还会注册包含您传递的字段名称的消息
添加到 reject 方法中。因此,如果您使用rejectValue("age", "too.darn.old")
,
除了too.darn.old
code 中,Spring 也会注册too.darn.old.age
和too.darn.old.age.int
(第一个包含字段名称,第二个包含类型
的字段)。这样做是为了方便开发人员在定位错误消息时提供帮助。
有关MessageCodesResolver
,并且可以找到 default 策略
在 Javadoc 的MessageCodesResolver
和DefaultMessageCodesResolver
,
分别。
3.3. Bean作和BeanWrapper
这org.springframework.beans
package 遵循 JavaBeans 标准。
JavaBean 是一个具有默认无参数构造函数的类,它遵循
命名约定,其中(例如)名为bingoMadness
愿意
具有 setter 方法setBingoMadness(..)
和 getter 方法getBingoMadness()
.为
有关 JavaBeans 和规范的更多信息,请参阅 JavaBeans。
beans 包中一个非常重要的类是BeanWrapper
interface 及其
相应的实现 (BeanWrapperImpl
).引用自 javadoc,BeanWrapper
提供设置和获取属性值(单独或在
bulk)、获取属性描述符和查询属性以确定它们是否为
readable 或 writable。此外,BeanWrapper
提供对嵌套属性的支持,
将 子属性 的 属性设置为 无限深度 。这BeanWrapper
还支持添加标准 JavaBeans 的功能PropertyChangeListeners
和VetoableChangeListeners
,而无需 target 类中的支持代码。
最后但并非最不重要的一点是,BeanWrapper
支持设置索引属性。
这BeanWrapper
通常不直接由应用程序代码使用,但由DataBinder
和BeanFactory
.
方式BeanWrapper
works 部分由它的名称表示:它将一个 bean 包装到
对该 Bean 执行作,例如设置和检索属性。
3.3.1. 设置和获取 Basic 和 Nested 属性
设置和获取属性是通过setPropertyValue
和getPropertyValue
的重载方法变体BeanWrapper
.请参阅他们的 Javadoc 以获取
详。下表显示了这些约定的一些示例:
表达 | 解释 |
---|---|
|
指示属性 |
|
指示嵌套属性 |
|
指示 indexed 属性的第三个元素 |
|
指示由 |
(如果您不打算使用
这BeanWrapper
径直。如果您只使用DataBinder
和BeanFactory
及其默认实现,您应该跳到部分PropertyEditors
.)
以下两个示例类使用BeanWrapper
以获取并设置
性能:
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
class Company {
var name: String? = null
var managingDirector: Employee? = null
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
class Employee {
var name: String? = null
var salary: Float? = null
}
以下代码片段显示了如何检索和作某些
instantiated 的属性Companies
和Employees
:
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)
// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)
// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?
3.3.2. 内置PropertyEditor
实现
Spring 使用PropertyEditor
要在Object
以及String
.它可以很方便
以不同于对象本身的方式表示属性。例如,Date
可以用人类可读的方式表示(因为String
:'2007-14-09'
),而
我们仍然可以将人类可读的形式转换回原始日期(或者,甚至
更好的是,将以人类可读形式输入的任何日期转换回Date
对象)。这
可以通过注册java.beans.PropertyEditor
.在BeanWrapper
或
或者,在特定的 IoC 容器中(如上一章所述),会给出
了解如何将属性转换为所需的类型。有关PropertyEditor
看的 javadocjava.beans
来自 Oracle 的软件包.
在 Spring 中使用属性编辑的几个示例:
-
在 bean 上设置属性是通过使用
PropertyEditor
实现。 当您使用String
作为您声明的某个 bean 的属性值 在 XML 文件中,Spring(如果相应属性的 setter 具有Class
参数)使用ClassEditor
尝试将参数解析为Class
对象。 -
在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种来完成的 之
PropertyEditor
可以在CommandController
.
Spring 内置了许多PropertyEditor
让生活更轻松。
它们都位于org.springframework.beans.propertyeditors
包。默认情况下,大多数 (但不是全部,如下表所示) 由BeanWrapperImpl
.如果属性编辑器可以以某种方式进行配置,则可以
仍然注册你自己的变体来覆盖默认的变体。下表描述
各种PropertyEditor
Spring 提供的实现:
类 | 解释 |
---|---|
|
字节数组的编辑器。将字符串转换为相应的字节
交涉。默认注册者 |
|
将表示类的 String 解析为实际的类,反之亦然。当
class 时,会创建一个 |
|
可定制的属性编辑器 |
|
用于集合的属性编辑器, 转换任何源 |
|
可定制的属性编辑器 |
|
可定制的属性编辑器 |
|
将字符串解析为 |
|
单向属性编辑器,可以接受一个字符串并生成(通过
中间 |
|
可以将字符串解析为 |
|
可以将字符串解析为 |
|
可以转换字符串(使用 javadoc 中定义的格式进行格式化 |
|
修剪字符串的 Property editor。(可选)允许转换空字符串
转换为 |
|
可以将 URL 的字符串表示形式解析为实际的 |
Spring 使用java.beans.PropertyEditorManager
设置 Search path for 属性
可能需要的编辑器。搜索路径还包括sun.bean.editors
哪
包括PropertyEditor
类型的实现,例如Font
,Color
和大部分
原始类型。另请注意,标准的 JavaBeans 基础结构
自动发现PropertyEditor
类(无需注册它们
显式地)如果它们与它们处理的类位于同一 package 中,并且具有相同的
name 作为该类,使用Editor
附加。例如,可以有以下内容
class 和 package 结构,这对于SomethingEditor
要成为的类
识别并用作PropertyEditor
为Something
-typed 属性。
com chank pop Something SomethingEditor // the PropertyEditor for the Something class
请注意,您也可以使用标准的BeanInfo
JavaBeans 机制
(在这里有一定程度的描述)。以下示例使用BeanInfo
机制设置为
显式注册一个或多个PropertyEditor
实例具有
关联类:
com chank pop Something SomethingBeanInfo // the BeanInfo for the Something class
以下 Java 源代码用于引用的SomethingBeanInfo
类
合伙人 ACustomNumberEditor
使用age
属性的Something
类:
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
class SomethingBeanInfo : SimpleBeanInfo() {
override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
try {
val numberPE = CustomNumberEditor(Int::class.java, true)
val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
override fun createPropertyEditor(bean: Any): PropertyEditor {
return numberPE
}
}
return arrayOf(ageDescriptor)
} catch (ex: IntrospectionException) {
throw Error(ex.toString())
}
}
}
注册其他自定义PropertyEditor
实现
当将 Bean 属性设置为字符串值时,Spring IoC 容器最终会使用
标准 JavaBeansPropertyEditor
实现将这些字符串转换为 Complex 类型的
财产。Spring 预先注册了一些自定义PropertyEditor
实现(例如,更改为
将表示为字符串的类名转换为Class
对象)。此外
Java 的标准 JavaBeansPropertyEditor
lookup 机制允许PropertyEditor
,请适当命名,并将其放置在与类相同的包中
为此,它提供支持,以便可以自动找到它。
如有需要注册其他定制PropertyEditors
,几种机制是
可用。最手动的方法,通常不方便或
推荐使用registerCustomEditor()
方法ConfigurableBeanFactory
接口,假设您有一个BeanFactory
参考。
另一种(稍微方便一点)机制是使用特殊的咖啡豆工厂
后处理器称为CustomEditorConfigurer
.虽然您可以使用 Bean Factory 后处理器
跟BeanFactory
implementations、CustomEditorConfigurer
具有
nested 属性设置,因此我们强烈建议您将它与ApplicationContext
,您可以在其中以与任何其他 bean 类似的方式部署它,并且
可以自动检测和应用。
请注意,所有 bean 工厂和应用程序上下文都会自动使用一些
内置的属性编辑器,通过使用BeanWrapper
自
处理属性转换。标准属性编辑器,其中BeanWrapper
寄存器在上一节中列出。
此外ApplicationContexts
还要覆盖或添加其他编辑器来处理
资源查找。
标准 JavaBeansPropertyEditor
实例用于转换属性值
表示为属性的实际复杂类型的字符串。您可以使用CustomEditorConfigurer
,一个 Bean Factory 后处理器,以方便地添加
支持其他PropertyEditor
实例复制到ApplicationContext
.
请考虑以下示例,该示例定义了一个名为ExoticType
和
另一个名为DependsOnExoticType
,它需要ExoticType
set as a property:
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
package example
class ExoticType(val name: String)
class DependsOnExoticType {
var type: ExoticType? = null
}
当事情设置正确时,我们希望能够将 type 属性分配为
string,其中PropertyEditor
转换为实际的ExoticType
实例。以下 Bean 定义显示了如何设置此关系:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
这PropertyEditor
实现可能类似于以下内容:
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
// converts string representation to ExoticType object
package example
import java.beans.PropertyEditorSupport
class ExoticTypeEditor : PropertyEditorSupport() {
override fun setAsText(text: String) {
value = ExoticType(text.toUpperCase())
}
}
最后,以下示例展示了如何使用CustomEditorConfigurer
注册新的PropertyEditor
使用ApplicationContext
,然后它就可以根据需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
用PropertyEditorRegistrar
向 Spring 容器注册属性编辑器的另一种机制是
创建并使用PropertyEditorRegistrar
.此接口在以下情况下特别有用
您需要在几种不同情况下使用同一组属性编辑器。
您可以编写相应的注册商并在每种情况下重复使用它。PropertyEditorRegistrar
实例与名为PropertyEditorRegistry
,该接口由 Spring 实现BeanWrapper
(以及DataBinder
).PropertyEditorRegistrar
实例特别方便
当与CustomEditorConfigurer
(在此处描述),它公开了一个属性
叫setPropertyEditorRegistrars(..)
.PropertyEditorRegistrar
已添加的实例
更改为CustomEditorConfigurer
以这种方式,可以轻松地与DataBinder
和
Spring MVC 控制器。此外,它还避免了在自定义时进行同步的需要
编辑: APropertyEditorRegistrar
有望创造新鲜的PropertyEditor
实例。
以下示例演示如何创建自己的PropertyEditorRegistrar
实现:
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
package com.foo.editors.spring
import org.springframework.beans.PropertyEditorRegistrar
import org.springframework.beans.PropertyEditorRegistry
class CustomPropertyEditorRegistrar : PropertyEditorRegistrar {
override fun registerCustomEditors(registry: PropertyEditorRegistry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType::class.java, ExoticTypeEditor())
// you could register as many custom property editors as are required here...
}
}
另请参阅org.springframework.beans.support.ResourceEditorRegistrar
有关示例PropertyEditorRegistrar
实现。请注意,在实现registerCustomEditors(..)
方法,它会为每个属性编辑器创建新实例。
下一个示例演示如何配置CustomEditorConfigurer
并注入我们的CustomPropertyEditorRegistrar
进入它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后(对于你们这些人来说,这与本章的重点有点不同
使用 Spring 的 MVC Web 框架),使用PropertyEditorRegistrars
在
与 data-binding 结合使用Controllers
(例如SimpleFormController
) 可以非常
方便。以下示例使用PropertyEditorRegistrar
在
实现initBinder(..)
方法:
public final class RegisterUserController extends SimpleFormController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods to do with registering a User
}
class RegisterUserController(
private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() {
protected fun initBinder(request: HttpServletRequest,
binder: ServletRequestDataBinder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder)
}
// other methods to do with registering a User
}
这种风格的PropertyEditor
注册可以导致简洁的代码(实现
之initBinder(..)
只有一行长),并让 commonPropertyEditor
registration code 封装在一个类中,然后在尽可能多的Controllers
根据需要。
3.4. Spring Type Conversion
Spring 3 引入了一个core.convert
提供常规类型转换的软件包
系统。系统定义了一个 SPI 来实现类型转换逻辑和一个 API
在运行时执行类型转换。在 Spring 容器中,您可以使用此系统
作为PropertyEditor
转换外部化 Bean 属性值的实现
strings 设置为所需的属性类型。您还可以在
需要类型转换的应用程序。
3.4.1. 转换器 SPI
实现类型转换逻辑的 SPI 简单且强类型,如下所示 接口定义显示:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
package org.springframework.core.convert.converter
interface Converter<S, T> {
fun convert(source: S): T
}
要创建您自己的转换器,请实现Converter
interface 和 parameterizeS
作为您要转换的 type 并将T
作为您要转换为的类型。您还可以透明地应用这样的
converter 如果是S
需要
转换为T
,前提是委托数组或集合
converter 也被注册了(它DefaultConversionService
执行)。
对于对convert(S)
,则保证 source 参数不为 null。你Converter
如果转换失败,可能会引发任何未经检查的异常。具体来说,它应该抛出一个IllegalArgumentException
报告无效的源值。
请注意确保您的Converter
实现是线程安全的。
中提供了几种转换器实现core.convert.support
package 设置为
一种便利。其中包括从字符串到数字和其他常见类型的转换器。
下面的清单显示了StringToInteger
类,这是一个典型的Converter
实现:
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
package org.springframework.core.convert.support
import org.springframework.core.convert.converter.Converter
internal class StringToInteger : Converter<String, Int> {
override fun convert(source: String): Int? {
return Integer.valueOf(source)
}
}
3.4.2. 使用ConverterFactory
当您需要集中整个类层次结构的转换逻辑时
(例如,从String
自Enum
对象),您可以实现ConverterFactory
,如下例所示:
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
package org.springframework.core.convert.converter
interface ConverterFactory<S, R> {
fun <T : R> getConverter(targetType: Class<T>): Converter<S, T>
}
将 S 参数化为要转换的起始类型,将 R 参数化为定义基本类型
您可以转换为的类的范围。然后实施getConverter(Class<T>)
,
其中 T 是 R 的子类。
考虑一下StringToEnumConverterFactory
例如:
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
3.4.3. 使用GenericConverter
当您需要复杂的Converter
实现中,请考虑使用GenericConverter
接口。使用更灵活但类型不太强的签名
比Converter
一个GenericConverter
支持在多个源和
目标类型。此外,一个GenericConverter
使源字段和目标字段可用
context 中,您可以在实施转化逻辑时使用。这样的上下文允许
类型转换由字段注释或在
字段签名。以下清单显示了GenericConverter
:
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
package org.springframework.core.convert.converter
interface GenericConverter {
fun getConvertibleTypes(): Set<ConvertiblePair>?
fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any?
}
要实现GenericConverter
有getConvertibleTypes()
返回支持的
source→target 类型对。然后实施convert(Object, TypeDescriptor,
TypeDescriptor)
以包含您的转化逻辑。来源TypeDescriptor
提供
访问保存要转换的值的 source 字段。目标TypeDescriptor
提供对要设置转换值的目标字段的访问。
一个很好的例子GenericConverter
是在 Java 数组之间进行转换的转换器
和一个集合。这样的ArrayToCollectionConverter
内省声明
目标集合类型,用于解析集合的元素类型。这样,每个
元素转换为 collection 元素类型,然后再将
collection 在 target 字段上设置。
因为GenericConverter 是一个更复杂的 SPI 接口,您应该使用
它只在你需要的时候。喜爱Converter 或ConverterFactory 用于基本型
转换需求。 |
用ConditionalGenericConverter
有时,您需要一个Converter
仅在特定条件为 true 时运行。为
示例中,您可能希望运行Converter
仅当存在特定注释时
在 target 字段上,或者您可能希望运行Converter
仅当特定方法
(例如static valueOf
方法)在 Target 类上定义。ConditionalGenericConverter
是GenericConverter
和ConditionalConverter
接口,用于定义此类自定义匹配条件:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
interface ConditionalConverter {
fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean
}
interface ConditionalGenericConverter : GenericConverter, ConditionalConverter
一个很好的例子ConditionalGenericConverter
是一个IdToEntityConverter
转换
在持久实体标识符和实体引用之间。这样的IdToEntityConverter
仅当目标实体类型声明静态查找器方法(例如,findAccount(Long)
).您可以在matches(TypeDescriptor, TypeDescriptor)
.
3.4.4. 使用ConversionService
应用程序接口
ConversionService
定义一个统一的 API,用于在
运行。转换器通常在以下 Facade 接口后面运行:
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);
}
package org.springframework.core.convert
interface ConversionService {
fun canConvert(sourceType: Class<*>, targetType: Class<*>): Boolean
fun <T> convert(source: Any, targetType: Class<T>): T
fun canConvert(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean
fun convert(source: Any, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any
}
最ConversionService
implementations 还实现ConverterRegistry
哪
提供用于注册转换器的 SPI。在内部,一个ConversionService
implementation 委托其注册的 converters 来执行类型转换逻辑。
一个强大的ConversionService
implementation 在core.convert.support
包。GenericConversionService
通用实现是否适合
在大多数环境中使用。ConversionServiceFactory
提供便捷的工厂
创建通用ConversionService
配置。
3.4.5. 配置ConversionService
一个ConversionService
是一个无状态对象,旨在在 application 中实例化
startup 的 URL,然后在多个线程之间共享。在 Spring 应用程序中,您通常
配置ConversionService
实例(或ApplicationContext
).
Spring 接到了这一点ConversionService
并在键入
转换需要由框架执行。你也可以注入这个ConversionService
放入任何 bean 中,然后直接调用它。
如果没有ConversionService 已向 Spring 注册,则原始PropertyEditor -基于
系统。 |
注册默认ConversionService
使用 Spring 时,添加以下 bean 定义
替换为id
之conversionService
:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认的ConversionService
可以在字符串、数字、枚举、集合、
映射和其他常见类型。要使用
自己的自定义转换器,将converters
财产。属性值可以实现
任何Converter
,ConverterFactory
或GenericConverter
接口。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
使用ConversionService
在 Spring MVC 应用程序中。参见 Spring MVC 一章中的转换和格式化。
在某些情况下,您可能希望在转换过程中应用格式。看这FormatterRegistry
SPI 系列有关使用FormattingConversionServiceFactoryBean
.
3.4.6. 使用ConversionService
编程
要使用ConversionService
实例中,您可以注入对
就像你对任何其他豆子所做的那样。以下示例显示了如何执行此作:
@Service
public class MyService {
public MyService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void doIt() {
this.conversionService.convert(...)
}
}
@Service
class MyService(private val conversionService: ConversionService) {
fun doIt() {
conversionService.convert(...)
}
}
对于大多数使用案例,您可以使用convert
方法,该方法指定targetType
,但它
不适用于更复杂的类型,例如参数化元素的集合。
例如,如果要将List
之Integer
更改为List
之String
编程
您需要提供源类型和目标类型的正式定义。
幸运TypeDescriptor
提供了各种选项来简化作,
如下例所示:
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
请注意,DefaultConversionService
自动注册
适用于大多数环境。这包括集合转换器、标量
转换器和 BasicObject
-自-String
变换 器。您可以注册相同的转换器
与任何ConverterRegistry
通过使用静态addDefaultConverters
方法上的DefaultConversionService
类。
值类型的转换器被重新用于数组和集合,因此有
无需创建特定的转换器即可从Collection
之S
更改为Collection
之T
,假设标准集合处理是合适的。
3.5. Spring Field 格式化
如上一节所述,core.convert
是一个
通用型转换系统。它提供了一个统一的ConversionService
API 作为
以及强类型Converter
SPI 用于实现一种类型的转换逻辑
到另一个。Spring 容器使用此系统来绑定 bean 属性值。在
此外,Spring 表达式语言 (SpEL) 和DataBinder
使用此系统可以
bind 字段值。例如,当 SPEL 需要强制Short
更改为Long
自
完成expression.setValue(Object bean, Object value)
尝试,则core.convert
系统执行强制转换。
现在考虑典型客户端环境的类型转换要求,例如
Web 或桌面应用程序。在此类环境中,您通常从String
支持客户端回发过程,以及返回到String
以支持
View 渲染过程。此外,您经常需要本地化String
值。越多
常规core.convert
Converter
SPI 不满足此类格式要求
径直。为了直接解决这些问题,Spring 3 引入了一个方便的Formatter
SPI 的
提供了一种简单而强大的替代方案PropertyEditor
客户端环境的实现。
通常,您可以使用Converter
SPI 当您需要实现通用型
conversion logic — 例如,用于在java.util.Date
以及Long
.
您可以使用Formatter
当您在客户端环境(例如 Web
application),并且需要解析和打印本地化的字段值。这ConversionService
为这两个 SPI 提供统一的类型转换 API。
3.5.1. 使用Formatter
SPI 系列
这Formatter
用于实现字段格式化逻辑的 SPI 简单且强类型。这
下面的清单显示了Formatter
接口定义:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
从Printer
和Parser
构建块接口。这
下面的清单显示了这两个接口的定义:
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
interface Printer<T> {
fun print(fieldValue: T, locale: Locale): String
}
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}
interface Parser<T> {
@Throws(ParseException::class)
fun parse(clientValue: String, locale: Locale): T
}
要创建自己的Formatter
中,实现Formatter
界面。
参数化T
设置为要格式化的对象类型,例如,java.util.Date
.实现print()
作以打印T
为
display in client locale 中。实现parse()
作来解析T
从客户端区域设置返回的格式化表示形式。你Formatter
应该抛出一个ParseException
或IllegalArgumentException
如果解析尝试失败。拿
确保您的Formatter
实现是线程安全的。
这format
子包提供了多个Formatter
实现以方便使用。
这number
package 提供NumberStyleFormatter
,CurrencyStyleFormatter
和PercentStyleFormatter
格式化Number
使用java.text.NumberFormat
.
这datetime
包提供了一个DateFormatter
格式化java.util.Date
对象替换为
一个java.text.DateFormat
.这datetime.joda
包提供全面的 datetime
基于 Joda-Time 库的格式设置支持。
以下内容DateFormatter
就是一个例子Formatter
实现:
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
class DateFormatter(private val pattern: String) : Formatter<Date> {
override fun print(date: Date, locale: Locale)
= getDateFormat(locale).format(date)
@Throws(ParseException::class)
override fun parse(formatted: String, locale: Locale)
= getDateFormat(locale).parse(formatted)
protected fun getDateFormat(locale: Locale): DateFormat {
val dateFormat = SimpleDateFormat(this.pattern, locale)
dateFormat.isLenient = false
return dateFormat
}
}
Spring 团队欢迎社区驱动的Formatter
贡献。请参阅 GitHub Issues to contribute。
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);
}
package org.springframework.format
interface AnnotationFormatterFactory<A : Annotation> {
val fieldTypes: Set<Class<*>>
fun getPrinter(annotation: A, fieldType: Class<*>): Printer<*>
fun getParser(annotation: A, fieldType: Class<*>): Parser<*>
}
要创建实施:
.将 A 参数化为字段annotationType
您希望与之关联的
格式化逻辑 — 例如org.springframework.format.annotation.DateTimeFormat
.
.有getFieldTypes()
返回可以使用注释的字段类型。
.有getPrinter()
返回一个Printer
以打印带注释的字段的值。
.有getParser()
返回一个Parser
要解析clientValue
对于带注释的字段。
以下示例AnnotationFormatterFactory
implementation 将@NumberFormat
注释以使数字样式或模式为
指定:
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
}
}
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
override fun getFieldTypes(): Set<Class<*>> {
return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
}
override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
return configureFormatterFrom(annotation, fieldType)
}
override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
return configureFormatterFrom(annotation, fieldType)
}
private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
return if (annotation.pattern.isNotEmpty()) {
NumberStyleFormatter(annotation.pattern)
} else {
val style = annotation.style
when {
style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
else -> NumberStyleFormatter()
}
}
}
}
要触发格式设置,您可以使用 @NumberFormat 注释字段,如下所示 示例显示:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
格式注释 API
可移植格式注释 API 存在于org.springframework.format.annotation
包。您可以使用@NumberFormat
格式化Number
字段(如Double
和Long
和@DateTimeFormat
格式化java.util.Date
,java.util.Calendar
,Long
(用于毫秒时间戳)以及 JSR-310java.time
和 Joda-Time 值类型。
以下示例使用@DateTimeFormat
要格式化java.util.Date
作为 ISO 日期
(yyyy-MM-dd):
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
class MyModel(
@DateTimeFormat(iso= ISO.DATE) private val date: Date
)
3.5.3. 使用FormatterRegistry
SPI 系列
这FormatterRegistry
是用于注册格式化程序和转换器的 SPI。FormattingConversionService
是FormatterRegistry
适合
大多数环境。您可以以编程方式或声明方式配置此变体
作为 Spring bean 中,例如通过使用FormattingConversionServiceFactoryBean
.因为这个
implementation 还实现ConversionService
,可以直接配置
用于 Spring 的DataBinder
和 Spring 表达式语言 (SpEL)。
下面的清单显示了FormatterRegistry
SPI:
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Formatter<?> formatter);
void addFormatterForAnnotation(AnnotationFormatterFactory<?> factory);
}
package org.springframework.format
interface FormatterRegistry : ConverterRegistry {
fun addFormatterForFieldType(fieldType: Class<*>, printer: Printer<*>, parser: Parser<*>)
fun addFormatterForFieldType(fieldType: Class<*>, formatter: Formatter<*>)
fun addFormatterForFieldType(formatter: Formatter<*>)
fun addFormatterForAnnotation(factory: AnnotationFormatterFactory<*>)
}
如前面的清单所示,您可以按字段类型或注释注册格式化程序。
这FormatterRegistry
SPI 允许您集中配置格式规则,而不是
在您的控制器之间复制此类配置。例如,您可能希望
强制所有日期字段都以某种方式格式化,或者强制字段具有特定的
annotation 以某种方式格式化。使用共享的FormatterRegistry
,您可以定义
这些规则一次,每当需要格式化时都会应用它们。
3.5.4. 使用FormatterRegistrar
SPI 系列
FormatterRegistrar
是一个 SPI,用于通过
FormatterRegistry 的下面的清单显示了它的接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
package org.springframework.format
interface FormatterRegistrar {
fun registerFormatters(registry: FormatterRegistry)
}
一个FormatterRegistrar
在注册多个相关转换器时很有用,并且
给定格式类别的格式化程序,例如日期格式。它也可以是
在声明式注册不足时很有用 — 例如,当格式化程序
需要在与自身不同的特定字段类型下编制索引<T>
或者
注册一个Printer
/Parser
双。下一节提供了有关
转换器和格式化程序注册。
3.5.5. 在 Spring MVC 中配置格式化
参见 Spring MVC 一章中的转换和格式化。
3.6. 配置全局日期和时间格式
默认情况下,日期和时间字段未使用@DateTimeFormat
从
strings 结合使用DateFormat.SHORT
风格。如果您愿意,可以通过以下方式更改此设置
定义您自己的全局格式。
为此,请确保 Spring 不注册默认格式化程序。相反,请注册 格式化程序:
-
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
-
org.springframework.format.datetime.DateFormatterRegistrar
或org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar
为 Joda-Time。
例如,以下 Java 配置注册了一个全局yyyyMMdd
格式:
@Configuration
public class AppConfig {
@Bean
public FormattingConversionService conversionService() {
// Use the DefaultFormattingConversionService but do not register defaults
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
// Ensure @NumberFormat is still supported
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
registrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
registrar.registerFormatters(conversionService);
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): FormattingConversionService {
// Use the DefaultFormattingConversionService but do not register defaults
return DefaultFormattingConversionService(false).apply {
// Ensure @NumberFormat is still supported
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
// Register JSR-310 date conversion with a specific global format
val registrar = DateTimeFormatterRegistrar()
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
registrar.registerFormatters(this)
// Register date conversion with a specific global format
val registrar = DateFormatterRegistrar()
registrar.setFormatter(DateFormatter("yyyyMMdd"))
registrar.registerFormatters(this)
}
}
}
如果您更喜欢基于 XML 的配置,可以使用FormattingConversionServiceFactoryBean
.以下示例显示了如何执行此作(这次使用 Joda
时间):
<?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.joda.JodaTimeFormatterRegistrar">
<property name="dateFormatter">
<bean class="org.springframework.format.datetime.joda.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 提供了一种通用的验证方法,通过 constraint declaration 和 元数据。要使用它,您可以使用 声明性验证约束,然后由运行时强制执行。有 built-in constraints,您还可以定义自己的自定义 constraints。
请考虑以下示例,它显示了一个简单的PersonForm
model 具有两个属性:
public class PersonForm {
private String name;
private int age;
}
class PersonForm(
private val name: String,
private val age: Int
)
Bean 验证允许您声明约束,如下例所示:
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
class PersonForm(
@get:NotNull @get:Size(max=64)
private val name: String,
@get:Min(0)
private val age: Int
)
然后,Bean 验证器根据声明的 约束。有关 API 的 API 创建。请参阅 Hibernate Validator 文档 特定约束。了解如何将 Bean 验证提供程序设置为 Spring Bean,请继续阅读。
3.7.2. 配置 Bean 验证提供程序
Spring 提供了对 Bean 验证 API 的全面支持,包括
Bean Validation 提供程序作为 Spring Bean。这样,您就可以在邮件中注入javax.validation.ValidatorFactory
或javax.validation.Validator
验证位于何处
在您的应用程序中需要。
您可以使用LocalValidatorFactoryBean
将默认 Validator 配置为 Spring
bean,如下例所示:
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
前面示例中的基本配置触发 bean 验证以通过以下方式初始化 使用其默认的引导机制。Bean Validation 提供程序,例如 Hibernate Validator 应存在于 Classpath 中,并被自动检测。
注入验证器
LocalValidatorFactoryBean
同时实现javax.validation.ValidatorFactory
和javax.validation.Validator
以及 Spring 的org.springframework.validation.Validator
.
您可以将对这些接口中任一接口的引用注入到需要调用
验证逻辑。
您可以注入对javax.validation.Validator
如果您更喜欢使用 Bean
验证 API,如下例所示:
import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import javax.validation.Validator;
@Service
class MyService(@Autowired private val validator: Validator)
您可以注入对org.springframework.validation.Validator
如果您的 bean
需要 Spring Validation API,如下例所示:
import org.springframework.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import org.springframework.validation.Validator
@Service
class MyService(@Autowired private val validator: Validator)
配置自定义约束
每个 bean 验证约束由两部分组成:
-
一个
@Constraint
注解,用于声明约束及其可配置属性。 -
的
javax.validation.ConstraintValidator
实现 约束的行为。
要将声明与实现关联,每个@Constraint
注解
引用相应的ConstraintValidator
implementation 类。在运行时,ConstraintValidatorFactory
实例化引用的实现,当
约束注释。
默认情况下,LocalValidatorFactoryBean
配置SpringConstraintValidatorFactory
使用 Spring 创建ConstraintValidator
实例。这样,您的自定义ConstraintValidators
像任何其他 Spring bean 一样从依赖注入中受益。
以下示例显示了自定义@Constraint
声明后跟关联的ConstraintValidator
使用 Spring 进行依赖注入的实现:
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
import javax.validation.ConstraintValidator;
public class MyConstraintValidator implements ConstraintValidator {
@Autowired;
private Foo aDependency;
// ...
}
import javax.validation.ConstraintValidator
class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {
// ...
}
如前面的示例所示,ConstraintValidator
implementation 可以有其依赖项@Autowired
就像任何其他 Spring bean 一样。
Spring 驱动的方法验证
您可以集成 Bean Validation 1.1 支持的方法验证功能(并且,作为
自定义扩展(也是由 Hibernate Validator 4.3 提供的)通过MethodValidationPostProcessor
bean 定义:
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
要获得 Spring 驱动的方法验证的资格,所有目标类都需要被注释
与 Spring 的@Validated
注解,也可以选择声明验证
要使用的组。看MethodValidationPostProcessor
,了解 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息。
其他配置选项
默认的LocalValidatorFactoryBean
配置足以满足大多数
例。各种 Bean 验证有许多配置选项
构造,从消息插值到遍历解析。请参阅LocalValidatorFactoryBean
javadoc 了解有关这些选项的更多信息。
3.7.3. 配置DataBinder
从 Spring 3 开始,您可以配置DataBinder
实例具有Validator
.一次
配置后,您可以调用Validator
通过调用binder.validate()
.任何验证Errors
会自动添加到 Binder 的BindingResult
.
以下示例演示如何使用DataBinder
以编程方式调用 validation
绑定到目标对象后的逻辑:
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()
// bind to the target object
binder.bind(propertyValues)
// validate the target object
binder.validate()
// get BindingResult that includes any validation errors
val results = binder.bindingResult
您还可以配置DataBinder
具有多个Validator
实例dataBinder.addValidators
和dataBinder.replaceValidators
.这在以下情况下很有用
将全局配置的 bean 验证与 Spring 相结合Validator
配置
本地的 DataBinder 实例。参见 Spring MVC 验证配置。
3.7.4. Spring MVC 3 验证
参见 Spring MVC 一章中的 验证。
4. Spring 表达式语言 (SpEL)
Spring 表达式语言(简称“SPEL”)是一种强大的表达式语言,它 支持在运行时查询和作对象图。语言语法为 类似于 Unified EL,但提供了额外的功能,最明显的是方法调用和 基本的字符串模板功能。
虽然还有其他几种可用的 Java 表达式语言 — OGNL、MVEL 和 JBoss EL,仅举几例 — Spring 表达式语言的创建是为了提供 Spring 社区,该社区具有一种受支持的表达式语言,可用于所有 Spring 产品组合中的产品。它的语言功能由 Spring 产品组合中项目的要求,包括工具要求 以获取 Spring Tools for Eclipse 中的代码完成支持。 也就是说,SPEL 基于一个与技术无关的 API,它允许其他表达式语言 如果需要,可以集成 implementations。
虽然 SpEL 是 Spring 中表达式评估的基础 portfolio 中,它不直接与 Spring 绑定,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是一个 独立的表达式语言。这需要创建一些引导 infrastructure 类,例如 parser 的 Parser 等。大多数 Spring 用户不需要处理 此基础结构,而是只能创作表达式字符串进行评估。 这种典型用途的一个示例是将 SPEL 集成到创建 XML 或 基于注释的 Bean 定义,如定义 Bean 定义的表达式支持中所示。
本章介绍表达式语言、其 API 及其语言的功能
语法。在几个地方,Inventor
和Society
类用作目标
对象进行表达式计算。这些类声明和用于
填充它们列在本章末尾。
表达式语言支持以下功能:
-
文本表达式
-
布尔运算符和关系运算符
-
正则表达式
-
类表达式
-
访问属性、数组、列表和映射
-
方法调用
-
关系运算符
-
分配
-
调用构造函数
-
Bean 引用
-
数组构造
-
内联列表
-
内联映射
-
三元运算符
-
变量
-
用户定义的函数
-
集合投影
-
集合选择
-
模板化表达式
4.1. 评估
本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在 语言参考 中找到。
以下代码介绍了 SpEL API 来评估文本字符串表达式Hello World
.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 | message 变量的值为'Hello World' . |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 | message 变量的值为'Hello World' . |
您最有可能使用的 SPEL 类和接口位于org.springframework.expression
package 及其子软件包,例如spel.support
.
这ExpressionParser
interface 负责解析表达式字符串。在
前面的示例,表达式 String 是由周围的 single 表示的字符串文本
引号。这Expression
interface 负责评估之前定义的
expression 字符串。可以引发的两个异常ParseException
和EvaluationException
,调用parser.parseExpression
和exp.getValue
,
分别。
SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。
在下面的方法调用示例中,我们调用concat
method 的字符串文本:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 | 的值message 现在是 'Hello World!'。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | 的值message 现在是 'Hello World!'。 |
以下调用 JavaBean 属性的示例调用String
财产Bytes
:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 | 此行将 Literals 转换为字节数组。 |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | 此行将 Literals 转换为字节数组。 |
SPEL 还通过使用标准点表示法(例如prop1.prop2.prop3
) 以及相应的属性值设置。
还可以访问 Public 字段。
以下示例演示如何使用点表示法获取文本的长度:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1 | 'Hello World'.bytes.length 给出文本的长度。 |
val parser = SpelExpressionParser()
// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1 | 'Hello World'.bytes.length 给出文本的长度。 |
可以调用 String 的构造函数,而不是使用字符串文本,如下所示 示例显示:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 | 构造一个新的String 从 Literal 中将其设为大写。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
1 | 构造一个新的String 从 Literal 中将其设为大写。 |
请注意 generic 方法的使用:public <T> T getValue(Class<T> desiredResultType)
.
使用此方法无需将表达式的值强制转换为所需的值
result 类型。一EvaluationException
如果值无法强制转换为
类型T
或使用已注册的类型转换器进行转换。
SPEL 更常见的用法是提供一个经过评估的表达式字符串
针对特定对象实例 (称为根对象) 。以下示例显示了
如何检索name
属性从Inventor
class 或
创建一个布尔条件:
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
4.1.1. 理解EvaluationContext
这EvaluationContext
接口在计算表达式以解析
属性、方法或字段,并帮助执行类型转换。Spring 提供两个
实现。
-
SimpleEvaluationContext
:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。 -
StandardEvaluationContext
:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SPEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求
U 显式选择对表达式中属性和方法的支持级别。
默认情况下,create()
static factory method 仅允许对属性进行读取访问。
您还可以获取构建器来配置所需的确切支持级别,并针对
以下一项或多项组合:
-
习惯
PropertyAccessor
仅 (无反射) -
用于只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SPEL 使用 Spring 核心中提供的转换服务
(org.springframework.core.convert.ConversionService
).此转换服务随之而来
具有许多用于常见转换的内置转换器,但也完全可扩展,因此
您可以在类型之间添加自定义转换。此外,它是
generics-aware。这意味着,当您在
表达式中,SPEL 会尝试转换以保持任何对象的类型正确性
它相遇。
这在实践中意味着什么?假设赋值,使用setValue()
正在使用
要设置List
财产。属性的类型实际上是List<Boolean>
.斯佩尔
识别出需要将 List 的元素转换为Boolean
以前
被放置在其中。以下示例显示了如何执行此作:
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b is false
Boolean b = simple.booleanList.get(0);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")
// b is false
val b = simple.booleanList[0]
4.1.2. 解析器配置
可以使用解析器配置对象配置 SpEL 表达式解析器
(org.springframework.expression.spel.SpelParserConfiguration
).配置
object 控制某些表达式组件的行为。例如,如果你
index 添加到数组或集合中,并且指定索引处的元素为null
,
您可以自动创建元素。当使用由
属性引用链。如果您索引到数组或列表中
并指定超出数组当前大小末尾的索引,或者
list 中,您可以自动增加数组或列表以容纳该索引。以下内容
示例演示如何自动增加列表:
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
class Demo {
var list: List<String>? = null
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3]")
val demo = Demo()
val o = expression.getValue(demo)
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
4.1.3. SPEL 编译
Spring Framework 4.1 包括一个基本的表达式编译器。表达式通常是 interpreted,这在评估过程中提供了很大的动态灵活性,但 不提供最佳性能。对于偶尔的表达式使用, 这很好,但是,当被其他组件(如 Spring Integration)使用时, 性能可能非常重要,并且没有真正需要动态性。
SPEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为,并使用该类 类来实现更快的表达式计算。由于周围缺乏打字 expressions,编译器会使用在解释的评估期间收集的信息 的表达式。例如,它不知道类型 的属性引用,但在第一次解释的 evaluation 时,它会找出它是什么。当然,基于这样的派生 如果各种表达式元素的类型 随时间变化。因此,编译最适合于其 type information 不会在重复计算时发生变化。
请考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
因为前面的表达式涉及数组访问,所以一些属性取消引用 和数值运算,则性能提升可能非常明显。在示例中 Micro Benchmark 运行 50000 次迭代,使用 interpreter 的 Expression,并且仅使用 3ms 的编译版本。
编译器配置
默认情况下,编译器未打开,但您可以通过以下两种方式之一打开它 不同的方式。您可以使用解析器配置过程来打开它 (前面讨论过)或使用系统 当 SPEL 用法嵌入到另一个组件中时,属性。本节 讨论这两个选项。
编译器可以在以下三种模式之一下运行,这些模式在org.springframework.expression.spel.SpelCompilerMode
enum 中。模式如下:
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。 -
MIXED
:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户进入的异常IMMEDIATE
mode 则在内部处理。
IMMEDIATE
模式存在,因为MIXED
mode 可能会导致表达式出现问题
有副作用。如果编译的表达式在部分成功后崩溃,则
可能已经做了一些影响系统状态的事情。如果此
已发生,调用方可能不希望它在解释模式下静默重新运行。
因为表达式的一部分可能运行两次。
选择模式后,使用SpelParserConfiguration
配置解析器。这
以下示例显示了如何执行此作:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
指定编译器模式时,还可以指定类加载器(允许传递 null)。 编译的表达式在提供的 any 下创建的子类加载器中定义。 请务必确保,如果指定了类加载器,则它可以看到 表达式求值过程。如果未指定类加载器,则使用默认类加载器 (通常是在表达式计算期间运行的线程的上下文类加载器)。
配置编译器的第二种方法是在 SpEL 嵌入到其他一些
组件,并且可能无法通过配置对象对其进行配置。在这些
情况下,可以使用 system 属性。您可以设置spring.expression.compiler.mode
property 添加到SpelCompilerMode
枚举值 (off
,immediate
或mixed
).
4.2. Bean 定义中的表达式
您可以将 SPEL 表达式与基于 XML 或基于注释的配置元数据一起使用,以便
定义BeanDefinition
实例。在这两种情况下,定义表达式的语法都是
形式#{ <expression string> }
.
4.2.1. XML配置
可以使用表达式设置属性或构造函数参数值,如下所示 示例显示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
应用程序上下文中的所有 bean 都可以作为预定义的变量使用,其
通用 bean 名称。这包括标准上下文 Bean,例如environment
(类型org.springframework.core.env.Environment
) 以及systemProperties
和systemEnvironment
(类型Map<String, Object>
) 访问运行时环境。
以下示例显示了对systemProperties
bean 作为 SPEL 变量:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
请注意,您不必在此处为预定义变量加上 symbol 前缀。#
您还可以按名称引用其他 Bean 属性,如下例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
</bean>
4.2.2. 注解配置
要指定默认值,您可以将@Value
字段、方法、
以及方法或构造函数参数。
以下示例设置字段变量的默认值:
public class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
以下示例显示了等效的 but on a property setter 方法:
public class PropertyValueTestBean {
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class PropertyValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
自动装配的方法和构造函数也可以使用@Value
注解,如下所示
示例显示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
private lateinit var defaultLocale: String
@Autowired
fun configure(movieFinder: MovieFinder,
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
this.movieFinder = movieFinder
this.defaultLocale = defaultLocale
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
// ...
}
4.3. 语言参考
本节描述了 Spring 表达式语言的工作原理。它涵盖以下内容 主题:
4.3.1. 文字表达式
支持的文字表达式类型包括字符串、数值(int、real、hex)、 boolean 和 null。字符串由单引号分隔。放置单引号本身 在字符串中,使用两个单引号字符。
下面的清单显示了 Literals 的简单用法。通常,它们不会被使用 像这样孤立地进行,而是作为更复杂的表达式的一部分——例如, 在逻辑比较运算符的一侧使用 Literals。
ExpressionParser parser = new SpelExpressionParser();
// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
val parser = SpelExpressionParser()
// evals to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
// evals to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
val trueValue = parser.parseExpression("true").value as Boolean
val nullValue = parser.parseExpression("null").value
数字支持使用负号、指数表示法和小数点。 默认情况下,使用 Double.parseDouble() 解析实数。
4.3.2. 属性、数组、列表、映射和索引器
使用属性引用进行导航非常简单。为此,请使用句点来表示嵌套的
property 值。的Inventor
类pupin
和tesla
中填充了
数据列在 Classes used in the examples 部分中。
要导航“向下”并获取 Tesla 的出生年份和 Pupin 的出生城市,我们使用以下命令
表达 式:
// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
// evals to 1856
val year = parser.parseExpression("Birthdate.Year + 1900").getValue(context) as Int
val city = parser.parseExpression("placeOfBirth.City").getValue(context) as String
属性名称的首字母允许不区分大小写。的内容 数组和列表是使用方括号表示法获取的,如下例所示 显示:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// Inventions Array
// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String.class);
// Members List
// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
context, ieee, String.class);
// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
context, ieee, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// Inventions Array
// evaluates to "Induction motor"
val invention = parser.parseExpression("inventions[3]").getValue(
context, tesla, String::class.java)
// Members List
// evaluates to "Nikola Tesla"
val name = parser.parseExpression("Members[0].Name").getValue(
context, ieee, String::class.java)
// List and Array navigation
// evaluates to "Wireless communication"
val invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
context, ieee, String::class.java)
映射的内容是通过在
括弧。在以下示例中,由于Officers
map 是字符串,我们可以指定
字符串:
// Officer's Dictionary
Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");
// Officer's Dictionary
val pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor::class.java)
// evaluates to "Idvor"
val city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String::class.java)
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia")
4.3.3. 内联列表
您可以使用表示法直接在表达式中表示列表。{}
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>
{}
本身意味着一个空列表。出于性能原因,如果列表本身是
完全由固定文本组成,创建一个常量列表来表示
expression (而不是在每次评估时构建一个新列表)。
4.3.4. 内联映射
您还可以使用{key:value}
表示法。这
以下示例显示了如何执行此作:
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, >
val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<, *>
{:}
本身意味着一张空地图。出于性能原因,如果映射本身是由
的固定文本或其他嵌套常量结构(列表或映射)中,将创建一个常量映射
来表示表达式(而不是在每次求值时构建新 map)。引用 map 键
是可选的。上面的示例不使用带引号的键。
4.3.5. 数组构造
您可以使用熟悉的 Java 语法构建数组,并可选择提供初始化器 以在构造时填充数组。以下示例显示了如何执行此作:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
当前,在构造 Multi-dimensional 数组。
4.3.6. 方法
您可以使用典型的 Java 编程语法来调用方法。您还可以调用方法 在 Literals 上。还支持变量参数。以下示例说明如何 invoke 方法:
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)
// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean::class.java)
4.3.7. 运算符
Spring 表达式语言支持以下类型的运算符:
关系运算符
关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)使用标准运算符表示法来支持。这 下面的清单显示了一些 Operators 示例:
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
大于和小于比较 如果您更喜欢数字比较,请避免使用基于数字的比较 |
除了标准关系运算符之外,SPEL 还支持instanceof
和常规
基于表达式matches
算子。下面的清单显示了这两种方法的示例:
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
val falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
//evaluates to false
val falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
小心原始类型,因为它们会立即被装箱到 wrapper 类型。
所以1 instanceof T(int) 计算结果为false 而1 instanceof T(Integer) 计算结果为true 不出所料。 |
每个符号运算符也可以指定为纯字母等效运算符。这 避免了所使用的符号对 表达式嵌入的 (,例如在 XML 文档中)。文本等价物是:
-
lt
(<
) -
gt
(>
) -
le
(<=
) -
ge
(>=
) -
eq
(==
) -
ne
(!=
) -
div
(/
) -
mod
(%
) -
not
(!
).
所有文本运算符都不区分大小写。
逻辑运算符
SPEL 支持以下逻辑运算符:
-
and
(&&
) -
or
(||
) -
not
(!
)
以下示例演示如何使用逻辑运算符
// -- AND --
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- AND --
// evaluates to false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)
// evaluates to true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- OR --
// evaluates to true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)
// evaluates to true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- NOT --
// evaluates to false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
数学运算符
您可以对数字和字符串使用加号运算符。您可以使用减法、乘法、 和除法运算符仅对数字。您还可以使用 模数 (%) 和指数幂 (^) 运算符。强制实施标准运算符优先级。这 以下示例显示了正在使用的数学运算符:
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
val testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000
// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
赋值运算符
要设置属性,请使用赋值运算符 ()。这通常是
done 在对=
setValue
但也可以在对getValue
.这
下面的清单显示了使用 ASSIGNMENT 运算符的两种方法:
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic")
// alternatively
val aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)
4.3.8. 类型
您可以使用特殊的T
运算符指定java.lang.Class
(
type) 的 intent 的 intent静态方法也是使用此运算符调用的。这StandardEvaluationContext
使用TypeLocator
查找类型,并使用StandardTypeLocator
(可以替换)是在理解java.lang
包。这意味着T()
对 中的类型的引用java.lang
不需要
完全限定,但所有其他类型的引用都必须是。以下示例显示了如何作
要使用T
算子:
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)
val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)
val trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean::class.java)
4.3.9. 构造函数
您可以使用new
算子。您应该使用完全限定的类名
对于除基元类型 (int
,float
等)和 String 的 String。以下内容
示例演示如何使用new
operator 调用构造函数:
Inventor einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor.class);
//create new inventor instance within add method of List
p.parseExpression(
"Members.add(new org.spring.samples.spel.inventor.Inventor(
'Albert Einstein', 'German'))").getValue(societyContext);
val einstein = p.parseExpression(
"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
.getValue(Inventor::class.java)
//create new inventor instance within 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
method 开启EvaluationContext
实现。
有效的变量名称必须由以下一个或多个受支持的变量组成 字符。
|
以下示例演示如何使用变量。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")
parser.parseExpression("Name = #newName").getValue(context, tesla)
println(tesla.name) // "Mike Tesla"
这#this
和#root
变量
这#this
variable 始终被定义并引用当前评估对象
(根据这些引用解析不合格的引用)。这#root
variable 始终
定义并引用根上下文对象。虽然#this
可能因
计算表达式,#root
始终引用根。以下示例
演示如何使用#this
和#root
变量:
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
"#primes.?[#this>10]").getValue(context) as List<Int>
4.3.11. 函数
您可以通过注册可在
expression 字符串。该函数通过EvaluationContext
.这
以下示例演示如何注册用户定义的函数:
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)
例如,请考虑以下反转字符串的实用程序方法:
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
fun reverseString(input: String): String {
val backwards = StringBuilder(input.length)
for (i in 0 until input.length) {
backwards.append(input[input.length - 1 - i])
}
return backwards.toString()
}
然后,您可以注册并使用上述方法,如下例所示:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)
val helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String::class.java)
4.3.12. Bean 引用
如果评估上下文已经配置了 bean 解析器,则可以
使用 symbol 从表达式中查找 bean。以下示例显示了如何作
为此,请执行以下作:@
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)
要访问工厂 Bean 本身,您应该在 Bean 名称前加上一个符号。
以下示例显示了如何执行此作:&
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)
4.3.13. 三元运算符 (If-Then-Else)
您可以使用三元运算符在 表达式。下面的清单显示了一个最小示例:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
val falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
在这种情况下,布尔值false
返回 String 值'falseExp'
.一个 更多
实际示例如下:
parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
parser.parseExpression("Name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"
val queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
请参阅 Elvis 运算符的下一节,了解 三元运算符。
4.3.14. Elvis 运算符
Elvis 运算符是三元运算符语法的缩写,用于 Groovy 语言。 使用三元运算符语法时,通常必须将变量重复两次,因为 以下示例显示:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
相反,您可以使用 Elvis 运算符(因与 Elvis 的发型相似而命名)。 以下示例演示如何使用 Elvis 运算符:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'
下面的清单显示了一个更复杂的示例:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Elvis Presley
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Nikola Tesla
tesla.setName(null)
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Elvis Presley
您可以使用 Elvis 运算符在表达式中应用默认值。以下内容
示例演示如何在
这将注入一个 system 属性 |
4.3.15. Safe Navigation作符
安全导航运算符用于避免NullPointerException
并来自
Groovy 语言。通常,当您引用某个对象时,可能需要验证
在访问对象的方法或属性之前,它不为 null。为避免这种情况,
Safe Navigation 运算符返回 null,而不是引发异常。以下内容
示例演示如何使用 Safe Navigation 运算符:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
var city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java)
println(city) // Smiljan
tesla.setPlaceOfBirth(null)
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java)
println(city) // null - does not throw NullPointerException!!!
4.3.16. 集合选择
选择项是一项强大的表达式语言功能,可用于转换 source 集合添加到另一个集合中。
选择使用.?[selectionExpression]
.它会过滤集合和
返回包含原始元素子集的新集合。例如
选择让我们轻松获得塞尔维亚发明家的列表,如下例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
val list = parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
可以在列表和地图上进行选择。对于列表,选择
criteria 根据每个单独的 list 元素进行评估。对于地图,
根据每个映射条目(Java 类型的对象Map.Entry
).每个映射条目都有其键和值,可作为属性访问
选择。
以下表达式返回一个由原始 map 的那些元素组成的新 map 其中 entry 值小于 27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
val newMap = parser.parseExpression("map.?[value<27]").getValue()
除了返回所有选定的元素外,您还可以仅检索
first 或 last 值。要获取与所选内容匹配的第一个条目,语法为.^[selectionExpression]
.要获取最后一个匹配的选择,语法为.$[selectionExpression]
.
4.3.17. 集合投影
Projection 允许集合驱动子表达式的计算,而
result 是一个新集合。projection 的语法是.![projectionExpression]
.为
例如,假设我们有一个发明人列表,但希望
他们出生的城市。实际上,我们想评估 'placeOfBirth.city'
Inventor 列表中的每个条目。以下示例使用 projection 来执行此作:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("Members.![placeOfBirth.city]") as List<*>
您还可以使用地图来驱动投影,在这种情况下,投影表达式为
根据 Map 中的每个条目(表示为 JavaMap.Entry
).结果
的 of a projection across a map 是一个列表,其中包含对投影的评估
expression 来触发每个 Map 条目。
4.3.18. 表达式模板
表达式模板允许将文本文本与一个或多个评估块混合。
每个评估块都用前缀和后缀字符分隔,您可以
定义。常见的选择是用作分隔符,如下例所示
显示:#{ }
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
val randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
TemplateParserContext()).getValue(String::class.java)
// evaluates to "random number is 0.7038186818312008"
字符串是通过连接文本文本来计算的'random number is '
使用
在分隔符内计算表达式的结果(在本例中为结果
的调用#{ }
random()
方法)。的第二个参数parseExpression()
方法
属于ParserContext
.这ParserContext
interface 用于影响
解析表达式以支持表达式模板化功能。
的定义TemplateParserContext
遵循:
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
class TemplateParserContext : ParserContext {
override fun getExpressionPrefix(): String {
return "#{"
}
override fun getExpressionSuffix(): String {
return "}"
}
override fun isTemplate(): Boolean {
return true
}
}
4.4. 示例中使用的类
本节列出了本章中示例中使用的类。
package org.spring.samples.spel.inventor;
import java.util.Date;
import java.util.GregorianCalendar;
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
public Inventor(String name, String nationality) {
GregorianCalendar c= new GregorianCalendar();
this.name = name;
this.nationality = nationality;
this.birthdate = c.getTime();
}
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
this.nationality = nationality;
this.birthdate = birthdate;
}
public Inventor() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public PlaceOfBirth getPlaceOfBirth() {
return placeOfBirth;
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
this.placeOfBirth = placeOfBirth;
}
public void setInventions(String[] inventions) {
this.inventions = inventions;
}
public String[] getInventions() {
return inventions;
}
}
class Inventor(
var name: String,
var nationality: String,
var inventions: Array<String>? = null,
var birthdate: Date = GregorianCalendar().time,
var placeOfBirth: PlaceOfBirth? = null)
package org.spring.samples.spel.inventor;
public class PlaceOfBirth {
private String city;
private String country;
public PlaceOfBirth(String city) {
this.city=city;
}
public PlaceOfBirth(String city, String country) {
this(city);
this.country = country;
}
public String getCity() {
return city;
}
public void setCity(String s) {
this.city = s;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
class PlaceOfBirth(var city: String, var country: String? = null) {
package org.spring.samples.spel.inventor;
import java.util.*;
public class Society {
private String name;
public static String Advisors = "advisors";
public static String President = "president";
private List<Inventor> members = new ArrayList<Inventor>();
private Map officers = new HashMap();
public List getMembers() {
return members;
}
public Map getOfficers() {
return officers;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isMember(String name) {
for (Inventor inventor : members) {
if (inventor.getName().equals(name)) {
return true;
}
}
return false;
}
}
package org.spring.samples.spel.inventor
import java.util.*
class Society {
val Advisors = "advisors"
val President = "president"
var name: String? = null
val members = ArrayList<Inventor>()
val officers = mapOf<Any, Any>()
fun isMember(name: String): Boolean {
for (inventor in members) {
if (inventor.name == name) {
return true
}
}
return false
}
}
5. 使用 Spring 进行面向方面编程
面向方面编程 (AOP) 通过以下方式补充面向对象编程 (OOP) 提供了另一种思考程序结构的方法。模块化的关键单元 在 OOP 中是类,而在 AOP 中,模块化单元是方面。方面 实现跨关注点(例如事务管理)的模块化 多个类型和对象。(此类担忧通常被称为“横切”担忧 在 AOP 文献中。
Spring 的关键组件之一是 AOP 框架。虽然 Spring IoC 容器不依赖于 AOP(这意味着如果您不想,则不需要使用 AOP to),AOP 补充了 Spring IoC,提供了一个非常强大的中间件解决方案。
AOP 在 Spring Framework 中用于:
-
提供声明式企业服务。最重要的此类服务是声明式事务 Management。
-
让用户实现自定义方面,以补充他们对 AOP 的 OOP 使用。
如果您只对通用的声明式服务或其他预打包的服务感兴趣 声明式中间件服务(如池)中,你不需要直接使用 Spring AOP,并且可以跳过本章的大部分内容。 |
5.1. AOP 概念
让我们从定义一些核心的 AOP 概念和术语开始。这些术语不是 特定于 Spring。遗憾的是,AOP 术语并不是特别直观。 但是,如果 Spring 使用自己的术语,那将更加令人困惑。
-
Aspect:跨多个类的关注点的模块化。 事务管理是企业 Java 中横切关注点的一个很好的例子 应用。在 Spring AOP 中,切面是使用常规类实现的 (基于模式的方法)或用
@Aspect
annotation (@AspectJ 样式)。 -
连接点:程序执行过程中的一个点,例如执行 方法或异常的处理。在 Spring AOP 中,连接点始终是 表示方法执行。
-
Advice:一个 aspect 在特定连接点采取的行动。不同类型的 建议包括 “around”、“before” 和 “after” 建议。(讨论了建议类型 稍后。许多 AOP 框架,包括 Spring,将通知建模为拦截器,并且 在 Join Point 周围维护一个拦截器链。
-
切入点:与连接点匹配的谓词。建议与 pointcut 表达式并在与该 pointcut 匹配的任何连接点(例如 执行具有特定名称的方法)。匹配连接点的概念 by pointcut 表达式是 AOP 的核心,而 Spring 使用 AspectJ pointcut 表达式语言。
-
简介:代表类型声明其他方法或字段。Spring AOP 允许您将新接口(和相应的实现)引入到任何 advised 对象。例如,你可以使用 introduction 使 bean 实现一个
IsModified
接口,以简化缓存。(引言称为 inter-type 声明。 -
Target object:由一个或多个方面通知的对象。也称为 “Advised Object” 的 Visd 对象。由于 Spring AOP 是使用运行时代理实现的,因此 object 始终是代理对象。
-
AOP proxy:由 AOP 框架创建的对象,用于实现 aspect 合约(通知、方法执行等)。在 Spring 框架中,AOP 代理 是 JDK 动态代理或 CGLIB 代理。
-
编织:将方面与其他应用程序类型或对象链接起来,创建一个 advised 对象。这可以在编译时完成(使用 AspectJ 编译器,用于 example)、load time 或 runtime 中。Spring AOP 与其他纯 Java AOP 框架一样, 在运行时执行 weaving。
Spring AOP 包括以下类型的建议:
-
Before advice:在连接点之前运行但没有 防止执行流继续到连接点的能力(除非它抛出 一个例外)。
-
After returning advice:在连接点完成后运行的通知 通常(例如,如果方法返回而不引发异常)。
-
抛出后通知:如果方法通过抛出 例外。
-
After (finally) advice:无论 连接点退出 (正常或异常返回)。
-
Around advice:围绕连接点的建议,例如方法调用。 这是最有力的建议。Around advice 可以执行自定义行为 方法调用之前和之后。它还负责选择是否 继续执行连接点,或者通过返回其 自己的返回值或引发异常。
围绕建议是最普遍的建议。从 Spring AOP 开始,就像 AspectJ 一样,
提供了全系列的建议类型,我们建议你使用最弱的
advice 类型。例如,如果您只需要
使用方法的返回值更新 Cache,则最好实现
返回 advice 后比 around 建议,虽然 around 建议可以完成
同样的事情。使用最具体的 Advice 类型提供更简单的编程模型
出错的可能性较小。例如,您不需要调用proceed()
方法上的JoinPoint
用于 around 建议,因此,您不能不调用它。
所有通知参数都是静态类型的,因此您可以使用
适当的类型(例如,方法执行的返回值的类型),而不是
比Object
阵 列。
由切入点匹配的连接点的概念是 AOP 的关键,它区分了 它来自仅提供拦截的旧技术。切入点使建议成为 独立于面向对象的层次结构进行定位。例如,您可以应用 around advice 为一组跨 多个对象(例如服务层中的所有业务作)。
5.2. Spring AOP 的功能和目标
Spring AOP 是用纯 Java 实现的。无需特殊编译 过程。Spring AOP 不需要控制类加载器层次结构,因此 适合在 Servlet 容器或应用程序服务器中使用。
Spring AOP 目前只支持方法执行连接点(通知执行 of 方法)。字段拦截未实现,尽管支持 可以在不破坏核心 Spring AOP API 的情况下添加字段拦截。如果您需要 要建议字段访问和更新连接点,请考虑使用 AspectJ 等语言。
Spring AOP 的 AOP 方法与大多数其他 AOP 框架不同。目标是 而不是提供最完整的 AOP 实现(尽管 Spring AOP 相当 有能力的)。相反,目标是在 AOP 实现和 Spring IoC,帮助解决企业应用程序中的常见问题。
因此,例如,Spring 框架的 AOP 功能通常用于 与 Spring IoC 容器结合使用。Aspect 是使用普通 bean 配置的 定义语法(尽管这允许强大的 “自动代理” 功能)。这是一个 与其他 AOP 实现的关键区别。你不能做一些事情 轻松或高效地使用 Spring AOP,例如 Advise 非常细粒度的对象(通常为 domain 对象)。在这种情况下,AspectJ 是最好的选择。但是,我们的 经验表明,Spring AOP 为大多数 适合 AOP 的 enterprise Java 应用程序。
Spring AOP 从不努力与 AspectJ 竞争以提供全面的 AOP 溶液。我们相信,基于代理的框架(如 Spring AOP)和成熟的 像 AspectJ 这样的框架很有价值,而且它们是互补的,而不是在 竞争。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以实现 AOP 的所有使用都在一个一致的基于 Spring 的应用程序中的应用 架构。此集成不会影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP 保持向后兼容。有关 Spring AOP API 的讨论,请参见下一章。
Spring Framework 的核心原则之一是非侵入性。这 是不应强制引入特定于框架的类的想法, 接口连接到您的业务或域模型。但是,在某些地方, Spring 框架 确实为您提供了将特定于 Spring Framework 的依赖项引入 代码库。为您提供此类选项的基本原理是因为,在某些情况下,它 可能只是更容易阅读或编写此类 一种方法。但是,Spring Framework(几乎)始终为您提供选择:您有 自由地决定哪种选项最适合您的特定用途 案例或场景。 与本章相关的一个选择是 AOP 框架(以及 which AOP style) 进行选择。您可以选择 AspectJ 和/或 Spring AOP。你 还可以选择 @AspectJ 注解样式方法或 Spring XML configuration-style 方法。本章选择介绍 @AspectJ 风格 First 的方法不应被视为 Spring 团队 更喜欢 @AspectJ Comments 样式的方法,而不是 Spring XML 配置样式。 有关“为什么和为什么”的更完整讨论,请参见选择要使用的 AOP 声明样式 每种样式。 |
5.3. AOP 代理
Spring AOP 默认使用标准 JDK 动态代理作为 AOP 代理。这 允许代理任何接口(或一组接口)。
Spring AOP 也可以使用 CGLIB 代理。这对于代理类而不是 接口。默认情况下,如果业务对象未实现 接口。由于对接口而不是类进行编程是一种很好的做法,因此业务 类通常实现一个或多个业务接口。在那些(希望很少见的)情况下,您可以强制使用 CGLIB 需要通知未在接口上声明的方法,或者需要通知 将代理对象作为具体类型传递给方法。
掌握 Spring AOP 是基于代理的事实是很重要的。请参阅 Understanding AOP Proxies 以彻底检查它的具体内容 implementation detail 实际上是 Implementation。
5.4. @AspectJ 支持
@AspectJ 指的是一种将方面声明为常规 Java 类的样式,这些类带有 附注。@AspectJ 样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。Spring 使用 AspectJ 提供的库解释与 AspectJ 5 相同的注解 用于切入点解析和匹配。不过,AOP 运行时仍然是纯 Spring AOP,并且 不依赖于 AspectJ 编译器或 weaver。
使用 AspectJ 编译器和 weaver 可以使用完整的 AspectJ 语言和 在 Using AspectJ with Spring Applications中进行了讨论。 |
5.4.1. 启用 @AspectJ 支持
要在 Spring 配置中使用 @AspectJ 方面,您需要启用 Spring 对 基于 @AspectJ 方面配置 Spring AOP 并基于 无论他们是否受到这些方面的建议。自动代理是指,如果 Spring 确定 bean 由一个或多个 aspect 通知,则它会自动生成 该 bean 的代理,用于拦截方法调用并确保 Advice 运行 根据需要。
可以通过 XML 或 Java 样式的配置来启用 @AspectJ 支持。在任一
case 中,您还需要确保 AspectJ 的aspectjweaver.jar
library 位于
classpath 的 classpath (版本 1.8 或更高版本)。此库位于lib
目录中的 Alpha 发行版的 Alpha 发行版中,或者从 Maven Central 存储库中。
使用 Java 配置启用 @AspectJ 支持
使用 Java 启用 @AspectJ 支持@Configuration
,请添加@EnableAspectJAutoProxy
annotation 中,如下例所示:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
@Configuration
@EnableAspectJAutoProxy
class AppConfig
使用 XML 配置启用 @AspectJ 支持
要使用基于 XML 的配置启用 @AspectJ 支持,请使用aop:aspectj-autoproxy
元素,如下例所示:
<aop:aspectj-autoproxy/>
这假定您使用架构支持,如 基于 XML 架构的配置中所述。
请参阅 AOP 架构以了解如何
将aop
Namespace。
5.4.2. 声明一个 Aspect
启用 @AspectJ 支持后,在应用程序上下文中定义的任何 bean 都会显示
类@AspectJ(具有@Aspect
annotation) 会自动
由 Spring 检测到,并用于配置 Spring AOP。接下来的两个示例显示了
不是很有用的 aspect 所需的最小定义。
两个示例中的第一个示例显示了应用程序中的常规 bean 定义
context 指向具有@Aspect
注解:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>
两个示例中的第二个示例显示了NotVeryUsefulAspect
类定义、
它用org.aspectj.lang.annotation.Aspect
注解;
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
package org.xyz
import org.aspectj.lang.annotation.Aspect;
@Aspect
class NotVeryUsefulAspect
Aspects(用@Aspect
) 可以具有方法和字段,与任何
其他类。它们还可以包含切入点、建议和引言(类型间)
声明。
通过组件扫描自动检测各个方面 你可以在 Spring XML 配置中将 aspect 类注册为常规 bean,或者
通过 Classpath 扫描自动检测它们——与任何其他 Spring 管理的 bean 相同。
但是,请注意,@Aspect annotation 不足以进行
类路径。为此,您需要添加单独的@Component 注解
(或者,根据
Spring 的组件扫描仪)。 |
用其他方面提供建议? 在 Spring AOP 中,方面本身不能成为 advice 的目标
从其他方面来看。这@Aspect 注解将其标记为一个 Aspect,并且
因此,将其排除在自动代理之外。 |
5.4.3. 声明切入点
切入点确定感兴趣的连接点,从而使我们能够控制
当 Advice 运行时。Spring AOP 仅支持 Spring 的方法执行连接点
beans,因此你可以将切入点视为与 Spring 上方法的执行相匹配
豆。切入点声明有两个部分:由 name 和 any 组成的签名
参数和精确确定方法的切入点表达式
我们感兴趣的执行。在 AOP 的 @AspectJ 注解样式中,一个切入点
signature 由常规方法定义提供,切入点表达式为
通过使用@Pointcut
注释(用作切入点签名的方法
必须具有void
return 类型)。
一个示例可能有助于区分切入点签名和切入点
表达式 clear。以下示例定义了一个名为anyOldTransfer
那
匹配任何名为transfer
:
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
形成@Pointcut
annotation 是常规的
AspectJ 5 切入点表达式。有关 AspectJ 的切入点语言的完整讨论,请参阅
AspectJ
编程指南(对于扩展,还有 AspectJ 5
Developer's Notebook)或关于 AspectJ 的书籍之一(例如 Colyer 编写的 Eclipse AspectJ
等。al.,或 AspectJ in Action,Ramnivas Laddad 著)。
支持的切入点标号
Spring AOP 支持以下 AspectJ 切入点指示符 (PCD) 用于切入点 表达 式:
-
execution
:用于匹配方法执行连接点。这是主要的 使用 Spring AOP 时使用的切入点指示符。 -
within
:将匹配限制为某些类型中的连接点(执行 在使用 Spring AOP 时在匹配类型中声明的方法)。 -
this
:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中 bean 引用(Spring AOP 代理)是给定类型的实例。 -
target
:限制与连接点的匹配(使用 Spring AOP),其中目标对象(被代理的应用程序对象)是一个实例 的 -
args
:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中参数是给定类型的实例。 -
@target
:限制与连接点的匹配(使用 Spring AOP),其中执行对象的类具有给定类型的 Comments。 -
@args
:限制与连接点的匹配(使用 Spring 时方法的执行 AOP),其中传递的实际参数的运行时类型具有 给定类型。 -
@within
:限制匹配到具有给定 annotation(执行在具有给定注解的类型中声明的方法,当 使用 Spring AOP)。 -
@annotation
:将匹配限制为连接点的主题 (在 Spring AOP 中运行的方法)具有给定的注解。
由于 Spring AOP 将匹配限制为仅方法执行连接点,因此前面的讨论
的切入点指示符给出的定义比您在
AspectJ 编程指南。此外,AspectJ 本身具有基于类型的语义,并且在
执行连接点,则this
和target
引用同一对象:
对象执行该方法。Spring AOP 是一个基于代理的系统,它与众不同
在代理对象本身(绑定到this
) 和
proxy(绑定到target
).
由于 Spring 的 AOP 框架基于代理的性质,目标对象内的调用 根据定义,不会被拦截。对于 JDK 代理,只有公共接口方法 可以拦截对代理的调用。使用 CGLIB 时,对 public 和 protected 方法调用 代理被拦截(如有必要,甚至是包可见的方法)。然而 通过代理的常见交互应始终通过公共签名进行设计。 请注意,切入点定义通常与任何截获的方法匹配。 如果切入点严格来说是公开的,即使在 CGLIB 代理场景中 通过代理进行潜在的非公开交互,则需要相应地定义。 如果您的拦截需要包括目标中的方法调用甚至构造函数 类中,请考虑使用 Spring 驱动的原生 AspectJ 编织 Spring 的基于代理的 AOP 框架。这构成了 AOP 使用的不同模式 具有不同的特性,所以一定要让自己熟悉编织 在做出决定之前。 |
Spring AOP 还支持一个名为bean
.此 PCD 允许您限制
连接点与特定命名 Spring bean 或一组命名
Spring bean(使用通配符时)。这bean
PCD 具有以下形式:
bean(idOrNameOfBean)
bean(idOrNameOfBean)
这idOrNameOfBean
token 可以是任何 Spring bean 的名称。有限通配符
提供了使用该字符的支持,因此,如果您建立一些命名
约定,您可以编写一个*
bean
PCD 表达
以选择它们。与其他切入点指示符一样,bean
PCD 罐
与 (and) 一起使用,&&
||
(或)和!
(否定)运算符。
这 这 |
组合切入点表达式
您可以使用&&,
||
和!
.您还可以参考
按名称切入表达式。下面的示例显示了三个切入点表达式:
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} (1)
@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} (2)
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} (3)
1 | anyPublicOperation 如果 Method Execution 连接点表示执行,则匹配
任何公共方法。 |
2 | inTrading 如果方法执行在 Trading 模块中,则匹配。 |
3 | tradingOperation 如果方法执行表示
trading 模块。 |
@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 如果 Method Execution 连接点表示执行,则匹配
任何公共方法。 |
2 | inTrading 如果方法执行在 Trading 模块中,则匹配。 |
3 | tradingOperation 如果方法执行表示
trading 模块。 |
最佳实践是从较小的命名 组件,如前所述。当按名称引用切入点时,正常的 Java 可见性 规则适用(您可以看到相同类型的私有切入点,受保护的切入点位于 层次结构、任意位置的公共切入点等)。可见性不会影响切入点 匹配。
共享公共切入点定义
在使用企业应用程序时,开发人员通常希望引用
应用程序和特定作集从几个方面进行。我们
建议定义CommonPointcuts
捕获常见切入点表达式的 aspect
为此目的。此类方面通常类似于以下示例:
package com.xyz.myapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class CommonPointcuts {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.myapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.myapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.myapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
* the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
package com.xyz.myapp
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
@Aspect
class CommonPointcuts {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.myapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.web..*)")
fun inWebLayer() {
}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.myapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.service..*)")
fun inServiceLayer() {
}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.myapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.myapp.dao..*)")
fun inDataAccessLayer() {
}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
* the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
fun businessService() {
}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
fun dataAccessOperation() {
}
}
你可以在任何需要 切入点表达式。例如,要使服务层具有事务性,您可以 写下以下内容:
<aop:config>
<aop:advisor
pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
这<aop:config>
和<aop:advisor>
元素在基于 Schema 的 AOP 支持中进行了讨论。这
事务 Management 中讨论了事务元素。
例子
Spring AOP 用户可能会使用execution
切入点指示符。
执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
除返回类型模式 (ret-type-pattern
在前面的代码段中),
Name pattern 和 parameters pattern 是可选的。返回的类型模式确定
方法的返回类型必须是什么才能匹配 Join Point。 最常用作返回类型模式。它与任何 return 匹配
类型。仅当方法返回给定的
类型。名称模式与方法名称匹配。您可以将通配符用作 all 或
名称模式的一部分。如果指定声明类型 pattern,
包括尾随*
*
.
将其连接到 Name pattern 组件。
parameters 模式稍微复杂一些:匹配
方法,而()
(..)
匹配任意数量的 (零个或多个) 参数。
该模式与采用任意类型一个参数的方法匹配。(*)
(*,String)
匹配采用两个参数的方法。第一个可以是任何类型,而
second 必须是String
.查阅语言
Semantics 部分。
以下示例显示了一些常见的切入点表达式:
-
任何公共方法的执行:
execution(public * *(..))
-
执行名称以
set
:execution(* set*(..))
-
执行由
AccountService
接口:execution(* com.xyz.service.AccountService.*(..))
-
执行
service
包:execution(* com.xyz.service.*.*(..))
-
执行服务包或其子包之一中定义的任何方法:
execution(* com.xyz.service..*.*(..))
-
服务包中的任何连接点(仅在 Spring AOP 中执行方法):
within(com.xyz.service.*)
-
服务包中的任何连接点(仅在 Spring AOP 中执行方法)或其 子包:
within(com.xyz.service..*)
-
代理实现
AccountService
接口:this(com.xyz.service.AccountService)
'this' 更常用于 binding 形式。有关如何在通知正文中使 proxy 对象可用的信息,请参见 Declaring Advice 部分。 -
目标对象 实现
AccountService
接口:target(com.xyz.service.AccountService)
'target' 更常用于 binding 形式。参见 Declaring Advice 部分 了解如何使目标对象在通知正文中可用。 -
任何采用单个参数的连接点(仅在 Spring AOP 中执行方法) 其中,在运行时传递的参数为
Serializable
:args(java.io.Serializable)
'args' 更常以绑定形式使用。参见 Declaring Advice 部分 了解如何使方法参数在 Advice Body 中可用。 请注意,此示例中给出的切入点与
execution(* *(java.io.Serializable))
.如果在运行时传递的参数为Serializable
,并且如果方法签名声明了单个 type 为Serializable
. -
目标对象具有
@Transactional
注解:@target(org.springframework.transaction.annotation.Transactional)
您也可以在装订形式中使用 '@target' 。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中 target 对象具有
@Transactional
注解:@within(org.springframework.transaction.annotation.Transactional)
您还可以在装订形式中使用 '@within'。参见 Declaring Advice 部分 如何使 Annotation 对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法),其中执行方法具有
@Transactional
注解:@annotation(org.springframework.transaction.annotation.Transactional)
您还可以在装订形式中使用 '@annotation'。参见 Declaring Advice 部分 了解如何使 Annotation 对象在通知正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法)采用单个参数 其中,传递的参数的运行时类型具有
@Classified
注解:@args(com.xyz.security.Classified)
您还可以在绑定形式中使用 '@args'。参见 Declaring Advice 部分 如何使 Annotation 对象在 Advice Body 中可用。 -
名为
tradeService
:bean(tradeService)
-
Spring bean 上任何名称为 匹配通配符表达式
*Service
:bean(*Service)
编写好的切入点
在编译期间,AspectJ 处理切入点以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态地)给定的切入点是一个昂贵的过程。(动态匹配是指匹配 无法从静态分析中完全确定,并且测试放置在代码中以 确定代码运行时是否存在实际匹配项)。首次遇到 pointcut 声明时,AspectJ 会将其重写为匹配的最佳形式 过程。这是什么意思?基本上,切入点是用 DNF 重写的(析取 Normal Form) 和切入点的组件进行排序,以便这些组件 首先检查评估成本较低的 URL。这意味着您不必担心 关于了解各种切入点指示符的性能并可能提供它们 在切入点声明中以任何顺序。
然而,AspectJ 只能与它被告知的内容一起工作。为了获得最佳性能 匹配,您应该考虑他们想要实现的目标并缩小搜索范围 space for 在定义中尽可能匹配。现有标号 自然属于以下三组之一:kinded、scope 和 contextual:
-
Kinded 标号选择特定类型的连接点:
execution
,get
,set
,call
和handler
. -
范围界定号选择一组感兴趣的连接点 (可能有很多种):
within
和withincode
-
上下文指示符根据上下文进行匹配(并选择性地绑定):
this
,target
和@annotation
一个写得好的切入点应该至少包括前两种类型(kinded 和 范围界定)。您可以包含上下文指示符以根据 join point context 或 bind 该上下文以在通知中使用。仅提供 kinded 指示符或仅上下文指示符有效,但可能会影响编织 性能(使用的时间和内存),由于额外的处理和分析。范围 标号的匹配速度非常快,使用它们意味着 AspectJ 可以非常快速地 关闭不应进一步处理的联接点组。一个好的 如果可能,切入点应始终包含一个。
5.4.4. 声明通知
Advice 与切入点表达式相关联,并在之前、之后或周围运行 与切入点匹配的方法执行。切入点表达式可以是 对命名切入点或就地声明的切入点表达式的简单引用。
建议前
你可以使用@Before
注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
如果我们使用就地切入点表达式,我们可以将前面的示例重写为 以下示例:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
fun doAccessCheck() {
// ...
}
}
退货后通知
返回后,通知将在匹配的方法执行正常返回时运行。
您可以使用@AfterReturning
注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
您可以有多个 advice 声明(以及其他成员), 都在同一个方面。在这些 examples 来集中每个 API 的效果。 |
有时,您需要在通知正文中访问返回的实际值。
您可以使用@AfterReturning
,该 API 绑定返回值以获取
access,如下例所示:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning(
pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning = "retVal")
fun doAccessCheck(retVal: Any) {
// ...
}
}
在returning
attribute 必须与参数的名称相对应
在 Advice 方法中。当方法执行返回时,返回值将传递给
将 Advice 方法作为相应的参数值。一个returning
子句
将匹配限制为仅返回
指定类型(在本例中为Object
,它与任何返回值匹配)。
请注意,在以下情况下,无法返回完全不同的引用 使用后返回建议。
抛出后的建议
抛出后,当匹配的方法执行退出时,通过抛出一个
例外。您可以使用@AfterThrowing
注解,作为
以下示例显示:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doRecoveryActions() {
// ...
}
}
通常,您希望通知仅在引发给定类型的异常时运行。
而且你还经常需要访问 Advice Body 中引发的异常。您可以
使用throwing
属性来限制匹配(如果需要 — 使用Throwable
作为异常类型),并将引发的异常绑定到 Advice 参数。
以下示例显示了如何执行此作:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing
@Aspect
class AfterThrowingExample {
@AfterThrowing(
pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
throwing = "ex")
fun doRecoveryActions(ex: DataAccessException) {
// ...
}
}
在throwing
attribute 必须与
建议方法。当方法执行通过引发异常退出时,异常
作为相应的参数值传递给通知方法。一个throwing
第
还将匹配限制为仅那些抛出
指定类型 (DataAccessException
,在本例中)。
请注意, |
之后(最后)建议
After (finally) 通知在匹配的方法执行退出时运行。它由
使用@After
注解。建议后必须准备好处理正常和
异常返回条件。它通常用于释放资源和类似的
目的。以下示例演示如何使用 after finally 建议:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After
@Aspect
class AfterFinallyExample {
@After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doReleaseLock() {
// ...
}
}
请注意, |
周边建议
最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行(例如,启动和停止计时器)。 始终使用满足您要求的最弱的建议形式(即 如果 Before advice 可以,请不要使用 around advice)。
Around 建议是使用@Around
注解。第一个参数
advice method 的类型必须为ProceedingJoinPoint
.在建议的正文中,
叫proceed()
在ProceedingJoinPoint
导致底层方法运行。
这proceed
method 也可以传入Object[]
.使用数组中的值
作为方法继续执行时的参数。
的行为proceed 当使用Object[] 与
的行为proceed for around advice 由 AspectJ 编译器编译。对于周围
advice,传递给proceed 必须与传递给 around 通知的参数数量匹配(而不是数量
的参数),并将传递给 continue 的值以
given argument position 替换实体联接点处的原始值
该值已绑定到 (如果现在没有意义,请不要担心)。方法
taken by Spring 更简单,并且更匹配其基于代理的、仅执行的
语义学。只有在编译 @AspectJ 时,您才需要注意这种差异
为 Spring 编写的方面和使用proceed with arguments 替换为 AspectJ 编译器
和 weaver 一起。有一种方法可以编写这样的方面,它在两者之间 100% 兼容
Spring AOP 和 AspectJ,这将在下面关于通知参数的部分中讨论。 |
下面的示例展示了如何使用 around 建议:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint
@Aspect
class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return retVal
}
}
around 通知返回的值是
方法。例如,如果
有一个并且 invokeproceed()
如果不是。请注意,proceed
可以调用一次,
很多时候,或者根本不在周围的建议体内。所有这些都是合法的。
Advice 参数
Spring 提供了完全类型化的通知,这意味着您可以在
advice 签名(正如我们之前看到的 return 和 throw 示例)而不是
使用Object[]
数组。我们了解如何进行论证和其他上下文
值可用于本节后面的通知正文。首先,我们来看看如何
编写 generic advice,可以了解该 Advice 当前建议的方法。
访问当前JoinPoint
任何通知方法都可以将 type 为org.aspectj.lang.JoinPoint
(请注意,需要 Around Advice 才能声明第一个
type 为ProceedingJoinPoint
,它是JoinPoint
.
这JoinPoint
interface 提供了许多有用的方法:
-
getArgs()
:返回方法参数。 -
getThis()
:返回 proxy 对象。 -
getTarget()
:返回目标对象。 -
getSignature()
:返回所建议的方法的描述。 -
toString()
:打印所建议方法的有用描述。
有关更多详细信息,请参阅 javadoc。
将参数传递给 Advice
我们已经看到了如何绑定返回值或异常值(使用 after
返回和抛出建议后)。使参数值可用于通知
body 中,你可以使用args
.如果使用参数名称代替
type name 时,相应参数的值将作为
调用通知时的 parameter 值。一个例子应该更清楚地说明这一点。
假设您要建议执行采用Account
object 作为第一个参数,您需要在 Advice Body 中访问该账户。
您可以编写以下内容:
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
fun validateAccount(account: Account) {
// ...
}
这args(account,..)
切入点表达式的一部分有两个用途。首先,它
将匹配限制为仅该方法至少采用一个
parameter 的 Alpha 参数,并且传递给该参数的参数是Account
.
其次,它使实际的Account
对象可通过account
参数。
另一种写法是声明一个切入点,它 “提供”Account
object 值,然后引用命名的切入点
来自建议。这将如下所示:
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}
@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
// ...
}
有关更多信息,请参阅 AspectJ 编程指南 详。
代理对象 (this
)、目标对象 (target
) 和注释 (@within
,@target
,@annotation
和@args
) 都可以以类似的方式绑定。接下来的两个
示例展示了如何匹配带有@Auditable
注释并提取审计代码:
两个示例中的第一个显示了@Auditable
注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)
两个示例中的第二个示例显示了与@Auditable
方法:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
fun audit(auditable: Auditable) {
val code = auditable.value()
// ...
}
通知参数和泛型
Spring AOP 可以处理类声明和方法参数中使用的泛型。假设 您有一个泛型类型,如下所示:
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
interface Sample<T> {
fun sampleGenericMethod(param: T)
fun sampleGenericCollectionMethod(param: Collection<T>)
}
您可以通过以下方式将方法类型的拦截限制为某些参数类型 将 advice 参数键入要拦截方法的 parameter type:
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
// Advice implementation
}
此方法不适用于泛型集合。所以你不能定义 切入如下:
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
// Advice implementation
}
要做到这一点,我们必须检查集合的每个元素,而
合理,因为我们也无法决定如何对待null
值。为了实现
与此类似,您必须键入参数Collection<?>
和手动
检查元素的类型。
确定参数名称
通知调用中的参数绑定依赖于切入点中使用的匹配名称 Advice 和 PointCut 方法签名中声明的参数名称的表达式。 参数名称不能通过 Java 反射获得,因此 Spring AOP 使用 以下策略来确定参数名称:
-
如果用户已显式指定参数名称,则指定的 参数名称。建议和切入点注释都有 可选的
argNames
属性,可用于指定 带注释的方法。这些参数名称在运行时可用。以下示例 演示如何使用argNames
属性:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code and bean
}
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code and bean
}
如果第一个参数是JoinPoint
,ProceedingJoinPoint
或JoinPoint.StaticPart
type 中,您可以从值
的argNames
属性。例如,如果您将前面的通知修改为接收
连接点对象、argNames
attribute 不需要包含它:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code, bean, and jp
}
对JoinPoint
,ProceedingJoinPoint
和JoinPoint.StaticPart
types 特别方便
不收集任何其他 Join Point Context 的 Advice 实例。在这种情况下,您可以
省略argNames
属性。例如,以下建议不需要声明
这argNames
属性:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
fun audit(jp: JoinPoint) {
// ... use jp
}
-
使用
'argNames'
属性有点笨拙,所以如果'argNames'
属性 未指定,则 Spring AOP 会查看 类,并尝试从局部变量表中确定参数名称。这 只要类已使用 debug 编译,信息就存在 信息 ('-g:vars'
至少)。使用此标志进行编译的后果 上是:(1) 你的代码稍微更容易理解(逆向工程),(2) 类文件大小略大(通常无关紧要),(3) 编译器不会应用用于删除未使用的局部变量的优化。在 换句话说,使用此标志进行构建应该不会遇到任何困难。如果 @AspectJ 方面已经由 AspectJ 编译器 (ajc) 编译,即使没有 debug 信息,则无需添加 argNames
属性作为编译器 保留所需的信息。 -
如果代码在编译时没有必要的调试信息,则 Spring AOP 尝试推断绑定变量与参数的配对(例如,如果 切入点表达式中只绑定了一个变量,而 Advice 方法 只接受一个参数,配对是显而易见的)。如果变量的绑定是 ambiguous 给定可用信息,则
AmbiguousBindingException
是 扔。 -
如果上述策略都失败了,则
IllegalArgumentException
被抛出。
继续参数
我们之前说过,我们将描述如何编写proceed
调用
在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是
以确保 Advice 签名按顺序绑定每个 Method 参数。
以下示例显示了如何执行此作:
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
accountHolderNamePattern: String): Any {
val newPattern = preProcess(accountHolderNamePattern)
return pjp.proceed(arrayOf<Any>(newPattern))
}
在许多情况下,您仍然会执行此绑定(如前面的示例所示)。
建议订购
当多个建议都想在同一个连接点运行时,会发生什么情况? Spring AOP 遵循与 AspectJ 相同的优先规则来确定通知的顺序 执行。最高优先级的建议首先运行“在途中”(因此,给定两个部分 of before 建议,则优先级最高的那个先运行)。“On the way out” 从 join point 时,最高优先级 advice 将运行在 last 之后(因此,给定两个 after advice 的 Advice 中,优先级最高的 ID 将排在第二位)。
当在不同方面定义的两条通知都需要在同一条下运行时
join point 的执行顺序,除非你另有指定,否则执行顺序是 undefined。您可以
通过指定 Precedence 来控制执行顺序。这是在正常情况下完成的
Spring 方式,方法是实现org.springframework.core.Ordered
接口输入
aspect 类或使用@Order
注解。给定两个方面,
aspect 返回Ordered.getOrder()
(或 annotation 值)具有
优先级更高。
特定方面的每种不同的建议类型在概念上都是要适用的
直接连接到连接点。因此, 从 Spring Framework 5.2.7 开始,在相同的 当两条相同类型的建议(例如,两个 |
5.4.5. 简介
介绍(在 AspectJ 中称为类型间声明)使一个 aspect 能够声明 ,建议对象实现给定的接口,并且要提供 该接口代表这些对象。
您可以使用@DeclareParents
注解。此批注
用于声明匹配类型具有新的父级(因此得名)。例如
给定一个名为UsageTracked
以及名为DefaultUsageTracked
,以下方面声明 service 的所有 implementationrs
接口还实现了UsageTracked
接口(例如,通过 JMX 进行统计):
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
public static UsageTracked mixin;
@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
@Aspect
class UsageTracking {
companion object {
@DeclareParents(value = "com.xzy.myapp.service.*+", defaultImpl = DefaultUsageTracked::class)
lateinit var mixin: UsageTracked
}
@Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
}
要实现的接口由带注释的字段的类型决定。这value
属性的@DeclareParents
annotation 是一种 AspectJ 类型的模式。任何
bean 的 bean 实现UsageTracked
接口。请注意,在
在前面的示例的 advice 之前,服务 bean 可以直接用作
的UsageTracked
接口。如果以编程方式访问 Bean,则
您将编写以下内容:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
5.4.6. Aspect 实例化模型
这是一个高级主题。如果您刚开始使用 AOP,则可以安全地跳过 它直到以后。 |
默认情况下,应用程序中的每个方面都有一个实例
上下文。AspectJ 称其为单例实例化模型。可以定义
具有替代生命周期的 aspects。Spring 支持 AspectJ 的perthis
和pertarget
实例化模型;percflow
,percflowbelow
和pertypewithin
目前没有
支持。
您可以声明perthis
aspect 通过指定perthis
子句中的@Aspect
注解。请考虑以下示例:
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {
private int someState;
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
public void recordServiceUsage() {
// ...
}
}
@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
class MyAspect {
private val someState: Int = 0
@Before("com.xyz.myapp.CommonPointcuts.businessService()")
fun recordServiceUsage() {
// ...
}
}
在前面的示例中,perthis
子句是 1 个 aspect 实例
为执行业务服务的每个唯一服务对象创建(每个唯一的
对象绑定到this
在与切入点表达式匹配的连接点处)。坡向
实例是在第一次对 Service 对象调用方法时创建的。这
当 Service 对象超出范围时,aspect 超出范围。aspect 之前
实例,则其中的任何 Advice 都不会运行。一旦 aspect 实例
已创建,则其中声明的通知将在匹配的连接点运行,但只有
当 Service 对象是与此 aspect 关联的对象时。请参阅 AspectJ
编程指南,了解更多信息per
第。
这pertarget
实例化模型的工作方式与perthis
,但它
在匹配的连接点处为每个唯一的目标对象创建一个 aspect 实例。
5.4.7. AOP 示例
现在您已经了解了所有组成部分的工作原理,我们可以将它们放在一起进行 一些有用的东西。
业务服务的执行有时会由于并发问题(对于
例如,一个死锁失败者)。如果重试作,则可能会成功
下次尝试时。对于适合在此类
条件(无需因冲突而返回给用户的幂等作
resolution)中,我们希望透明地重试该作,以避免客户端看到PessimisticLockingFailureException
.这是一个明确贯穿的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想要重试作,所以我们需要使用 around advice,以便我们可以
叫proceed
多次。下面的清单显示了基本的 aspect 实现:
@Aspect
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
@Aspect
class ConcurrentOperationExecutor : Ordered {
private val DEFAULT_MAX_RETRIES = 2
private var maxRetries = DEFAULT_MAX_RETRIES
private var order = 1
fun setMaxRetries(maxRetries: Int) {
this.maxRetries = maxRetries
}
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
numAttempts++
try {
return pjp.proceed()
} catch (ex: PessimisticLockingFailureException) {
lockFailureException = ex
}
} while (numAttempts <= this.maxRetries)
throw lockFailureException
}
}
请注意,该 aspect 实现了Ordered
接口,以便我们可以设置
方面高于 Transaction Advice(我们希望每次
retry)。这maxRetries
和order
properties 都是由 Spring 配置的。这
main作发生在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>
为了优化 aspect 以便它仅重试幂等作,我们可以定义以下内容Idempotent
注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation
然后,我们可以使用 annotation 来注释服务作的实现。变化
到仅重试幂等作涉及优化切入点
表达式,以便仅@Idempotent
作匹配,如下所示:
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
// ...
}
5.5. 基于 Schema 的 AOP 支持
如果您更喜欢基于 XML 的格式,Spring 还提供了对定义方面的支持
使用aop
namespace 标签。完全相同的切入点表达式和通知类型
与使用 @AspectJ 样式时一样。因此,在本节中,我们重点关注
该语法,并让读者参考上一节中的讨论
(@AspectJ支持)用于理解编写切入点表达式和绑定
的建议参数。
要使用本节中描述的 aop 命名空间标签,您需要导入spring-aop
schema,如 XML 基于 Schema-based configuration 中所述。请参阅 AOP 架构,了解如何在aop
Namespace。
在你的 Spring 配置中,所有 aspect 和 advisor 元素都必须放在
一<aop:config>
元素(您可以有多个<aop:config>
元素中
应用程序上下文配置)。一<aop:config>
元素可以包含 pointcut、
advisor 和 aspect 元素(请注意,这些元素必须按此顺序声明)。
这<aop:config> 样式大量使用了 Spring 的自动代理机制。这可能会导致问题(例如建议
not being woven)如果您已经通过使用BeanNameAutoProxyCreator 或类似的东西。建议的使用模式是
仅使用<aop:config> 样式或仅AutoProxyCreator style 和
切勿混合使用它们。 |
5.5.1. 声明一个 Aspect
当您使用 schema 支持时,aspect 是定义为 您的 Spring 应用程序上下文。状态和行为在字段中捕获,并且 方法,以及 XML 中的切入点和通知信息。
您可以使用<aop:aspect>
元素,并引用支持 Bean
通过使用ref
属性,如下例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支持 aspect (aBean
在这种情况下)当然可以配置,并且
依赖项注入,就像任何其他 Spring bean 一样。
5.5.2. 声明切入点
您可以在<aop:config>
元素,让切入点
定义在多个方面和顾问之间共享。
表示服务层中任何业务服务的执行的切入点可以 定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
请注意,切入点表达式本身使用相同的 AspectJ 切入点表达式 语言,如 @AspectJ 支持中所述。如果使用基于架构的声明 样式中,@Aspects您可以在 切入点表达式。定义上述切入点的另一种方法如下:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.CommonPointcuts.businessService()"/>
</aop:config>
假设您有一个CommonPointcuts
aspect 中的定义,如共享公共切入点定义中所述。
那么在一个 aspect 中声明一个切入点与声明一个顶级切入点非常相似。 如下例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
与 @AspectJ 方面大致相同,使用基于 schema 的 schema 声明的切入点
定义样式可以收集连接点上下文。例如,以下切入点
收集this
object 作为连接点上下文,并将其传递给通知:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
必须声明通知以接收收集的加入点上下文,方法是包含 参数,如下所示:
public void monitor(Object service) {
// ...
}
fun monitor(service: Any) {
// ...
}
组合切入点子表达式时,&&
在 XML 中很尴尬
文档,因此您可以使用and
,or
和not
关键字代替&&
,||
和!
分别。例如,前面的切入点可以更好地写成
遵循:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
请注意,以这种方式定义的切入点由其 XML 引用id
,并且不能是
用作命名切入点以形成复合切入点。命名切入点支持
因此,基于架构的定义样式比 @AspectJ 提供的定义样式更受限制
风格。
5.5.3. 声明 Advice
基于 schema 的 AOP 支持使用与 @AspectJ 样式相同的五种通知,并且它们具有 完全相同的语义。
建议前
Before 通知在匹配的方法执行之前运行。它是在<aop:aspect>
通过使用<aop:before>
元素,如下例所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
这里dataAccessOperation
是id
在顶部定义的切入点 (<aop:config>
)
水平。要定义内联切入点,请将pointcut-ref
属性替换为
一个pointcut
属性,如下所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
正如我们在讨论 @AspectJ 样式时所指出的,使用命名切入点可以 显著提高代码的可读性。
这method
属性标识方法 (doAccessCheck
),它提供
建议。必须为 aspect 元素引用的 bean 定义此方法
其中包含建议。在执行数据访问作之前(方法执行
连接点匹配),则doAccessCheck
aspect 上的 method
bean 被调用。
退货后通知
返回后,通知在匹配的方法执行正常完成时运行。是的
在<aop:aspect>
和之前的建议一样。以下示例
演示如何声明它:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
与 @AspectJ 样式一样,您可以在 Advice Body 中获取返回值。
为此,请使用returning
属性来指定
应传递 return 值,如下例所示:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
这doAccessCheck
method 必须声明一个名为retVal
.此 type of this
parameter 约束匹配,其方式与@AfterReturning
.为
example,您可以按如下方式声明 Method Signature:
public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...
抛出后的建议
抛出后,当匹配的方法执行退出时,通过抛出一个
例外。它是在<aop:aspect>
通过使用after-throwing
元素
如下例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
与 @AspectJ 样式一样,您可以在通知正文中获取 thrown 异常。
为此,请使用throwing
attribute 来指定参数的名称
应传递异常,如下例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
这doRecoveryActions
method 必须声明一个名为dataAccessEx
.
此参数的类型以与@AfterThrowing
.例如,方法签名可以按如下方式声明:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
之后(最后)建议
After (finally) 通知运行,无论匹配的方法执行如何退出。
您可以使用after
元素,如下例所示:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是围绕建议。Around 建议 “around” 匹配的方法 执行。它有机会在方法运行之前和之后执行工作 并确定该方法何时、如何甚至是否真正开始运行。 Around 通知通常用于在 线程安全的方式(例如,启动和停止计时器)。始终使用最少的 满足您要求的强大建议形式。如果出现以下情况,请勿在建议周围使用 之前 建议 可以 完成 这项工作。
你可以使用aop:around
元素。的第一个参数
通知方法的类型必须为ProceedingJoinPoint
.在建议的正文中,
叫proceed()
在ProceedingJoinPoint
导致底层方法运行。
这proceed
方法也可以使用Object[]
.数组中的值
在方法执行过程中用作方法执行的参数。
参见 Around Advice 了解打电话的注意事项proceed
替换为Object[]
.
下面的示例展示了如何在 XML 中声明 around advice:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
的doBasicProfiling
advice 可以与
@AspectJ示例(当然不包括 Annotation),如下例所示:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return pjp.proceed()
}
Advice 参数
基于 schema 的声明样式支持完全类型化的通知,其方式与
针对 @AspectJ 支持进行描述 — 通过按名称将切入点参数与
通知方法参数。有关详细信息,请参阅 Advice Parameters 。如果您愿意
要显式指定通知方法的参数名称(不依赖于
检测策略),您可以使用arg-names
属性,其处理方式与argNames
属性(如确定参数名称中所述)。
以下示例演示如何在 XML 中指定参数名称:
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
这arg-names
attribute 接受以逗号分隔的参数名称列表。
以下基于 XSD 的方法的稍微复杂一些的示例显示了 一些 around 建议与许多强类型参数结合使用:
package x.y.service;
public interface PersonService {
Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
public Person getPerson(String name, int age) {
return new Person(name, age);
}
}
package x.y.service
interface PersonService {
fun getPerson(personName: String, age: Int): Person
}
class DefaultPersonService : PersonService {
fun getPerson(name: String, age: Int): Person {
return Person(name, age)
}
}
接下来是方面。请注意,profile(..)
method 接受多个
强类型参数,其中第一个参数恰好是用于
继续执行 method 调用。此参数的存在表明profile(..)
将用作around
建议,如下例所示:
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
try {
clock.start(call.toShortString());
return call.proceed();
} finally {
clock.stop();
System.out.println(clock.prettyPrint());
}
}
}
import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch
class SimpleProfiler {
fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
val clock = StopWatch("Profiling for '$name' and '$age'")
try {
clock.start(call.toShortString())
return call.proceed()
} finally {
clock.stop()
println(clock.prettyPrint())
}
}
}
最后,以下示例 XML 配置会影响 针对特定连接点的上述建议:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
<bean id="personService" class="x.y.service.DefaultPersonService"/>
<!-- this is the actual advice itself -->
<bean id="profiler" class="x.y.SimpleProfiler"/>
<aop:config>
<aop:aspect ref="profiler">
<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
expression="execution(* x.y.service.PersonService.getPerson(String,int))
and args(name, age)"/>
<aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
method="profile"/>
</aop:aspect>
</aop:config>
</beans>
请考虑以下驱动程序脚本:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;
public final class Boot {
public static void main(final String[] args) throws Exception {
BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
PersonService person = (PersonService) ctx.getBean("personService");
person.getPerson("Pengo", 12);
}
}
fun main() {
val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
val person = ctx.getBean("personService") as PersonService
person.getPerson("Pengo", 12)
}
使用这样的 Boot 类,我们将在标准输出上获得类似于以下内容的输出:
StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0 ----------------------------------------- ms % Task name ----------------------------------------- 00000 ? execution(getFoo)
建议订购
当多个通知需要在同一个连接点运行时(执行方法)
排序规则如 Advice Ordering 中所述。优先权
aspect 之间的order
属性中的<aop:aspect>
元素或
通过添加@Order
注解添加到支持该 aspect 的 bean 中,或者通过使用
Bean 实现Ordered
接口。
与同一中定义的通知方法的优先规则相反 例如,给定一个 作为一般的经验法则,如果您发现您定义了多条建议
在同一 |
5.5.4. 简介
介绍(在 AspectJ 中称为类型间声明)让一个 aspect 声明 that advised objects 实现给定的接口并提供 该接口代表这些对象。
您可以使用aop:declare-parents
元素中aop:aspect
.
您可以使用aop:declare-parents
元素来声明匹配的类型具有新的父级(因此得名)。
例如,给定一个名为UsageTracked
以及名为DefaultUsageTracked
,以下方面声明 service 的所有 implementationrs
接口还实现了UsageTracked
接口。(为了公开统计数据
例如,通过 JMX。
<aop:aspect id="usageTrackerAspect" ref="usageTracking">
<aop:declare-parents
types-matching="com.xzy.myapp.service.*+"
implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
<aop:before
pointcut="com.xyz.myapp.CommonPointcuts.businessService()
and this(usageTracked)"
method="recordUsage"/>
</aop:aspect>
支持usageTracking
然后 bean 将包含以下方法:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
要实现的接口由implement-interface
属性。这
的值types-matching
attribute 是一个 AspectJ 类型的模式。任何 bean 的
matching 类型实现UsageTracked
接口。请注意,在之前的
建议,服务 Bean 可以直接用作
这UsageTracked
接口。要以编程方式访问 bean,您可以编写
以后:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
5.5.6. 顾问
“advisors” 的概念来自 Spring 中定义的 AOP 支持 并且在 AspectJ 中没有直接的等价物。顾问就像一个小 具有单个建议的自包含方面。建议本身是 由 bean 表示,并且必须实现 Spring 中的通知类型中描述的建议接口之一。顾问可以利用 AspectJ 切入点表达式。
Spring 通过<aop:advisor>
元素。你最
通常看到它与 transactional advice 结合使用,后者也有自己的
namespace 支持。以下示例显示了一个 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
属性来定义内联切入点表达式。
要定义 advisor 的优先级,以便 advice 可以参与排序,
使用order
属性来定义Ordered
顾问的价值。
5.5.7. AOP 模式示例
本节显示 AOP 示例中的并发锁定失败重试示例在使用架构支持重写时的外观。
业务服务的执行有时会由于并发问题(对于
例如,一个死锁失败者)。如果重试作,则可能会成功
下次尝试时。对于适合在此类
条件(无需因冲突而返回给用户的幂等作
resolution)中,我们希望透明地重试该作,以避免客户端看到PessimisticLockingFailureException
.这是一个明确贯穿的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想要重试作,所以我们需要使用 around advice,以便我们可以
叫proceed
多次。下面的清单显示了基本的 aspect 实现
(这是一个使用 schema 支持的常规 Java 类):
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
try {
return pjp.proceed();
}
catch(PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while(numAttempts <= this.maxRetries);
throw lockFailureException;
}
}
class ConcurrentOperationExecutor : Ordered {
private val DEFAULT_MAX_RETRIES = 2
private var maxRetries = DEFAULT_MAX_RETRIES
private var order = 1
fun setMaxRetries(maxRetries: Int) {
this.maxRetries = maxRetries
}
override fun getOrder(): Int {
return this.order
}
fun setOrder(order: Int) {
this.order = order
}
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
var numAttempts = 0
var lockFailureException: PessimisticLockingFailureException
do {
numAttempts++
try {
return pjp.proceed()
} catch (ex: PessimisticLockingFailureException) {
lockFailureException = ex
}
} while (numAttempts <= this.maxRetries)
throw lockFailureException
}
}
请注意,该 aspect 实现了Ordered
接口,以便我们可以设置
方面高于 Transaction Advice(我们希望每次
retry)。这maxRetries
和order
properties 都是由 Spring 配置的。这
main作发生在doConcurrentOperation
围绕 Advice 方法。我们尝试
进行。如果我们失败时出现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>
请注意,目前,我们假设所有业务服务都是幂等的。如果
事实并非如此,我们可以优化 Aspect 以便它只真正重试
幂等作,通过引入Idempotent
annotation 和使用注释
对 Service作的实现进行注释,如下例所示:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
// marker annotation
}
这
将 aspect 更改为仅重试幂等作涉及优化
pointcut 表达式,以便仅@Idempotent
作匹配,如下所示:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
5.6. 选择要使用的 AOP 声明样式
一旦您确定某个 aspect 是实现给定 需求,你如何决定是使用 Spring AOP 还是 AspectJ 以及 方面语言(代码)样式、@AspectJ 注释样式还是 Spring XML 样式?这些 决策受多种因素影响,包括应用程序要求、 开发工具,以及团队对 AOP 的熟悉程度。
5.6.1. Spring AOP 还是完整的 AspectJ?
使用最简单的方法。Spring AOP 比使用完整的 AspectJ 更简单,因为 不需要将 AspectJ 编译器/编织器引入到你的开发中 并构建流程。如果只需要在 Spring 上通知作的执行 beans 中,Spring AOP 是正确的选择。如果您需要通知 不受 管理 的对象 Spring 容器(通常例如域对象)中,您需要使用 AspectJ 的如果你希望通知 join points 而不是 简单的方法执行(例如,字段 Get 或 Set 连接点等)。
当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为 “代码样式”)或@AspectJ注释样式。显然,如果您不使用 Java 5+,则已为您做出选择:使用代码样式。如果 aspect 播放 large 角色,并且您可以使用 AspectJ Development Tools (AJDT) 插件,AspectJ 语言语法是 首选选项。它更简洁、更简单,因为该语言是有意为之的 专为写作方面而设计。如果您不使用 Eclipse 或只有几个方面 在您的应用程序中没有主要作用,您可能需要考虑使用 @AspectJ样式,在 IDE 中坚持使用常规 Java 编译,并在 IDE 中添加 构建脚本的 aspect 编织阶段。
5.6.2. Spring AOP 的 @AspectJ 还是 XML?
如果您选择使用 Spring AOP,则可以选择 @AspectJ 或 XML 样式。 需要考虑各种权衡。
XML 样式对于现有的 Spring 用户来说可能是最熟悉的,并且它由真正的 POJO 的。当使用 AOP 作为配置企业服务的工具时,XML 可能是一个很好的 choice(一个很好的测试是,您是否认为 pointcut 表达式是 配置)。对于 XML 样式,它是 可以说,从您的配置中可以更清楚地了解系统中存在哪些方面。
XML 样式有两个缺点。首先,它没有完全封装 它在一个地方实现它所解决的需求。DRY 原则说 任何部分都应该有一个单一的、明确的、权威的表示 系统内的知识。使用 XML 样式时,了解需求如何 的实现被拆分到支持 Bean 类的声明和 XML 中 配置文件。当您使用 @AspectJ 样式时,此信息将被封装 在单个模块中:Aspect。其次,XML 样式在哪些方面稍受限制 它能表达的比@AspectJ风格:只有 “singleton” 方面实例化模型 ,并且无法组合在 XML 中声明的命名切入点。 例如,在 @AspectJ 样式中,您可以编写如下内容:
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
@Pointcut("execution(* get*())")
fun propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}
在 XML 样式中,您可以声明前两个切入点:
<aop:pointcut id="propertyAccess"
expression="execution(* get*())"/>
<aop:pointcut id="operationReturningAnAccount"
expression="execution(org.xyz.Account+ *(..))"/>
XML 方法的缺点是您无法定义accountPropertyAccess
通过组合这些定义来切入点。
@AspectJ 样式支持其他实例化模型和更丰富的切入点 组成。它的优点是将方面保持为模块化单元。它还具有 @AspectJ方面可以被理解(从而消费)的优势 Spring AOP 和 AspectJ.因此,如果您稍后决定需要 AspectJ 的功能 为了实现其他需求,您可以轻松地迁移到经典的 AspectJ 设置。 总的来说,Spring 团队更喜欢 @AspectJ 风格来自定义 aspects,而不仅仅是简单的 企业服务的配置。
5.7. 混合 aspect 类型
通过使用自动代理支持,完全可以混合@AspectJ样式方面,
架构定义<aop:aspect>
方面<aop:advisor>
声明的顾问,甚至代理
以及相同配置中其他样式的拦截器。所有这些都已实现
通过使用相同的底层支持机制,可以毫无困难地共存。
5.8. 代理机制
Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的
target 对象。JDK 动态代理内置于 JDK 中,而 CGLIB 是一种常见的
开源类定义库(重新打包为spring-core
).
如果要代理的目标对象实现了至少一个接口,则 JDK 动态 proxy 的 Proxy 的 S S S T目标类型实现的所有接口都是代理的。 如果目标对象未实现任何接口,则会创建一个 CGLIB 代理。
如果要强制使用 CGLIB 代理(例如,代理每个方法 为目标对象定义,而不仅仅是由其接口实现的对象), 您可以这样做。但是,您应该考虑以下问题:
-
使用 CGLIB,
final
方法,因为它们不能在 运行时生成的子类。 -
从 Spring 4.0 开始,你的代理对象的构造函数不再被调用两次, 因为 CGLIB 代理实例是通过 Objenesis 创建的。仅当您的 JVM 执行 不允许绕过构造函数,则可能会看到 double invocations 和 来自 Spring 的 AOP 支持的相应调试日志条目。
要强制使用 CGLIB 代理,请设置proxy-target-class
属性
的<aop:config>
元素设置为 true,如下所示:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
要在使用 @AspectJ 自动代理支持时强制使用 CGLIB 代理,请将proxy-target-class
属性的<aop:aspectj-autoproxy>
元素设置为true
,
如下:
<aop:aspectj-autoproxy proxy-target-class="true"/>
倍数 需要明确的是,使用 |
5.8.1. 理解 AOP 代理
Spring AOP 是基于代理的。掌握 在你编写自己的方面或使用任何 Spring 框架提供的基于 Spring AOP 的方面。
首先考虑你有一个普通的、未代理的、 nothing-special-about-it,直接对象引用,如下所示 代码片段显示:
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar()
}
fun bar() {
// some logic...
}
}
如果在对象引用上调用方法,则会直接在 该对象引用,如下图所示:

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

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