核心
1. IoC 容器
本章介绍了Spring的控制反转(IoC)容器。
1.1. 春季IoC容器与豆子介绍
本章介绍了Spring Framework中控制反转的实现 (国际奥委会)原则。IoC也称为依赖注入(DI)。这是一个过程 对象仅通过以下方式定义其依赖关系(即与之协作的其他对象) 构造函数参数、工厂方法的参数,或设置在 对象实例在构建完成或从工厂方法返回后。容器 然后在创建 bean 时注入这些依赖。这个过程从根本上来说是 即豆子本身的逆(因此得名控制反转) 通过直接控制依赖的实例化或位置 构建类或类似服务定位器模式的机制。
这org.springframework.beans和org.springframework.context软件包是基础
用于 Spring Framework 的 IoC 容器。这豆子工厂接口提供了先进的配置机制,能够管理任何类型的
对象。应用上下文是 的子接口豆子工厂.报告补充道:
-
更容易与 Spring 的 AOP 功能集成
-
消息资源处理(用于国际化)
-
活动出版
-
应用层特定的上下文,例如
WebApplicationContext用于网页应用。
简而言之,豆子工厂提供配置框架和基础
功能性,以及应用上下文增加了更多企业专用功能。
这应用上下文是 的完备超集豆子工厂并且被使用
本章中对Spring的IoC容器的描述中独占。更多内容
关于使用豆子工厂而不是ApplicationContext,看这豆子工厂.
在 Spring 中,构成你应用骨干且被管理的对象 春季IoC容器称为Beans。豆子是一个 由 Spring IoC 容器实例化、组装和管理。否则, 豆子只是你应用中众多对象之一。豆子及其依赖关系 其中,这些都反映在容器使用的配置元数据中。
1.2. 集装箱概述
这org.springframework.context.ApplicationContext接口代表春季 IoC
容器,负责实例化、配置和组装
豆。容器会收到指令,告诉你要哪些对象
通过读取配置元数据实现实例化、配置和组装。这
配置元数据以 XML、Java 注释或 Java 代码表示。它让
你表达了构成你应用的对象以及丰富的相互依赖关系
就在那些物体之间。
多个实现应用上下文提供接口
和Spring一起。在独立应用中,通常会创建
实例ClassPathXmlApplicationContext或文件系统Xml应用上下文.
虽然XML一直是定义配置元数据的传统格式,但你可以
指示容器使用 Java 注释或代码作为元数据格式,如下:
提供少量 XML 配置以声明式支持这些
额外的元数据格式。
在大多数应用场景中,实例化 或
更多春季IoC容器的实例。例如,在Web应用场景中,一个
简单的八行(大约)样板网页描述符 XML 在web.xml文件
通常应用程序本身就足够了(参见网页应用的便捷应用上下文实例化)。如果你使用 Eclipse 的 Spring Tools(这是一个基于 Eclipse 的开发)
环境),你只需几次鼠标点击就能轻松创建这种模板配置,或者
击 键。
下图展示了Spring工作的大致情况。你的应用类别
与配置元数据结合,使得在应用上下文是
创建并初始化,你拥有一个完全配置且可执行的系统,或者
应用。
1.2.1. 配置元数据
如前图所示,Spring IoC容器消耗的是一种形式 配置元数据。这些配置元数据代表了你作为 应用程序开发者,告诉 Spring 容器实例化、配置和组装 你应用中的对象。
配置元数据传统上以简单直观的XML格式提供, 这正是本章大部分内容用来传达关键概念和特征的 春季IoC容器。
| 基于XML的元数据并不是唯一允许的配置元数据形式。 Spring IoC 容器本身与该格式完全解耦 配置元数据实际上是写入的。如今,许多开发者选择基于Java的配置来开发他们的Spring应用。 |
有关在 Spring 容器中使用其他形式的元数据,请参见:
-
基于注释的配置:春季2.5引入 支持基于注释的配置元数据。
-
基于 Java 的配置:从 Spring 3.0 开始,具备许多功能 由 Spring 提供的 JavaConfig 项目成为 Spring 框架核心的一部分。 因此,你可以用 Java 来定义应用类之外的 beans 而不是XML文件。要使用这些新功能,请参见
@Configuration,@Bean,@Import, 和@DependsOn附注。
Spring结构至少包含一颗,通常不止一颗豆子
容器必须管理的定义。基于XML的配置元数据用于配置这些
豆子<豆/>顶层内部的元素<豆子/>元素。Java
配置通常使用@Bean- 注释方法中的@Configuration类。
这些 bean 定义对应的是构成你应用的实际对象。
通常,你定义服务层对象、数据访问对象(DAO)、展示
诸如支柱这样的对象行动实例、基础设施对象,如休眠SessionFactories, JMS队列,等等。通常,人们不会进行配置
容器中的细粒度域对象,因为通常是负责的
DAO和业务逻辑用于创建和加载域对象。不过,你可以使用
Spring与AspectJ的集成,用于配置外部创建的对象
控制IoC容器。参见使用 AspectJ 以
用 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"
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 | 这身份证属性是标识单个豆子定义的字符串。 |
| 2 | 这类属性定义了豆子的类型,并使用完全限定的
班级名。 |
该身份证属性指的是协作对象。的XML是
本例中未提及协作对象。更多信息请参见依赖。
1.2.2. 实例化容器
位置路径
供应给应用上下文构造函数是使得
来自各种外部资源的容器加载配置元数据,例如
作为本地文件系统,Java类路径,依此类推。
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对象关系映射标准上)。这物业名称元素指的是
JavaBean属性的名称,以及裁判元素指的是另一种豆子的名字
定义。这种联系身份证和裁判元素表示 之间的依赖关系
协作对象。关于配置对象依赖的细节,请参见依赖。
基于XML的配置元数据的组合
让豆子定义跨越多个XML文件是有用的。通常,每个人都是 XML 配置文件代表你架构中的逻辑层或模块。
你可以使用应用上下文构造器从所有这些中加载豆子定义
XML片段。该构造子接受多重函数资源地点,如上一节所示。或者,使用一个或多个
发生的事件<import/>元素用于从另一个文件加载豆子定义,或者
文件。以下示例展示了如何实现:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的例子中,外部豆定义从三个文件加载:services.xml,messageSource.xml和themeSource.xml.所有位置路径均为
相对于进行导入的定义文件,所以services.xml必须在
目录或类路径位置与导入文件相同,而messageSource.xml和themeSource.xml必须在资源位于
导入文件的位置。如你所见,前方斜杠被忽略了。然而,给定
这些路径是相对的,最好完全不使用斜杠。这
导入文件的内容,包括顶层<豆子/>元素,必须
根据 Spring Schema,是有效的 XML 豆定义。
|
虽然可以,但不推荐使用以下条件在父目录中引用文件
亲属“../“路径。这样做会对当前文件产生依赖
应用。特别地,不推荐用于 你总可以用完全限定的资源位置来代替相对路径:对于
例 |
命名空间本身提供导入指令功能。进一步
除了普通豆定义外,还有其他配置特征可在选集中获得
Spring提供的XML命名空间——例如,上下文和实用命名空间。
Groovy Bean定义DSL
作为外部化配置元数据的进一步例子,豆定义也可以 在 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的豆定义,甚至
支持Spring的XML配置命名空间。它还支持导入 XML
通过importBeans命令。
1.2.3. 使用容器
这应用上下文是先进工厂的接口,能够维护
不同Beans及其依赖关系的登记册。通过使用该方法T getBean(字符串名,Class<T> requiredType)你可以检索你的豆子实例。
这应用上下文你可以阅读豆子定义并访问它们,具体如下
示例如下:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
import org.springframework.beans.factory.getBean
// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// use configured instance
var userList = service.getUsernameList()
用Groovy配置,自助机制看起来非常相似。它有不同的背景 实现类是Groovy支持的(但也理解XML豆的定义)。 以下示例展示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")
最灵活的变体是通用应用上下文结合读卡
代表——例如,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()
你也可以使用GroovyBean定义阅读器对于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()
你可以在同一台上自由搭配这样的读者代表应用上下文,
阅读来自多种配置来源的豆子定义。
然后你可以使用getBean取回你的豆子实例。这应用上下文接口还有其他方法可以检索豆子,但理想情况下,是你的应用程序
代码绝不应该使用它们。确实,你的应用码不应该有调用getBean()因此完全不依赖 Spring API。例如
Spring 与 Web 框架的集成为各种 Web 提供了依赖注入
框架组件如控制器和JSF管理的豆子,允许你声明
通过元数据(如自动接线注释)对特定豆子的依赖。
1.3. Beans概述
春季IoC容器可管理一种或多种Beans。这些豆子是用
你提供给容器的配置元数据(例如,以XML形式)<豆/>定义)。
在容器内部,这些Beans定义表示为豆子定义包含(除其他信息外)以下元数据的对象:
-
包限定类名称:通常是 豆子的定义。
-
豆子的行为配置元素,说明豆子在 容器(范围、生命周期回调等)。
-
提及豆子工作所需的其他Beans。这些 引用也被称为协作者或依赖关系。
-
在新创建的对象中还可以设置其他配置——例如大小 管理 连接池。
这些元数据转化为构成每个豆子定义的一组属性。 下表描述了这些性质:
| 属性 | 解释如下...... |
|---|---|
类 |
|
名称 |
|
范围 |
|
构造子参数 |
|
性能 |
|
自动接线模式 |
|
懒初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了豆子定义外,还包含如何创建特定食物的信息
豆,那个应用上下文实现还允许注册现有的
这些对象是在容器外部(由用户创建的)创建的。这可以通过访问
ApplicationContext 的 BeanFactory 通过getBeanFactory()该方法返回
豆子工厂DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法。然而,典型应用仅适用于Beans
通过常规 BEAN 定义元数据定义。
|
豆子元数据和手动提供的单例实例需要尽早注册 尽可能地,这样容器在自动接线时才能正确判断它们 以及其他内省步骤。同时覆盖现有元数据和现有 单例实例在一定程度上支持,新 Beans 注册于 运行时(同时进行对工厂的实时访问)未被官方支持,且可能 导致并发访问异常、豆子容器状态不一致,或两者兼有。 |
1.3.1. 给豆子命名
每个豆子都有一个或多个标识符。这些标识符必须在承载该豆子的容器内唯一。一个豆子通常只有一个标识符。然而,如果它需要多个标识符,那么多余的标识符可以被视为别名。
在基于XML的配置元数据中,你会使用身份证属性,该名称属性,或两者都用于指定Beans标识符。 这身份证属性允许你指定恰好一个ID。通常这些名称是字母数字的(如“myBean”,“someService”等),但它们也可以包含特殊字符。如果你想为豆子引入其他别名,你也可以在名称属性,中间用逗号分隔(,)、分号(),或空白。作为历史上的说明,在 Spring 3.1 之前的版本中,;身份证属性为定义为xsd:ID类型,限制可能的字符。截至3.1,定义为XSD:字符串类型。 注意豆子身份证唯一性仍然由容器强制执行,但不再由XML解析器强制。
你没有义务提供名称或身份证为了一颗豆子。如果你不提供名称或身份证具体来说,容器为该豆子生成一个唯一的名称。 然而 如果你想用名字来称呼那颗豆子,可以通过裁判元素或服务定位器式的查询,你必须提供一个名称。不提供名称的动机与使用内部豆子和自动配线协作者有关。
在类路径中进行组件扫描时,Spring为未命名的组件生成豆名遵循前述规则:本质上,取简单的类名并将其初始字符变为小写。然而,在(不寻常的)特殊情况下,当存在多个字符且第一和第二个字符都是大写时,原始外壳会被保留。这些规则与定义为java.beans.Introspector.decapitalize(斯普林在这里也用到了这个)。 |
将豆子锯齿化到豆子定义之外
在豆子定义本身中,你可以为豆子提供多个名称,方法是使用由以下名称组成的组合身份证属性以及任意数量的其他名称名称属性。 这些名称可以是同一 bean 的等价别名在某些情况下非常有用,比如让应用程序中的每个组件通过使用专门针对该组件的 bean 名称来指代一个共同的依赖关系 本身。
指定所有豆子实际定义的别名并不总是充分, 然而。 有时会为定义的豆子引入别名 别处。 这在配置被拆分的大型系统中很常见每个子系统都有自己的对象定义集。在基于XML的配置元数据中,你可以使用<别名/>要完成的要素 这。 以下示例展示了如何实现:
<alias name="fromName" alias="toName"/>
在这里,一颗豆子(在同一容器里)被命名为fromName也可能,在使用该别名定义后,称为toName.
例如,子系统A的配置元数据可能通过以下名称指代数据源名称子系统A-dataSource. 子系统B的配置元数据可能指一个名为子系统B-数据源. 在组合主要应用同时使用这两个子系统时,主要应用以名称myApp-dataSource.如果要让三个名字都指向同一个对象,你可以
向配置元数据添加以下别名定义:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在每个组件和主应用程序可以通过名称来引用数据源 该定义是唯一且保证不会与其他定义冲突的(实际上是) 创建命名空间),但它们指的是同一个豆子。
1.3.2. 实例化豆子
豆子定义本质上是创建一个或多个对象的配方。这 容器在被要求时查看命名豆子的配方并使用配置 由该BEN定义封装的元数据,用于创建(或获取)实际对象。
如果你使用基于XML的配置元数据,你会指定对象的类型(或类别)
即要实例化为类属性<豆/>元素。这类属性(内部为类在豆子定义实例)通常是强制的。(关于例外情况,请参见使用实例工厂方法实例化和 Bean 定义继承。)
你可以使用类财产有两种方式之一:
-
通常,在容器 它本身通过某种程度上反思地调用其构造子,直接创造了豆子 等价于具有
新增功能算子。 -
具体指明包含
静态的工厂方法 调用以创建对象,在较少见的情况下,容器调用静态的在类上使用工厂方法来创建豆子。返回的对象类型 从呼唤静态的工厂方法可能是同一类别或其他 完全是个阶级。
构造子实例化
当你通过构造子方法创建豆子时,所有普通类都可以通过和 与Spring兼容。也就是说,被开发的类不需要实现 任何特定的接口,或者以特定方式编码。简单来说明是哪种豆子 课程应该足够了。不过,这取决于你用的是什么类型的IoC。 Bean,你可能需要一个默认(空)构造函数。
Spring IoC 容器几乎可以管理你想让它管理的任何类。是的 不仅限于管理真正的JavaBeans。大多数 Spring 用户更喜欢实际的 JavaBeans,且 仅有一个默认(无参数)构造函数以及相应的设定器和获取函数 在容器中的属性之后。你也可以选择更异国风情的非Beans风格 你的容器里有课程。比如说,如果你需要使用遗留连接池 绝对不符合 JavaBean 规范,Spring 可以作为 井。
使用基于XML的配置元数据,你可以指定你的Beans如下:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
关于向构造函数提供参数的机制(如有需要) 以及在构建对象后设置实例属性,参见“注入依赖”。
静态工厂方法的实例化
在定义用静态工厂方法创建的豆子时,请使用类属性指定包含静态的工厂方法和属性
叫工厂方法以指定工厂方法本身的名称。你应该
能够调用该方法(如后文所述,带可选参数)并返回活值
该对象随后被视为通过构造函数创建。
这种豆子定义的一个用法是静态的工厂在遗留代码中。
以下豆子定义规定,豆子通过调用
工厂方法。定义未指定返回对象的类型(类),
仅包含工厂方法的类。在这个例子中,createInstance()方法必须是静态的方法。以下示例展示了如何指定工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
以下示例展示了一个类,适用于前述豆子定义:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
class ClientService private constructor() {
companion object {
private val clientService = ClientService()
fun createInstance() = clientService
}
}
关于向工厂方法提供(可选)参数的机制详情 以及在对象从工厂返回后设置对象实例属性, 详见“依赖关系与配置”。
使用实例工厂方法实现实例化
类似于通过静电实现实例化
工厂方法,实例工厂方法调用非静态
用容器中现有的豆子制作新豆子的方法。用这个
机制,留下类属性为空,且在工厂豆属性
指定当前(或父或祖先)容器中包含的豆子名称
就是要调用来创建对象的实例方法。设定
工厂方法本身,具有工厂方法属性。以下示例显示
如何配置这样的豆子:
<!-- 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
}
}
这种方法表明工厂豆本身可以通过以下方式进行管理和配置 依赖注入(DI)。参见依赖关系和 详细配置。
确定豆子的运行时间类型
特定豆子的运行时间类型很难确定。在 中的指定类
BEAN 元数据定义只是初始类引用,可能被组合起来
具有声明的工厂方法或为工厂豆该类可能导致
不同的运行时类型,或者在实例级情况下根本没有设置
工厂方法(通过指定的方法解析工厂豆改用名字)。
此外,AOP 代理可能会将 bean 实例与基于接口的代理 包裹为
有限地暴露目标豆的实际类型(仅限其实现的接口)。
推荐的方法是,了解特定咖啡豆的实际运行时间类型
一个BeanFactory.getType(豆子工厂.getType)请指定豆名。这涵盖了上述所有因素
考虑情况,返回 a 的对象类型豆子工厂.getBean叫声为
我会用同一个豆子的名字回来。
1.4. 依赖关系
一个典型的企业应用不包含单个对象(或 Spring的说法)。即使是最简单的应用,也有几个对象协同工作 呈现最终用户认为连贯的应用。下一节将解释如何 你从定义多个独立的Beans定义,逐步实现完整 对象协作以实现目标的应用。
1.4.1. 依赖注入
依赖注入(DI)是一种对象定义其依赖关系的过程 (即它们所用的其他对象)仅通过构造子参数, 工厂方法的参数,或在对象实例之后设置的属性 它是工厂生产或回收的。容器随后注入这些 在生成豆子时产生依赖关系。这个过程本质上是相反的(因此 即控制的反转)的名称,即控制豆子本身控制实例化的 或通过直接构造类或 服务定位器模式。
采用DI原则的代码更干净,且当对象为 提供依赖关系。该对象不查找其依赖关系,但会 不知道依赖的位置或类别。因此,你的课程会变得更容易 测试时,尤其是当依赖对象是接口或抽象基类时, 这些工具允许在单元测试中使用存根或模拟实现。
DI主要有两种变体:基于构造函数的 依赖注入和基于Setter的依赖注入。
基于构造函数的依赖注入
基于构造子的DI通过容器调用具有
参数数量,每个参数代表一个依赖关系。呼叫静态的工厂方法
有具体论据来构造豆子几乎等价,而这场讨论
将参数处理为构造子和静态的工厂方法类似。这
以下示例展示了一个类只能用构造函数进行依赖注入
注射:
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 不依赖于容器特定接口、基类或注释。
构造子参数解析
构造函数的参数解析匹配通过参数的类型实现。如果没有 在豆定义的构造子参数中存在潜在歧义,即 在Beans定义中,构造子参数定义的顺序为 其中当豆子 是 时,这些参数会被提供给相应的构造器 被具象化。考虑以下类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
package x.y
class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
假设东西二和三物类之间没有继承关系,不是
存在潜在的歧义。因此,以下配置正常,而你则不行
需要在<构造者-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>
当引用另一个豆子时,类型已知,匹配可以发生(正如
上述例子的案例)。当使用简单类型时,例如:<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
)
在前述情景中,容器可以使用类型匹配和简单类型,如果
你通过使用以下方式明确指定构造子参数的类型类型属性
如下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
你可以使用指数属性以明确指定构造函数参数的索引,
如下示例所示:
<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>
请记住,要让它开箱即用,你的代码必须用 启用调试标志,使 Spring 能够从构造函数中查找参数名称。 如果你不能或不想用调试标志编译代码,可以使用@ConstructorProperties JDK注释来显式命名构造器参数。示例类会 然后需要观察如下:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
package examples
class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
基于定位器的依赖注入
基于 Setter 的 DI 是通过容器调用 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...
}
这应用上下文支持基于构造程序和基于定位器的 DI 来处理它所涉及的豆子
管理。它还支持基于 Setter 的 DI,在某些依赖关系已经被 Setter 覆盖之后
通过构造者方法注入。你配置依赖的形式为
一个豆子定义,你会与 结合使用属性编辑实例到
将属性从一种格式转换为另一种格式。然而,大多数 Spring 用户无法正常工作
直接(即程序化)使用这些类,而是通过 XML豆定义、注释分量(即注释为@Component,@Controller,依此类推),或@Bean基于Java的方法@Configuration类。
这些源随后在内部转换为豆子定义而且曾经
加载一个完整的 Spring IoC 容器实例。
依赖关系解决过程
容器执行Beans依赖解析的方法如下:
-
这
应用上下文创建并初始化的配置元数据满足 描述了所有的豆子。配置元数据可以通过 XML、Java 代码或 附注。 -
对于每个豆子,其依赖关系以属性(constructor)的形式表达 参数,或者说是静态工厂方法的参数(如果你用它代替 普通构造器)。当豆子 真正被创造出来。
-
每个性质或构造子参数都是对 集合值的实际定义,或者 这是对容器里另一颗豆子的暗示。
-
每个属性或构造子参数(作为值)都从其指定的参数转换 格式化为该属性或构造子参数的实际类型。默认情况下,Spring 可以将字符串格式的值转换为所有内置类型,例如
智力,长,字符串,布尔,等等。
Spring 容器在容器创建时会验证每个豆子的配置。 然而,豆子属性本身直到豆子实际生成后才会被设置。 创建了单例作用域且设置为预实例化(默认)的豆子 当容器被创建时。范畴定义在Beans范畴中。否则 只有在被要求时才会制作豆子。豆子的创造可能导致 将要创建的豆子图,作为豆子的依赖关系及其依赖关系 依赖关系(等等)被创建并分配。注意 以下分辨率不匹配 这些依赖可能在受影响豆子首次创建时才显现。
你一般可以信任Spring会做正确的事。它检测配置问题,
例如对不存在豆子的引用和循环依赖关系,在容器处
加载时间。Spring 尽可能晚地设置属性并解决依赖关系,当
豆子实际上是被创造出来的。这意味着一个Spring集装箱已经装载
正确地,如果存在
生成该对象或其依赖中的问题——例如,豆子抛出一个
例外因缺失或无效属性而产生。这可能会被推迟
部分配置问题的可见性是原因应用上下文实现者
默认预实例化单例豆。但代价是前期时间和内存
如果在真正需要之前就制作这些豆子,你会发现配置问题
当应用上下文是被创造的,而不是后来。你仍然可以覆盖这个默认设置
使得单例豆子懒惰地初始化,而不是急切地初始化
预实例化。
依赖注入的例子
以下示例使用基于XML的配置元数据来实现基于定位器的DI。一个小的 Spring XML 配置文件的一部分规定了一些 bean 定义如下:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例展示了对应的ExampleBean类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
class ExampleBean {
lateinit var beanOne: AnotherBean
lateinit var beanTwo: YetAnotherBean
var i: Int = 0
}
在上述例子中,设定者被声明为匹配指定的属性 在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)
豆定义中指定的构造函数参数被用作
构造器ExampleBean.
现在考虑这个例子的一个变体,其中 Spring 不是用构造子,而是
被告知要打电话给静态的返回该对象实例的工厂方法:
<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
}
}
}
对静态的工厂方法由以下公司提供<构造者-arg/>元素
这和实际使用了建造器完全一样。该类别的类型为
工厂方法返回的 不必与
包含静态的工厂方法(尽管在本例中确实如此)。一个实例
(非静态)工厂方法也可以以基本相同的方式使用(除
从该应用中工厂豆属性代替类属性),所以我们这里不讨论这些细节。
1.4.2. 依赖关系与配置详细说明
如前节所述,你可以定义 bean属性和构造函数参数作为对其他托管 beans(协作者)的引用或作为内联定义的值。Spring 基于 XML 的配置元数据支持其子元素类型<财产/>和<构造者-arg/>该元素 目的。
直数值(原语、字符串等)
这值属性<财产/>元素指定一个属性或构造子参数作为人类可读的字符串表示。Spring的转换服务用于将这些值从一个字符串到属性或参数的实际类型。以下示例展示了各种值被设置:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
以下示例使用p命名空间,进行更简洁的XML配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
前面的 XML 更简洁。然而,错别字是在运行时发现的,而非设计时,除非你使用集成开发环境(如 IntelliJIDEA 或 Eclipse 的 Spring 工具)它支持在创建 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 容器将文本转换为<价值/>元素变成java.util.Properties通过使用 JavaBeans 实现实例属性编辑机制。 这 是一个不错的捷径,也是春季团队少数支持使用嵌套的<价值/>元素值属性风格。
这IDREF元素
这IDREF元素仅仅是一种无误传递身份证(字符串值 - 非引用) 容器中另一个豆子的<构造者-arg/>或<财产/>元素。 以下示例展示了如何使用它:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面的豆定义片段在运行时与以下以下片段完全等价:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式优于第二种,因为使用IDREF标签允许容器在部署时验证所引用的 BEAN 是否真的存在 存在。 在第二种变体中,传递的值不进行验证传递给目标名称的属性客户端豆。 只有在客户端豆子实际上是实例化的。如果客户端Bean 是一个原型 Bean,这个拼写错误及其异常只有在容器部署很久之后才能被发现。
这当地属性IDREFelement 在 4.0 豆中已不再支持
XSD,因为它不比常规函数提供价值豆不再有参考。改变
你现在的IDREF地方参考文献伊德雷夫·比恩升级到4.0架构时, |
一个常见的地方(至少在2.0春季之前的版本中)是<idref/>元素 带来价值的体现在AOP拦截器的配置中代理工厂豆豆子的定义。 用<idref/>当你指定拦截机名称时,可以防止拦截机ID拼写错误。
对其他豆子(合作者)的提及
这裁判元素是 内部的最后一个元素<构造者-arg/>或<财产/>定义元素。在这里,你将豆子指定属性的值设置为对容器管理的另一个豆子(协作者)的引用。被引用的豆子是该豆子的依赖关系,其属性被设置,并且会按需初始化在属性设置前按需进行。(如果协作者是单例豆,它可以已经被容器初始化了。)所有引用最终都是对另一个对象的引用。作用域和验证取决于你是否指定了另一个对象的ID还是名称豆或父母属性。
通过豆属性<参考/>标签是最通用的形式,允许创建对同一容器内任意豆子的引用,无论是否在同一XML文件中。该豆属性可能与身份证目标豆的属性或为与名称目标豆的属性。以下示例展示了如何使用裁判元素:
<ref bean="someBean"/>
通过父母属性创建了一个对豆子的引用该豆子位于当前容器的父容器中。的值父母属性可以与以下任一相同身份证目标豆的属性或值中的名称目标豆的属性。目标豆必须位于
当前容器的父容器。你应该主要使用这个Beans参考版本
当你有一个容器层级结构,想用父豆包裹现有的豆子时
带有与母豆同名代理的容器。以下对
列表展示了如何使用父母属性:
<!-- 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>
这当地属性裁判element 在 4.0 豆中已不再支持
XSD,因为它不比常规函数提供价值豆不再有参考。改变
你现在的地方文献参考文献裁判 比恩升级到4.0架构时, |
内豆
一个<豆/>元素在<财产/>或<构造者-arg/>元素定义了一个
内豆,如下例子所示:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部豆的定义不需要定义的ID或名称。如果有指定的话,容器
不使用该数值作为标识符。容器也会忽略范围旗帜开启
创造,因为内在豆子总是匿名的,总是与外在一起创造
豆。无法独立取用内豆或注射入内
与外围豆子不同的合作豆。
作为极端情况,可以从自定义作用域接收销毁回调——例如,单个单位子内的请求范围内豆。成立 内层豆子实例与其包含豆子绑定,但销毁回调允许它 参与请求范围的生命周期。这并不常见。内豆 通常只需分享它们的豆子范围。
收集
这<列表/>,<set/>,<地图/>和<道具/>元素设置了属性
以及爪哇语的论证收集类型列表,设置,地图和性能,
分别。以下示例展示了如何使用它们:
<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 容器也支持合并集合。应用
开发者可以定义父<列表/>,<地图/>,<set/>或<道具/>元素
并生孩子<列表/>,<地图/>,<set/>或<道具/>元素继承和
覆盖父集合的值。也就是说,子集合的值为
将父集合和子集合的元素与子集合的元素合并的结果
集合元素覆盖父集合指定值。
本节关于合并,讨论了亲子豆的机制。不熟悉的读者 关于父母和子女的Beans定义,在继续阅读前,建议先阅读相关章节。
以下示例展示了集合合并:
<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>
注意合并=真属性<道具/>的元素adminEmails的属性孩子豆子的定义。当孩子豆豆解决了
并且由容器实例化,得到的实例具有adminEmails
性能包含合并子节点结果的集合adminEmails与父母的收藏adminEmails收集。以下列表
显示结果:
孩子性能集合的值集继承了所有来自
父母<道具/>,以及子节点的值支持值覆盖了 中的值
父母收藏。
这种合并行为类似于<列表/>,<地图/>和<set/>收藏类型。在具体情况下<列表/>元素,语义
与列表集合类型(即命令值的集合)被维持。父节点的值都排在所有子节点之前
值。在地图,设置和性能收藏类型,无排序
存在。因此,底层的集合类型不存在排序语义
相关地图,设置和性能容器的实现类型
内部用途。
收藏合并的局限性
你不能合并不同的集合类型(例如地图以及一个列表).如果你
请尝试这样做,适当的例外被抛出。这合并属性必须为
在较低的继承子节点定义中指定。具体说明合并属性
父集合定义是多余的,无法实现所需的合并。
强类型集合
随着 Java 5 引入泛型类型,你可以使用强类型集合。
也就是说,可以声明收集类型使得它只能包含(例如)字符串元素。 如果你使用 Spring 进行依赖注入强类型收集变成豆子时,你可以利用 Spring 的类型转换支持,使得你的强类型元素收集实例在添加到收集. 以下 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>
当帐户的属性东西豆子准备注入,泛型关于强类型元素类型的信息Map<String,float>是 通过反射获得。因此,Spring的类型转换基础设施识别各种值元素为类型浮,以及字符串的值(9.99,2.75和3.99)被转换为实际浮类型。
空字符串值和空字符串值
Spring 将性质等空参数视为空参数字符串. 这 随后基于XML的配置元数据片段集合电子邮件与空的性质字符串价值(“”)。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上述示例等价于以下 Java 代码:
exampleBean.setEmail("");
exampleBean.email = ""
这<null/>元素手柄零值。 以下列表展示了一个示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等价于以下 Java 代码:
exampleBean.setEmail(null);
exampleBean.email = null
带有p命名空间的XML快捷方式
p-命名空间允许你使用豆元素的属性(而非嵌套)<财产/>元素)来描述你的房产价值,合作Beans,或两者兼有。
Spring 支持带有命名空间的可扩展配置格式,这些格式基于 XML 模式定义。 这豆配置格式在本章定义在 XML Schema 文档中。然而,p-命名空间并未定义在 XSD 文件中,仅存在于 Spring 的核心中。
以下示例展示了两个XML片段(第一个使用标准XML格式,第二个使用p命名空间),它们解析为相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<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命名空间中的一个属性,称为电子邮件在 Bean 定义中。这告诉 Spring 包含一个属性声明。如前所述,p-命名空间没有模式定义,所以你可以将属性的名称设置为属性名称。
下一个例子包含另外两个豆子定义,它们都引用了另一个豆子:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<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-命名空间的属性值还使用一种特殊格式来声明属性引用。而第一个 bean定义使用<property name=“spouse” ref=“jane”/>从Bean创建参考John致比恩珍,第二种Beans定义p:spouse-ref=“Jane”作为一个属性来实现完全相同的工作。在这种情况下,配偶是属性名称,而-裁判部分表示这不是直数值,而是指的是另一颗豆子。
p-命名空间不如标准XML格式灵活。例如,声明属性引用的格式与以裁判,而标准XML格式则不支持。我们建议您谨慎选择方法,向团队成员沟通,以避免生成同时使用所有三种方法的XML文档。 |
带有 c 命名空间的 XML 快捷方式
类似于带有p命名空间的XML快捷方式,c命名空间在Spring3.1中引入,允许内嵌属性来配置构造函数参数,而不是嵌套构造者-ARG元素。
以下示例使用了c:命名空间以实现与基于构造子的依赖注入相同的功能:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
</beans>
这c:命名空间使用与p:一(一个尾随-裁判为 Bean 引用)用于按构造函数的参数名称设置。 同样地 即使 XSD 模式中未定义,仍需在 XML 文件中声明;(它存在于 Spring 核心中。)
对于极少数构造函数参数名称不可用的情况(通常情况下字节码编译时未进行调试信息),你可以使用退回到参数索引,具体如下:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
由于XML文法,索引符号需要先行 ,
因为 XML 属性名称不能以数字开头(尽管一些 IDE 允许)。
也有相应的索引符号可用_<构造者-主计算>但元素
但此法不常用,因为通常声明顺序已足够。 |
实际上,构造子解析机制在匹配方面相当高效 除非你真的需要,否则我们建议使用名称表示法 在整个配置中。
1.4.3. 使用依赖
如果一个豆子是另一个豆子的依赖,通常意味着一个豆子被设置为
他人的财产。通常你可以通过以下方式实现这一点<参考/>元素基于XML的配置元数据。然而,有时之间存在依赖关系
豆子就不那么直接。例如,当类中的静态初始化器需要
触发,例如用于数据库驱动程序注册。这依赖属性可以
明确要求在使用该元素的豆子之前初始化一个或多个豆子
被初始化。以下示例使用了依赖属性以表达a
对单颗豆子的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表达对多颗豆子的依赖,提供豆子名称列表,作为
这依赖属性(逗号、空白和分号有效
分隔符):
<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" />
这依赖属性可以指定初始化时间依赖关系和,
仅为单粒豆,对应的
毁灭时间依赖性。定义依赖关系
先摧毁给定的豆子,而不是该豆本身被毁掉。
因此依赖也可以控制关机顺序。 |
1.4.4. 懒惰初始化豆子
默认情况下,应用上下文实现积极地创建并配置所有单例豆作为初始化的一部分
过程。通常,这种预实例化是理想的,因为
配置或周围环境是立即发现的,而非数小时
甚至几天后。当这种行为不受欢迎时,你可以预防
通过标记豆子定义为
懒惰初始化。懒初始化的豆子告诉IoC容器创建豆子
比如在首次请求时,而不是启动时。
在 XML 中,这种行为由懒散开始属性<豆/>元素,如下例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前述配置被应用上下文这懒惰豆
当应用上下文开始
而不懒惰豆子被热切地预设了。
然而,当一个懒惰初始化的豆子是单例豆的依赖时,则
不是懒初始化,而是应用上下文在
启动,因为它必须满足单例的依赖关系。懒初始化豆
注入到未进行懒惰初始化的其他单例豆中。
你也可以在容器层面控制懒惰初始化,方法是使用默认-懒惰-初始化属性<豆子/>元素,如下例所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 自动接线合作者
春季容器可以自动连接合作豆之间的关系。您可以
让Spring自动解析协作者(其他豆子)来为你的豆子做准备,具体方式如下
检查应用上下文.自动接线具有以下特点
优势:
-
自动接线可以显著减少指定属性或构造器的需求 参数。(本章其他地方讨论的豆子模板等机制也很有价值 在这方面。)
-
自动接线可以随着你的对象演变而更新配置。例如,如果你需要 要向类添加依赖关系,该依赖可以自动满足,且 你需要修改配置。因此,自动接线尤其有用 在开发过程中,不排除切换到显式布线的选项 代码库变得更稳定。
使用基于XML的配置元数据(参见依赖注入)时,你
可以指定豆定义的自动线模式,用自动线属性<豆/>元素。自动接线功能有四种模式。你指定了自动接线
因此可以选择哪些要自动接线。下表描述了
四种自动接线模式:
| 模式 | 解释 |
|---|---|
|
(默认)没有自动接线。豆子引用必须定义为 |
|
按属性名称自动布线。Spring寻找一颗与
需要自动接线的财产。例如,如果豆子定义设置为
autowire,按名称,包含 |
|
若某性质类型恰好有一个该类型的豆子存在,则该性质为自接线。
那个容器。如果存在多个例外,则会抛出致命异常,表示
你可能不会用 |
|
类似于 |
跟byType或构造 函数自动接线模式,你可以布线阵列和
打字集合。在这种情况下,所有自动接线候选者都包含在容器内,且
匹配期望类型以满足依赖性。你可以自动接线
强类型地图如果期望的键类型为字符串.一个自动接线的地图实例的值由所有符合预期类型的豆子实例组成,且地图实例的键包含对应的Beans名称。
自动接线的局限性与劣势
自动接线在项目中持续使用时效果最佳。如果自接线是 一般来说不常用,开发者可能会觉得只布线一个或 豆子的两个定义。
考虑自动布线的局限性和缺点:
-
显式依赖关系
属性和构造者-ARG设置总是覆盖 自动接线。你不能对简单的属性如原语自动接线,字符串和类(以及具有此类简单性质的数组)。该限制为 这是设计中的。 -
自动接线不如显式接线精确。不过,如前表所述, Spring小心翼翼地避免猜测,以免出现意想不到的模糊情况 结果。你 Spring 管理对象之间的关系不再存在 有明确的记录。
-
布线信息可能无法被生成文档的工具获取 一个春季容器。
-
容器内的多个Beans定义可能匹配于 设置器方法或构造函数的参数将被自动接线。对于数组、集合,或
地图在某些情况下,这不一定是个问题。然而,对于 假设只有一个数值,这种歧义不会被任意解决。如果没有唯一豆子 定义可用,则抛出异常。
在后一种情况下,你有几个选择:
排除豆子在自动接线之外
按每个豆子计算,你可以排除豆子进行自动接线。在 Spring 的 XML 格式中,设置为
这自动线候选属性<豆/>元素变false.容器
这使得该特定的BEN定义无法被自动接线基础设施调用
(包括注释风格配置,如@Autowired).
这自动线候选属性设计仅影响基于类型的自动配线。
它不会影响通过名称的显式引用,即使
指定的豆子不会被标记为自动接线候选。因此,自动接线
但如果名字一致,也会注入一颗豆子。 |
你也可以根据与豆名的模式匹配来限制自动线路候选。这
顶级<豆子/>元素接受其内部一个或多个模式默认自动线候选属性。例如,限制自动线候选人身份
给任何名字以存储 库,给出一个值*存储 库.自
提供多种模式,并用逗号分隔的列表定义它们。显式值为true或false对于Beans定义自动线候选属性总是取
优先。对于这些豆子,模式匹配规则不适用。
这些方法对那些你绝不想被注射到其他豆子里很有用 豆子是通过自动接线完成的。这并不意味着排除豆本身不能被配置为 使用自动接线。相反,豆子本身并不适合自动接线其他豆子。
1.4.6. 方法注入
在大多数应用场景中,容器中的大多数豆子都是单粒豆。当一颗单粒豆需要 与其他单粒豆合作,或者非单粒豆需要协作 对于另一个非单例豆,通常通过定义一个来处理依赖关系 豆子作为他者的财产。当豆子生命周期出现时,问题就会出现 不同。假设单例豆A需要使用非单例(原型)豆B, 也许在A上的每个方法调用时。容器仅生成单点豆A 一次,因此只有一次机会设置属性。容器不能 每次需要豆子B时,都为豆子A提供一个新的豆子实例。
解决方案是放弃某种控制权的反转。你可以做
豆A通过实现应用上下文感知接口
以及制作getBean(“B”)呼叫集装箱请求 (a
通常是新的)豆子B实例,每次豆子A需要时都会进行。以下示例
展示了这种方法:
// 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 Framework。方法注入,Spring IoC 的一个较为先进的功能 容器,可以让你更干净利落地处理这个用例。
查找方法注入
查找方法注入是容器覆盖 上方法的能力 容器管理的豆子,返回另一个命名豆子的查找结果。 容器。查找通常涉及一个原型豆,如描述的情景 在前一节。春季框架 通过从 CGLIB 库生成字节码实现该方法注入 动态生成一个覆盖该方法的子类。
|
在指挥经理在上一个代码片段中的类,以及
Spring 容器动态覆盖了createCommand()方法。这指挥经理类没有任何 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
}
在包含要注入方法的客户端类中(指挥经理在这方面
需要以下形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果该方法为抽象,动态生成子类实现了该方法。
否则,动态生成子类覆盖定义在 的具体方法
原始班级。请考虑以下例子:
<!-- 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(我的指挥)如果真的需要的话,豆子就是原型。如果是
单示例,即同一实例myCommand(我的指挥)每次都归还豆豆。
或者,在基于注释的组件模型中,你可以声明查询
通过@Lookup注释,如下示例所示:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup("myCommand")
protected abstract fun createCommand(): Command
}
或者,更习惯地说,你也可以指望目标豆子会被 声明查找方法的返回类型:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup
protected abstract fun createCommand(): Command
}
注意,通常你应该用具体的声明声明这种带注释的查找方法 存根实现,以便它们与 Spring 组件兼容 扫描规则,抽象类默认被忽略。但这一限制并不 应用到明确注册或明确导入的Beans。
|
另一种访问不同范围目标豆的方法是 你也可能找到 |
任意方法替换
一种不如查找方法注入有用的方法形式是能够 用另一种方法实现替换管理豆中的任意方法。你 可以放心跳过这部分,直到你真正需要这个功能。
基于XML的配置元数据,您可以使用替换方法元素变
用另一种方法替换现有方法实现,用于部署的 BEAN。考虑
以下类,其方法称为计算值我们想要覆盖的:
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.MethodReplacerInterface提供了新的方法定义,如下示例所示:
/**
* 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类型/>在<替换方法/>元素表示被覆盖方法的方法签名。签名
对于参数只有在方法被超载且有多个变体时才是必要的
存在于该类中。为方便起见,参数的类型字符串可以是
全限定类型名称的子串。例如,以下全部匹配java.lang.字符串:
java.lang.String
String
Str
因为参数数量通常足以区分每种可能的变量 选择,这个快捷方式可以节省大量输入,因为你只需输入 与参数类型匹配的最短字符串。
1.5. 豆子镜
当你创建豆子定义时,你是在创建一个实际实例的配方 该类的 bean 定义。Beans定义是 Recipe 很重要,因为它意味着像类一样,你可以创建许多对象 来自单一配方的实例。
你不仅可以控制各种依赖关系和配置值,这些都涉及
可以插入一个由特定BEAN定义创建的对象,同时也控制
根据特定BEAN定义创建的对象范围。这种方法如下
强大且灵活,因为你可以选择你所创建对象的范围
通过配置,而不是在 Java 中将对象的范围烘焙进去
班级层级。豆子可以定义为部署在多个范围之一。
Spring 框架支持六个作用域,其中四个仅在
你用的是Web-Aware。应用上下文.你也可以自定义瞄准镜。
下表描述了支持的范围:
| 范围 | 描述 |
|---|---|
(默认)将单个 Bean 定义映射到每个 Spring IoC 的单个对象实例 容器。 |
|
将单个豆定义作用域为任意数量的对象实例。 |
|
将单个 bean 定义作用域到单个 HTTP 请求的生命周期。那是
每个HTTP请求都有自己由单个豆子创建的豆子实例
定义。仅在具备网络功能的 Spring 环境中有效 |
|
将单个豆定义作用域到HTTP的生命周期 |
|
将单个豆子定义范围到一个生命周期 |
|
将单个豆子定义范围到一个生命周期 |
从 Spring 3.0 开始,线程作用域可用,但默认不注册。为
更多信息,请参见相关文档SimpleThreadScope(简易线程镜).
关于如何注册此或任何其他自定义示波器的说明,请参见“使用自定义示波器”。 |
1.5.1. 单例示波器
仅管理一个共享的单例豆,所有对豆子的请求 与该豆定义相符的ID会得到那个特定的豆子 实例被 Spring 容器返回。
换句话说,当你定义一个豆子定义并且它的作用域为 单例,Spring IoC 容器只创建一个对象实例 由那个豆子定义来定义。该单一实例存储在此类缓存中 Singleton 豆子,以及所有后续关于该命名豆子的请求和参考 返回缓存对象。下图展示了单例示波器的工作原理:
Spring 的单点豆概念不同于定义在 《四人帮(火杯)》图案书。GoF单例硬编码了 使得每个对象只创建一个特定类的一个实例 ClassLoader。Spring单例的范围最好用单个容器来描述 以及皮豆。这意味着,如果你为某一特定类定义一个豆子 单个 Spring 容器,Spring 容器只创建一个实例 该类的 bean 定义。单例示波器是默认的示波器 春季。要在XML中定义豆子为单例,可以定义豆子,如图所示 以下示例:
<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. 原型瞄准镜
豆子部署的非单例原型范围导致了新的
每当对该特定豆子的请求出现时,都会实例。也就是说,豆子
被注入到另一颗豆子中,或者你通过getBean()方法调用
容器。通常情况下,你应该对所有有状态的豆子和
无状态Beans的单例范围。
下图展示了Spring原型的示波器:
(数据访问对象 (DAO)通常不被配置为原型,因为典型的DAO不成立 任何对话状态。我们更容易重复利用 单元素图。)
以下示例将豆子定义为XML中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他示波器不同,Spring 不管理 原型豆。容器实例化、配置并以其他方式组装一个 原型对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 上都被调用 对象无论范围大小,原型中配置销毁 生命周期回调不被调用。客户端代码必须清理原型范围 物品并释放原型豆所持有的昂贵资源。为了获得 Spring 容器释放原型范围豆子所持有的资源,尝试使用 自定义豆后处理器,包含 需要清理的豆子。
在某些方面,春季容器作为原型瞄准镜豆的作用是
Java 的替代新增功能算子。之后的所有生命周期管理都必须如此
让客户处理。(关于春季Beans生命周期的详细信息)
容器,详见生命周期回调。)
1.5.3. 带有原型豆依赖的单例豆
当你使用依赖原型豆子的单例范围豆子时,请注意 依赖关系在实例化时被解析。因此,如果你对 a 进行依赖注入 原型范围豆被实例化为单例范围豆,新的原型豆被实例化 然后通过依赖注入单粒豆。原型实例是唯一的 实例 曾被提供给单例范围的豆子。
然而,假设你希望单例范围的豆子获取一个新的实例 在运行时反复以原型范围进行 Bean 的作。你不能对 a 原型镜豆子注入你的单粒豆子,因为这种注射只会发生 一次,当Spring容器实例化单例豆并解析时 并注入其依赖关系。如果你需要一个新的原型豆实例,请在 运行时多次,详见方法注入。
1.5.4. 请求、会话、应用和WebSocket作用域
这请求,会期,应用和Websocket仅提供示波器
如果你使用网页感知的 Spring应用上下文实现(例如:XmlWeb应用上下文).如果你用这些示波器搭配普通的Spring IoC容器,
例如:ClassPathXmlApplicationContext一非法州例外抱怨的
大约是一个未知的豆子镜。
初始网页配置
支持Beans在请求,会期,应用和Websocket层级(网页范围豆),一些次要初始配置为
在定义你的豆子之前必须这样做。(此初始设置并非必需
标准瞄准镜:单身 人士和原型.)
如何实现初始设置取决于你具体的 Servlet 环境。
如果你在 Spring Web MVC 中访问了 scopeed beans,实际上是在请求中
由Spring处理调度器服务,无需特殊设置。调度器服务已经暴露了所有相关状态。
如果你使用 Servlet 2.5 的网页容器,且请求在 Spring 之外处理调度器服务(例如,使用 JSF 或 Struts 时),你需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener.
对于 Servlet 3.0+,可以通过使用WebApplicationInitializer接口。或者,对于较旧的容器,可以添加以下声明
你的网页应用web.xml文件:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果你的听众设置有问题,可以考虑用Spring的RequestContextFilter.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>
调度器服务,RequestContextListener和RequestContextFilter所有这些都能完全正确
同样的步骤,也就是将 HTTP 请求对象绑定到线那就是维修
那个请求。这使得请求和会话范围的豆子更易获得
在通话链的下游。
请求范围
考虑以下 XML 的豆子定义配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器创建了一个新的实例登录作通过使用登录作为每一个HTTP请求定义BEAN。也就是说,登录作BEAN 在 HTTP 请求层级被限制。你可以更改内部
实例的状态可以随你意愿创建,因为其他实例
由同一人创造登录作Beans定义:不会看到这些状态变化。
它们针对个人需求而设。当请求完成处理后,
作用域为请求的 BEAN 会被丢弃。
当使用注释驱动组件或 Java 配置时,@RequestScope注解
可以用来将分量分配到请求范围。以下示例展示了如何
具体方法:
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
会话范围
考虑以下 XML 的豆子定义配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器创建了一个新的实例用户偏好通过使用用户偏好BEAN 对单个 HTTP 生命周期的定义会期.其他方面
词汇,用户偏好BEAN 实际上是 HTTP 的范围会期水平。如
使用请求范围的 BEANS,你可以更改 的实例的内部状态
你可以随意创建,知道其他HTTP也存在会期实例
使用由同一实例创建的实例用户偏好豆子定义:不要看到这些
状态变化,因为它们是针对单个HTTP 特有的会期.当
HTTP会期最终被丢弃,即对该特定HTTP的范围的豆子会期也被丢弃。
使用注释驱动组件或 Java 配置时,可以使用@SessionScope注释以将分量分配到会期范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
应用范围
考虑以下 XML 的豆子定义配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器创建了一个新的实例应用偏好通过使用应用偏好BEAN 定义一次,适用于整个 Web 应用。也就是说,应用偏好豆子在ServletContext并作为常规存储ServletContext属性。这与春季单粒豆有些相似,但
有两个重要区别:它是单例ServletContext,而非每春应用上下文(在任何给定的网页应用中可能有多个)
而且它实际上是暴露的,因此可以被看到为ServletContext属性。
使用注释驱动组件或 Java 配置时,可以使用@ApplicationScope注释以将分量分配到应用范围。这
以下示例展示了如何实现:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
Scoped Beans 作为依赖
Spring IoC 容器不仅管理你的对象(豆子)实例化, 还有协作者(或依赖关系)的布线。如果你想注射(对于 例如)将一个HTTP请求范围的豆子连接到另一个寿命更长的子豆,你可以 选择注入AOP代理代替被放大镜的豆子。也就是说,你需要注射 一个代理对象,它暴露了与作用域对象相同的公共接口,但 还要从相关作用域(如HTTP请求)中获取真实目标对象。 并将方法调用委托到真实对象上。
|
你也可以使用 宣告时 另外,带示波器的代理并不是访问短示波器豆子的唯一方式
生命周期安全时尚。你也可以声明你的注射点(即
构造者或设定者参数或自动接线字段)作为 作为扩展变体,你可以声明 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
豆子定义(参见选择创建代理类型及基于XML模式的配置)。
为什么Beans的定义会在请求,会期以及自定义瞄准镜
关卡要求<aop:scoped-proxy/>元素?
考虑以下单点豆的定义,并将其与
你需要为上述作用域定义什么(注意以下内容用户偏好目前的豆子定义尚不完整):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中,单例豆(用户管理器)被注入一个参考
HTTP会期-有望远镜的豆(用户偏好).这里的重点是用户管理器豆子是单元素集:每颗豆子被恰好实例化一次
容器及其依赖关系(此例中仅有一个,即用户偏好豆子)是
而且只注射过一次。这意味着用户管理器豆子只在
完全一样用户偏好对象(即最初注入的对象)。
当你把短寿命的有镜豆注射到一个
更长寿命的 scoped bean(例如,注入 HTTP会期-有范围的合作
豆子作为单粒豆的依赖)。相反,你需要一个单人用户管理器对象,以及 HTTP 的生命周期内会期,你需要一个用户偏好对象
该 是 HTTP 特有的会期.因此,容器创造了一个对象
暴露了与用户偏好理想情况下,类(一个
对象是用户偏好实例),它可以获取实数用户偏好对象来自作用域机制(HTTP 请求,会期,因此
第四)。容器将该代理对象注入到用户管理器豆子,也就是
却不知道用户偏好参考是代理。在这个例子中,当用户管理器实例调用依赖注入的方法用户偏好对象,实际上是在代理上调用一个方法。代理随后取出真实值用户偏好来自(此例中)HTTP 的对象会期以及
方法调用到检索的实数用户偏好对象。
因此,注入时需要以下(正确且完整的)配置请求-和会话范围将豆子变成协作对象,如下示例
显示:
<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/>element,创建一个基于CGLIB的类代理。
|
CGLIB 代理只拦截公共方法调用!不要调用非公开方法 在这样的代理上。它们不会被委派给实际的目标对象。 |
或者,你可以配置 Spring 容器来创建标准的 JDK
基于接口的代理,用于此类有望域的豆子,具体为false对于
这代理目标类属性<aop:scoped-proxy/>元素。使用 JDK
基于接口的代理意味着你不需要在
应用类路径以影响此类代理。然而,这也意味着 的类
Scoped 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. 定制瞄准镜
豆子的范围机制是可扩展的。你可以定义自己的
范围,甚至重新定义现有范围,尽管后者被认为是不良做法
而且你无法覆盖内置的单身 人士和原型范围。
创建自定义范围
要将自定义范围集成到 Spring 容器中,你需要实现org.springframework.beans.factory.config.Scope.接口,具体描述如下
部分。关于如何实现自己的示波器,请参见范围这些实现与 Spring Framework 本身以及范围Javadoc,
这更详细地解释了你需要实施的方法。
这范围接口有四种方法:从作用域获取对象,从中移除
瞄准镜,让他们被摧毁。
例如,会话范围实现会返回会话范围的豆子(如果 不存在时,方法在将豆子绑定为 会议内容,供今后参考)。以下方法从以下返回对象 基础范围:
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
例如,会话范围实现会从以下
底层会议。物品应该被归还,但你可以归还零如果
找不到指定名称的对象。以下方法将对象从
基本范围:
Object remove(String name)
fun remove(name: String): Any
以下方法注册一个回调,作用域应在 当作用波中指定的对象被销毁时,或是被摧毁:
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
有关销毁回调的更多信息,请参阅 javadoc 或 Spring 范围实现。
以下方法获取底层范围的会话标识符:
String getConversationId()
fun getConversationId(): String
每个范围的标识符都不同。对于会话范围的实现,这是 标识符可以是会话标识符。
使用自定义瞄准镜
在你编写并测试一个或多个自定义软件之后范围实现,你需要做
春季容器知道你的新瞄准镜。以下方法是核心
注册新方法范围使用春季容器:
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
该方法声明为ConfigurableBeanFactory该接口是可用的
通过豆子工厂大部分混凝土上的财产应用上下文这些实现与 Spring 一起发布。
第一个论点registerScope(..)方法是与 相关的唯一名称
一个瞄准镜。Spring 容器中此类名称的例子有单身 人士和原型.第二个论证registerScope(..)方法是一个实际实例
习俗范围你希望注册和使用的实现。
假设你写下你的定制范围实现,然后按图示注册
下一个例子。
下一个例子使用SimpleThreadScope(简易线程镜),该项目包含在春季中,但不包含在内
默认注册。你自己的定制说明也一样范围实现。 |
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
然后你可以创建符合自定义范围规则的豆子定义范围如下:
<bean id="..." class="..." scope="thread">
有个习俗范围实施时,你不局限于程序注册
范围的。你也可以这样做范围声明式注册,通过使用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/>在<豆子>声明工厂豆实现时,作用域是工厂豆本身,而不是对象
返回getObject(). |
1.6. 定制豆子的性质
Spring 框架提供了多种界面,你可以用来自定义自然 一颗豆子。本节将其分为以下几类:
1.6.1. 生命周期回调
为了与容器对豆子生命周期的管理交互,你可以实现
Spring初始化Bean和一次性豆接口。容器呼叫afterPropertiesSet()对于前者和摧毁()后者让豆子
在初始化和摧毁你的豆子时执行特定动作。
|
The JSR-250 如果你不想用JSR-250注释,但又想删除
考虑耦合 |
在内部,Spring Framework 使用豆子后处理器实现以处理任意
它可以找到并调用相应方法的回调接口。如果你需要定制
Spring 默认不具备的功能或其他生命周期行为,你可以
实现a豆子后处理器你自己。更多信息请参见集装箱扩展点。
除了初始化和销毁回调外,Spring 管理的对象还可以
同时实现生命周期接口,使这些对象能够参与
启动和关闭流程,由容器自身生命周期驱动。
生命周期回调接口在本节中进行了介绍。
初始化回调
这org.springframework.beans.factory.InitializingBean界面让一个豆子
在容器设置完所有必要的属性后,执行初始化工作
豆。这初始化Bean接口指定了一种单一方法:
void afterPropertiesSet() throws Exception;
我们建议您不要使用初始化Bean界面,因为它
这不必要地将代码与春季绑定。或者,我们建议使用
这@PostConstruct注释或
指定POJO初始化方法。对于基于XML的配置元数据,
你可以使用初始化方法属性以指定具有空值的方法名称
无争议的签名。通过 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接口允许
当装有豆子的容器被摧毁时,豆子会获得回电。这一次性豆接口指定了一种单一方法:
void destroy() throws Exception;
我们建议您不要使用一次性豆回调接口,因为它
这不必要地将代码与春季绑定。或者,我们建议使用
这@PreDestroy注释或
指定一个由BEAN定义支持的通用方法。基于XML的应用
配置元数据,你可以使用销毁方法属性<豆/>.
通过 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 耦合。
你可以分配销毁方法a的属性<豆子>元素A特殊(推测)值,指示 Spring 自动检测公共关闭或关闭对特定Beans进行方法。(任何实现java.lang.自动关闭或java.io.可关闭因此会匹配。)你也可以设置
这场特别节目(推测)值默认销毁方法a的属性<豆子>将此行为应用于整组豆子(参见默认初始化和销毁方法)。注意这是
Java 配置的默认行为。 |
默认初始化和销毁方法
当你写初始化和销毁方法回调时,这些方法不使用
Spring专用初始化Bean和一次性豆回调接口,你
通常编写的方法名称如下init(),initialize(),处置(),因此
上。理想情况下,这类生命周期回调方法的名称应在
项目要求所有开发者使用相同的方法名称并确保一致性。
你可以配置 Spring 容器“查找”命名初始化并销毁
每颗豆子上都有回调方法的名称。这意味着你作为一个应用程序
开发者可以编写你的应用类,并使用一个叫做的初始化回调init(),无需配置init-method=“init”每个豆子的属性
定义。Spring IoC 容器在创建 bean 时调用该方法(以及
根据上述标准生命周期回调合同。该功能还强制执行一致的命名规范
初始化和销毁方法回调。
假设你的初始化回调方法被命名为init()而你的毁灭
回调方法被命名摧毁().你的类则类似于
以下示例:
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.")
}
}
}
然后你可以用这个类来做类似以下的豆子:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
关于默认初始化方法属性在顶层<豆子/>元素
属性使 Spring IoC 容器识别一个名为开始在豆子上
类作为初始化方法回调。当豆子被创造并组装时,如果
BEAN 类有这样的方法,在适当的时候调用。
你也可以用类似的方式配置销毁方法回调(在 XML 中),通过使用默认销毁方法属性在顶层<豆子/>元素。
当已有的Beans已有以方差命名的回调方法时
根据该惯例,你可以通过指定(在 XML 中)来覆盖默认值
方法名称,使用初始化方法和销毁方法属性<豆/>本身。
Spring 容器保证调用配置好的初始化回调
在豆子被提供所有依赖后立即生效。因此,初始化
回调是在原始豆子引用上调用的,这意味着AOP拦截器等
第四个尚未应用于豆子。目标豆首先完全成型,且
然后应用一个AOP代理及其拦截链。如果目标
Bean和代理是独立定义的,你的代码甚至可以和RAW交互
目标豆,绕过代理。因此,应用
拦截机与开始方法,因为这样做会耦合
将Bean目标锁定到其代理或拦截者,并在代码中留下奇怪的语义
直接与生目标豆相互作用。
结合生命周期机制
截至春季2.5,你有三种控制Beans生命周期行为的选项:
-
习惯
init()和摧毁()方法 -
这
@PostConstruct和@PreDestroy附注.你可以结合这些机制来控制特定的咖啡豆。
如果为一个豆子配置了多个生命周期机制,且每个机制
配置为不同的方法名称,然后每个配置的方法在
订单列于此注释后。然而,如果配置了相同的方法名称——例如,init()对于初始化方法——对于多个生命周期机制,
该方法只需运行一次,如前文所述。 |
为同一豆子配置的多种生命周期机制,具有不同的 初始化方法的调用方式如下:
-
注释的方法
@PostConstruct -
afterPropertiesSet()定义如下初始化Bean回调接口 -
自定义配置
init()方法
销毁方法的调用顺序相同:
-
注释的方法
@PreDestroy -
摧毁()定义如下一次性豆回调接口 -
自定义配置
摧毁()方法
启动与关机回拨
这生命周期接口定义了任何拥有自身对象的基本方法
生命周期要求(例如启动和停止某个后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何由 Spring 管理的对象都可以实现生命周期接口。然后,当应用上下文它自身接收起始和停止信号(例如,停止/重启
运行时的场景),它将这些调用串联到所有生命周期实现
在该语境下定义。它通过委派给生命周期处理器显示
以下列表:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意生命周期处理器本身是 的扩展生命周期接口。它还增加了两种对上下文刷新的反应方法
并且关闭了。
|
注意正则 另外,请注意,停止通知并不保证会先于销毁。
正常关机时,全部 |
启动和关机调用的顺序可能很重要。如果“依赖”
存在任何两个对象之间的关系,依赖侧从其之后开始。
依赖,而它在依赖之前就停止了。然而,有时直接的
依赖关系尚不清楚。你可能只知道某个类型的对象应该开始
在其他类型的物体出现之前。在这种情况下,SmartLifecycle接口定义
另一种选择,即getPhase()其超级接口上定义的方法,相 控.以下列表展示了相 控接口:
public interface Phased {
int getPhase();
}
以下列表展示了SmartLifecycle接口:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
开始时,相位最低的物体先开始。停车时,
顺序相反。因此,一个实现SmartLifecycle和
谁的getPhase()方法返回Integer.MIN_VALUE将成为最早开始的队伍之一
也是最后一个停下的。在光谱的另一端,相位值为Integer.MAX价值表示该对象应最后启动并停止
首先(很可能是因为它需要其他进程在运行)。在考虑
相位值,同时也要知道任何“正常”的默认相位生命周期不实现的对象SmartLifecycle是0.因此,任何
负相位值表示物体应在这些标准之前开始
组件(并在它们之后停止)。反之,任何正相位值也成立。
停止方法定义为SmartLifecycle接受回电。任何
实现时必须调用该回调run()在该实现后的方法
关闭流程已完成。这在必要时实现异步关机,因为
默认实现生命周期处理器接口默认生命周期处理器等待该对象组的超时值
在每个阶段中调用回应。默认的每相超时为30秒。
你可以通过定义一个名为生命周期处理器在上下文中。如果你只是想修改超时,
定义如下内容即可:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,该生命周期处理器接口定义了回调方法
这也让人耳目一新,也为背景画上了句号。后者推动了停摆
仿佛停止()曾被明确调用,但当上下文
关闭。而“刷新”回调则启用了另一个功能SmartLifecycle豆。当上下文被刷新(所有对象都已刷新)时
实例化并初始化),该回调被调用。在那个时候,
默认生命周期处理器检查每个返回的布尔值SmartLifecycle对象的isAutoStartup()方法。如果true,该对象为
从那个点开始,而不是等待上下文的明确调用,或者
自己开始()方法(与上下文刷新不同,上下文开始不会发生
对于标准上下文实现,自动执行)。这阶段值和任意
“依赖关系”决定了前述的启动顺序。
在非网页应用中优雅关闭 Spring IOC 容器
|
本节仅适用于非网页应用。Spring 的网络版 |
如果你在非网页应用环境中使用 Spring 的 IoC 容器(对于 例如,在富客户端桌面环境中),注册一个关闭钩子 JVM。这样做可以确保顺利关机并调用相应的销毁方法 单点豆,这样所有资源都会被释放。你仍然必须配置 并且正确实现这些摧毁回调。
要注册关闭钩子,请调用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.应用上下文感知和豆名觉察
当应用上下文创建一个实现org.springframework.context.ApplicationContextAware接口,实例被提供
并提及应用上下文.以下列表展示了定义
关于应用上下文感知接口:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,豆子可以通过程序作应用上下文创造了他们,
通过应用上下文接口或通过将引用投射到已知的
该接口的子类(例如ConfigurableApplicationContext,这揭示了
附加功能)。其中一种用途是程序化检索其他豆子。
有时这种能力很有用。不过,一般来说,你应该避免,因为
它将代码与 Spring 结合,不遵循控制反转风格,
其中合作者作为Beans的属性提供。其他方法应用上下文提供文件资源访问,发布应用事件,
以及访问消息源.这些额外功能详见附加功能应用上下文.
自动接线是获得应用上下文.传统 构造 函数和byType自动接线模式
(如Autowiring Collaborators中所述)可以提供类型的依赖关系应用上下文对于构造函数参数或设定器方法参数,
分别。为了更灵活,包括自动接线场和
多参数方法,使用基于注释的自动接线功能。如果你愿意,
这应用上下文被自动接线到字段、构造函数参数或方法中
期望应用上下文类型为字段,构造函数,或
相关方法具有@Autowired注解。更多信息请参见用@Autowired.
当应用上下文创建一个实现org.springframework.beans.factory.BeanNameAware该类配备了
指的是其关联对象定义中定义的名称。以下列表
显示了 BeanNameAware 接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调是在正常Beans性质的人口生成后,但在
初始化回调,例如InitializingBean.afterPropertiesSet()或者是一种习俗
init-方法。
1.6.3. 其他意识到的接口
此外应用上下文感知和豆名觉察(前文讨论),
春季提供多种选择意识到的回调接口,让豆子指向容器
它们需要一定的基础设施依赖。一般来说,这个名字意味着
依赖类型。下表总结了最重要的意识到的接口:
| 名称 | 注入依赖 | 解释如下...... |
|---|---|---|
|
声明 |
|
|
事件发布者 |
|
|
类加载器过去用来加载Beans。 |
|
|
声明 |
|
|
申报豆的名字。 |
|
|
资源适配器 |
|
|
定义了 Weaver 用于加载时的处理类定义。 |
|
|
用于解析消息的配置策略(支持参数化和 国际化)。 |
|
|
春季JMX通知发布者。 |
|
|
配置了加载器以实现低层资源访问。 |
|
|
当前 |
|
|
当前 |
再次注意,使用这些接口会把你的代码绑定到 Spring API,而不是 遵循反转控制风格。因此,我们推荐他们用于基础设施 需要程序化访问容器的豆子。
1.7. Beans定义继承
豆子定义可以包含大量配置信息,包括构造函数 参数、属性值以及容器特定的信息,例如初始化 方法、静态工厂方法名称等。婴儿豆定义 来自父定义的配置数据。子定义可以覆盖部分 或者根据需要添加其他变量。使用父豆和子豆定义可以节省很多 打字。实际上,这是一种模板化。
如果你与应用上下文程序界面,Child Bean
定义用ChildBean定义类。大多数用户无法正常工作
他们能站在这个层面。相反,它们在类中声明性地配置豆定义
例如:ClassPathXmlApplicationContext.当你使用基于XML的配置时
元数据,你可以通过使用父母属性
指定该属性的父豆值。以下示例展示了如何
具体方法:
<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 | 注意父母属性。 |
子豆定义使用父定义中的Beans,如果没有 是 但也可以覆盖。在后一种情况下,子Beans必须是 与父节点兼容(即必须接受父节点的属性值)。
子豆定义继承作用域、构造子参数值、属性值,以及
方法会覆盖父方法,并可选择添加新值。任何作用域,初始化
方法、销毁方法,或静态的你指定的工厂方法设置
覆盖相应的父设置。
其余设置总是取自子定义:取决于, 自动布线模式、依赖检查、单例和懒初始化。
前面的例子通过以下方式明确标记母豆定义为抽象的
这抽象属性。如果父定义没有明确指定类,则
标记母豆定义为抽象是必需的,如下示例
显示:
<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>
母豆不能单独实例化,因为它不完整,而它确实是
也明确标记为抽象.当定义为抽象是的
仅可作为纯模板豆定义使用,作为 的父定义
儿童定义。尝试使用这样的抽象单独看母豆,通过引用
作为另一个豆子的参考属性或做显式getBean()与
父豆 ID 返回错误。同样,容器内部preInstantiateSingletons()方法忽略豆子定义为
抽象。
应用上下文默认预实例化所有单例。因此,它
重要的是(至少对于单粒豆来说),如果你有一个(母)豆子定义
你打算只用作模板,这个定义指定了一个类,即
必须确保将抽象属性设置为true,否则应用程序
上下文实际上会(尝试)预实例化抽象豆。 |
1.8. 容器延伸点
通常,应用开发者不需要子类应用上下文实现类。相反,Spring IoC 容器可以通过插电来扩展
特殊集成接口的实现。接下来的几个章节将介绍这些
集成接口。
1.8.1. 通过使用 a 来定制豆子豆子后处理器
这豆子后处理器接口定义了你可以实现的回调方法
提供你自己的(或覆盖容器默认)实例化逻辑和依赖
分辨率逻辑,等等。如果你想在之后实现一些自定义逻辑
Spring container 完成了实例化、配置和初始化 bean,你可以
插入一个或多个自定义插件豆子后处理器实现。
你可以配置多个豆子后处理器实例,你可以控制顺序
其中这些豆子后处理器实例通过设置次序财产。
只有当豆子后处理器实现命令接口。如果你自己写的豆子后处理器,你应该考虑实现
这命令界面也一样。更多细节请参见豆子后处理器和命令接口。另见注释
上编程
注册豆子后处理器实例.
|
要更改实际的豆子定义(即定义豆子的蓝图),
你需要使用 |
这org.springframework.beans.factory.config.BeanPostProcessor接口包括
正好有两种回调方法。当此类类被注册为后处理器时
对于容器创建的每个豆子实例,
后处理器在容器之前都会收到回调
初始化方法(例如InitializingBean.afterPropertiesSet()或者任何
宣布开始方法)被调用,并且在任何 bean 初始化后调用。
后处理器可以对豆子实例采取任何作,包括忽略
完全是回溯。豆后处理器通常检查回调接口,
或者它可能用代理包裹一颗豆子。一些春季AOP基础设施课程包括
以BEAN后处理器的形式实现,以提供代理封装逻辑。
一应用上下文自动检测定义在
实现豆子后处理器接口。这应用上下文将这些豆子注册为后处理器,以便调用
以后,关于豆子的创造。Bean后处理器可以部署在容器中
和其他豆子一样。
请注意,在声明 a豆子后处理器通过使用@Bean工厂方法
配置类,工厂方法的返回类型应为实现
类别本身,或者至少是org.springframework.beans.factory.config.BeanPostProcessor接口,清楚地表明了该豆子的后处理特性。否则,应用上下文在完全创建之前无法按类型自动检测。
自豆子后处理器需要提前实例化才能应用于
在上下文中,其他豆子的初始化至关重要。
|
程序化注册 虽然推荐的方法是豆子后处理器实例豆子后处理器注册方式如下应用上下文自动检测(如前所述),你可以注册它们
程序上对抗ConfigurableBeanFactory通过使用addBeanPostProcessor方法。这在你需要评估条件逻辑之前非常有用
注册,甚至用于层级结构中跨上下文复制豆后处理器。
但请注意豆子后处理器程序添加的实例不尊重
这命令接口。在这里,登记顺序决定了顺序
处决的。还要注意豆子后处理器实例程序注册
无论有没有,都会先于通过自动检测注册的人员处理
明确的顺序。 |
豆子后处理器实例与AOP自动代理实现 对于任何这样的豆子,你应该会看到一条信息日志信息: 如果你的咖啡豆接线 |
以下示例展示了如何编写、注册和使用豆子后处理器实例
在应用上下文.
举例:Hello World,豆子后处理器-风格
第一个例子说明了基本用法。示例展示了一个自定义豆子后处理器调用toString()每个豆子的计算方法为
它由容器创建,并将生成字符串打印到系统控制台。
以下列表展示了该习俗豆子后处理器实现类定义:
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
}
}
如下豆元素使用实例化追踪豆后处理器:
<?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>
注意实例化追踪豆后处理器仅仅是定义的。事实并非如此
甚至有个名字,而且因为它是豆子,可以像注入任何依赖一样
另一颗豆子。(前述配置也定义了由Groovy支撑的豆子
脚本。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. 用 a 自定义配置元数据豆工厂后处理
我们接下来要关注的扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor.语义
该接口与豆子后处理器,只有一个大调
差异:豆工厂后处理作于 BEAN 配置元数据。
也就是说,Spring IoC 容器允许豆工厂后处理请阅读
配置元数据,并在容器实例化前可能更改它
除了其他豆子豆工厂后处理实例。
你可以配置多个豆工厂后处理你可以在 中控制顺序
这些豆工厂后处理实例通过设置次序财产。
然而,只有当豆工厂后处理实现命令接口。如果你自己写的豆工厂后处理你应该
考虑实现命令界面也一样。参见 javadoc豆工厂后处理和命令更多详情请见Interfaces。
|
如果你想更改实际的豆子实例(即创建的对象)
然后你需要使用 也 |
当豆子工厂后处理器在应用上下文,以便对配置元数据进行更改,使得
定义容器。春季包含多个预设的Beans工厂
后处理,例如PropertyOverrideConfigurer和PropertySourcesPlaceholderConfigurer.你也可以用自定义豆工厂后处理例如,注册自定义属性编辑器。
一应用上下文自动检测任何部署到该系统中的豆子
实现豆工厂后处理接口。它用这些豆子作为豆子工厂
在适当的时候,后处理器。你可以部署这些后处理器豆子,作为
你会选择任何其他豆子。
如同豆子后处理器S,通常你不想配置豆工厂后处理S代表懒惰初始化。如果没有其他豆子引用Bean(工厂)后处理,该后处理器根本不会被实例化。
因此,标记为懒初始化将被忽略,且Bean(工厂)后处理即使你设置了默认-懒惰-初始化归属为true关于你的声明<豆子 />元素。 |
示例:类名替换PropertySourcesPlaceholderConfigurer
你可以使用PropertySourcesPlaceholderConfigurer外部化房产价值
通过使用标准 Java 在独立文件中的豆子定义性能格式。
这样做使部署应用的人能够针对特定环境进行定制
属性,如数据库的URL和密码,无需复杂性或风险
修改容器的主XML定义文件。
考虑以下基于XML的配置元数据片段,其中数据来源占位符定义为:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
示例展示了从外部配置的属性性能文件。运行时,
一个PropertySourcesPlaceholderConfigurer应用到替换某些元数据时
数据源的属性。替换的值指定为
形式${property-name},遵循Ant和log4j以及JSP EL的风格。
实际的数值来自标准Java中的另一个文件性能格式:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
因此,${jdbc.username}字符串在运行时被替换为值 'sa',
其他与属性文件中键匹配的占位值同样适用。
这PropertySourcesPlaceholderConfigurer在大多数属性中检查占位符和
豆子定义的属性。此外,你还可以自定义占位前缀和后缀。
与上下文命名空间在春季2.5引入,你可以配置属性占位符
配备专用配置元素。你可以提供一个或多个地点作为
逗号分隔列表位置属性,如下例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
这PropertySourcesPlaceholderConfigurer不仅要查找性能你指定的文件。默认情况下,如果无法在指定的属性文件中找到属性,
它与Spring作对环境性质与正则Java系统性能。
|
你可以使用
如果该类在运行时无法解析为有效类,则解析
在即将被创建时失败,即 |
示例:该PropertyOverrideConfigurer
这PropertyOverrideConfigurer,另一款豆子工厂的后处理器,类似于PropertySourcesPlaceholderConfigurer但与后者不同的是,原始定义
Beans属性可以设置默认值或完全没有值。如果是覆盖性能文件中没有某个 bean 属性的条目,默认情况下
使用上下文定义。
注意豆子定义不自觉被覆盖,因此不被覆盖
从XML定义文件中立刻就能看出覆盖配置器的使用情况
使用。在多重情况下PropertyOverrideConfigurer定义不同
对于同一Beans属性,最后一个获胜,因为覆盖机制。
属性文件配置行的格式如下:
beanName.property=value
以下列表展示了该格式的一个示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
该示例文件可与包含名为数据来源该Drivers和网址性能。
只要路径的每个组件都支持复合属性名称
但被覆盖的最终属性已经是非空的(大概已被初始化)
由制造商决定)。在以下示例中,萨米的属性鲍勃的属性佛瑞德的属性汤姆豆
设置为标量值123:
tom.fred.bob.sammy=123
| 指定的覆盖值始终为字面值。它们没有被翻译成 豆豆的致敬。当XML豆中的原始值时,这一惯例同样适用 定义指定了豆子的引用。 |
与上下文在春季2.5引入的命名空间中,可以配置
用专用配置元素覆盖属性,如下示例所示:
<context:property-override location="classpath:override.properties"/>
1.8.3. 用工厂豆
你可以实现org.springframework.beans.factory.FactoryBean对于对象的接口
本身就是工厂。
这工厂豆接口是可插拔到 Spring IoC 容器的节点
实例化逻辑。如果你有复杂的初始化代码,那更准确地表达为
Java 与(可能)冗长的 XML 不同,你可以自己创建工厂豆,在该类中写入复初始化,然后代入你的
习惯工厂豆放进容器里。
这FactoryBean<T>接口提供三种方法:
-
T getObject()返回该工厂创建的对象实例。这 实例可以共享,取决于该工厂是否返回单例 或者原型。 -
布尔值 isSingleton():返回true如果这样工厂豆返回单元素集或false否则。该方法的默认实现会返回true. -
Class<?> getObjectType(): 返回由getObject()方法 或零如果事先不知道类型。
这工厂豆该概念和界面在春季的多个地方被采用
框架。超过50种实现工厂豆与春之星的接口舰
本身。
当你需要向容器请求实际情况时工厂豆实例本身,而非
它产出的豆子,豆子的前缀身份证当 时 & 符号为 ()
将&getBean()方法应用上下文.所以,对于给定的工厂豆带有身份证之我的豆子调用getBean(“我的豆子”)在容器上返回
乘积工厂豆,而调用getBean(“&myBean”)返回工厂豆实例本身。
1.9. 基于注释的容器配置
XML 设置的替代方案是基于注释的配置,它依赖于
用于连接组件的字节码元数据,而不是用括号声明。
开发者不是用XML来描述豆子线路,而是移动配置
通过对相关类、方法或
现场宣言。如同所述示例:该AutowiredAnnotationBeanPostProcessor用
一个豆子后处理器结合注释,是扩展
春季IoC容器。例如,Spring 2.0引入了强制执行的可能性
要求的性质@Required注解。Spring
2.5版本使得我们能够遵循同样的总体方法来驱动Spring的依赖
注射。本质上,@Autowired注释功能与
在《Autowiring Collaborators》中有描述,但控制更细致且更宽
适用性。春季2.5还增加了对JSR-250注释的支持,例如:@PostConstruct和@PreDestroy.春季3.0增加了对JSR-330(依赖性)的支持
Java的注入)包含在javax.inject例如包@Inject和@Named.有关这些注释的详细信息可在相关章节中找到。
|
注释注入在XML注入之前进行。因此,XML 配置 覆盖两种方法中连接属性的注释。 |
一如既往,你可以将后处理器注册为单独的豆定义,但它们
也可以通过在基于XML的Spring中包含以下标签来隐式注册
配置(注意包含上下文命名空间):
<?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/>element 隐式注册以下后处理器:
|
|
1.9.1. @Required
这@Required注释适用于豆属性设置器方法,如下所示
例:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Required
lateinit var movieFinder: MovieFinder
// ...
}
该注释表示受影响的Beans属性必须填充于
配置时间,通过豆定义中的显式属性值,或通过
自动接线。如果受影响的Beans属性尚未被
填充。这允许急切且明确地失败,避免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对传统二传方法的注释,
如下示例所示:
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
// ...
}
|
确保你的目标组件(例如, 对于通过类路径扫描找到的XML定义的豆子或组件类,容器
通常一开始就知道具体的混凝土类型。然而,对于 |
你也可以指示Spring提供所有特定类型的豆子应用上下文通过添加@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>
// ...
}
|
你的目标豆可以实现 你可以声明 注意标准 |
甚至打字地图只要预期的密钥类型为字符串.
映射值包含所有预期类型的豆子,键则包含
对应的Beans名称,如下示例所示:
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>
// ...
}
默认情况下,当某一矿井没有匹配的候选豆子时,自动接线会失败 注射点。对于声明的数组、集合或映射,至少有一个 匹配元素是预期的。
默认行为是将带注释的方法和字段视为必须的
依赖。你可以像下面的例子所示改变这种行为,
使框架能够跳过不可满足的注入点,通过标记为
非必需(即通过设置必填属性@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
// ...
}
如果非必需方法的依赖(或其其中一个)则不会被调用 在多个参数的情况下,依赖关系不可用。非必填字段则会 在这种情况下根本不会被填充,默认值保持原位。
注入构造函数和工厂方法参数是一个特例,因为必填属性@Autowired由于Spring的构造器,其含义略有不同
解析算法可能涉及多个构造函数。构造 函数
工厂方法参数默认是必需的,但有少数特殊参数
单构造器场景中的规则,如多元素注入点(数组,
集合,映射)如果没有匹配的豆子,则解析为空实例。这
允许一个统一的实现模式,使所有依赖都可以在
唯一的多参数构造器——例如,声明为单一公共构造器
没有@Autowired注解。
|
每个给定Beans中只有一个构造器可以声明 这 |
或者,你也可以表达某个依赖的非必需性质
通过 Java 8java.util.Optional,如下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从 Spring Framework 5.0 开始,你还可以使用@Nullable注释(任何类型的)
在任何包中——例如,javax.annotation.Nullable或仅仅是杠杆
Kotlin 内置的零安全支持:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
class SimpleMovieLister {
@Autowired
var movieFinder: MovieFinder? = null
// ...
}
你也可以使用@Autowired对于已知且可解析的接口
依赖:豆子工厂,应用上下文,环境,ResourceLoader,应用事件发布者和消息源.这些接口及其扩展
接口,例如ConfigurableApplicationContext或资源模式解析器是
自动解决,无需特殊设置。以下示例是自动线路
一应用上下文对象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var context: ApplicationContext
// ...
}
|
这 |
1.9.3. 基于注释的自动配线微调@Primary
由于按类型自动接线可能导致多个候选,通常需要
对选拔过程的控制权更多。实现这一点的一种方式是用Spring的@Primary注解。@Primary表示应给予特定豆子
当多个豆子都可能被自动接线到单一值时的偏好
Dependency。如果候选人中恰好存在一个主豆,则该豆为
自动接线值。
考虑以下构型,定义firstMovieCatalog作为
主要电影目录:
@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 { ... }
// ...
}
在上述构型中,如下电影推荐是自动接线的firstMovieCatalog:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
Beans的相应定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 带限定符的注释自动配线微调
@Primary是按类型使用自动布线的有效方法,当
主要候选人可以确定。当你需要对选拔过程有更多控制权时,
你可以用Spring的@Qualifier注解。你可以关联限定词值
对于特定参数,缩小类型集合,使得特定的豆子为
为每个论点选择。在最简单的情况下,这可以是一个简单的描述值,如
如下例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
你也可以指定@Qualifier对单个构造函数参数的注释,或者
方法参数,如下例所示:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
以下示例展示了对应的Beans定义。
<?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 | 带有主要限定符值与构造子参数连接,使得
具有相同限定值。 |
| 2 | 带有行动限定符值与构造子参数连接,使得
具有相同限定值。 |
对于备选匹配,豆子名称被视为默认的限定值。所以,你
可以定义豆子,满足身份证之主要取代嵌套的限定词元素,选择了
结果相同。然而,尽管你可以用这个惯例来指代
具体的Beans名称,@Autowired本质上是关于类型驱动的注入,满足
可选语义限定词。这意味着即使有豆子的名字,也要保持限定值
备用时,类型匹配的语义总是逐渐缩小。他们不会
语义上表达对唯一豆子的引用身份证. 良好的限定词值为主要或EMEA地区或持续,表达特定成分的特征,这些特征与豆子无关身份证,在匿名豆的情况下可以自动生成定义,如前述例子中的定义。
限定符也适用于类型化集合,如前所述——例如,对布景<电影目录>. 在这种情况下,所有匹配的豆子,根据声明的限定符,都被注入为一个集合。这意味着限定词不必是 独特。 相反,它们构成了过滤条件。例如,你可以定义 倍数电影目录具有相同限定词值“action”的豆子,所有这些注入到布景<电影目录>注释为@Qualifier(“行动”).
|
允许限定符值在类型匹配候选中选择目标豆名,不需要 |
话虽如此,如果你打算用名称表达注释驱动注入,请不要主要使用@Autowired,即使它能够通过豆名从类型匹配候选中选择。类型匹配的候选者。相反,使用JSR-250@Resource注释,即语义上定义为通过唯一名称识别特定目标组件,其中声明的类型对匹配进程无关。@Autowired具有不同的语义:在按类型选择候选豆子后,指定的字符串限定符值仅考虑在这些类型选择的候选中(例如,匹配帐户与标有相同限定标签的豆子的资格赛)。
对于本身被定义为集合的豆子,地图,或数组类型,@Resource是一个很好的解决方案,用唯一名称指代特定的集合或数组豆。话虽如此,从4.3版本开始,你可以匹配集合,地图,以及通过Spring的数组类型@Autowired类型匹配算法也一样,只要元素类型信息在中保持@Bean返回类型签名或集合继承层级。在这种情况下,你可以使用限定符值在同类型集合中进行选择,如前一段所述。
截至4.3,@Autowired还考虑注入的自指(即引用回到当前注入的豆子)。注意自注入是一种备用。对其他组件的规则依赖始终具有优先权。从这个意义上说,自引用不参与常规候选选择,因此属于特定,绝不主。相反,它们总是以最低优先级出现。实际上,你应仅在最后手段中使用自指(例如,对于通过豆的交易代理调用同一实例上的其他方法)。在这种情况下,考虑将受影响的方法分解到一个独立的委托豆中。或者,你可以使用@Resource,可以获得一个代理回到当前豆子通过其唯一名称。
|
试图注入从中获得的结果 |
@Autowired适用于字段、构造子和多参数方法,允许通过参数级别的限定符注释进行缩小范围。相比之下,@Resource仅支持字段和带有单参数的 bean 属性设置器方法。因此,如果你的注入目标是构造函数或多参数方法,应坚持使用限定符。
你可以自定义限定词注释。为此,定义一个注释和
提供@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
}
// ...
}
接下来,你可以提供候选Beans定义的信息。你可以添加<资格赛/>标签作为<豆/>然后指定类型和值以匹配你自定义的限定符注释。类型与
注释的完全限定类别名称。或者,作为方便,如果没有风险
存在名称冲突,你可以用简称。以下示例
展示了两种方法:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在“类路径扫描与管理组件”中,你可以看到基于注释的替代方案 以XML形式提供限定词元数据。具体请参见“提供带有注释的限定词元数据”。
在某些情况下,使用无值的注释可能就足够了。这可以是 当注释具有更通用的目的且可广泛应用时非常有用 有几种不同类型的依赖关系。例如,你可以提供一个离线 该目录在没有互联网连接时仍可搜索。首先,定义一下 简单注释,如下示例所示:
@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 class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
| 1 | 该元素指定了限定词。 |
你也可以自定义限定符注释,接受
对单纯的加法或替代值属性。如果多个属性值为
然后在要自动接线的字段或参数上指定,豆子定义必须匹配
所有此类属性值都被视为自动线候选。举个例子,
考虑以下注释定义:
@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)
在这种情况下格式是一个枚举,定义如下:
public enum Format {
VHS, DVD, BLURAY
}
enum class Format {
VHS, DVD, BLURAY
}
待自动接线的字段会用自定义限定符注释并包含数值
对于这两个属性:类型和格式,如下示例所示:
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
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
// ...
}
最后,豆子定义应包含匹配的限定符值。这个例子
还展示了你可以用Beans的元属性来代替<资格赛/>元素。如果有,那<资格赛/>元素及其属性
但自动接线机制会退回到<元/>如果不存在此类限定词,则标记,如前两个Beans定义中
以下示例:
<?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()
}
假设前述豆子实现了一个通用接口,(即,Store<String>和Store<Integer>你可以@Autowire这商店接口,通用为
作为限定词使用,如下示例所示:
@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
自动配线列表时也适用通用限定词,地图实例和数组。这
以下示例:自动接线 通用列表:
// 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是豆工厂后处理这样你就可以注册自定义的限定符
注释类型,即使没有用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通过以下方式确定自动线候选:
-
这
自动线候选每个豆子定义的价值 -
任何
默认自动线候选在<豆子/>元素 -
存在
@Qualifier注释及任何自定义注册注释 其中CustomAutowireConfigurer
当多个豆子都符合自动线候选条件时,“主豆”的判定为
具体如下:如果候选中恰好有一个Beans定义具有主要属性设置为true,被选中。
1.9.7. 注入@Resource
Spring 也支持使用 JSR-250 进行喷射@Resource注解
(javax.annotation.Resource)在字段或豆属性设置器方法上。
这是Java EE中常见的模式:例如,在JSF管理的豆子和JAX-WS中
端点。Spring 也支持这种模式用于 Spring 管理的对象。
@Resource取一个名称属性。默认情况下,Spring 将该值解释为
豆子的名字要注入。换句话说,它遵循了名称语义,
如下例所示:
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. |
如果没有明确指定名称,默认名称将由字段名称或
二传方法。对于字段,它采用字段名称。以二传法为例,
它采用了Bean的物业名称。以下示例将有豆子
叫电影查找器注入其二传法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
class SimpleMovieLister {
@Resource
private lateinit var movieFinder: MovieFinder
}
注释中提供的名字通过应用上下文其中CommonAnnotationBeanPostProcessor他已经意识到了。
如果你配置 Spring 的,可以通过 JNDI 解析这些名称SimpleJndiBeanFactory明确地。不过,我们建议你依赖默认行为,
利用 Spring 的 JNDI 查找功能来保持间接层级。 |
在@Resource使用方式未明确指明名称,类似
自@Autowired,@Resource找到一个主要类型匹配,而不是特定的命名豆
并且解决了众所周知的可解析依赖关系:该豆子工厂,应用上下文,ResourceLoader,应用事件发布者和消息源接口。
因此,在以下例子中,customerPreferenceDao菲尔德首先寻找一颗豆子
命名为“customerPreferenceDao”,然后退回到该类型的主类型匹配CustomerPreferenceDao:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; (1)
public MovieRecommender() {
}
// ...
}
| 1 | 这上下文场是基于已知可解析依赖类型进行注入的:应用上下文. |
class MovieRecommender {
@Resource
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Resource
private lateinit var context: ApplicationContext (1)
// ...
}
| 1 | 这上下文场是基于已知可解析依赖类型进行注入的:应用上下文. |
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
在这种情况下,目录参数 和 域 等于电影目录价值。
Spring 默认提供了一个宽松的嵌入式值解析器。它会尝试解决
属性值,如果无法解析,则属性名称(例如)${catalog.name})
将作为值注入。如果你想严格控制不存在的
价值观,你应该声明一个PropertySourcesPlaceholderConfigurerBean,作为以下角色
示例如下:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Configuration
class AppConfig {
@Bean
fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
在配置PropertySourcesPlaceholderConfigurer使用 JavaConfig 的@Bean方法必须是静态的. |
使用上述配置确保如果任何占位符无法解决,Spring初始化失败。也可以采用以下方法${}setPlaceholderPrefix,setPlaceholderSuffix或setValueSepacator自定义
占位符。
Spring Boot 默认配置为PropertySourcesPlaceholderConfigurer豆子那个
将获得application.properties和application.yml文件。 |
Spring 内置的转换器支持允许简单的类型转换(到整数或智力例如)会自动处理。多个逗号分隔值可以是
自动转换为字符串阵列无需额外努力。
可以给出以下默认值:
@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)
一个泉水豆子后处理器使用转换服务幕后处理
转换字符串在@Value对目标类型。如果你愿意的话
为你自己的自定义类型提供转换支持,你也可以提供自己的转换服务豆子实例如下示例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): ConversionService {
return DefaultFormattingConversionService().apply {
addConverter(MyCustomConverter())
}
}
}
什么时候@Value包含一个SpEL表达该值将动态变化
运行时计算结果如下示例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(
@Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)
SpEL还支持使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
@Component
class MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)
1.9.9. 使用@PostConstruct和@PreDestroy
这CommonAnnotationBeanPostProcessor不仅能识别@Resource注解
还有JSR-250生命周期注释:javax.annotation.PostConstruct和javax.annotation.PreDestroy.在春季2.5引入,支持这些
注释为初始化回调和销毁回调中描述的生命周期回调机制提供了替代方案。前提是CommonAnnotationBeanPostProcessor注册于春季应用上下文,
携带这些注释之一的方法会在生命周期的同一时刻被调用
作为对应的 Spring 生命周期接口方法,或显式声明回调
方法。在以下示例中,缓存在初始化时被预填充,且
销毁后清除:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
fun clearMovieCache() {
// clears the movie cache upon destruction...
}
}
关于结合多种生命周期机制的效果,请参见“结合生命周期机制”。
|
喜欢 |
1.10. 类路径扫描与托管组件
本章大多数示例使用 XML 来指定生成的配置元数据
每豆子定义在春季容器内。上一节
(基于注释的容器配置)演示了如何提供大量配置
通过源级注释进行元数据。然而,即使在这些例子中,“基础”
BEAN 定义在 XML 文件中明确定义,而注释仅驱动
依赖注入。本节描述了隐式检测
通过扫描类路径来选择候选组件。候选分量是
与Filter条件匹配,并注册相应的豆定义
那个容器。这消除了使用 XML 来进行豆子注册的需求。相反,是你
可以使用注释(例如,@Component),AspectJ类型的表达式,或者你自己的表达式
自定义筛选条件,选择哪些类的BEAN定义注册于
那个容器。
|
从 Spring 3.0 开始,Spring JavaConfig 项目提供了许多功能
是核心Spring Framework的一部分。这允许你用 Java 来定义 Beans
而不是使用传统的XML文件。看看 |
1.10.1.@Component以及进一步的刻板印象注释
这@Repository注释是任何满足该角色的类的标记,或者
仓库的刻板印象(也称为数据访问对象或DAO)。用途包括
该标记的自动异常翻译功能,详见例外翻译。
斯普林进一步补充了刻板印象:@Component,@Service和@Controller.@Component是任何Spring管理组件的通用刻板印象。@Repository,@Service和@Controller是 的专门化@Component为
更具体的用例(包括持久化、服务和展示
分别是图层)。因此,你可以用@Component,但通过注释@Repository,@Service或@Controller相反,你的职业更适合用工具处理或关联
带有面貌。例如,这些刻板印象注释是理想的目标
点切。@Repository,@Service和@Controller也可以
在未来Spring Framework的版本中包含额外的语义。因此,如果你是
在使用@Component或@Service对于你的服务层,@Service是
显然是更好的选择。同样,如前所述,@Repository已经
支持作为持久化层自动异常转换的标记。
1.10.2. 使用元注释和组合注释
Spring提供的许多注释都可以作为元注释使用。
自己的代码。元注释是一种可以应用于另一个注释的注释。
例如,@Service前面提到的注释通过元注释为@Component,如下示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
| 1 | 这@Component原因@Service被以与@Component. |
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
| 1 | 这@Component原因@Service被以与@Component. |
你也可以将元注释组合起来创建“组合注释”。例如
这@RestController春季MVC的注释由以下组成@Controller和@ResponseBody.
此外,组合注释还可以选择性地重新声明从
元注释以实现自定义。这对你来说尤其有用
只想暴露元注释属性的子集。比如,Spring的@SessionScope注释 将范围名称硬编码为会期但仍然允许
对代理模式.以下列表展示了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但未声明代理模式如下:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope
class SessionScopedService {
// ...
}
你也可以覆盖代理模式,如下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
更多详情请参见 Spring Annotation Programming Model 维基页面。
1.10.3. 自动检测类并注册 Bean 定义
Spring 可以自动检测刻板分类并注册对应的类别豆子定义实例应用上下文.例如,以下两个类别
符合此类自动检测的条件:
@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
}
要自动检测这些类并注册对应的豆子,你需要添加@ComponentScan给你的@Configuration类,其中basePackages属性
是这两个类的共同父包。(或者,你可以指定一个
包含每个类父包的逗号、分号或空格分隔列表。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
为了简洁起见,前面的例子本可以使用值属性
注释(即,@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:annotation-config>.通常不需要包含<context:annotation-config>元素<上下文:组件扫描>. |
|
扫描类路径包需要对应的目录 类路径中的条目。用Ant建造JAR时,确保不要这样做 激活JAR任务中仅文件开关。另外,classpath 目录可能不适合 在某些环境中,基于安全策略进行暴露——例如,独立应用在 JDK 1.7.0_45及更高版本(需要在你的清单中设置“Trusted-Library”——详见 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描通常正常。
不过,确保你的组件类已经导出到你的 |
此外,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor当你使用该
组件扫描元素。这意味着这两个分量是自动检测的,且
线路连接——所有设备都没有以XML形式提供任何豆子配置元数据。
你可以禁用 的注册AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor通过包含annotation-config属性
其值为false. |
1.10.4. 使用滤镜自定义扫描
默认情况下,类注释为@Component,@Repository,@Service,@Controller,@Configuration,或者一个自定义注释,而该注释本身被注释为@Component是
唯一检测到的候选成分。不过,你可以修改和扩展这种行为
通过应用自定义过滤器。添加为includeFilters或排除过滤器属性
这@ComponentScan注释(或称为<上下文:include-filter />或<上下文:排除过滤器 />子元素的<上下文:组件扫描>元素
XML 配置)。每个滤波元件都需要类型和表达属性。
下表描述了过滤选项:
| Filter类型 | 表达式示例 | 描述 |
|---|---|---|
注释(默认) |
|
在目标组件中,一种在类型层级存在或元存在的注释。 |
可分配的 |
|
目标组件可以分配给(扩展或实现)的类(或接口)。 |
Aspectj |
|
一个AspectJ类型的表达式,以匹配目标组件。 |
正则表达式 |
|
一个正则表达式,用目标组件的类名匹配。 |
习惯 |
|
一个自定义实现 |
以下示例展示了忽略所有的配置@Repository附注
并改用“存根”仓库:
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
class AppConfig {
// ...
}
以下列表展示了等效的 XML:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
你也可以通过设置来禁用默认过滤器useDefaultFilters=false在
注释或提供use-default-filters=“false”作为<分量扫描/>元素。这实际上禁用了自动检测类别
注释或元注释为@Component,@Repository,@Service,@Controller,@RestController或@Configuration. |
1.10.5. 在组件中定义豆子元数据
Spring 组件还可以向容器贡献 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()方法。然而,它也贡献了一个具有工厂的Beans定义
方法指的publicInstance().这@Bean注释标识
工厂方法及其他Beans定义属性,例如限定符值
这@Qualifier注解。其他方法级注释可指定为@Scope,@Lazy,以及自定义限定符注释。
除了用于组件初始化的角色外,你还可以放置@Lazy注注在标记为@Autowired或@Inject.在此背景下,
它会导致注入一个懒分辨率代理。然而,这种代理方法
相当有限。对于复杂的懒惰交互,尤其是组合时
对于可选依赖,我们推荐ObjectProvider<MyTargetBean>相反。 |
如前所述,支持自动接线字段和方法,并附带额外内容
支持自布线@Bean方法。以下示例展示了如何实现:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
示例中自动接线字符串方法参数状态到以下值年龄另一个名为privateInstance.Spring 表达式语言元素
通过以下符号定义该性质的价值#{ <表情> }.为@Value注释,表达式解析器预设为在
解析表达文本。
从 Spring Framework 4.3 起,你也可以声明 为 的工厂方法参数注入点(或者说它的更具体子类:依赖描述符) 到
访问触发当前 BEAN 创建的请求注入点。
注意,这仅适用于实际创建豆子实例,不适用于
注入现有实例。因此,这一特性最为合理
原型范围的豆子。对于其他示波器,工厂方法只会检测到
触发在给定作用域内创建新 BEAN 实例的注入点
(例如,触发创建懒惰单例豆的依赖)。
在这种情况下,你可以用语义谨慎使用提供的注入点元数据。
以下示例展示了如何使用注入点:
@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组件中的方法处理方式与其不同
Spring内部的对应物@Configuration类。区别在于@Component类并未通过CGLIB增强来拦截方法和字段的调用。
CGLIB 代理是指调用方法或字段的方式@Bean方法
在@Configuration类创建了 BEAN 元数据引用,指向协作对象。
这些方法不采用常规的 Java 语义调用,而是经过
容器,以提供 Spring 的常规生命周期管理和代理功能
即使通过程序调用来指代其他豆子,也包括@Bean方法。
相比之下,调用方法或字段@Bean平原内的方法@Component类具有标准的Java语义,没有特殊的CGLIB处理或其他方法
约束适用。
|
你可以声明 静态调用 Java 语言的可见性
最后,一个类别可以容纳多个 |
1.10.6. 命名自动检测组件
当组件作为扫描过程的一部分被自动检测时,其豆名为
由豆名生成器那个扫描者知道的策略。默认情况下,任何
春季刻板印象注释(@Component,@Repository,@Service和@Controller)包含一个名称值因此,该名称被赋予了
对应的Beans定义。
如果这样的注释没有包含任何名称值或任何其他检测到的分量
(例如通过自定义过滤器发现的),默认的豆名生成器返回
未大写的非限定类别名称。例如,如果以下分量
类别检测到,名称会是myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果你不想依赖默认的豆子命名策略,可以提供自定义方式
命名豆子的策略。首先,实现豆名生成器界面,并确保包含默认的无 arg 构造函数。然后,提供完整的
在配置扫描仪时,作为以下示例注释,经过限定的类名
以及豆子定义秀。
如果你遇到命名冲突,因为多个自动检测组件分别具有
相同的非限定类别名称(即名称相同但位于
不同的包),你可能需要配置一个豆名生成器这默认为
生成豆子名称的完全限定类别名称。截至 Spring Framework 5.2.3,完全限定注释豆名生成器位于包装中org.springframework.context.annotation可用于此类目的。 |
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
一般来说,在其他情况下,考虑用注释指定名称 组件可能对它进行明确引用。另一方面, 只要容器负责布线,自动生成的名称就足够了。
1.10.7. 提供自动检测组件的示波器
与 Spring 管理组件一般一样,默认且最常见的作用域
自动检测的分量是单身 人士.不过,有时候你需要换个瞄准镜
可以由@Scope注解。你可以提供
注释中的作用域,如下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope注释只在具体Beans(对于注释类)进行内省分析
组件)或工厂方法(用于@Bean方法)。与XML豆相比
定义中,没有豆子定义继承和继承的概念
类级层级对元数据而言无关紧要。 |
关于 Spring 语境中“请求”或“会话”等网页特定范围的详细信息,
参见请求、会话、应用和WebSocket的范围。与这些示波器的预建注释一样,
你也可以使用Spring的元注释来撰写自己的范围注释
方法:例如,一个自定义注释,元注释为@Scope(“原型机”),
也可能声明自定义的范围代理模式。
提供定制的示波域分辨率策略,而非依赖
基于注释的方法,你可以实现ScopeMetadataResolver接口。一定要包含默认的无arg构造器。然后你可以提供
在配置扫描仪时,完全限定的类别名称,以下示例是两者的示例
注释和Beans定义显示: |
@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 Beans》中描述为依赖关系。
为此,组件扫描中提供了作用域代理属性
元素。三种可能的值是:不,接口和目标类.例如
以下配置可生成标准的JDK动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8. 提供带有注释的限定元数据
这@Qualifier注释在《带限定符的注释自动配线微调》中有所讨论。
该节中的例子展示了@Qualifier注释和
自定义限定符注释,在解析 AutoWire 时提供细致控制
候选人。因为这些例子基于XML豆的定义,限定词
候选豆定义的元数据通过以下方式提供限定 符或元子元素的豆XML中的元素。当依赖类路径扫描
组件自动检测,你可以提供带有类型级别的限定符元数据
候选人类别注释。以下三个例子说明了这一点
技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
| 与大多数基于注释的替代方案一样,请记住注释元数据是 绑定于类定义本身,而 XML 的使用 则允许多个 BEAN 同类型的 以提供其限定符元数据的变体,因为 元数据是按实例提供,而非按类别提供。 |
1.10.9. 生成候选组件索引
虽然类路径扫描速度非常快,但可以提升启动性能 通过在编译时创建候选项目的静态列表来处理大型应用。在这方面 模式中,所有成为组件扫描目标的模块必须使用该机制。
你现在的@ComponentScan或<上下文:组件扫描/>指令必须保留
未更改,以请求上下文以扫描某些包中的候选人。当应用上下文检测到这样的索引时,它会自动使用它而不是扫描
课程路径。 |
要生成索引,应为每个包含 的模块添加一个额外的依赖 组件是组件扫描指令的目标。以下示例显示 如何用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及更早版本中,依赖应在仅编译配置,如下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.25.RELEASE"
}
在Gradle 4.6及以后版本中,依赖应在注释处理处理器配置,如下示例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.2.25.RELEASE"
}
这spring-context-indexer工件生成META-INF/spring.components归档
包含在jar文件中。
在你的IDE中使用这个模式时,spring-context-indexer一定是
注册为注释处理机以确保索引在
候选组件会更新。 |
当META-INF/spring.components文件被发现
在阶级路径上。如果某些库(或用例)部分提供索引,
但无法为整个应用构建,你可以退回到普通类路径
排列(仿佛根本不存在索引)通过设置spring.index.ignore自true,要么作为JVM系统属性,要么通过春季房产机制。 |
1.11. 使用 JSR 330 标准注释
从 Spring 3.0 开始,Spring 支持 JSR-330 标准注释 (依赖注入)。这些注释的扫描方式与Spring相同 附注。要使用它们,你需要在你的类路径中拥有相关的罐子。
|
如果你用Maven,那
|
1.11.1. 依赖注入@Inject和@Named
而不是@Autowired,你可以使用@javax.注入。注入如下:
import javax.inject.Inject;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.findMovies(...);
// ...
}
}
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
如同@Autowired,你可以使用@Inject在现场层面,方法层面
以及构造子-参数层级。此外,你可以声明你的注入点为提供商,允许按需访问短范围的豆子或懒惰访问
其他豆子通过Provider.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注释,如下示例所示:
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不具备
一个必填属性。以下两组示例展示了如何使用@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 {
// ...
}
与@ComponentJSR-330@Named以及JSR-250管理豆注释不可组合。你应该用Spring的刻板印象模型来建造
自定义组件注释。 |
1.11.3. JSR-330标准注释的局限性
当你使用标准注释时,你应该知道有些重要内容 功能不可用,如下表所示:
| Spring | javax.inject.* | javax.inject 限制 / 注释 |
|---|---|---|
@Autowired |
@Inject |
|
@Component |
@Named / @ManagedBean |
JSR-330 不提供可组合模型,仅提供识别命名组件的方法。 |
@Scope(“单例”) |
@Singleton |
JSR-330的默认瞄准镜和Spring的类似 |
@Qualifier |
@Qualifier / @Named |
|
@Value |
- |
无对应物 |
@Required |
- |
无对应物 |
@Lazy |
- |
无对应物 |
ObjectFactory |
提供商 |
|
1.12. 基于Java的容器配置
本节介绍如何在Java代码中使用注释来配置Spring 容器。课程涵盖以下主题:
1.12.1. 基本概念:@Bean和@Configuration
Spring 新 Java 配置支持的核心工件包括@Configuration- 注释类和@Bean-注释方法。
这@Bean注释用于表示方法实例化、配置和
初始化一个新的对象,由 Spring IoC 容器管理。对于熟悉的人来说
与Spring's合作<豆子/>XML 配置,以及@Bean注释的作用与
这<豆/>元素。你可以使用@Bean- 带注释的方法,任意Spring@Component.然而,它们最常被用于@Configuration豆。
注释类@Configuration表明其主要目的是作为
Beans定义来源。此外@Configuration类允许 Interbean
依赖关系通过调用其他@Bean方法属于同一类。
最简单的@Configuration课程内容如下:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun myService(): MyService {
return MyServiceImpl()
}
}
前述AppConfig班级相当于下一个春季<豆子/>XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
这@Bean和@Configuration注释将在后续章节中详细讨论。
不过,首先,我们介绍了制作Spring容器的各种方法,包括:
基于Java的配置。
1.12.2. 通过使用AnnotationConfigApplicationContext
以下章节记录了斯普林的AnnotationConfigApplicationContext,春季引入
3.0。多功能应用上下文实施不仅能够接受@Configuration类作为输入,但也为纯@Component课程与课程
并附有JSR-330元数据注释。
什么时候@Configuration类作为输入提供,称为@Configuration类别本身
注册为Beans定义,并全部声明@Bean类内的方法
也被注册为Beans定义。
什么时候@Component以及JSR-330级,均注册为Beans
定义,并且假设DI元数据如下@Autowired或@Inject是
在必要时用于这些职业。
简单构造
类似于 Spring XML 文件作为实例化ClassPathXmlApplicationContext,你可以使用@Configuration当
实例化一个AnnotationConfigApplicationContext.这使得
Spring容器的无XML使用情况,如下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
如前所述,AnnotationConfigApplicationContext不仅限于工作
跟@Configuration类。任何@Component或者提供JSR-330注释型
作为构造子的输入,如下示例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
上述例子假设MyServiceImpl(我的服务Impl),依赖1和依赖2使用Spring
依赖注入注释,如@Autowired.
通过使用注册(班级<?>...)
你可以实例化一个AnnotationConfigApplicationContext通过使用无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()
}
启用组件扫描扫描(字符串......)
要启用组件扫描,你可以在@Configuration类别如下:
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
// ...
}
| 1 | 该注释支持组件扫描。 |
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig {
// ...
}
| 1 | 该注释支持组件扫描。 |
|
有经验的 Spring 用户可能熟悉 XML 声明的等价物,来自
斯普林斯
|
在前面的例子中,com.acme包裹会被扫描以寻找任何@Component- 注释类,这些类注册为春豆
容器内的定义。AnnotationConfigApplicationContext暴露了扫描(字符串......)该方法能够实现与
以下示例展示了:
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包裹(或任何包裹)
在下面),在通话时会被接收到扫描().后refresh(),所有@Bean方法在容器内被处理并注册为豆定义。 |
支持Web应用AnnotationConfigWebApplicationContext
一个WebApplicationContext的变体AnnotationConfigApplicationContext可获得
跟AnnotationConfigWebApplicationContext.你可以在
配置春季ContextLoaderListenerservlet 听者,春季 MVC调度器服务,等等。如下web.xml摘要配置为典型
Spring MVC 网页应用(注意使用context类上下文参数和
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注释@Configuration- 注释或@Component——注释类。
宣布一颗豆子
要声明一个豆子,可以用@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>
这两个声明都造出了一颗名为转移服务可于以下地区获得应用上下文,绑定到类型为的对象实例TransferServiceImpl(TransferServiceImpl),作为
以下文字图片显示:
transferService -> com.acme.TransferServiceImpl
你也可以申报你的@Bean带有接口(或基类)的方法
返回类型,如下示例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
然而,这限制了预先类型预测的可见性,仅限于指定的
接口类型(转运服务).然后,满型 (TransferServiceImpl(TransferServiceImpl))
只有在受影响的单粒豆被实例化后,容器才会知道。
非懒惰的单粒豆会根据其声明顺序实例化,
所以你可能会根据不同组件的时间看到不同的类型匹配结果
尝试通过非声明类型匹配(例如@Autowired TransferServiceImpl,
该函数仅在转移服务豆子已被实例化)。
如果你持续用声明服务接口来称呼类型,你的@Bean回流类型也可以安全地加入这一设计决策。然而,对于分量
实现多个接口或为可能指的组件的
实现类型,声明最具体的返回类型会更安全
(至少要和你豆子的注射点要求的具体程度一样)。 |
Beans依赖关系
一个@Bean-注释方法可以有任意数量的参数来描述
构建那个豆子所需的依赖。例如,如果我们转运服务需要账户仓库,我们可以通过一个方法实现这种依赖关系
参数,如下例所示:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
解析机制基本与基于构造子的依赖相同 注射。详情请参见相关章节。
接收生命周期回调
任何定义为@Bean注释支持常规生命周期回调
并且可以使用@PostConstruct和@PreDestroy来自JSR-250的注释。详情请参见JSR-250注释
详。
常规的 Spring 生命周期回调被完全支持 如下
井。如果豆子实现初始化Bean,一次性豆或生命周期他们
相应的方法由容器调用。
标准集合*意识到的接口(如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContextAware 等)也得到全面支持。
这@Bean注释支持指定任意初始化和销毁
回调方法,类似于 Spring XML 的初始化方法和销毁方法属性
在豆元素,如下例所示:
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配置定义的豆子具有公开发 你可能想默认对通过JNDI获得的资源这样做,因为
生命周期由应用程序外部管理。特别是,一定要确保每次都这样做
对于 以下示例展示了如何防止对 Java
Kotlin
此外, |
在豆号根据上述示例,同样可以称呼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 工作时,你可以用你的对象做任何你想做的事,也能做 不必总是依赖容器生命周期。 |
指定Beans范围
春季包括@Scope注释以便你指定豆子的作用域。
使用@Scope注解
你可以指定你的豆子定义为@Bean注释应包含
具体范围。你可以使用豆子示波器部分中指定的任何标准示波器。
默认范围为单身 人士但你可以用@Scope注解
如下示例所示:
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope和作用域代理
Spring 提供了一种通过有望域代理处理有作用域依赖的便捷方式。最简单的创作方式
当使用 XML 配置时,这样的代理是<aop:scoped-proxy/>元素。
用 Java 配置你的豆子@Scope注释提供等效支持
其中代理模式属性。默认为ScopedProxyMode.DEFAULT哪
通常表示除非有不同的默认值,否则不应创建有作用域代理
已在组件扫描指令层面配置。你可以具体说明ScopedProxyMode.TARGET_CLASS,ScopedProxyMode.INTERFACES或ScopedProxyMode.NO.
如果你将 XML 参考文档中的 Scoped 代理示例(参见 Scoped 代理)移植到@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方法名称作为
结果豆子。然而,这一功能可以通过以下方式被覆盖名称属性
如下示例所示:
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
豆子混叠
正如《命名豆子》中所述,有时赠送一颗豆子是理想的
多个名称,也就是豆子锯齿。这名称属性@Bean注释为此接受字符串数组。以下示例展示了如何设置
Beans的若干别名:
@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...
}
}
Beans描述
有时,提供更详细的豆子文字描述会更有帮助。这可以 尤其适用于豆子暴露(可能通过JMX)进行监测。
为@Bean,你可以使用@Description注释,如下示例所示:
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}
1.12.4. 使用@Configuration注解
@Configuration是类级注释,表示对象是
Beans定义。@Configuration类通过宣告豆子@Bean-注释
方法。召唤@Bean方法@Configuration类也可以用来定义
豆间依赖关系。看基本概念:@Bean和@Configuration作为一个总体介绍。
注入豆间依赖
当豆子之间存在相互依赖时,表达这种依赖性就很简单 由于一个豆子方法调用另一个方法,如下示例所示:
@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()
}
在上述例子中,豆一号获得一个引用豆二通过构造器
注射。
这种声明豆间依赖的方法仅在@Bean方法
在@Configuration类。你不能声明豆间依赖关系
通过使用普通@Component类。 |
查找方法注入
如前所述,查找方法注入是一种 高级功能,你应该很少用。它在 单例范围的豆依赖于原型范围的豆。使用 Java 来实现这一点 配置类型为实现这一模式提供了自然的方式。这 以下示例展示了如何使用查找方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.setState(commandState)
return command.execute()
}
// okay... but where is the implementation of this method?
protected abstract fun createCommand(): Command
}
通过使用 Java 配置,你可以创建一个子类指挥经理哪里
摘要createCommand()方法被覆盖的方式使其查找新的
(原型)命令对象。以下示例展示了如何实现:
@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你会还给我
通常会有两个实例(每个服务一个)。那绝对是
问题在于:春季时,实制豆子有单身 人士默认范围。这是
魔法的来源:全部@Configuration职业在启动时被子分类
跟CGLIB.在子类中,子方法首先检查容器中的任意
在调用父方法并创建新实例之前,先缓存(带作用域的)豆子。
| 行为可能因豆子的范围而异。我们在说话 关于单人,这里有。 |
|
从春季3.2开始,不再需要在类路径中添加CGLIB,因为CGLIB
课程已重新包装为 |
|
由于CGLIB动态添加特征,存在一些限制
启动时间。特别地,配置类不能是最终的。然而,作为
在4.3中,允许在配置类上使用任何构造器,包括使用 如果你想避免CGLIB施加的任何限制,可以考虑声明你的 |
1.12.5. 基于 Java 的配置编写
Spring基于Java的配置功能允许你撰写注释,这可以减少 你配置的复杂性。
使用@Import注解
就像<import/>element 被用于 Spring XML 文件中,以辅助模块化
配置,以及@Import注释支持加载@Bean定义来自
另一个配置类,如下示例所示:
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@Configuration
class ConfigA {
@Bean
fun a() = A()
}
@Configuration
@Import(ConfigA::class)
class ConfigB {
@Bean
fun b() = B()
}
现在,不必同时说明两者ConfigA.class和ConfigB.class什么时候
仅仅实例化上下文ConfigB需要明确提供,因为
以下示例展示了:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
// now both beans A and B will be available...
val a = ctx.getBean<A>()
val b = ctx.getBean<B>()
}
这种方法简化了容器实例化,因为只需处理一个类
而非要求你记住可能大量的@Configuration施工期间的课程。
截至 Spring Framework 4.2,@Import同时支持对常规组件的引用
类,类似于AnnotationConfigApplicationContext.register方法。
如果你想避免分量扫描,这尤其有用,因为你用了几个
配置类作为入口点,明确定义所有组件。 |
对导入的依赖注入@Bean定义
前面的例子是可行的,但比较简单。在大多数实际场景下,豆子的
跨配置类相互依赖。使用XML时,这不是
问题,因为没有编译器参与,你可以声明ref=“someBean”并且相信Spring会在容器初始化时解决这个问题。
使用@ConfigurationJava编译器对
配置模型,即引用其他豆子必须是有效的Java语法。
幸运的是,解决这个问题很简单。正如我们之前讨论过的,
一个@Bean该方法可以有任意数量的参数来描述豆子
依赖。请考虑以下更现实的情景,涉及多个@Configuration类别,每个类别取决于其他类别中申报的豆子:
@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类别如下
最终,容器里又多了一颗豆子:这意味着他们可以利用@Autowired和@Value注射和其他功能和其他豆子一样。
|
确保你注入的依赖关系是最简单的类型。 另外,要特别小心 |
以下示例展示了如何将一颗豆子自动接线到另一颗豆子上:
@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课程自春季起才支持。
框架4.3。另外也不需要特别说明@Autowired如果目标
Bean 只定义了一个构造子。 |
在前述情景中,使用@Autowired效果良好,能满足需求
模块化,但确定自动接线豆定义的准确位置为
仍然有些模糊。例如,作为一名开发者,正在考虑ServiceConfig,怎么样
你知道具体在哪里@Autowired AccountRepository豆子被宣布了?事实并非如此
在代码中明确表示,这可能完全没问题。请记住,Eclipse的Spring Tools提供了以下工具
可以渲染图表,展示所有布线情况,这可能就是你需要的全部。也
你的 Java IDE 可以轻松找到所有声明和账户仓库类型
并快速显示 的位置@Bean返回该类型的方法。
如果这种模糊性不可接受,且你希望实现直接导航
从你的IDE内部从一个@Configuration类别切换到另一个类别,考虑自动接线
配置类本身。以下示例展示了如何实现:
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
在前述情形中,其中账户仓库定义为完全显式的。
然而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类及其依赖关系也没有区别
而不是通常的基于接口的代码导航过程。
如果你想影响某些豆子的启动顺序,可以考虑
并宣布其中一些@Lazy(首次访问创建而非启动时)
或当@DependsOn某些其他豆子(确保特定的其他豆子是
在当前豆子之前被创造,超出了当前豆子的直接依赖所暗示的范围)。 |
有条件包含@Configuration类别或@Bean方法
通常有条件地启用或禁用一个完整@Configuration类
甚至是个别人@Bean方法,基于某个任意系统状态。一个常见的
例如使用@Profile注释:仅在特定情况下激活豆子
春季已启用配置文件环境(详情请参见豆子定义配置文件)
这@Profile注释实际上是通过使用更灵活的注释实现的
叫@Conditional.
这@Conditional注释表示具体情况org.springframework.context.annotation.Condition应当实现的
在@Bean已注册。
实现条件接口提供比赛(...)返回的方法true或false.例如,以下列表展示了实际情况条件实现用于@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 配置
斯普林斯@Configuration职业支持并不打算成为100%完整的替代
适用于 Spring XML。一些设施,如 Spring XML 命名空间,仍然是理想的方式
配置容器。在方便或必要的情况下,你有
选择:要么以“以XML为中心”的方式实例化容器,例如,ClassPathXmlApplicationContext或者以“Java 中心”的方式实例化,通过使用AnnotationConfigApplicationContext以及@ImportResource用于导入XML
按需。
以XML为中心的使用@Configuration类
可能更倾向于从 XML 引导 Spring 容器并包含@Configuration课程以临时方式进行。例如,在一个大型现有代码库中
使用 Spring XML 的话,创建起来更简单@Configuration在
根据需要进行,并从现有的XML文件中包含它们。在本节后面,我们将介绍
使用选项@Configuration类处于这种“以XML为中心”的情境中。
记住这一点@Configuration类最终是 Bean 定义
容器。在本系列示例中,我们创建@Configuration命名的类别AppConfig和
包含在system-test-config.xml作为<豆/>定义。因为<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 <豆/>不声明身份证元素。虽然这样做是可以接受的,但由于没有其他Beans,这并非必要
从未提及它,而且不太可能直接从容器中直接按名称提取。
同样,数据来源豆子只按类型自动接线,所以是显式豆子身份证并非严格要求。 |
因为@Configuration是元注释,记为@Component,@Configuration-注释
类自动成为组件扫描的候选对象。使用相同的场景
描述 在前一个例子中,我们可以重新定义system-test-config.xml利用分量扫描技术。
注意,在这种情况下,我们不必明确声明<context:annotation-config/>因为<上下文:组件扫描/>使得同样的
功能性。
以下示例展示了修改后的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、一个属性文件,以及主要类)展示了如何使用
这@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. 环境抽象
配置文件是一组有名称的逻辑豆定义,需注册于
只有当该配置文件处于激活状态时才使用容器。豆子可以被分配到配置文件
无论是用XML定义还是带注释的。该职位的作用环境对象
与配置文件的关系在于确定当前活跃的配置文件(如果有的话),
以及哪些配置文件(如果有的话)默认应该激活。
属性在几乎所有应用中都扮演重要角色,可能起源于
多种来源:属性文件、JVM 系统属性、系统环境
变量、JNDI、servlet 上下文参数、临时调用性能对象地图对象,因此
上。该职位的作用环境与属性相关的对象是
用户拥有便捷的服务界面,用于配置属性源和解析
这些财产。
1.13.1. 豆子定义配置文件
豆定义配置文件在核心容器中提供了一种机制,允许 不同Beans在不同环境中的注册。“环境”这个词, 对不同用户来说可能有不同的含义,这个功能能帮助很多人 使用场景包括:
-
在开发中对内存数据源进行工作,还是查找相同的数据源 在质量保证或生产环境中,来自JNDI的数据源。
-
仅在部署应用到 表演环境。
-
为客户A与客户注册定制豆子实现 B级部署。
考虑一个实际应用中的第一个用例,需要数据来源.在测试环境中,配置可能如下:
@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()
}
现在考虑该应用如何部署到质量保证或生产环境中
假设应用程序的数据源已注册
与生产应用服务器的 JNDI 目录相符。我们数据来源豆
现在看起来是这样的列表:
@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/>包含以下的语句${占位符}结算的标记
根据环境的值,选择正确的配置文件路径
变量。Bean 定义配置文件是核心容器功能,提供
解决这个问题。
如果我们推广前面示例中展示的环境特定豆的用例 定义,我们最终需要注册某些豆子定义 某些情境下,但另一些则不然。你可以说你想注册一个 在情境A中,Beans定义的某些轮廓,以及在不同的轮廓中 情况B。我们首先更新配置以反映这一需求。
用@Profile
这@Profile注释功能可以让你表明某个组件符合注册资格
当一个或多个指定配置文件处于激活状态时。使用我们之前的例子,我们
可以重写数据来源配置如下:
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("development")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
如前所述,其中@Bean方法,通常选择使用程序化
JNDI查询,使用任一SpringJndiTemplate/JndiLocatorDelegate助手或
纯正的JNDI初始上下文之前显示的用法,但未显示JndiObjectFactoryBean变体,这会强制你将返回类型声明为工厂豆类型。 |
配置文件字符串可能包含一个简单的配置文件名称(例如,生产)或
侧脸表情。配置文件表达式允许更复杂的配置文件逻辑为
表达式(例如,制作与美国东部).以下作符支持于
个人资料表达:
-
!:该配置文件的逻辑“非” -
&:这些剖面的逻辑“和” -
|:这些配置文件的逻辑“或”
你不能把和混合在一起&|不使用括号的运算符。例如制作与美国东部 |欧盟中部不是一个有效的表达。它必须表示为生产 &(美国东部 | 欧盟中部). |
你可以使用@Profile作为一种元注释
创建自定义注释。以下示例定义了自定义@Production你可以直接替换的注释@Profile(“制作”):
@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方法和@Import与该类相关的注释除非有一个或多个
指定的配置文件是激活的。如果@Component或@Configuration等级被标记
跟@Profile({“p1”, “p2”})该类别除非被注册或处理
“P1”或“P2”配置文件已被激活。如果给定的配置文件前缀为
NOT 算符 (!),只有当该配置文件未被标记时,注释元素才会被注册
积极。例如,给定@Profile({“p1”, “!p2”}),注册将在档案中进行
“p1”是激活的,或者如果配置文件“p2”未激活。 |
@Profile也可以在方法层面声明只包含一个特定的豆子
对于配置类(例如,对于特定豆子的其他变体),定义为
以下示例展示了:
@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 | 这独立数据源该方法仅适用于发展轮廓。 |
| 2 | 这jndiDataSource该方法仅适用于生产轮廓。 |
@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 | 这独立数据源该方法仅适用于发展轮廓。 |
| 2 | 这jndiDataSource该方法仅适用于生产轮廓。 |
|
跟 如果你想定义具有不同轮廓条件的替代豆子,
使用指向同一豆子名称的不同 Java 方法名,方法是使用 |
XML Bean 定义配置文件
XML对应的是轮廓属性<豆子>元素。我们之前的样本
配置可以用两个XML文件重写,具体如下:
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
也可以避免这种分裂和筑巢<豆子/>同一文件中的元素,
如下示例所示:
<beans 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扔掉了,因为容器找不到
春季豆数据来源.
激活个人资料有多种方式,但最直接的做法是
它在程序上针对环境API通过以下方式提供应用上下文.以下示例展示了如何实现:
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()
}
此外,你还可以通过春季.档案.活跃属性,可以通过系统环境指定
变量、JVM 系统属性、servlet 上下文参数web.xml,甚至作为
JNDI条目(参见地产来源抽象化).在积分测试中,主动
配置文件可以通过以下方式声明@ActiveProfiles注释春季测试模块(参见带有环境配置文件的上下文配置)。
请注意,画像不是“非此即彼”的选择。你可以激活多个
同时进行多个档案。在程序上,你可以为setActiveProfiles()方法,接受字符串。。。瓦拉格。以下示例
激活多个配置文件:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
声明性地,春季.档案.活跃可以接受逗号分隔的配置文件名称列表,
如下示例所示:
-Dspring.profiles.active="profile1,profile2"
默认配置文件
默认配置文件代表默认启用的配置文件。考虑 以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
如果没有配置文件激活,则数据来源被创造出来。你可以看到这个
作为一种为一种或多种Beans提供默认定义的方式。如果有的话
配置文件已启用,默认配置文件不适用。
你可以通过以下方式更改默认配置文件的名称setDefaultProfiles()上
这环境或者,声明式地,通过使用spring.profiles.default财产。
1.13.2.地产来源抽象化
斯普林斯环境抽象提供可配置的搜索作
财产来源层级。请考虑以下列表:
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")
在前面的片段中,我们看到一个高层次的方式,询问斯普林是否我的财产财产是
为当前环境定义。回答这个问题,环境对象执行
对一组地产来源对象。一个地产来源是任意键值对源的简单抽象,且
斯普林斯标准环境配置为两个 PropertySource 对象——其中一个代表 JVM 系统属性集合
(System.getProperties())以及一个表示系统环境变量集合的
(System.getenv()).
这些默认属性源存在于标准环境,用于独立用途
应用。StandardServletEnvironment填充了额外的默认属性源,包括servlet config和servlet
上下文参数。它可以选择性地启用JndiPropertySource.
详情请参见javadoc。 |
具体来说,当你使用标准环境,呼叫env.containsProperty(“我的财产”)如果我的财产系统性质或我的财产环境变量 位于
运行。
|
搜索是层级的。默认情况下,系统属性优先于系统属性
环境变量。所以,如果 对于一个公共
|
最重要的是,整个机制是可配置的。也许你有定制的来源
这些属性你想整合进这个搜索中。为此,实现
并实例化你自己的地产来源并将其加入地产来源对于
当前环境.以下示例展示了如何实现:
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
在前述法典中,我的财产源在 中以最高优先级被添加
搜索。如果它包含我的财产财产,财产被检测并归还,使得
任何我的财产其他任何财产地产来源.这可变属性源API提供了多种方法,允许对 集合进行精确作。
财产来源。
1.13.3. 使用@PropertySource
这@PropertySource注释提供了一种方便且声明式的机制来添加地产来源去Spring的环境.
给定一个名为app.properties包含键值对testbean.name=myTestBean,
以下内容@Configuration类别用途@PropertySource以一种方式
呼唤testBean.getName()返回我的测试豆:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
任何${…}存在于@PropertySource资源位置为
针对已登记的财产来源的一组,已通过该项决议
环境,如下例子所示:
@Configuration
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
假设我的占位符已经存在于其中一个属性源中
注册(例如系统属性或环境变量),占位符为
解析为相应的值。如果不行,那默认/路径被使用
默认情况下。如果没有指定默认值且无法解决某个属性,则IllegalArgumentException被抛出。
这@PropertySource注释是可重复的,按照Java 8的惯例。
然而,所有这些@PropertySource注释需要在同一位置声明
层级,可以直接在配置类上,也可以作为
同样的自定义注释。直接注释和元注释混合不构成
推荐,因为直接注释实际上覆盖了元注释。 |
1.14. 注册 a加载时间编织者
这加载时间编织者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>
一旦配置好应用上下文,任何一个在该内的豆子应用上下文可能实施加载时间WeaverAware,从而获得加载时间的引用
韦弗实例。这在与Spring的JPA支持结合时尤其有用,尤其是在加载时编织
对于JPA类变换来说是必要的。
请咨询LocalContainerEntityManagerFactoryBean更多细节请看Javadoc。关于 AspectJ 加载时织入的更多信息,请参见 Spring Framework 中的 AspectJ 载时织入。
1.15. 附加能力应用上下文
正如章节引言中讨论的,org.springframework.beans.factory包为管理和作豆子提供了基本功能,包括在
程序化的方式。这org.springframework.contextpackage 会添加应用上下文接口,扩展了豆子工厂除了扩展其他接口外
接口,以在更广泛的应用中提供额外功能
以框架为导向的风格。许多人使用应用上下文完全在
声明式的表达方式,甚至不是通过程序创建,而是依赖于
支援类如上下文加载器以自动实例化应用上下文作为Java EE网络应用正常启动过程的一部分。
为了增强豆子工厂功能以更框架导向的风格,上下文
包还提供以下功能:
-
通过 i18n 格式访问消息,通过
消息源接口。 -
通过
ResourceLoader接口。 -
事件发布,即实现
ApplicationListener接口 通过使用应用事件发布者接口。 -
加载多个(层级)上下文,让每个上下文聚焦于一个 特定层,例如应用的网页层,通过
分层豆工厂接口。
1.15.1. 国际化使用消息源
这应用上下文接口扩展了一个称为消息源和
因此,提供了国际化(“i18N”)功能。Spring还能提供分层消息源接口,可以分层解析消息。
这些界面共同构成了春季效果信息的基础
分辨率。这些接口定义的方法包括:
-
字符串 getMessage(字符串代码,Object[] args,字符串默认,Locale loc):基本 用于从以下地区获取消息的方法消息源.当找不到任何消息时 对于指定的地点,使用默认消息。任何通过的论点都变成 替换值,使用消息格式标准提供的功能 图书馆。 -
String getMessage(String code, Object[] args, Locale loc):本质上是一样的 与之前的方法不同,但有一个不同:不能指定默认消息。如果 找不到消息,aNoSuchMessageException被抛出。 -
String getMessage(MessageSourceResolvable resolvable, Locale locale):所有属性 前述方法中的 也被包裹在一个名为消息来源可解决你可以用这个方法来使用。
当应用上下文加载时,它会自动搜索消息源在语境中定义了“豆”。豆子必须有名字消息来源.如果这样一个豆子
找到后,所有对前述方法的调用都委托给消息源。如果没有
消息源被找到,则应用上下文试图找到包含
同名的豆子。如果有,它会用那颗豆子作为消息源.如果应用上下文找不到任何消息来源,空的委派消息源实例化后能够接受对
上述方法。
Spring带来了三条消息源实现资源包消息源,ReloadableResourceBundleMessageSource和静态消息源.所有这些分层消息源为了实现嵌套
消息。这静态消息源很少使用,但提供了程序化的方式向源添加消息。以下示例展示了资源包消息源:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
这个例子假设你有三个资源组合,分别是格式,异常和窗户在你的类路径中定义。任何解析消息的请求都被以JDK标准的方式处理,通过以下方式资源包对象。 为了示例的目的,假设上述两个资源包文件的内容如下:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
下一个示例展示了一个程序来运行消息源功能性。 记住这些应用上下文实现也包括消息源实现和 可以转换为消息源接口。
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
println(message)
}
上述程序的结果如下:
Alligators rock!
总结一下,消息源定义在一个名为beans.xml哪 存在于你类路径的根源。 这消息来源BEAN 定义指的是通过其资源丛的数量基号财产。 三个文件在列表中传递给基号属性存在于你的类路径和 的根节点 和format.properties,exceptions.properties和windows.properties分别。
下一个示例展示了传递给消息查找的参数。这些参数被转换为字符串对象并插入查找消息中的占位符。
<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)
}
}
调用 后产生的输出执行()方法如下:
The userDao argument is required.
关于国际化(“i18n”),春季的各种情况消息源实现遵循与标准JDK相同的局部分辨率和备用规则资源包. 简而言之,继续举例消息来源定义 之前,如果你想解决针对英国的消息 (en-GBlocale,你会创建名为format_en_GB.properties,exceptions_en_GB.properties和windows_en_GB.properties分别。
通常,地点分辨率由 应用。 在下面的例子中,(英国)消息所针对的地点解析是手动指定的:
# 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.
你也可以使用消息来源接口以获取对任意的引用消息源已被定义。任何定义在应用上下文实现了消息来源接口注入应用上下文的消息源当豆子被创造和配置时。
因为Spring消息源基于 Java 的资源包, 它不会合并具有相同基名的捆集,但只会使用第一个找到的捆集。之后具有相同基名的消息捆集被忽略。 |
作为替代方案资源包消息源,Spring提供了ReloadableResourceBundleMessageSource类。 该变体支持相同的捆绑包文件格式,但比基于 JDK 的标准更灵活资源包消息源实现。 特别是,它允许从从任意 Spring 资源位置读取文件(而不仅仅是从类路径),并且支持热重新加载捆绑属性文件(同时高效地缓存它们)。参见ReloadableResourceBundleMessageSource详情请用Javadoc。 |
1.15.2. 标准与自定义事件
事件处理在应用上下文通过ApplicationEvent类和ApplicationListener接口。 如果一个实现ApplicationListener接口部署到上下文中,每次ApplicationEvent发布到......应用上下文,那个豆子被通知了。本质上,这就是标准的观察者设计模式。
截至春季4.2版本,事件基础设施得到了显著改进,提供了基于注释的模型以及发布任意事件(即不一定从ApplicationEvent). 当这样的对象发布时,我们会将其包裹在事件中为您服务。 |
下表描述了春季赛事的标准赛事:
| 事件 | 解释 |
|---|---|
|
发表时间 |
|
发表时间 |
|
发表时间 |
|
发表时间 |
|
一个网络特定的事件,告诉所有 Beans HTTP 请求已被处理。这
请求完成后,事件才会发布。本事件仅适用于
使用 Spring 的网页应用 |
|
的一个子类 |
你也可以创建并发布自己的自定义活动。以下示例展示了一个
扩展 Spring 的简单类ApplicationEvent基础职业:
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
发布一个习俗ApplicationEvent,称publishEvent()方法应用事件发布者.通常,这通过创建一个实现应用程序、事件、出版者并登记为春季豆。如下
示例展示了这样一个类:
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 容器检测到电子邮件服务实现应用程序、事件、出版者并自动调用setApplicationEventPublisher().实际上,传入的参数是Spring
容器本身。你通过应用上下文与其交互应用事件发布者接口。
接受这个习俗ApplicationEvent,你可以创建一个实现ApplicationListener并注册为春季豆。以下示例
显示了这样的类:
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是用你的类型进行一般参数化的
自定义事件(BlockedListEvent在前面的例子中)。这意味着onApplicationEvent()方法可以保持类型安全,避免下抛。
你可以注册任意数量的事件监听者,但请注意,默认情况下,事件
听众同步接收事件。这意味着publishEvent()方法
直到所有听众都处理完事件为止。这样做的一个优点
同步和单线程方法的特点是,当监听者接收到事件时,
如果交易上下文为
可用。如果需要其他事件发布策略,请参见 javadoc
为SpringApplicationEventMulticaster接口
和SimpleApplicationEventMulticaster配置选项的实现。
以下示例展示了用于注册和配置每个 的豆定义 上述课程:
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
<bean id="blockedListNotifier" class="example.BlockedListNotifier">
<property name="notificationAddress" value="[email protected]"/>
</bean>
把所有事情汇聚在一起,当发送邮件()方法电子邮件服务豆子是
如果有任何邮件应该被屏蔽,就会调用一个自定义事件BlockedListEvent已出版。这blockedListNotifierBean注册为ApplicationListener并且BlockedListEvent,此时它可以
通知相关人员。
| Spring的事件机制设计用于Spring豆之间的简单通信 在同一应用环境中。然而,对于更复杂的企业来说, 独立维护的 Spring Integration 项目满足了集成需求 完全支持构建轻量化、模式导向、事件驱动 这些架构基于著名的Spring编程模型。 |
基于注释的事件监听器
你可以在管理豆的任何方法上注册事件监听器,方法是使用@EventListener注解。这BlockedListNotifier可以重写如下:
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明它监听的事件类型, 但这次采用了灵活的名称,且未实现特定的监听器接口。 事件类型也可以通过通用词缩小范围,只要是实际事件类型 在实现层级中解析你的通用参数。
你的方法应该听多个事件,还是想定义它,不 事件类型也可以在注释本身上指定。这 以下示例展示了如何实现:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
也可以通过使用条件属性
定义 a 的注释SpEL表达,应当匹配
实际上是在某个特定事件中调用该方法。
以下示例展示了我们的通知符如何被重写为只有在内容事件属性等于我的事件:
@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表达是在专门的情境下评估的。下表列出了
提供给上下文以便你用于条件事件处理的项:
| 名称 | 位置 | 描述 | 示例 |
|---|---|---|---|
事件 |
根对象 |
实际 |
|
参数数组 |
根对象 |
调用方法时使用的参数(作为对象数组)。 |
|
论元名称 |
评估背景 |
任何方法参数的名称。如果因为某些原因,这些名字无法获得
(例如,因为编译后的字节码中没有调试信息),个体
参数也可用 |
|
注意#root.事件即使你的方法,也能访问底层事件
签名实际上指的是被发布的任意对象。
如果你需要发布一个事件,是处理另一个事件的结果,你可以更改 方法签名以返回应发布的事件,如下示例所示:
@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()方法发布了新的列表更新事件对于每个BlockedListEvent它能处理。如果你需要发布多个活动,可以回来
一个收集或者说是一系列事件。
异步监听器
如果你希望某个特定的监听器异步处理事件,可以重复使用以下条件定期@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
}
使用异步事件时请注意以下限制:
-
如果异步事件监听器抛出
例外,它不会传播到 访客。看AsyncUncaughtExceptionHandler更多细节请阅读。 -
异步事件监听器方法无法通过返回 价值。如果你需要发布另一个事件作为处理结果,注入一个
应用事件发布者手动发布事件。
排序听众
如果你需要先调用一个监听者,可以添加@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是实际被创造的实体类型。比如你
可以创建以下监听者定义,仅接收实体创建事件对于人:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
// ...
}
由于类型擦除,只有当触发的事件能解决泛型时,这才有效
事件监听器过滤的参数(即类似class PersonCreatedEvent 扩展了 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的资源抽象,详见资源。
应用上下文是ResourceLoader,可以用来加载资源对象。
一个资源本质上是JDK的更丰富的功能版本java.net.URL类。
事实上,这些实现资源包裹一个实例java.net.URL哪里
适当。一个资源几乎可以从
透明的方式,包括来自类路径(classpath,文件系统位置)的任意地点
可用标准网址描述,以及其他一些变体。如果资源位置
字符串是一条没有特殊前缀的简单路径,这些资源的来源为
具体且适合实际应用上下文类型。
你可以配置部署到应用上下文中的豆来实现特殊
回调接口,ResourceLoaderAware,将自动回拨于
初始化时间与应用上下文本身传递为ResourceLoader.
你也可以暴露 类型的属性资源用于访问静态资源。
它们像其他属性一样被注入其中。你可以具体说明这些资源性质为简单字符串路径并依赖于从这些文本自动转换
字符串到实际资源当豆子被部署时,对象。
提供给应用上下文构造者实际上是
资源字符串,简单形式根据具体情况适当处理
上下文实现。例如ClassPathXmlApplicationContext处理 简单
位置路径作为类路径位置。你也可以使用位置路径(资源字符串)
并带有特殊前缀,强制从类路径或URL加载定义,
无论实际的语境类型如何。
1.15.4. 便捷的 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参数。如果参数不成立
存在,听者用/网-INF/applicationContext.xml默认情况下。当
参数确实存在,监听者将字符串通过使用 预定义
分隔符(逗号、分号和空白)并使用这些值作为位置,其中
会搜索应用程序上下文。也支持Ant式的路径模式。
例子包括/网-INF/*Context.xml(对于所有名称以Context.xml而网内目录)和/网-INF/**/*Context.xml(对于任意子目录中的所有此类文件网内).
1.15.5. Spring展开应用上下文作为 Java EE RAR 文件
可以部署Spring应用上下文作为一个RAR文件,封装了
上下文及其所有在Java EE RAR部署中所需的Beans和库JAR。
单位。这相当于自力更生一个独立软件应用上下文(仅主持
在Java EE环境中),能够访问Java EE服务器的设施。RAR部署
是部署无头WAR文件更自然的替代方案——实际上,
一个没有任何HTTP入口的WAR文件,仅用于启动Spring应用上下文在 Java EE 环境中。
RAR部署非常适合不需要HTTP入口点但
而仅由消息端点和调度作业组成。在这种情况下,豆子可以
使用应用服务器资源,如JTA事务管理器和绑定JNDI的JDBC数据来源实例与JMS连接工厂实例和也可以注册于
平台的JMX服务器——全程通过Spring的标准事务管理和JNDI
以及JMX支持设施。应用组件也可以与应用交互
服务器的JCA工作经理通过斯普林斯任务执行者抽象化。
参见 javadocSpringContextResourceAdapter用于RAR部署中涉及的配置细节。
对于将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:
-
包 所有应用类都集成到一个RAR文件中(这是一个带有不同内容的标准JAR文件) 文件扩展名)。
-
将所有必要的库 JAR 添加到 RAR 归档的根节点中。
-
添加一个
元步兵/ra.xml部署描述符(如Java doc forSpringContextResourceAdapter) 以及对应的Spring XML bean定义文件(通常为元基础applicationContext.xml). -
把生成的RAR文件丢进你的 应用服务器的部署目录。
此类RAR部署单位通常是自给自足的。它们不会暴露组件
对外世界,甚至不对同一应用的其他模块。与 的交互
基于RAR的应用上下文通常通过与JMS共享的目的地发生
其他模块。基于RAR的应用上下文例如,还可以安排一些工作
或者对文件系统中的新文件做出反应(或类似作)。如果需要支持同步
外部访问时,它可以(例如)导出 RMI 端点,这些端点可能会被使用
由同一台机器上的其他应用模块读取。 |
1.16. 该豆子工厂
这豆子工厂API 为 Spring 的 IoC 功能提供了基础。
其具体契约主要用于与 Spring 其他部分的集成,
相关的第三方框架及其DefaultListableBeanFactory实现
是更高层级中的关键代表通用应用上下文容器。
豆子工厂以及相关接口(例如豆工厂觉醒,初始化Bean,一次性豆)是其他框架组件的重要集成点。
由于无需注释甚至反射,它们实现了非常高效的作
容器与其组件之间的相互作用。应用级豆子可能
使用相同的回调接口,但通常更倾向于声明式依赖
而是通过注释或程序化配置注入。
注意,核心豆子工厂API 级别及其DefaultListableBeanFactory实现时,不对配置格式或任何内容做出假设
组件注释。所有这些口味都是通过延长的
(例如:XmlBeanDefinitionReader和AutowiredAnnotationBeanPostProcessor)
在共享上运行豆子定义对象作为核心元数据表示。
这正是Spring的集装箱如此灵活且可扩展的核心所在。
1.16.1.豆子工厂或应用上下文?
本节解释了豆子工厂和应用上下文容器层级及其对自助机制的影响。
你应该用一个应用上下文除非你有充分的理由不这么做,否则通用应用上下文及其子类AnnotationConfigApplicationContext作为自定义引导的常见实现。这些是主要条目
指向 Spring 的核心容器,用于所有常见用途:配置加载
文件,触发类路径扫描,程序化注册 BEAN 定义
以及注释类,以及(截至5.0版本)注册函数豆定义。
因为应用上下文包含 A. 的所有功能豆子工厂是的
一般推荐用在平原上豆子工厂,除非是满的场景
需要对豆子加工进行控制。在应用上下文(例如通用应用上下文实现),检测到多种Beans
按惯例(即按豆名或Beans型——特别是后处理器),
而平原DefaultListableBeanFactory对任何特殊豆子持中立态度。
对于许多扩展容器功能,如注释处理和AOP代理,
这豆子后处理器延伸点是必不可少的。
如果你只用普通的DefaultListableBeanFactory,此类后处理器则不
默认被检测并激活。这种情况可能会让人困惑,因为
你的豆子配置其实没什么问题。相反,在这种情况下,
容器需要通过额外的设置完全启动。
下表列出了由豆子工厂和应用上下文接口和实现。
| 特征 | 豆子工厂 |
应用上下文 |
|---|---|---|
豆子实例化/接线 |
是的 |
是的 |
综合生命周期管理 |
不 |
是的 |
自动 |
不 |
是的 |
自动 |
不 |
是的 |
方便 |
不 |
是的 |
内置 |
不 |
是的 |
要显式注册豆后处理器,使用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
应用一个豆工厂后处理走向平原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)
在这两种情况下,显式注册步骤都不方便,这也就是
为什么要多样应用上下文变体优先于平原DefaultListableBeanFactory在Spring背向应用中,尤其是在
依靠豆工厂后处理和豆子后处理器扩展实例
典型企业环境中的容器功能。
|
一 |
2. 资源
本章介绍了Spring如何管理资源,以及如何利用资源 Spring。课程涵盖以下主题:
2.1. 引言
Java 标准java.net.URL类处理程序和各种URL前缀的标准处理程序,
遗憾的是,这些资源还不足以满足所有低层次资源的访问需求。为
例如,没有标准化的网址实现,可用于访问
需要从类路径或相对于ServletContext.虽然可以注册新的处理器以获得专门化网址前缀(类似于现有处理前缀的处理程序,如http:),通常为
相当复杂,而且网址界面仍缺少一些理想的功能,
例如,一种检测被指向资源存在的方法。
2.2. 资源接口
斯普林斯资源界面旨在成为一个更强大的抽象界面
获取低层次资源。以下列表显示了资源接口
定义:
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
}
作为资源界面显示,它扩展了输入流源接口。以下列表展示了输入流源接口:
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
interface InputStreamSource {
val inputStream: InputStream
}
其中一些最重要的方法来自资源接口包括:
-
getInputStream(): 定位并打开资源,返回输入流为 阅读资源。每次召唤都应返回一个新消息输入流.来电者有责任关闭该流。 -
存在()返回 a布尔表明该资源是否实际存在于 实体形态。 -
isOpen()返回 a布尔指示该资源是否代表句柄 有一条开放的溪流。如果true这输入流不可多次读取且 必须只读取一次,然后关闭,以避免资源泄漏。返回false为 所有常见的资源实现,除了输入流资源. -
getDescription()返回该资源的描述,用于错误处理 在使用资源时输出。这通常是完全限定的文件名,或者 资源的实际网址。
其他方法则可以让你获得实际情况网址或文件表示
资源(如果底层实现兼容并支持该条件)
功能性)。
Spring本身使用资源广泛抽象,作为参数类型
当需要资源时,方法签名多。某些 Spring API 中的其他方法
(例如,构建子对各种应用上下文实现)取一个字符串以简洁或简洁的形式,用来创造资源适合
该上下文实现,或者通过在字符串路径,设
呼叫者指定具体的资源必须被创建并使用。
虽然资源界面在Spring中被大量使用,到了Spring,实际上
作为通用工具类单独使用,在你自己的代码中非常有用,方便访问
资源,即使你的代码不了解或关心Spring的其他部分。
虽然这把你的代码和 Spring 绑定了,但它实际上只是绑定到这小部分
实用类,作为更强大的替代网址并且可以是
它被视为相当于你为此目的使用的其他库。
这资源抽象并不能取代功能。
它会尽可能包裹。例如,一个UrlResource将 URL 打包并使用
包裹网址去完成它的工作。 |
2.3. 内置资源实现
春季包括以下内容资源实现:
2.3.1.UrlResource
UrlResource包裹 ajava.net.URL可以用来访问任何
通常通过URL(如文件、HTTP目标、FTP目标等)访问。都
URL具有标准化字符串表示,使得适当的标准化
前缀用于表示不同URL类型之间的不同类型。这包括文件:为
访问文件系统路径,http:通过HTTP协议访问资源,FTP:通过FTP等方式访问资源。
一个UrlResource由 Java 代码通过显式使用UrlResource构造 函数
但通常在调用一个接受字符串论点旨在代表一条路径。对于后一种情况,是 JavaBeans属性编辑最终决定哪种类型的资源去创造。如果路径
字符串包含对它来说已知的前缀(例如Classpath:),它
创造合适的专业化资源就是因为那个前缀。然而,如果没有
识别前缀,假设字符串是标准的 URL 字符串,且
生成一个UrlResource.
2.3.2.ClassPathResource
该类代表应从类路径中获得的资源。它使用 无论是线程上下文类加载器、给定的类加载器,还是给定的 加载资源。
这资源实现支持解析为java.io.file如果
资源存在于文件系统中,但不存在于位于
jar 并且没有被扩展(无论是通过 Servlet 引擎还是其他环境)
到文件系统。针对此,各种资源实现始终支持
作为java.net.URL.
一个ClassPathResource由 Java 代码通过显式使用ClassPathResource但通常在调用一个 API 方法时隐式创建的,该方法对字符串论点旨在代表一条路径。对于后一种情况,是 JavaBeans属性编辑承认特殊前缀,Classpath:,在弦路径上,且
生成一个ClassPathResource那就这样吧。
2.3.4.ServletContextResource
这是一艘资源实现ServletContext能够解释
相关网页应用根目录内的相对路径。
它始终支持流访问和网址访问,但允许java.io.file仅限进入
当网页应用归档被扩展且资源物理地位于
文件系统。无论它是否被扩展并存储在文件系统中,还是被访问
直接从JAR或数据库(可以想象)直接获取,实际上是
依赖于 Servlet 容器。
2.4. 该ResourceLoader
这ResourceLoader接口旨在由能够返回的对象实现
(即装载)资源实例。以下列表显示了ResourceLoader界面定义:
public interface ResourceLoader {
Resource getResource(String location);
}
interface ResourceLoader {
fun getResource(location: String): Resource
}
所有应用上下文都实现了ResourceLoader接口。因此,所有
应用上下文可用于获得资源实例。
你打电话时getResource()在特定应用上下文和位置路径上
指定没有特定的前缀,你会得到一个资源类型为
根据该具体应用上下文。例如,假设如下
代码片段被运行在ClassPathXmlApplicationContext实例:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
val template = ctx.getResource("some/resource/path/myTemplate.txt")
对抗ClassPathXmlApplicationContext,该代码返回ClassPathResource.如果同样的方法运行
对抗文件系统Xml应用上下文实例时,它会返回文件系统资源.对于一个WebApplicationContext,它会返回ServletContextResource.它同样会返回针对每个上下文的适当对象。
因此,你可以根据具体应用的方式加载资源 上下文。
另一方面,你也可以强行ClassPathResource无论
通过指定特殊条件Classpath:前缀,如下
示例如下:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,你可以强制 aUrlResource通过指定任一标准来使用java.net.URL前缀。以下两对示例使用文件和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")
下表总结了转换策略字符串对象为资源对象:
| 前缀 | 示例 | 解释 |
|---|---|---|
Classpath: |
|
从类路径加载过来。 |
文件: |
装载如同 |
|
http: |
装载如同 |
|
(无) |
|
这取决于标的资产 |
2.5. 该ResourceLoaderAware接口
这ResourceLoaderAware接口是一种特殊的回调接口,用于识别
期望被提供ResourceLoader参考。如下
列表展示了ResourceLoaderAware接口:
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
interface ResourceLoaderAware {
fun setResourceLoader(resourceLoader: ResourceLoader)
}
当一个类实现ResourceLoaderAware并且部署到应用上下文中
(作为春季管理的Beans),它被认可为ResourceLoaderAware由应用
上下文。应用上下文随后调用setResourceLoader(ResourceLoader),
作为参数提供自身(记住,Spring 中的所有应用上下文都是实现的
这ResourceLoader界面)。
因为应用上下文是ResourceLoader,豆子也可以实现应用上下文感知接口并直接使用提供的应用上下文到
加载资源。不过,一般来说,最好还是用专业的ResourceLoader如果你只需要接口,那就更好。代码只与资源加载耦合
接口(可以视为工具接口),而非整个 Spring应用上下文接口。
在应用组件中,你也可以依赖自动接线ResourceLoader如
实现ResourceLoaderAware接口。“传统”构造 函数和byType自动接线模式(如《自动接线协作者》中描述)
能够提供ResourceLoader对于构造子参数或
分别是设定器方法参数。为了更多灵活性(包括能够
自动线字段和多参数方法),考虑使用基于注释的
自动接线功能。在这种情况下,ResourceLoader是自动接线到场中,
构造函数参数,或期望ResourceLoader类型
因为该场、构造器或方法携带@Autowired注解。
更多信息请参见用@Autowired.
2.6. 资源作为依赖关系
如果豆子本身会通过某种方式确定并供应资源路径,
对于动态过程来说,豆子使用以下ResourceLoader与加载资源的接口。例如,考虑加载某些模板
排序,具体需要的资源取决于用户的角色。如果
资源是固定的,消除使用是合理的ResourceLoader完全接口,让豆子暴露资源它需要的属性,
并期待他们被注射进去。
之所以容易注入这些属性,是因为所有应用上下文
注册并使用一个特殊的JavaBeans属性编辑,可以转换字符串路径
自资源对象。所以,如果我的豆子具有 模板属性 类型为资源,可以
用一个简单的字符串配置该资源,如下示例所示:
<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
注意资源路径没有前缀。因此,由于应用上下文本身是
将被用作ResourceLoader,资源本身通过ClassPathResource一个文件系统资源,或ServletContextResource,
这取决于具体的情境类型。
如果你需要强制执行资源输入 To 使用的类型,你可以用前缀。
以下两个例子展示了如何强制 aClassPathResource以及一个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文件。
当该位置路径没有前缀时,具体资源制造的类型
该路径 和 用于加载豆定义 依赖于 和 适用于
具体应用背景。例如,考虑以下例子,它生成ClassPathXmlApplicationContext:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
豆子定义是从类路径加载的,因为ClassPathResource是
使用。然而,考虑以下示例,它生成文件系统Xml应用上下文:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
现在豆子定义是从文件系统位置加载的(此例中,相对于 当前工作目录)。
注意,在
位置路径覆盖默认类型资源创建以加载
定义。请考虑以下例子:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
用文件系统Xml应用上下文从 classpath 加载 bean 定义。然而,它仍然是文件系统Xml应用上下文.如果它随后被用作ResourceLoader任何
未前缀路径仍被视为文件系统路径。
构建ClassPathXmlApplicationContext实例 — 捷径
这ClassPathXmlApplicationContext暴露若干构造子以支持
方便的实例化。基本思想是你只需提供字符串数组
仅包含XML文件本身的文件名(不含引导路径)
信息)并且还提供一个类.这ClassPathXmlApplicationContext然后从所提供的类中推导出路径信息。
请考虑以下目录布局:
com/
foo/
services.xml
daos.xml
MessengerService.class
以下示例展示了ClassPathXmlApplicationContext由定义在 的豆子组成的实例
文件命名services.xml和daos.xml(这些都位于类路径上)可以实例化为:
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. 应用上下文构建器资源路径中的万用符
应用上下文构造器中的资源路径值可以是简单路径(如
如前所述),每个目标都与目标一一对应资源或者,或者说,五月
包含特殊的“classpath*:” 前缀或内部 Ant 风格正则表达式
(通过使用 Spring 的路径匹配器实用性)。后者实际上都是
变数。
这种机制的一个用途是需要进行组件式应用汇编。都
组件可以“发布”上下文定义片段到已知的位置路径,并且,
当最终应用上下文使用相同的路径创建时,前缀为classpath*:,所有组件片段都会自动被拾取。
注意,这种通配符是针对应用上下文中资源路径的专用
构造子(或当你使用路径匹配器直接的效用类层级),并且是
施工时已解决。这和资源打字本身。
你不能使用classpath*:前缀用于构造一个实际资源如
资源一次只指向一个资源。
Ant式图案
路径位置可以包含Ant式的模式,如下示例所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径位置包含Ant式模式时,解析器会采用更复杂的方法来尝试解析
通配符。它产生资源对于通往最后一个非万用符段的路径,
从中获取一个网址。如果这个URL不是罐:URL 或容器特定变体
(例如:邮编:在WebLogic中,WSJAR在WebSphere中,依此类推),ajava.io.file是
从中获得并用于通过文件系统遍历来解析通配符。在
对于jar URL,解析器要么获得java.net.JarURLConnection从它或
手动解析jar URL,然后遍历jar文件内容进行解析
外卡。
对便携性的影响
如果指定的路径已经是一个文件URL(无论是隐式的,还是因为基站ResourceLoader是文件系统之一或显式的),万用卡保证为
完全便携地工作。
如果指定的路径是类路径位置,解析器必须获得最后一个
通过创建非通配符路径段 URL 来实现Classloader.getResource()叫。自此以来
只是路径的一个节点(不是末尾的文件),实际上它是未定义的(在ClassLoaderjavadoc)在这种情况下究竟返回了哪种类型的URL。实际上,
它总是java.io.file表示目录(其中 classpath 资源
解析到文件系统位置)或某种jar URL(其中classpath资源)
解析为一个罐子位置)。不过,这次作存在便携性问题。
如果为最后一个非万用字段获得了jar URL,解析器必须能够
拿一个java.net.JarURLConnection从它中或手动解析jar URL,以便能够
走动罐子里的内容物,解决万用牌。这在大多数环境中都有效
但在其他方面失败,我们强烈建议资源的变数解决
从罐子购买后,在使用之前一定要在你所在的环境中彻底测试。
这classpath*:前缀
在构建基于XML的应用上下文时,位置字符串可能会使用
特殊classpath*:前缀,如下示例所示:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
该特殊前缀指定所有与名称匹配的类路径资源
必须获得(内部,这本质上是通过调用ClassLoader.getResources(...)然后合并形成最终应用
上下文定义。
通配类路径依赖于getResources()基础的方法
Classloader。因为现在大多数应用服务器都提供自己的类加载器
实现时,行为可能会有所不同,尤其是在处理jar文件时。一个
简单的测试阶级路径*工作方法是使用 classloader 从以下文件加载文件
在 classpath 上的一个罐子里:getClass().getClassLoader().getResources(“<someFileInsideTheJar>”).试试用
这些文件名称相同,但被放置在两个不同的位置。如果
如果返回不当结果,请查看应用服务器文档
这些设置可能会影响Classloader的行为。 |
你也可以结合classpath*:前缀为路径匹配器模式
其余位置路径(例如,classpath*:META-INF/*-beans.xml).在这方面
在该情形下,解析策略相当简单:AClassLoader.getResources()叫声为
用于最后一个非通配符路径段,以获取所有匹配的资源
类加载器层级,然后每个资源的层级相同路径匹配器分辨率
前述策略用于通配子路径。
与万用卡相关的其他说明
注意classpath*:,与蚁式图案结合时,只有
在模式开始前至少有一个根目录,除非实际
目标文件存储在文件系统中。这意味着像Classpath*:*.xml可能不会从jar文件的根节点检索文件,而是只从文件中获取
从扩展目录的根源开始。
Spring 检索类路径条目的能力源自 JDKClassLoader.getResources()方法,仅返回
空字符串(表示潜在的搜索根)。春季评估URLClassLoader运行时配置和java.class.path在jar文件中实现
同样如此,但这并不保证会导致便携行为。
|
扫描类路径包需要对应的目录 类路径中的条目。用Ant构建JAR时,不要只激活文件 JAR任务的切换。此外,类路径目录可能不会因安全原因被公开 某些环境中的策略——例如,JDK 1.7.0_45 上的独立应用程序 以及更高级别(需要在你的清单中设置“可信库”。参见 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描通常正常。 这里也强烈建议将资源放入专门的目录, 避免了前述搜索 jar 文件根级时的可移植性问题。 |
蚁式模式Classpath:资源不保证能找到匹配的
资源:如果搜索的根包在多个类路径位置中都有。
请考虑以下资源位置的示例:
com/mycompany/package1/service-context.xml
现在考虑一条类似Ant的路径,有人可能会用它来寻找该文件:
classpath:com/mycompany/**/service-context.xml
这样的资源可能只存在于一个位置,但当路径如前述示例时
用于尝试解析,解析器根据返回的(第一个)URL(返回)来工作getResource(“com/mycompany”);.如果这个基础包节点存在多个
Classloader 的位置,实际的终端资源可能不存在。因此,在这种情况下
你应该更倾向于classpath*:采用相同的Ant式图案,且
搜索包含根包的所有类路径位置。
2.7.3.文件系统资源警告
一个文件系统资源不附着于FileSystemApplicationContext(那个
是,当 aFileSystemApplicationContext不是实际的ResourceLoader) treat
正如你所预期的,绝对路径和相对路径。相对路径相对于
当前工作目录,而绝对路径相对于
文件系统。
然而,出于向后兼容性(历史)原因,当FileSystemApplicationContext是ResourceLoader.这FileSystemApplicationContext所有部队都附着文件系统资源实例
把所有位置路径都当作相对的,无论它们是否以前导斜线开头。
实际上,这意味着以下例子是等价的:
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")
以下例子也是等价的(尽管它们作为一个不同是合理的 情况是相对的,另一个是绝对的):
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")
实际上,如果你需要真正的绝对文件系统路径,应避免使用
绝对路径文件系统资源或文件系统Xml应用上下文和
强制使用UrlResource通过使用文件: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 提供了
一个不排除任何一种验证(和数据绑定)的设计。
具体来说,验证不应绑定于网页层级,且应易于本地化,
而且应该可以插入任何可用的验证器。考虑到这些担忧,
Spring带来了验证器一份既基本又极具实用性的合同
在应用的每一层中。
数据绑定有助于让用户输入动态绑定到域
应用程序的模型(或你用来处理用户输入的任何对象)。Spring
提供了恰如其分的DataBinder正是要做到这一点。这验证器以及DataBinder组成验证该包主要用于但不
仅限于网页层。
这豆包装机是 Spring Framework 中的一个基本概念,并且被广泛应用于
地方。不过,你大概不需要使用豆包装机径直。不过因为这是参考文档,我们觉得有必要说明
也许有必要。我们解释豆包装机在本章中,如果你是
你可能会用它,毕竟你在尝试将数据绑定到对象时很可能会用。
斯普林斯DataBinder以及下层豆包装机两者都使用PropertyEditorSupport用于解析和格式化属性值的实现。这属性编辑和PropertyEditorSupport类型是 JavaBeans 规范的一部分,并且
本章将详细说明。春季3引入了核心。转换提供
通用类型转换功能,以及更高级的“格式化”包
格式化UI字段值。你可以把这些软件包当作更简单的替代方案PropertyEditorSupport实现。本章也讨论了这些问题。
Spring 通过设置基础设施和适配器支持 Java Beans的验证
Spring的验证器合同。应用程序可以全局启用 Bean 验证,
如 Java Bean Validation 所述,并专门用于所有验证
需要。在网页层,应用程序还可以进一步注册控制器本地的 Spring验证器每实例数DataBinder,如描述配置DataBinder,可以
对于插入自定义验证逻辑非常有用。
3.1. 通过使用 Spring 验证器接口进行验证
春季有验证器你可以用来验证对象的接口。这验证器接口的工作原理是使用错误对象,使得在验证的同时,
验证者可以向错误对象。
考虑以下一个小型数据对象的例子:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
class Person(val name: String, val age: Int)
下一个示例提供了验证行为人通过实现
遵循两种方法org.springframework.validation.Validator接口:
-
辅助(职业):这个验证器验证所提供的实例类? -
validate(Object, org.springframework.validation.Errors):验证给定对象 在验证错误时,将错误登记为给定的错误对象。
实现验证器这相当简单,尤其是当你知道验证工具Spring Framework 也提供的辅助类。如下
示例实现验证器为人实例:
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")
}
}
}
这静态的 rejectIfEmpty(..)方法验证工具类别习惯于
拒绝名称如果是零或者空字符串。看看验证工具Java doc
看看除了之前示例外,它还提供了哪些功能。
虽然完全可以实现单一验证器用于验证每个
对于富对象中嵌套对象,封装验证可能更好
每个嵌套对象类的逻辑验证器实现。一个简单的
“富”物体的例子是客户端由两个组成字符串性质(一个名字和第二个名字)以及一个复形地址对象。地址对象
可以独立于客户端物体,因此是一个不同的地址验证器已经实施。如果你想要你的客户验证器以重复利用所包含的逻辑
在地址验证器课程 不依赖复制粘贴,你可以
依赖注入或实例化一个地址验证器在你的客户验证器,
如下示例所示:
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()
}
}
}
验证错误会被报告给错误对象传递给验证者。在
Spring Web MVC 的 MVC,你可以使用<Spring:绑定/>用标签检查错误信息,但
你也可以检查错误反对自己。关于
它提供的方法可以在 Java 文档中找到。
3.2. 将代码解析为错误消息
我们讲了数据绑定和验证。本节介绍输出对应消息的方法
验证错误。在前文示例中,
我们拒绝了名称和年龄领域。如果我们想通过使用消息源,我们可以通过拒绝该字段时提供的错误代码来实现
(这里指的是“名字”和“年龄”)。当您拨打电话(无论是直接还是间接,通过使用,
例如,验证工具阶级)拒绝值或者是其中之一拒绝方法
来自错误接口,底层实现不仅注册代码,你
但同时还会注册若干额外的错误代码。这MessageCodesResolver确定哪些错误编码错误接口寄存器。默认情况下,默认消息代码解析器使用的是,例如,这不仅是注册消息
用你提供的代码,同时还会注册包含你经过字段名称的消息
拒绝法。所以,如果你拒绝一个域,用rejectValue(“age”, “too.darn.old”),
除了太老了代码,Spring也注册太老了和too.darn.old.age.int(前者包含字段名称,后者包含类型
该领域)。这样做是为了方便开发者在针对错误信息时使用。
关于MessageCodesResolver默认策略如下
在MessageCodesResolver和默认消息代码解析器, 分别。
3.3. 豆子控与豆包装机
这org.springframework.beans该软件包遵循 JavaBeans 标准。
JavaBean 是一个默认无参数构造子的类,其后果
命名规范,其中(例如)一个属性宾果疯狂愿意
采用二传方法set宾果疯狂(..)以及一个getter方法getBingo疯狂().为
关于 JavaBeans 及其规范的更多信息,请参见 javabeans。
豆子包里有一个相当重要的类别是豆包装机接口及其
对应的实现(BeanWrapperImpl(豆包装师)).正如javadoc引用的,豆包装机提供设置和获取属性值的功能(单独或单独获取)
bulk),获取属性描述符,并查询属性以确定它们是否属于
可读或可书写。另外,还有豆包装机支持嵌套属性,
使得子属性的属性设置可以无限深度。这豆包装机同时支持添加标准 JavaBeans 的能力PropertyChangeListeners和可否决变更听众,无需在目标类中支持代码。
最后但同样重要的是豆包装机支持设置索引属性。
这豆包装机通常应用代码不直接使用,但被DataBinder以及豆子工厂.
方式豆包装机作品部分名称可指:它将豆子包裹到
对该豆子执行作,比如设置和检索属性。
3.3.1. 设置与获取基本和嵌套属性
设置和获取属性是通过setPropertyValue和getPropertyValue过载方法的变体豆包装机.请参见他们的Javadoc
详。下表展示了这些惯例的一些例子:
| 表达 | 解释 |
|---|---|
|
表示该物业 |
|
表示嵌套属性 |
|
表示索引性质的第三个元素 |
|
表示由 |
(如果你不打算与你合作,接下来的部分对你来说并不重要
这豆包装机径直。如果你只使用那个DataBinder以及豆子工厂以及它们的默认实现,你应该跳到以下内容章节物业编辑.)
以下两个示例类使用了豆包装机去拿并设置
性能:
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
}
以下代码片段展示了一些如何检索和作某些
实例化的性质公司和员工:
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. 内置属性编辑实现
Spring 使用了一个概念属性编辑实现对象以及一个字符串.这很方便
以不同于对象本身的方式表示属性。例如,一个日期可以用人类易读的方式表示(作为字符串:'2007-14-09'),而
我们仍然可以将人类可读的形式转换回原始日期(甚至,甚至
更好的是将任何输入的日期以人类可读的形式转换回日期物体)。这
行为可以通过注册 类型为 的自定义编辑器来实现java.beans.PropertyEditor.在 a 上注册自定义编辑器豆包装机或
或者,在特定的IoC容器中(如上一章所述),给出
了解如何将属性转换为所需类型。更多相关信息属性编辑看Java doc 的java.beansOracle 提供的软件包.
以下是几个在 Spring 中使用属性编辑的例子:
-
在豆子上设置属性的方法是
属性编辑实现。 当你使用字符串作为你声明的某个豆子属性的价值 在XML文件中,Spring(如果对应属性的设定符具有类参数)用途ClassEditor尝试将参数解析为类对象。 -
在Spring的MVC框架中解析HTTP请求参数,是通过使用各种方式完成的 之
属性编辑你可以在所有子类中手动绑定的实现指挥控制器.
Spring有许多内置功能属性编辑实现让生活更轻松。
它们都位于org.springframework.beans.propertyeditors包。大多数(但非全部,如下表所示)默认注册于BeanWrapperImpl(豆包装师).如果属性编辑器可以以某种方式配置,你可以
仍然注册你自己的变体以覆盖默认版本。下表描述了
各种属性编辑Spring提供的实现:
| 类 | 解释 |
|---|---|
|
字节数组编辑器。将字符串转换为对应的字节
交涉。默认注册 |
|
解析表示类与实际类的字符串,反之亦然。当
找不到类,一个 |
|
可定制属性编辑器 |
|
用于收藏的属性编辑器,可以转换任何来源 |
|
可定制属性编辑器 |
|
可自定义属性编辑器,适用于任何 |
|
将字符串解析为 |
|
单向属性编辑器,可以对字符串进行生成(通过
中间 |
|
可以将字符串解析为 |
|
可以将字符串解析为 |
|
可以转换字符串(格式化为 javadoc 中定义的格式) |
|
属性编辑器,可以裁剪字符串。可选地允许变换空字符串
变成了 |
|
可以将URL的字符串表示解析为实际的 |
Spring使用java.beans.PropertyEditorManager设定属性的搜索路径
可能需要编辑。搜索路径还包括Sun.Bean.编辑哪
包括属性编辑以下类型的实现字体,颜色,以及大多数
原始类型。还要注意,标准的 JavaBeans 基础设施
自动发现属性编辑课程(无需注册
明确地说,如果它们与所处理的类处于同一包中,并且具有相同的条件
名称为该类别,且编辑 器附加。例如,可以有以下
类和包结构,这就足够满足某样编辑器未来班级
被认可并用作属性编辑为东西-类型属性。
com
chank
pop
Something
SomethingEditor // the PropertyEditor for the Something class
注意你也可以使用标准Beans信息这里也有JavaBeans的机制
(这里有部分描述)。以下示例使用Beans信息机制
显式注册一个或多个属性编辑具有
相关班级:
com
chank
pop
Something
SomethingBeanInfo // the BeanInfo for the Something class
以下为所引用的 Java 源代码某物豆信息类
助理自定义数字编辑器其中年龄的属性东西类:
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())
}
}
}
注册额外自定义属性编辑实现
当将 bean 属性设置为字符串值时,Spring IoC 容器最终会使用
标准JavaBeans属性编辑将这些字符串转换为复类型
财产。Spring 预注册了许多自定义属性编辑实现(例如,到
将一个用字符串表示的类名转换为类目的)。此外
Java 标准 JavaBeans属性编辑查找机制允许属性编辑对于一个类,应适当命名并置于与该类相同的包中
它为其提供支持,以便自动找到。
如果需要注册其他自定义物业编辑,有几个机制包括
可用。最手动的方法,通常并不方便
推荐的做法是registerCustomEditor()方法ConfigurableBeanFactory接口,假设你有豆子工厂参考。
另一种(稍微方便一点)的机制是使用特殊的豆子工厂
后处理器称为CustomEditorConfigurer.不过你也可以用豆子工厂的后处理器
跟豆子工厂实现,CustomEditorConfigurer有
嵌套属性设置,因此我们强烈建议你将其与应用上下文,你可以像其他豆子一样部署,且
该系统可自动检测并应用。
注意,所有豆子工厂和应用场景都会自动使用若干
内置属性编辑器,通过它们的使用豆包装机自
处理房产转换。标准属性编辑器豆包装机登记册已列于上一节。
此外应用上下文还要覆盖或添加额外的编辑器来处理
资源查找以符合特定应用上下文类型的方式进行。
标准 JavaBeans属性编辑实例用于转换属性价值
用字符串表示为该属性的实际复类型。你可以使用CustomEditorConfigurer方便地补充,是豆子工厂的后处理器。
支持更多属性编辑实例到应用上下文.
考虑以下示例,定义了一个名为异域类型和
另一类DependsOnExoticType,需要异域类型作为一个属性集合:
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
}
当设置正确后,我们希望能够将类型属性分配为
字符串,其中属性编辑转换为实际异域类型实例。以下豆子定义展示了如何建立这种关系:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
这属性编辑实现方式可能类似于以下内容:
// 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注册新属性编辑其中应用上下文,然后可以根据需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
用PropertyEditor注册商
另一种用 Spring 容器注册属性编辑器的机制是
创建并使用PropertyEditor注册商.该接口在
你需要在多个不同情况下使用同一套属性编辑器。
你可以写对应的注册商,并在每种情况下重复使用。PropertyEditor注册商实例与一个名为PropertyEditorRegistry,一个由Spring实现的接口豆包装机(和DataBinder).PropertyEditor注册商实例特别方便
当与CustomEditorConfigurer(此处描述),该性质揭示了
叫setPropertyEditorRegistrars(..).PropertyEditor注册商新增实例
转给CustomEditorConfigurer这样可以很容易地与DataBinder和
Spring MVC 控制器。此外,它避免了自定义时的同步需求
编辑:APropertyEditor注册商预计将创造新鲜的属性编辑每次豆子创建尝试的实例。
以下示例展示了如何创建自己的PropertyEditor注册商实现:
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举个例子PropertyEditor注册商实现。注意其实现中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 网络框架),使用PropertyEditor注册商在
与数据绑定的结合控制器(例如:SimpleFormController)可以非常
方便。以下示例使用了一个PropertyEditor注册商在
实现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
}
这种风格属性编辑注册可以促成简洁的代码(实现
之initBinder(..)只有一行长)并且共识属性编辑注册代码被封装在一个类中,然后在多个类之间共享控制器按需。
3.4. Spring型改装
春季3引入了核心。转换提供通用类型转换的包
系统。系统定义了一个 SPI 来实现类型转换逻辑和一个 API
在运行时执行类型转换。在Spring容器中,你可以使用这个系统
作为替代方案属性编辑用于转换外部化豆子属性价值的实现
字符串映射到所需的属性类型。你也可以在你的任何地方使用公共 API
需要类型转换的应用。
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
}
要创建自己的转换器,实现转炉接口与参数化S作为你转换的类型和T根据你转换的类型。你也可以透明地应用这样的
转换器如果是集合或数组S必须是
转换为数组或集合T,前提是 委派数组或集合
转换器也已经注册了(DefaultConversionService默认会)。
对于每个调用转换(S),源参数保证不会为空。你转炉如果转换失败,可以抛出任何未检查的异常。具体来说,它应该抛出一个IllegalArgumentException报告无效的源值。
务必确保你的转炉实现是线程安全的。
在core.convert.support封包为
一种便利。这些包括字符串转换为数字的转换器及其他常见类型。
以下列表显示了字符串对整数这是典型的类别转炉实现:
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. 使用转换器工厂
当你需要集中整个类层级的转换逻辑时
(例如,当从 转换时字符串自枚举对象),你可以实现转换器工厂,如下示例所示:
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. 使用通用转换器
当你需要高级服务时转炉实现时,考虑使用通用转换器接口。签名更灵活但类型不那么强烈
比转炉一个通用转换器支持多个源和
目标类型。此外,还有一个通用转换器提供源和目标场的可用性
这些上下文是你实现转换逻辑时可以用的。这样的上下文使得
类型转换可以由字段注释或在
场特征。以下列表展示了 的接口定义通用转换器:
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?
}
实现通用转换器有getConvertibleTypes()返回支持的
来源→目标类型对。然后实现convert(Object, TypeDescriptor,
TypeDescriptor)以包含你的转换逻辑。源头类型描述符提供
访问包含被转换值的源字段。目标类型描述符提供访问目标字段,将转换为后的值设置。
一个很好的例子通用转换器是一种在 Java 数组之间转换的转换器
以及一个收藏。这样的ArrayToCollection转换器内省宣称
目标集合类型用来解析集合的元素类型。这使得每个人
在
收集设置在目标字段上。
因为通用转换器这是一个更复杂的SPI接口,你应该使用
只有在你需要的时候才会这样。喜爱转炉或转换器工厂对于基本类型
转换需求。 |
用条件通用转换器
有时候,你想要一个转炉只有在特定条件成立时才运行。为
举个例子,你可能想运行一个转炉只有当存在特定的注释时才会
在目标场上,或者你可能想运行一个转炉只有当特定方法时才会这样
(例如静态值方法)在目标类上被定义。条件通用转换器是 的并集通用转换器和条件转换器允许你定义此类自定义匹配标准的接口:
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
一个很好的例子条件通用转换器是IdToEntity转换器那能转化
在持久实体标识符和实体引用之间。这样的IdToEntity转换器只有当目标实体类型声明静态查找方法时才可能匹配(例如,findAccount(长篇)).你可以在实现matches(TypeDescriptor, TypeDescriptor).
3.4.4. 该转换服务应用程序接口
转换服务定义了一个统一的 API,用于在 执行类型转换逻辑
运行。变换器通常运行在以下门面界面之后:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
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
}
最转换服务实现也实现转换器注册表哪
为注册转换器提供SPI功能。内部,转换服务实现将类型转换逻辑委托给注册转换器执行。
一个稳健的转换服务实现方式包括core.convert.support包。通用转换服务通用实现适用于
在大多数环境中使用。ConversionServiceFactory提供了一个方便的工厂
创建共同点转换服务配置。
3.4.5. 配置 a转换服务
一个转换服务是一个无状态对象,设计用于在应用时实例化
启动后,多个线程之间共享。春季申请通常会
配置一个转换服务每个Spring容器的实例(或应用上下文).
Spring会接纳这些转换服务并且每当 a 类型时都会使用它
转换需要由框架完成。你也可以注射这个转换服务直接调用任何一颗豆子。
如果没有转换服务注册于Spring,即原厂属性编辑-基于
系统被使用。 |
注册默认转换服务用Spring,添加以下豆子定义
带有身份证之转换服务:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认转换服务可以在字符串、数字、枚举、集合之间转换,
地图和其他常见类型。用你的
自有定制转换器,设置变换 器财产。属性值可以实现
任何转炉,转换器工厂或通用转换器接口。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
也常用转换服务在春季MVC应用中。详见春季MVC章节中的转换与格式。
在某些情况下,你可能希望在转换时使用格式。看这FormatterRegistrySPI关于如何使用的详细信息FormattingConversionServiceFactoryBean.
3.4.6. 使用转换服务编程
与转换服务实例上,你可以注入一个引用到
就像你对其他豆子做的那样。以下示例展示了如何实现:
@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(...)
}
}
在大多数情况下,你可以使用转换该方法指定了目标类型,但它
不适用于更复杂的类型,例如参数化元素的集合。
例如,如果你想转换一个列表之整数转给列表之字符串编程
你需要正式定义源类型和目标类型。
幸运类型描述符提供了多种选项,使作更简单,
如下示例所示:
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自动注册 以下转换器
适合大多数环境。这包括收集转换器、标量
转换器,以及基础对象-自-字符串变换 器。你可以注册相同的转换器
对任意转换器注册表通过使用静电addDefaultConverters方法DefaultConversionService类。
值类型的转换器被用于数组和集合,因此有
不需要专门做一个转换器来从收集之S转给收集之T,假设标准的收集处理是合适的。
3.5. 春季场格式化
如前节所述,核心。转换是
通用类型转换系统。它提供了一个统一的转换服务API 作为
作为强型转炉用于实现单一类型转换逻辑的SPI
对另一个人来说。Spring 容器使用该系统绑定Beans属性值。在
加上 Spring 表达式语言(SpEL)和DataBinder利用这个系统来
绑定场值。例如,当 SpEL 需要强制短转给长自
完成一个expression.setValue(Object bean, Object value)尝试,核心。转换系统执行强制。
现在考虑典型客户端环境的类型转换要求,例如
网页版或桌面版应用。在这种情况下,你通常会从字符串支持客户的后期回溯流程,同时也支持返回字符串以支持
查看渲染过程。此外,你通常还需要进行本地化字符串值。越多
常规核心。转换 转炉SPI 不满足此类格式要求
径直。为了直接应对这些问题,春季3引入了方便的福尔马特SPI
提供了一种简单且稳健的替代方案属性编辑客户端环境的实现。
一般来说,你可以使用转炉需要实现通用类型时的SPI
转换逻辑——例如,用于在java.util.Date以及一个长.
你可以使用福尔马特在客户端环境(如网络)工作时使用 SPI
应用)并需要解析和打印局部字段值。这转换服务为两个 SPI 提供统一的类型转换 API。
3.5.1. 该福尔马特SPI
这福尔马特用于实现字段格式化逻辑的SPI简单且类型强。这
以下列表显示福尔马特界面定义:
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
福尔马特从打印机和解析 器构建模块接口。这
以下列表展示了这两个接口的定义:
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
}
创造属于你自己的福尔马特,实现福尔马特界面之前显示。
参数化T成为你希望格式化的对象类型——例如,java.util.Date.实现print()打印 的作T为
显示在客户端区域。实现解析(parse)解析 的作T来自客户端本地返回的格式化表示。你福尔马特应该抛出解析例外或IllegalArgumentException如果解析尝试失败。拿
请确保您的福尔马特实现是线程安全的。
这格式子包提供以下福尔马特实现方式是方便的。
这数套餐内容数字样式形态,CurrencyStyleFormatter和PercentStyleFormatter格式数使用java.text.NumberFormat. 这约会时间软件包提供日期材料格式java.util.Date具有
一个java.text.DateFormat.这Datetime.joda套餐提供全面的约会时间
基于Joda-Time库的格式支持。
如下日期材料是一个例子福尔马特实现:
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
}
}
春季团队欢迎社区驱动的学生福尔马特贡献。请参见GitHub Issues以进行贡献。
3.5.2. 注释驱动格式化
字段格式可以按字段类型或注释进行配置。去束缚
对 的注释福尔马特实现注释FormatterFactory.如下
列表展示了注释FormatterFactory接口:
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为场注释类型你希望与之交往的对象
格式化逻辑——例如。org.springframework.format.annotation.DateTimeFormat.
.有getFieldTypes()返回可使用注释的字段类型。
.有getPrinter()返回A打印机用来打印带注释字段的值。
.有getParser()返回A解析 器解析 aclientValue对于带注释的字段。
以下示例注释FormatterFactory实现绑定@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格式数例如:双和长和@DateTimeFormat格式java.util.Date,java.util.日历,长(用于毫秒时间戳)以及JSR-310java.time以及Joda-Time值类型。
以下示例使用@DateTimeFormat格式化为ajava.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. 该FormatterRegistrySPI
这FormatterRegistry是用于注册格式化器和转换器的SPI。FormattingConversionService是 的实现FormatterRegistry适用于
大多数环境。你可以编程或声明式配置这个变体
作为春豆,例如通过使用FormattingConversionServiceFactoryBean.因为这个
实现也包括实现转换服务你可以直接配置它
与Spring的DataBinder以及Spring表达式语言(SpEL)。
以下列表显示了FormatterRegistrySPI:
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<*>)
}
如前述所示,你可以按字段类型或注释注册格式化器。
这FormatterRegistrySPI 允许你集中配置格式规则,而不是
在你的手柄上复制这种配置。例如,你可能想
强制所有日期字段都以特定格式化,或者字段具有特定格式
注释的格式是特定的。与共享FormatterRegistry,你定义
这些规则只能用一次,并且在需要格式化时应用。
3.5.4. 该FormatterRegistrarSPI
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当注册多个相关转换器时非常有用,
针对特定格式类别(如日期格式)的格式化器。它也可以是
当声明式注册不足时非常有用——例如,当格式化器
需要被索引在与自身不同的特定字段类型下<了>或当
注册打印机/解析 器双。下一节提供更多信息
转换器和格式化器注册。
3.5.5. 在 Spring MVC 中配置格式化
详见春季MVC章节中的转换与格式。
3.6. 配置全局日期和时间格式
默认情况下,日期和时间字段未标注为@DateTimeFormat转换为
通过使用日期格式。简短风格。如果你愿意,也可以改成
定义你自己的全球格式。
为此,确保 Spring 不注册默认格式化器。相反,注册 通过以下工具手动制作格式化器:
-
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar -
org.springframework.format.datetime.DateFormatterRegistrar或org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar为乔达时间。
例如,以下 Java 配置注册一个全局是的格式:
@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>
请注意,在网页中配置日期和时间格式时还有额外的考虑因素 应用。请参阅WebMVC转换与格式化或WebFlux转换与格式化。
3.7. Java 豆验证
Spring 框架支持Java Bean Validation API。
3.7.1. 豆验证概述
Bean 验证通过约束声明和 提供了一种通用的验证方法 用于Java应用程序的元数据。使用时,你需要注释域模型属性 声明式验证约束,随后由运行时强制执行。有 内置约束,你也可以自定义自定义约束。
考虑以下例子,展示了一个简单的人形具有两个性质的模型:
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
)
3.7.2. 配置 Bean 验证提供者
Spring 对 Bean 验证 API 提供全面支持
Beans验证提供者作为春季豆。这样你就可以注入一个javax.validation.ValidatorFactory或javax.validation.Validator无论验证为
申请时需要。
你可以使用LocalValidatorFactoryBean将默认验证器配置为 Spring
Bean,正如以下例子所示:
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
前述示例中的基本配置触发豆验证以初始化为 使用其默认的引导机制。一个 Bean 验证提供商,比如 Hibernate 验证器,预期存在于类路径中,并自动被检测。
注入验证器
LocalValidatorFactoryBean实现了这两种javax.validation.ValidatorFactory和javax.validation.Validator以及斯普林的org.springframework.validation.Validator.
你可以在需要调用的豆子中注入这些接口中的任意一个引用
验证逻辑。
你可以注入一个引用javax.validation.Validator如果你更喜欢和豆子合作
验证API直接使用,如下示例所示:
import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import javax.validation.Validator;
@Service
class MyService(@Autowired private val validator: Validator)
你可以注入一个引用org.springframework.validation.Validator如果你的豆子
需要 Spring Validation API,如下示例所示:
import org.springframework.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import org.springframework.validation.Validator
@Service
class MyService(@Autowired private val validator: Validator)
自定义约束配置
每个豆验证约束由两部分组成:
-
一个
@Constraint声明约束及其可配置属性的注释。 -
一个实现
javax.validation.ConstraintValidator实现 约束的行为。
要将声明与实现关联,每个@Constraint注解
引用对应的约束验证器实现类。运行时,a约束验证器工厂当
约束注释会在你的领域模型中遇到。
默认情况下,LocalValidatorFactoryBean配置 aSpringConstraintValidatorFactory利用Spring创造约束验证器实例。这让你可以自定义约束验证器像其他春季豆一样,从依赖注入中受益。
以下示例展示了一种自定义@Constraint声明后接着一个相关约束验证器使用 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 {
// ...
}
如前例所示,a约束验证器实现可以有其依赖关系@Autowired就像其他春季豆一样。
Spring驱动方法验证
你可以将 Bean Validation 1.1(以及作为Hibernate Validator 4.3 的自定义扩展)支持的方法验证功能集成到 Spring 上下文中方法验证后处理器豆子定义:
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注释,也可以选择声明验证组以使用。 看方法验证后处理器用于Hibernate Validator和Bean Validation 1.1提供者的设置细节。
附加配置选项
默认LocalValidatorFactoryBean配置对于大多数人来说已足够 例。 有多种 Bean 验证的配置选项结构,从消息插值到遍历解析。参见LocalValidatorFactoryBean关于这些选项的更多信息,请参考Javadoc。
3.7.3. 配置 aDataBinder
从春季3开始,你可以配置一个DataBinder实例验证器. 一次 配置完成后,你可以调用验证器通过呼叫binder.validate(). 任何验证错误自动添加到活页夹中绑定结果.
以下示例展示了如何使用DataBinder程序化调用验证绑定目标对象后的逻辑:
Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());
// bind to the target object
binder.bind(propertyValues);
// validate the target object
binder.validate();
// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()
// bind to the target object
binder.bind(propertyValues)
// validate the target object
binder.validate()
// get BindingResult that includes any validation errors
val results = binder.bindingResult
你也可以配置一个DataBinder多重验证器实例通过dataBinder.addValidators和dataBinder.replaceValidators. 这在将全局配置的豆验证与 Spring 结合时非常有用验证器配置 在本地的 DataBinder 实例上。参见 Spring MVC 验证配置。
3.7.4. 春季MVC 3验证
详见春季MVC章节中的验证。
4. 春季表达式语言(SpEL)
Spring表达式语言(“简称”SpEL)是一种强大的表达式语言,支持在运行时查询和作对象图。该语言语法为类似于统一EL,但提供了额外功能,最显著的是方法调用和基本字符串模板功能。
虽然还有其他几种 Java 表达式语言可用——OGNL、MVEL 和 JBossEL 等,仅举几例——Spring 表达式语言的创建目的是为 Spring社区提供一种支持良好的表达式语言,可以适用于所有Spring 产品组合中的产品。其语言特性由Spring 项目的需求驱动,包括工具需求用于支持 Spring Eclipse 工具中的代码完成。话虽如此,SpEL 基于一个技术无关的 API,允许其他表达式语言实现在需要时进行整合。
虽然 SpEL 作为 Spring 项目组合内表达式评估的基础但它并不直接关联于 Spring,可以独立使用。 自 为了自成体系,本章中的许多示例都将 SpEL 当作独立表达式语言使用。这需要创建一些自助式基础设施类,比如解析器。大多数 Spring 用户无需处理这些基础设施,而是只能编写表达式字符串进行评估。这种典型用例之一是将 SpEL 集成到创建 XML 或基于注释的 bean 定义中,如 Expression 支持定义 bean 定义时所展示的。
本章介绍表达式语言、其API及其语言的特性 语法。 在多个地方,发明家和社会类被用作目标表达式评估对象。这些类声明及其用于填充它们的数据列于章节末尾。
表达式语言支持以下功能:
-
字面表达
-
布尔和关系算子
-
正则表达式
-
类表达式
-
访问属性、数组、列表和映射
-
方法调用
-
关系算符
-
分配
-
调用构造子
-
比恩的引用
-
阵列构造
-
内联列表
-
内联地图
-
三元算符
-
变量
-
用户自定义函数
-
收藏投影
-
馆藏选择
-
模板表达式
4.1. 评估
本节介绍了 SpEL 接口及其表达式语言的简单使用。完整的语言参考文献可在 Language Reference 中找到。
以下代码介绍了 SpEL API 以评估字面字符串表达式,世界您好.
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
| 1 | 消息变量的值为《你好,世界》. |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
| 1 | 消息变量的值为《你好,世界》. |
你最可能使用的 SpEL 类和接口位于org.springframework.expression包及其子包,例如spel.support.
这表达解析器接口负责解析表达式字符串。 在 前述例子中,表达字符串是由周围的单一表示的字符串字面表示 引号。 这表达接口负责评估之前定义的表达式字符串。可以抛出的两个例外,解析例外和评估例外, 在调用时解析器.解析表达式和经验值, 分别。
SpEL支持多种功能,如调用方法、访问属性,以及调用构造函数。
在下面的方法调用示例中,我们称康卡特字符串字面量:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
| 1 | 的价值消息现在是《Hello World!》。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
| 1 | 的价值消息现在是《Hello World!》。 |
以下调用 JavaBean 属性的示例将字符串属性字节:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
| 1 | 这行将文字转换为字节数组。 |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
| 1 | 这行将文字转换为字节数组。 |
SpEL还支持通过使用标准点符号(例如)进行嵌套属性道具1.道具2.道具3)以及相应的属性值设置。公共字段也可以访问。
以下示例展示了如何使用点符号来计算字面长度:
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给出文字长度。 |
字符串的构造子可以被调用,而不是使用字符串的字面值,如下所示示例说明:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
| 1 | 构建一个新的字符串从字面上开始,把它变成大写字母。 |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
| 1 | 构建一个新的字符串从字面上开始,把它变成大写字母。 |
请注意通用方法的使用:public <T> T getValue(Class<T> desiredResultType). 使用这种方法可以省去将表达式的值铸造为所需的结果类型。 一评估例外如果无法将该值铸造为 类型T或通过注册型转换器进行转换。
SpEL更常见的用途是提供一个表达式字符串,该表达式字符串被评估针对特定对象实例(称为根对象)。以下示例展示了如何检索名称来自发明家类或创建一个布尔条件:
// 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. 理解评价背景
这评价背景在评估表达式时,会使用接口来解析属性、方法或字段,并帮助执行类型转换。Spring 提供了两个功能 实现。
-
SimpleEvaluationContext:揭示了 SpEL 语言中一些关键特性的子集配置选项,适用于不需要全部范围的表达式类别SpEL 语言语法,并且应被有意义地限制。示例包括但不限于数据绑定表达式和基于属性的过滤器。 -
标准评估上下文: 暴露了完整的 SpEL 语言功能和配置选项。你可以使用它来指定默认根对象并配置所有可用的与评估相关的策略。
SimpleEvaluationContext设计上仅支持SpEL语言语法的子集。它排除了Java类型的引用、构造子和豆子引用。它还要求你必须明确选择表达式中属性和方法的支持级别。默认情况下,create()静态工厂方法仅允许对属性的读取访问。
你也可以找建商来配置所需的精确支持水平,针对性
以下一种或以下组合:
-
习惯
PropertyAccessor只有(无反射) -
只读访问的数据绑定属性
-
读写数据绑定属性
类型转换
默认情况下,SpEL 使用 Spring 核心提供的转换服务
(org.springframework.core.convert.ConversionService).这项转换服务将提供
内置多种转换器用于常见转换,同时也完全可扩展,因此
你可以在类型间添加自定义转换。此外,它
懂通用药。这意味着,当你处理泛型类型时
SpEL尝试转换以保持任意对象的类型正确性
它会遭遇。
这在实际作中意味着什么?假设赋值,使用setValue(),正在使用
设 a列表财产。房产的类型实际上是列表<布尔>.SpEL
识别列表中的元素需要转换为布尔以前
被放进去。以下示例展示了如何实现:
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).配置
对象控制部分表达式组件的行为。例如,如果你
索引到数组或集合中,且指定索引的元素为零,
你可以自动创建该元素。这在使用由
属性引用链。如果你索引到数组或列表
以及指定一个超出当前数组大小末端的索引,或者
列表,你可以自动扩展数组或列表以容纳该索引。如下
示例展示了如何自动增长列表:
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. 特殊英语编译
Spring Framework 4.1 包含一个基本的表达式编译器。表达式通常为 解释,这在评估过程中提供了很大的动态灵活性,但 但无法提供最佳性能。偶尔用表达式, 这没问题,但当被其他组件如Spring Integration使用时, 性能非常重要,其实并不需要那么多动态感。
SpEL编译器旨在满足这一需求。在评估过程中,编译器 生成一个 Java 类,在运行时体现表达式行为并使用 类以实现更快的表达式求值。因为缺乏打字 编译器使用解释评估过程中收集的信息 在执行编译时的表达式。例如,它不知道类型 在表达式中,仅仅从表达式中获得属性引用,但在第一次解释 评估,它会发现它是什么。当然,基于此类衍生的汇编 信息如果各种表达元素的类型不同,可能会在后续引发麻烦 随着时间变化。因此,编译最适合用于表达式 类型信息在反复评估中不会改变。
考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
由于前述表达式涉及数组访问,某些性质对去引用, 以及数值运算,性能提升非常明显。举个例子 Micro 基准测试运行了5万次迭代,评估过程耗时75毫秒,使用了 仅使用编译版表达式,且仅3毫秒。
编译器配置
编译器默认不会开启,但你可以在两种模式中开启 不同的方式。你可以通过解析器配置过程开启它 (前文讨论)或通过使用系统 当 SpEL 使用嵌入到其他组件中时,属性。本节 讨论了这两种选择。
编译器可以以三种模式之一运行,这些模式被捕获于org.springframework.expression.spel.SpelCompilerMode枚举。模式如下:
-
关掉(默认):编译器已关闭。 -
立即的:在即时模式下,表达式会尽快被编译。这 通常在第一次解释的评估之后。如果编译后的表达式失败 (通常由于类型变化,如前所述),表达式的调用者 评估获得例外。 -
混合在混合模式下,表达式会在解释和编译之间静默切换 随时间推移的模式。经过一定次数的解释运行后,它们切换为编译 如果编译后的形式出现问题(例如类型发生变化,如 上述表达式会自动切回解释形式 再。过一段时间,它可能会生成另一个编译表单并切换到该表单。基本上 例外是用户进入立即的模式则由内部处理。
立即的模态存在是因为混合模式可能会对表达式产生问题,
有副作用。如果编译表达式在部分成功后爆炸,则
可能已经做了影响系统状态的事情。如果是这样
发生了,调用者可能不希望它静默地以解释模式重运行,
因为表达式的部分可能运行两次。
选择模式后,使用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)
当你指定编译器模式时,也可以指定一个类加载器(允许传递空值)。 编译后的表达式在任意提供的子类加载器中定义。 重要的是,如果指定了一个类加载器,它能够看到所有涉及的类型 表达式评估过程。如果你没有指定classloader,则使用默认的classloader (通常是表达式评估过程中运行的线程的上下文类加载器)。
第二种配置编译器的方法是用于将 SpEL 嵌入在其他编译器中
组件,可能无法通过配置对象进行配置。在这些中
在这种情况下,可以使用系统属性。你可以设置spring.expression.compiler.mode财产转让给Spel编译器模式enum值(不对劲,立即的或混合).
4.2. Beans定义中的表达
你可以使用基于XML或注释的配置元数据的SpEL表达式
定义豆子定义实例。在这两种情况下,定义表达式的语法都是
形式#{ <表达字符串> }.
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>
应用上下文中的所有豆子都以预定义变量的形式提供,且其
普通豆名。这包括标准上下文豆子,如环境(类型)org.springframework.core.env.Environment) 以及系统属性和systemEnvironment(类型)Map<String,对象>)用于访问运行时环境。
以下示例展示了访问系统属性豆作为 SpEL 变量:
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
注意,你不必在这里用符号作为预定义变量前缀。#
你也可以按名称称呼其他Beans属性,如下示例所示:
<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
}
以下示例展示了在属性设定器方法上的等价方法:
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. 语言参考
本节描述了春季表达语言的工作原理。涵盖了以下内容 主题:
4.3.1. 字面表达
支持的文字表达式类型包括字符串、数值(整数、实数、十六进制), 布尔值和零值。字符串由单引号划分。如果直接用一个引号 在字符串中,使用两个单引号字符。
以下列表展示了字面值的简单用法。通常,这些设备并未被使用。 单独使用,但作为更复杂表达式的一部分——例如, 在逻辑比较算子的一侧使用文字。
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. 属性、数组、列表、映射和索引器
使用房产推荐信导航非常简单。为此,使用句号表示嵌套
房产价值。以下实例发明家类蛹和特斯拉,居住着
示例部分所用类别中列出的数据。
要“向下”导航并获得特斯拉的出生年份和普平的出生城市,我们使用以下方法
表达 式:
// 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)
映射的内容通过在
括弧。在以下例子中,因为人员映射是字符串,我们可以指定
字符串:
// Officer's Dictionary
Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");
// Officer's Dictionary
val pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor::class.java)
// evaluates to "Idvor"
val city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String::class.java)
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia")
4.3.3. 内联列表
你可以通过符号直接表达表达式中的列表。{}
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>
{}单纯意味着一个空列表。出于性能考虑,如果列表本身是
完全由固定的文字组成,创建一个常量列表以表示
表达式(而不是在每次评估上建立新的列表)。
4.3.4. 在线地图
你也可以直接用表达式表达映射,方法是用{key:value}表示法。这
以下示例展示了如何实现:
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, >
val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<, *>
{:}单纯意味着一张空白的地图。出于性能考虑,如果地图本身是
由固定文字或其他嵌套常量结构(列表或映射)组成,创建一个常量映射
用来表示表达式(而不是在每次评估中构建新的映射)。地图检索表引用
是可选的。上述示例不使用引号键。
4.3.5. 阵列构建
你可以使用熟悉的 Java 语法构建数组,并可选择提供初始化器 在建设时就让阵列被填充。以下示例展示了如何实现:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
你目前无法在建造时提供初始化器 多维阵列。
4.3.6. 方法
你可以用典型的Java编程语法调用方法。你也可以调用方法 字面意义上的。也支持变量参数。以下示例展示了如何 调用方法:
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)
// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean::class.java)
4.3.7. 操作员
Spring表达式语言支持以下类型的运算符:
关系运算符
关系算符(相等、不相等、小于、小于或相等、大于, 并且大于或等)都被标准算子表示法支持。这 以下列表展示了一些操作员的示例:
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
|
大于与小于之比较 如果你更喜欢数字比较,就避免基于数字 |
除了标准的关系算子外,SpEL 还支持实例以及正规
基于表达式比赛算子。以下列表展示了两者的示例:
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
val falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
//evaluates to false
val falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
要小心原始类型,因为它们会立刻被封装成包裹类型,
所以1个T(int) 实例评估为false而1个T(整数)实例评估为true不出所料。 |
每个符号运算符也可以作为纯字母的等价物指定。这 避免了符号对文档类型具有特殊含义的问题 该表达式被嵌入(例如在 XML 文档中)。文本对应词有:
-
中尉(<) -
燃气轮机(>) -
乐(<=) -
通用 电气(>=) -
情 商(==) -
ne(!=) -
分区(/) -
国防部(%) -
不(!).
所有文本运算符均不区分大小写。
逻辑算子
SpEL支持以下逻辑运算符:
-
和(&&) -
或(||) -
不(!)
以下示例展示了如何使用逻辑算子
// -- 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
赋值算符
设置属性时,使用赋值算符()。这通常是
在呼叫范围内完成=集合值但也可以在调用中完成getValue.这
以下列表展示了使用赋值算符的两种方式:
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic")
// alternatively
val aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)
4.3.8. 类型
你可以用特价T用算符指定 的实例java.lang.class(
类型)。静态方法也可通过使用该算符调用。这标准评估上下文使用TypeLocator以求得类型,以及标准类型定位器(可替换)是基于对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. 制造商
你可以通过使用新增功能算子。你应该使用完全合格的类别名称
对于除原始类型外的所有类型(智力,浮,依此类推)和弦。如下
示例展示了如何使用新增功能用来调用构造子的算符:
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方法评价背景实现。
|
有效的变量名必须由以下支持的一种或多种组成 字符。
|
以下示例展示了如何使用变量。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")
parser.parseExpression("Name = #newName").getValue(context, tesla)
println(tesla.name) // "Mike Tesla"
这#this和#root变量
这#this变量始终定义,指当前的评估对象
(对不加限定的引用进行解决)。这#root变量总是
定义并指根上下文对象。虽然#this可能作为 的组成部分而变化
对一个表达式进行了评估,#root总是指根。以下示例
展示如何使用#this和#root变量:
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
// create an array of integers
val primes = ArrayList<Int>()
primes.addAll(listOf(2, 3, 5, 7, 11, 13, 17))
// create parser and set variable 'primes' as the array of integers
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataAccess()
context.setVariable("primes", primes)
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
val primesGreaterThanTen = parser.parseExpression(
"#primes.?[#this>10]").getValue(context) as List<Int>
4.3.11. 职能
你可以通过注册可在
表达字符串。该功能通过评价背景.这
以下示例展示了如何注册用户自定义函数:
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. 比恩参考文献
如果评估上下文已经用豆解析器配置了,你可以
用符号从表达式中查找豆子。以下示例展示了如何
具体方法:@
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)
要访问工厂豆本身,你应该在豆子名称前加上符号。以下示例展示了如何作:&
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. 三元算符(如果-那么-否则)
你可以在表达式中使用三元运算符来执行条件逻辑表达式。以下列表展示了一个简约的示例:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
val falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
在这种情况下,布尔值false结果是返回字符串值“falseExp”. 一个更现实的例子如下:
parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
parser.parseExpression("Name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"
val queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
关于三元算子的更简短语法,请参见下一节关于 Elvis 算子的章节。
4.3.14. 猫王操作员
Elvis 算子是三元算子语法的缩短,用于 Groovy 语言。使用三元算子语法时,通常需要重复一个变量两次,如下所示以下示例展示了:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
相反,你可以使用猫王算子(因与猫王发型相似而得名)。以下示例展示了如何使用猫王算子:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'
以下列表展示了一个更复杂的例子:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Elvis Presley
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Nikola Tesla
tesla.setName(null)
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Elvis Presley
|
你可以使用 Elvis 算子来应用表达式中的默认值。以下示例展示了如何在
这将注入系统性质 |
4.3.15. 安全导航操作员
安全导航操作员用于避免NullPointerException并且来自Groovy 语言。通常,当你有对象的引用时,可能需要在访问该对象的方法或属性之前验证它不是空。为了避免这种情况,安全导航作符返回空,而不是抛出异常。以下示例展示了如何使用安全导航作符:
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. 收藏选拔
选择是一种强大的表达式语言功能,允许您通过从其条目中选择,将源集合转换为另一个集合。
选择使用语法.? [选择表达]. 它过滤该集合,并且返回一个包含原始元素子集的新集合。 例如 选择功能使我们能够轻松获得塞尔维亚发明家名单,如下示例所示:
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
val list = parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
选择可以在列表和映射上进行。对于列表,选择准则会针对每个单独的列表元素进行评估。对于一个映射,则选择准则会针对每个映射条目(Java 类型的对象)进行评估地图。条目). 每个映射条目都有其键和值,作为属性可用于选择。
以下表达式返回一个新映射,该映射包含原映射的元素其中入点小于 27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
val newMap = parser.parseExpression("map.?[value<27]").getValue()
除了返回所有选中的元素外,你只能检索第一个或最后一个值。要获得与选择匹配的第一个条目,语法为.^[selectionExpression]. 要获得最后匹配的选择,语法为.$[选择表达].
4.3.17. 收藏投影
投影让集合驱动子表达式的值,结果结果是一个新的集合。投影的语法为.! [投影表情]. 为 举例来说,假设我们有一个发明家名单,但想要列出他们出生的城市列表。实际上,我们想对发明人列表中的每条条目进行评估。以下示例使用投影来实现这一点:
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("Members.![placeOfBirth.city]") as List<*>
你也可以用映射来驱动投影,在这种情况下,投影表达式是对地图中的每个元素进行评估(以 Java 表示)地图。条目). 结果映射映射在地图上的生成是一个列表,包含投影对每个地图条目的表达式的评估。
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()方法)。第二个论证解析表达(parseExpression)方法
是 类型解析上下文.这解析上下文界面被用来影响
表达式被解析以支持表达式模板功能。
的定义模板解析上下文遵循:
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),通过以下方式 提供了另一种关于项目结构的思考方式。模块性的关键单元 在面向对象中是类,而在AOP中,模单位是方面。方面 支持跨越关注点(如事务管理)的模块化 多种类型和物品。(此类问题通常被称为“交叉切入”问题 在AOP文献中。)
Spring 的关键组件之一是 AOP 框架。而春季国际奥委会 容器不依赖于AOP(也就是说,如果你不想用AOP,也可以不使用 AOP AOP补充了Spring IoC,提供了一个非常强大的中间件解决方案。
AOP在Spring Framework中用于:
-
提供声明式企业服务。最重要的此类服务是声明式事务管理。
-
让用户实现自定义方面,补充他们使用 AOP 的面向编程。
| 如果你只对通用声明式服务或其他预包装服务感兴趣 声明式中间件服务如池化,你不需要直接作 春季AOP,可以跳过大部分章节。 |
5.1. AOP概念
让我们先定义一些核心的AOP概念和术语。这些术语不是 Spring限定。遗憾的是,AOP的术语并不直观。 然而,如果Spring用自己的术语,那就更让人困惑了。
-
方面:跨越多个类别的关注点模块化。 事务管理是企业Java中跨领域关注的一个很好的例子 应用。在 Spring AOP 中,切面通过使用普通类实现 (基于模式的方法)或用
@Aspect注释(@AspectJ风格)。 -
连接点:程序执行过程中的一个点,例如执行 方法或异常处理。在春季AOP中,连接点总是固定 表示方法的执行。
-
建议:某方面在特定连接点采取的行动。不同类型的 建议包括“周围”、“之前”和“之后”的建议。(讨论了建议类型 稍后。)许多AOP框架,包括Spring,都将建议建模为拦截者, 在连接点周围保持一连串拦截器。
-
点割:匹配连接点的谓词。建议与 点切割表达式,并且在与点切割匹配的任意连接点运行(例如, 执行某个特定名称的方法)。连接点匹配的概念 by pointcut 表达式是 AOP 的核心,Spring 使用 AspectJ 点切割 表达式语言默认使用。
-
引言:代表某一类型声明额外的方法或字段。Spring AOP 允许你为任何 建议目标。例如,你可以用引言让豆子实现一个
IsModified(变体)接口,以简化缓存。(在 AspectJ 社区中,介绍称为类型间声明。) -
目标对象:由一个或多个方面建议的对象。也称为“建议对象”。由于 Spring AOP 通过运行时代理实现,这个对象始终是一个代理对象。
-
AOP 代理:由 AOP 框架创建的对象,用于实现合同(建议方法执行等)。在 Spring 框架中,AOP 代理是 JDK 动态代理或 CGLIB 代理。
-
织入:将切面与其他应用类型或对象关联,以创建推荐对象。这可以在编译时完成(例如使用 AspectJ 编译器,加载时,或运行时完成。Spring AOP 和其他纯 Java AOP 框架一样,在运行时执行编织。
春季AOP包括以下类型的建议:
-
Before advice:在连接点之前运行但不具备阻止执行流进入连接点的能力(除非抛出例外)。
-
返回建议后:建议在连接点完成后运行通常(例如,如果方法返回时未抛出异常)。
-
抛出建议后:如果方法通过抛出 例外。
-
(最终)建议之后:建议无论通过何种方式运行连接点退出(正常或例外返回)。
-
Around advice:围绕连接点的建议,如方法调用。这是最强大的建议类型。Around advice 可以执行自定义行为在方法调用之前和之后。它还负责选择是继续前往连接点,还是通过返回自己的返回值或抛出异常来捷径执行建议的方法。
Around advice 是最通用的建议类型。由于 Spring AOP,像 AspectJ 一样,提供了完整的建议类型,我们建议你使用功能最弱的建议类型,能够实现所需的行为。例如,如果你只需要用方法的返回值更新缓存,你最好实现在返回建议之后,而不是 round advice,尽管 around advice 可以实现同样的事情。使用最具体的建议类型能提供更简单的编程模型错误的可能性更低。例如,你不需要调用继续()方法JoinPoint用于提供建议,因此你不能不使用它。
所有建议参数都是静态类型,因此你可以使用建议参数相应类型(例如方法执行返回值的类型),而不是 比对象阵 列。
通过点切割匹配的连接点概念是AOP的关键,它区别于它与仅提供拦截的旧技术。点切割使建议能够独立于面向对象层级而被定位。例如,你可以将关于提供声明式事务管理的建议应用于跨越多个对象(例如服务层中的所有业务作)。
5.2. 春季AOP能力与目标
Spring AOP 是用纯 Java 实现的。不需要特殊的编译 过程。 Spring AOP 无需控制类加载器层级结构,因此适合用于 servlet 容器或应用服务器。
Spring AOP 目前仅支持方法执行连接点(建议执行在 Spring Beans 上的方法)。字段拦截尚未实现,尽管支持字段拦截可以在不破坏 Spring AOP 核心 API 的情况下添加。如果您需要建议字段访问和更新连接点,可以考虑使用如 AspectJ 这样的语言。
Spring AOP 对 AOP 的方法与大多数其他 AOP 框架不同。其目标是不提供最完整的 AOP 实现(尽管 Spring AOP 相当能力强)。相反,目标是实现 AOP 实现与Spring IoC,以帮助解决企业应用中的常见问题。
例如,Spring 框架的 AOP 功能通常用于 与春季国际海洋中心容器(Spring IoC)容器结合。切面通过使用普通豆来配置 定义语法(尽管这支持强大的“自动代理”功能)。这是一艘 与其他AOP实现的关键区别。有些事情你做不到 通过 Spring AOP 轻松或高效地处理,例如建议非常细粒度的对象(通常, 域对象)。在这种情况下,AspectJ是最佳选择。然而,我们的 经验表明,春季AOP为大多数问题提供了极佳的解决方案。 适用于AOP的企业级Java应用程序。
春季AOP从不与AspectJ竞争,提供全面的AOP 溶液。我们认为无论是基于代理的框架,比如Spring AOP,还是全面开发的 像AspectJ这样的框架很有价值,而且它们是互补的,而不是 竞争。Spring 能够无缝集成 Spring AOP 和 IoC 与 AspectJ,以支持 所有在一致的基于Spring的应用中AOP的使用 架构。此集成不影响 Spring AOP API 或 AOP 联盟 应用程序接口。Spring AOP保持向后兼容。关于 Spring AOP API 的讨论,请参见下一章。
|
春季框架的核心原则之一是非侵入式。这 意思是你不应该被迫引入针对框架的类,并且 与您的业务或领域模型相衔接。然而,在某些地方,春季框架 它确实给你提供了在 Spring Framework 专用依赖中引入的选项 代码库。给你这些选项的理由是,在某些情况下, 可能这样更容易阅读,或者在这样的环境中编写某些特定功能 一条路。然而,Spring Framework(几乎)总是给你一个选择:你有 有自由做出最适合您具体用途的选择 情景或情景。 与本章相关的一个选择是AOP框架(和) 选择哪种AOP风格。你可以选择AspectJ、春季AOP,或者两者兼用。你 还可以选择@AspectJ注释风格的方法或 Spring XML 配置式方法。本章选择引入 首先采用@AspectJ式做法不应被视为春季队伍的标志 更倾向于@AspectJ注释式方法,而非 Spring XML 配置风格。 关于“为什么和因由”的更完整讨论,请参见选择使用哪种AOP声明风格” 每种风格。 |
5.3. AOP代理
Spring AOP 默认使用标准的 JDK 动态代理作为 AOP 代理。这 使任何接口(或一组接口)都可以被代理。
Spring AOP 也可以使用 CGLIB 代理。这是用来代理类的,而不是 接口。默认情况下,如果业务对象未实现 接口。由于编程对接口而非类编程是良好的做法,商业 类通常实现一个或多个业务接口。在那些(希望极为罕见的)情况下,强制使用 CGLIB 是可能的 需要建议一个界面上没有声明的方法,或者你需要在哪里 将代理对象作为具体类型传递给方法。
需要理解的是,春季AOP是基于代理的。详见《理解AOP代理》一文,详细探讨了这一切 实施细节实际上意味着。
5.4. @AspectJ支持
@AspectJ 指的是一种将切面声明为常规 Java 类并注释为 附注。@AspectJ风格由 AspectJ 项目引入,作为 AspectJ 5 版本的一部分。Spring 解释与 AspectJ 5 相同的注释,使用由 AspectJ 提供的库 用于点切解析和匹配。不过AOP运行时间仍然是纯春季AOP,而且 不依赖 AspectJ 编译器或织网器。
| 使用 AspectJ 编译器和编织器可以使用完整的 AspectJ 语言,详见《使用 AspectJ 与 Spring 应用》一文。 |
5.4.1. 启用@AspectJ支持
要在 Spring 配置中使用@AspectJ方面,你需要启用 Spring 支持基于 @AspectJ Spring 的 aspect 配置 Spring AOP,并根据这些 Aspect 是否建议它们。我们所说的自动代理是指,如果 Spring确定一个 bean 被一个或多个 aspect 建议,它会自动生成该 bean 的代理,用于拦截方法调用并确保 a a a vice根据需要运行。
@AspectJ支持可以通过XML或Java风格配置来启用。无论哪种情况你还需要确保AspectJ的aspectjweaver.jar库位于您的应用程序的 classpath(版本 1.8 或更高版本)中。该库可在自由AspectJ发行版的目录或Maven Central仓库。
5.4.2. 声明一个方面
启用@AspectJ支持后,任何在你的应用上下文中定义的豆子类是一个@AspectJ方面(具有@Aspect注释)会自动被 Spring 检测到并用于配置 Spring AOP。接下来的两个示例展示了对于一个不太有用的方面所需的最小定义。
两个例子中的第一个展示了应用中的正则豆定义上下文指向具有@Aspect注解:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of the aspect here -->
</bean>
两个例子中的第二个展示了不太有用的方面类定义,该类被注释为org.aspectj.lang.annotation.Aspect注解;
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
package org.xyz
import org.aspectj.lang.annotation.Aspect;
@Aspect
class NotVeryUsefulAspect
方面(注释为@Aspect)可以有方法和字段,与任何其他类相同。它们还可以包含点切、建议和引入(类型间) 声明。
|
通过组件扫描自动检测方面 你可以在 Spring XML 配置中将切面类注册为普通 Beans,或者通过 classpath 扫描自动检测它们——与其他 Spring 管理的 Bean 一样。不过,请注意@Aspect注释不足以实现类路径的自动检测。为此,你需要单独添加一个@Component注解 (或者,另一种说法是自定义的刻板类型注释,符合Spring的组件扫描仪规则)。 |
|
用其他方面辅导? 在春季AOP中,面向本身不能成为其他面向建议的目标。来自其他面向。 这@Aspect对类的注释将其标记为一个方面,因此,排除其自动代理。 |
5.4.3. 宣布点切
点切确定连接兴趣点,从而使我们能够控制何时运行建议。Spring AOP 只支持为 Springbeans 提供方法执行连接点,所以你可以把点切割看作是与 Spring 上方法的执行匹配 豆。 点切口声明由两部分组成:一个签名,包含名称和任意参数;另一个是点切口表达式,用来确定我们感兴趣的执行方法。在AOP的@AspectJ注释风格中,点切签名由正则方法定义提供,点切口表达式为通过使用@Pointcut注释(作为点割签名的方法)必须具有无效返回类型)。
一个例子可以帮助区分点切割签名和表达式清晰。以下示例定义了一个名为anyOldTransfer那 匹配任意名为 的方法的执行转移:
@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature
@Pointcut("execution(* transfer(..))") // the pointcut expression
private fun anyOldTransfer() {} // the pointcut signature
构成@Pointcut注释是正则的
AspectJ 5 点切割表达式。关于AspectJ点切割语言的完整讨论,请参见
AspectJ
编程指南(以及扩展版 AspectJ 5
开发者笔记)或AspectJ相关书籍(如Colyer的《Eclipse AspectJ》
ET。拉姆尼瓦斯·拉达德的《行动中的面貌》)。
支持的点切指定符
Spring AOP 支持以下 AspectJ 点切割标识符(PCD),用于 pointcut 表达 式:
-
执行: 用于匹配方法执行连接点。这是主场 用于使用 Spring AOP 的 pointcut 指示符。 -
在: 限制匹配以在某些类型内连接点(执行 在使用 Spring AOP 时,在匹配类型内声明的方法)。 -
这: 限制匹配到连接点(使用 Spring 时方法的执行 其中 bean 引用(Spring AOP 代理)是给定类型的实例。 -
目标:限制匹配到连接点(使用时方法的执行) Spring AOP)中目标对象(代理的应用对象)是一个实例 该类型。 -
args: 限制匹配到连接点(使用 Spring 时方法的执行 AOP)中,参数是给定类型的实例。 -
@target:限制匹配到连接点(使用时方法的执行) Spring AOP)中,执行对象的类具有给定类型的注释。 -
@args: 限制匹配到连接点(使用 Spring 时方法的执行 AOP)中实际传递参数的运行时类型带有 给定类型。 -
@within:在满足给定的类型内,匹配点的连接极限 注释(在 使用春季AOP)。 -
@annotation:将匹配限制为连接点,且连接点的主语 (Spring AOP 中运行的方法)具有给出的注释。
由于 Spring AOP 仅将匹配限制在方法执行连接点,上述讨论
点切指示符给出的定义比你在
AspectJ编程指南。此外,AspectJ本身具有基于类型的语义,且在
执行连接点,两者兼有这和目标指向同一对象:该
对象执行该方法。Spring AOP 是一个基于代理的系统,具有区别
在代理对象本身之间(该对象绑定于这)以及
代理(绑定于目标).
|
由于 Spring AOP 框架基于代理的特性,目标对象内部调用 按定义,是不被截获的。对于JDK代理,只有公共接口方法。 代理上的通话可以被拦截。在CGLIB中,公有和受保护的方法调用 代理会被拦截(甚至如有必要,也可以用包可见的方法)。然而 通过代理的常见交互应始终通过公开签名设计。 注意,点割定义通常会与任何截获的方法进行匹配。 如果一个点割严格只针对公有,即使在 CGLIB 代理场景中 通过代理进行潜在的非公开互动,需要相应定义。 如果你的拦截需求包括方法调用,甚至目标内的构造子 类,考虑使用Spring驱动的原生 AspectJ 织造 Spring基于代理的AOP框架。这构成了AOP使用方式的不同 各有不同特性,所以一定要熟悉编织 然后才做决定。 |
Spring AOP 还支持一个额外的 PCD,名为豆.这个PCD允许你限制
将连接点与特定命名的春豆或一组命名的
春季豆(使用万用卡时)。这豆PCD的形式如下:
bean(idOrNameOfBean)
bean(idOrNameOfBean)
这豆豆的名字Tokens可以是任何春季豆的名字。有限外卡
游戏提供了使用该角色的辅助,所以如果你确定了一些命名
春季Beans的惯例,你可以写一个*豆PCD表达
去选择他们。与其他点切标记一样,豆PCD可以
与(且)一起使用,&&||(或),且!(否定)操作员也一样。
|
这 这 |
结合点切割表达式
你可以通过以下方式组合点切割表达式&&, ||和!.你也可以参考
点切表达式按名称。下例展示了三个点割表达式:
@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 | 任何公共运营如果方法执行连接点代表执行,则匹配
任何公开方式。 |
| 2 | 在贸易中如果方法执行在交易模块中,则匹配。 |
| 3 | 贸易运营如果方法执行代表 中的任何公共方法,则 匹配
交易模块。 |
@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 | 任何公共运营如果方法执行连接点代表执行,则匹配
任何公开方式。 |
| 2 | 在贸易中如果方法执行在交易模块中,则匹配。 |
| 3 | 贸易运营如果方法执行代表 中的任何公共方法,则 匹配
交易模块。 |
最佳实践是从更小的命名结构构建更复杂的点割表达式 如前所述,分量。当以名称提及点切时,正常的 Java 可见性 规则适用(你可以在同一类型中看到私有点切,受保护的切入点在 层级、任意公共点切等)。可见度不会影响点切 匹配。
共享共同的点割定义
在处理企业应用时,开发者通常希望引用 的模块
应用和特定作集合从多个方面进行。我们
建议定义一个共同点割捕捉常见点切表达式的体
为此目的。这样的相位通常类似于以下示例:
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>元素在基于模式的AOP支持中进行了讨论。这
事务元素在事务管理中讨论。
例子
春季AOP用户很可能会使用执行最常用的是Pointcut的指示器。
执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
除了返回类型的模式外的所有部分(RET-类型-模式在前面的片段中),
名称模式和参数模式是可选的。返回类型模式确定
方法的返回类型必须是什么,才能匹配连接点。 最常用作返回类型的模式。它匹配任何回馈
类型。只有当方法返回给定的
类型。名称模式与方法名称相匹配。你可以用万能符表示为全部或
这是名字模式的一部分。如果你指定一个宣告类型模式,
包含尾部**.将其连接到名称模式组件。
参数模式稍微复杂一些:匹配于
该方法不取参数,而()(..)匹配任意数量(零或更多)的参数。
该模式与取任意类型一个参数的方法相匹配。(*)(*,弦)匹配一个需要两个参数的方法。第一个可以是任意类型的,而
第二必须是字符串.请参考语言
更多信息请参见 AspectJ 编程指南的语义部分。
以下示例展示了一些常见的点割表达式:
-
任何公共方法的执行:
execution(public * *(..))
-
任何名称开头为 的方法的执行
设置:execution(* set*(..))
-
由
会计服务接口:execution(* com.xyz.service.AccountService.*(..))
-
在
服务包:execution(* com.xyz.service.*.*(..))
-
在服务包或其子包中定义的任何方法的执行:
execution(* com.xyz.service..*.*(..))
-
服务包内的任何连接点(仅在 Spring AOP 中执行方法):
within(com.xyz.service.*)
-
服务包内的任何连接点(仅在 Spring AOP 中执行方法)或其其中一个 子软件包:
within(com.xyz.service..*)
-
任何连接点(仅在 Spring AOP 中执行方法)代理实现
会计服务接口:this(com.xyz.service.AccountService)
“这个”更常用装订形式。请参阅“声明建议”部分,了解如何在建议正文中使代理对象可用。 -
任何连接点(仅在 Spring AOP 中执行方法)目标对象 实现
会计服务接口:target(com.xyz.service.AccountService)
“目标”一词更常以绑定形式使用。详见“申报建议”部分 如何让目标对象在建议文中可用。 -
任何只在 Spring AOP 中执行方法的连接点,只需一个参数 其中运行时传递的参数为
序列 化:args(java.io.Serializable)
“args”更常以装订形式使用。详见“申报建议”部分 关于如何在建议正文中提供方法论证。 注意,本例中给出的切点不同于
执行(* *(java.io.Serializable)).如果运行时传递的参数为序列 化,执行版本匹配,若方法签名声明单个 参数类型序列 化. -
任何连接点(仅在 Spring AOP 中执行方法)目标对象具有
@Transactional注解:@target(org.springframework.transaction.annotation.Transactional)
你也可以用“@target”作为装订形式。详见“申报建议”部分 如何在建议正文中使注释对象可用。 -
任何连接点(仅在 Spring AOP 中执行方法)声明的类型 目标对象具有
@Transactional注解:@within(org.springframework.transaction.annotation.Transactional)
你也可以用“@within”作为装订形式。详见“申报建议”部分 如何在建议正文中使注释对象可用。 -
任何连接点(仅在 Spring AOP 中执行方法)执行方法具有
@Transactional注解:@annotation(org.springframework.transaction.annotation.Transactional)
你也可以用“@annotation”作为装订形式。详见“申报建议”部分 关于如何使注释对象在建议正文中可用。 -
任何连接点(仅在 Spring AOP 中执行方法)且只使用单一参数, 其中传递参数的运行时类型具有
@Classified注解:@args(com.xyz.security.Classified)
你也可以用“@args”作为装订形式。详见“申报建议”部分 如何在建议主体中使注释对象可用。 -
在名为 Spring Bean 的 Spring Bean 上,任何连接点(仅在 Spring AOP 中执行方法)
贸易服务:bean(tradeService)
-
Spring Beans 上任何连接点(仅在 Spring AOP 中执行方法)具有 匹配万用词表达式
*服务:bean(*Service)
写出优秀的点切
编译过程中,AspectJ 处理点切以优化匹配 性能。检查代码并确定每个连接点是否匹配(静态或 动态上)给定的点割是一个代价高昂的过程。(动态匹配指匹配 无法通过静态分析完全确定,且在代码中放置测试 在代码运行时判断是否存在实际匹配)。第一次遇到 点切割声明,AspectJ将其重写为匹配的最优形式 过程。这意味着什么?基本上,点割会被重写成DNF(析取式) 法式)和点割的分量被排序为 那些评估成本较低的保险会先被检查。这意味着你无需担心 关于理解各种点切割指示器的性能,并可能提供它们 在点切声明中,顺序不限。
然而,AspectJ只能根据被告知工作。为了实现 匹配时,你应该考虑他们想要达到什么目标,并缩小搜索范围 定义中尽可能匹配的空间。现有的标识 自然地分为三类:类型型、范围型和情境型:
-
类型标识符选择特定类型的连接点:
执行,获取,设置,叫和处理器. -
范围指示符选择一组连接兴趣点(可能有多种类型):
在和代码内 -
上下文标识符根据上下文匹配(并可选择绑定):
这,目标和@annotation
一个写得好的点割应至少包含前两种类型(类型和范围界定)。你可以包含上下文标识符以匹配连接点上下文或绑定该上下文以便用于建议。仅提供分类标识符或仅上下文标识符是可行的,但可能会影响编织性能(使用的时间和内存),因为额外的处理和分析。 范围 标识符匹配速度非常快,使用它们意味着AspectJ可以非常快速地剔除不应进一步处理的连接点群。一个好的点切割应尽可能包含一个连接点。
5.4.4. 宣告建议
建议与点切口表达式关联,运行于点切之前、之后或绕行点切边匹配的方法执行。点切口表达式可以是对命名点切的简单引用,也可以是现场声明的点切表达式。
咨询前
你可以在某方面申报,方法是使用@Before注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
如果我们使用原位点切割表达式,可以将上述示例重写为以下示例:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
@Aspect
class BeforeExample {
@Before("execution(* com.xyz.myapp.dao.*.*(..))")
fun doAccessCheck() {
// ...
}
}
回馈建议后
返回后,当匹配的方法执行正常返回时,建议会执行。你可以通过以下方式声明它@AfterReturning注解:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
fun doAccessCheck() {
// ...
}
}
| 你可以有多个建议声明(以及其他成员),全部都在同一方面。我们只展示一个建议声明以聚焦每个示例的影响。 |
有时,你需要在建议文中访问返回的实际值。你可以使用以下形式@AfterReturning这绑定返回值以获得访问,如下示例所示:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning
@Aspect
class AfterReturningExample {
@AfterReturning(
pointcut = "com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
returning = "retVal")
fun doAccessCheck(retVal: Any) {
// ...
}
}
所用名称返回属性必须对应于参数的名称在advice方法中。当方法执行返回时,返回值会传递给advice方法作为对应的参数值。 一个返回子句还限制匹配仅限于返回指定类型值的方法执行(此例中,对象,与任意返回值相匹配)。
请注意,在使用使用之后退回建议时,无法退回完全不同的推荐信。
投掷后建议
抛出建议后,当匹配方法执行时,通过抛出 例外。 你可以用以下方式声明它@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() {
// ...
}
}
通常,你希望建议只在抛出某类例外时运行,而且你也常常需要在建议主体中访问抛出的异常。 您可以 使用以下扔属性 都限制匹配(如有需要 — 使用可投掷否则,作为异常类型),并将抛出的异常绑定到一个建议参数。以下示例展示了如何实现:
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) {
// ...
}
}
所用名称扔属性必须对应于 中advice 方法的名称。当方法执行通过抛出异常退出时,该异常作为对应的参数值传递给 advice 方法。 一个扔第 还限制匹配仅限于抛出指定类型(DataAccessException,在此中)。
|
注意 |
在(终于)得到建议之后
在(最终)建议运行后,当匹配的方法执行结束时。它由使用@After注解。 之后建议必须准备好应对正常和异常返回条件。它通常用于释放资源等 目的。 以下示例展示了如何使用“Final Tips”:
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注解。 第一个参数advice 方法必须是类型推进加入点.在建议正文中,
叫继续()在推进加入点导致底层方法运行。
这进行方法也可以通过对象[]. 数组中的值被使用作为方法执行的参数,当方法继续执行时。
行为进行当被召唤时对象[]与的行为略有不同进行对于由AspectJ编译器编译的关于的关于的建议。对于使用传统AspectJ语言编写的建议,传递到的参数数进行必须匹配传递给 around adpromise 的参数数(而非底层连接点所取参数数),并且在给定参数位置取代了实体在连接点的原始值值被绑定于 (如果现在不理解也不必担心)。Spring 采取的方法更简单,且更符合其基于代理的仅执行方式 语义学。 只有在编译@AspectJ为 Spring 编写的方面时,你才需要意识到这个区别进行并与 AspectJ 编译器以及 Weaver 进行参数。有一种方法可以写出这些方面,并且在Spring AOP 和 AspectJ 之间 100% 兼容,这部分将在下文关于建议参数的部分中讨论。 |
以下示例展示了如何使用周边建议:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint
@Aspect
class AroundExample {
@Around("com.xyz.myapp.CommonPointcuts.businessService()")
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return retVal
}
}
环绕建议返回的值即调用者所看到的返回值 方法。 例如,一个简单的缓存方面可以返回缓存中的值,如果它有一个 并且调用继续()如果不成立。注意进行可以被引用一次,多次,或者根本不在周围建议的正文中。这些都是合法的。
建议参数
Spring 提供全类型建议,意味着你需要在建议签名中声明所需的参数(正如我们之前在返回和抛掷示例中看到的),而不是工作对象[]数组一直在使用。我们将在本节后面看到如何为建议主体提供论证及其他上下文值。首先,我们来看看如何编写通用建议,以了解该建议当前所建议的方法。
通往洋流的通道JoinPoint
任何建议方法的第一个参数都可以声明一个类型的参数org.aspectj.lang.JoinPoint(注意,需要 a a 建议来声明第一个类型为 的参数推进加入点,是 的子类JoinPoint. 这JoinPoint接口提供了多种实用方法:
-
getArgs()返回方法参数。 -
getThis()返回代理对象。 -
getTarget()返回目标对象。 -
getSignature()返回所建议方法的描述。 -
toString():印刷了对所建议方法的有用描述。
详情请参见 javadoc。
将参数传递给建议
我们已经见过如何绑定返回值或异常值(使用之后返回和抛出后 advice)。为了使参数值对建议正体可用,可以使用args. 如果你用参数名代替类型名称在 ARGS 表达式中,对应参数的值会被传递为当调用建议时,参数值。举个例子可以让这一点更清晰。假设你想建议执行需要帐户第一个参数是对象,你需要在建议主体中访问该账户。
你可以写以下内容:
@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(账户,..)点割表达式的一部分有两个目的。首先,它
仅在方法执行至少一次的情况下进行匹配
参数,传递给该参数的参数是 的实例帐户.
其次,它使实际帐户通过帐户参数。
另一种写法是声明一个点割,“提供”帐户当对象值匹配到连接点时,然后引用命名的点切割
根据建议。这看起来如下:
@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编程指南 详。
代理对象(这),目标对象(目标),以及注释(@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>)
}
你可以通过以下方式限制方法类型的拦截到特定参数类型 将建议参数输入到你想要拦截该方法的参数类型中:
@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
}
要实现这一点,我们必须检查集合中的每一个元素,而集合并非如此
合理,因为我们也无法决定如何治疗零价值观的整体。实现
类似这样,你需要将参数输入为收藏<?>并且是手动的
检查元素的类型。
确定论元名称
建议调用中的参数绑定依赖于点切中使用的名称匹配 在建议和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,推进加入点或加入点。静态部分类型,你可以在值中省略参数名称
关于argNames属性。例如,如果你修改了前面的建议,以获得
连接点对象,argNames属性不必包含:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}
@Before(value = "com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames = "bean,auditable")
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
val code = auditable.value()
// ... use code, bean, and jp
}
对第一个参数的特殊处理JoinPoint,推进加入点和加入点。静态部分类型 特别方便于
建议实例不收集其他连接点上下文。在这种情况下,你可以
省略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 查看 类,尝试从本地变量表中确定参数名称。这 只要类经过调试编译,信息就存在 信息 ('-g:vars'至少)。与此旗帜编纂的后果 包括:(1)你的代码稍微容易理解(逆向工程),(2) 类文件大小略大(通常无关紧要),(3) 编译器不会执行移除未使用的局部变量的优化。在 换句话说,开着这个旗帜建造应该不会遇到任何困难。如果AspectJ编译器(ajc)编译了@AspectJ方面,即使没有 调试信息,你无需添加 argNames属性,作为编译器 记住所需的信息。 -
如果代码编译时没有必要的调试信息,请使用 Spring AOP 尝试推导出将变量与参数的配对(例如,如果 pointcut 表达式中只有一个变量被绑定,建议方法 只取一个参数,配对显而易见)。如果变量的绑定为 鉴于现有信息,歧义不明确,且
AmbiguousBindingException是 扔。 -
如果上述所有策略均失败,则
IllegalArgumentException被抛出。
继续进行辩论
我们之前提到过,我们会描述如何写出进行通话
这些论证在春季AOP和AspectJ中始终有效。解是
以确保建议签名按顺序绑定每个方法参数。
以下示例展示了如何实现:
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
accountHolderNamePattern: String): Any {
val newPattern = preProcess(accountHolderNamePattern)
return pjp.proceed(arrayOf<Any>(newPattern))
}
在很多情况下,你还是会做这种绑定(就像前面的例子)。
建议订购
当多个建议都想在同一个连接点运行时会发生什么? 春季AOP遵循与AspectJ相同的优先规则来确定建议顺序 执行。最高优先级的建议先行“进入时”(即给定两枚棋子) 在建议之前,优先级最高的先行者)。“在离开的路上” 加入点,最高优先级的建议最后运行(即给定两个 之后的片段 建议,优先级最高的那条将排第二)。
当两条建议分别定义在不同方面时,都需要同时执行
除非特别说明,否则执行顺序未定义。您可以
通过指定优先顺序来控制执行顺序。这是正常情况下的
通过实现org.springframework.core.Ordered接口
方面类或用@Order注解。给定两个方面,
方面返回较低的值Ordered.getOrder()(或注释值)具有
优先级更高。
|
每个方面的不同建议类型在概念上都是应用的
直接到连接点。因此,一个 自 Spring Framework 5.2.7 起,建议方法在该框架中定义 当两条同类建议(例如,两条)时 |
5.4.5. 介绍
引入(在 AspectJ 中称为类型间声明)使某方面能够声明 建议对象实现给定接口,并提供 代表这些对象的接口。
你可以通过以下方式进行介绍@DeclareParents注解。此注释
用于声明匹配类型有新的父(因此得名)。例如
给定一个名为使用追踪以及该接口的实现默认使用追踪,以下方面声明所有服务的实现者
接口还实现了使用追踪接口(例如通过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()
}
}
要实现的接口由注释字段的类型决定。这值属性@DeclareParents注释是一种AspectJ类型的模式。任何
匹配类型的豆实现了使用追踪接口。注意,在
在上述示例建议之前,服务豆可以直接用作
该系统的实现使用追踪接口。如果程序化访问豆子,
你会写下以下内容:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
5.4.6. 切面实例化模型
| 这是一个高级话题。如果你刚开始接触AOP,可以放心跳过 等会儿再说。 |
默认情况下,应用程序中每个方面都有一个实例
上下文。AspectJ称之为单例实例化模型。可以定义
具有替代生命周期的方面。Spring 支持 AspectJ佩里斯和每目标实例化模型;Percflow,下层和pertypewithin(pertypewithin)目前没有
支持。
你可以声明佩里斯通过指定一个体佩里斯该条款@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() {
// ...
}
}
在上述例子中,该效应佩里斯条款是那个方面实例
为每个执行业务服务的唯一服务对象创建(每个 唯一
绑定到 的对象这在连接点与点切割表达式匹配的连接点。相位
实例是在第一次在服务对象上调用方法时创建的。这
当服务对象超出作用域时,方面也在作用域之外。在相位之前
实例被创建,里面的任何建议都不会运行。一旦处于相关实例
已经创建,其中声明的建议在匹配的连接点运行,但仅限于
当服务对象是该方面所关联的对象时。参见AspectJ
关于更多信息的编程指南每第。
这每目标实例化模型的工作原理完全相同佩里斯,但它
为每个匹配连接点的唯一目标对象创建一个面向实例。
5.4.7. AOP示例
既然你已经了解了所有组成部分的工作原理,我们可以将它们整合起来来实现 有用的东西。
业务服务的执行有时可能因并发问题而失败(对于
例如,僵局失败者)。如果重试该行动,成功的可能性很大
下次再试。对于适合重新尝试的商业服务
条件(幂冪级作,无需返回用户进行冲突
我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException.这是一项明确跨越界限的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想重试作,需要参考相关建议以便
叫进行多次。以下列表展示了基本的方面实现:
@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
}
}
注意,该切面实现了命令接口,这样我们就可以设置 的优先级
比交易建议更高的方面(我们希望每次都获得新的交易)
重试)。这maxRetries和次序这两个属性都由 Spring 配置。这
主要作用发生在doConcurrentOperation关于建议。注意,对于
我们对每个人应用重试逻辑业务服务().我们试图继续前行,
如果我们用 a 失败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>
为了精炼体,使其只重试幂零运算,我们可以定义如下幂等注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent// marker annotation
然后我们可以用注释来注释服务作的实现。变革
对于只重试幂零元作的方面,需要细化点割
表达式仅使得@Idempotent作匹配如下:
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
}
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
// ...
}
5.5. 基于模式的AOP支持
如果你更喜欢基于XML的格式,Spring还支持定义切面
使用AOP命名空间标签。同样的切分表达式和建议类型
就像使用支持的 @AspectJ 风格一样。因此,本节我们重点关注
该语法并引导读者回到上一节的讨论
(@AspectJ支持)理解点切割表达式的写作和绑定
关于建议参数。
要使用本节描述的aop命名空间标签,你需要导入春季-AOPschema,如XML Schema-based configuration中所述。请参阅AOP模式,了解如何导入标签AOPNamespace。
在你的Spring配置中,所有方面和顾问元素都必须放在其中
一<aop:config>元素(你可以有多个<aop:config>元素
应用上下文配置)。一<aop:config>元素可以包含 pointcut,
顾问和方面元素(注意这些元素必须按该顺序声明)。
这<aop:config>这种配置风格大量使用了Spring的自动代理机制。这可能会引发问题(比如建议
如果你已经通过 使用 了显式自动代理,则不编织)豆名自动代理创作者或者类似的。推荐的使用模式是
仅使用以下两者<aop:config>风格或仅是自动代理创建器风格和
绝不要混在一起。 |
5.5.1. 声明一个方面
当你使用模式支持时,切面是定义为 bean 的普通 Java 对象 你的 Spring 应用上下文。状态和行为被捕捉在场中, 对象的方法以及点切和建议信息都被捕捉在XML中。
你可以通过以下方式声明一个方面<aop:aspect>元素,并引用背衬豆
通过使用裁判属性,如下例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支撑这方面(aBean在这种情况下,当然可以配置和
依赖就像其他春季豆一样注入。
5.5.2. 宣布切角
你可以在<aop:config>元素,让 点切割
定义应在多个方面和顾问间共享。
表示服务层中任何业务服务执行的点切断 定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
注意,点切表达式本身使用了相同的AspectJ切点表达式 语言如@AspectJ支持中描述的。如果你使用基于模式的声明 样式中,你可以引用类型(@Aspects)中定义的命名点割 尖锐的表情。另一种定义上述点割的方法如下:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.CommonPointcuts.businessService()"/>
</aop:config>
假设你有一个共同点割面向,详见《共享常见点割定义》。
那么在一个方面内声明点割与声明顶层点割非常相似, 如下示例所示:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
类似于@AspectJ方面,点割通过基于模式的宣告
定义风格可以收集连接点上下文。例如,以下点切割
收集这对象作为连接点上下文,并将其传递给建议:
<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 中 是尴尬的
文档,所以你可以使用和,或和不关键词&&,||和!分别。例如,之前的点割可以更好地写为
遵循:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
注意,以这种方式定义的点割会用其XML来指代身份证并且不可能
用作命名的点切以形成复合切点。命名的点切支撑在
基于模式的定义风格因此比@AspectJ所提供的更为有限
风格。
5.5.3. 宣告建议
基于模式的AOP支持使用与@AspectJ样式相同的五种建议,并且有 语义完全相同。
咨询前
Before a Advisor 在匹配方法执行前运行。它被声明为<aop:aspect>通过使用<aop:之前>元素,如下例所示:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
这里dataAccess作是身份证定义在顶部的点割(<aop:config>)
水平。要定义内联的点割,则替换为点切-参考属性
一个点切属性,具体如下:
<aop:aspect id="beforeExample" ref="aBean">
<aop:before
pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
method="doAccessCheck"/>
...
</aop:aspect>
正如我们在讨论@AspectJ风格时提到的,使用命名点割可以 显著提升代码的可读性。
这方法属性标识一个方法(doAccessCheck) 提供 的主体
建议。该方法必须为体元所引用的豆子定义
里面有建议。在执行数据访问作(方法执行)之前
连接点由点切割表达式匹配),该doAccessCheck在该方面的方法
Bean被召唤。
回馈建议后
返回后,当匹配的方法执行正常完成时,建议会执行。是的
在<aop:aspect>就像之前的建议一样。以下示例
展示了如何声明:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
method="doAccessCheck"/>
...
</aop:aspect>
和@AspectJ风格一样,你可以在建议文中获得回报价值。
为此,可以使用返回属性指定参数名称,使其
返回值应传递,如下示例所示:
<aop:aspect id="afterReturningExample" ref="aBean">
<aop:after-returning
pointcut-ref="dataAccessOperation"
returning="retVal"
method="doAccessCheck"/>
...
</aop:aspect>
这doAccessCheck方法必须声明一个参数,名为retVal.这种类型
参数以描述的方式约束匹配@AfterReturning.为
例如,你可以声明方法签名如下:
public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...
投掷后建议
在抛出建议后,当匹配的方法执行通过抛出
例外。它被声明为<aop:aspect>通过使用抛球后元素
如下示例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
method="doRecoveryActions"/>
...
</aop:aspect>
和 @AspectJ 一样,你可以在建议文中获得投掷例外。
为此,可以使用扔属性指定参数名称
该例外应通过,如下示例所示:
<aop:aspect id="afterThrowingExample" ref="aBean">
<aop:after-throwing
pointcut-ref="dataAccessOperation"
throwing="dataAccessEx"
method="doRecoveryActions"/>
...
</aop:aspect>
这doRecoveryActions(恢复行动)方法必须声明一个参数,名为dataAccessEx.
该参数的类型以与描述的匹配方式约束匹配@AfterThrowing.例如,方法签名可以声明如下:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...
在(终于)得到建议之后
在(终于)无论匹配方法如何执行后,建议都会运行。
你可以用后元素,如下例所示:
<aop:aspect id="afterFinallyExample" ref="aBean">
<aop:after
pointcut-ref="dataAccessOperation"
method="doReleaseLock"/>
...
</aop:aspect>
周边建议
最后一种建议是关于建议的。周围的建议是“绕过”匹配的方法 执行。它有机会在方法运行前后都进行工作 以及确定该方法何时、如何,甚至是否能运行。 Around 建议常用于在 线程安全的方式(例如启动和停止计时器)。永远用最少的 有力的建议,符合你的需求。如果不使用周围的建议 在建议能起作用之前。
你可以通过以下方式来申报相关建议aop:关于元素。第一个参数
建议方法必须是 类型推进加入点.在建议正文中,
叫继续()在推进加入点导致底层方法运行。
这进行方法也可以调用对象[].数组中的值
作为方法执行时的参数。
关于来电的注意点,请参见Around Advice进行带有对象[].
以下示例展示了如何在XML中声明关于建议:
<aop:aspect id="aroundExample" ref="aBean">
<aop:around
pointcut-ref="businessService"
method="doBasicProfiling"/>
...
</aop:aspect>
实施doBasicProfiling建议可以完全相同于
@AspectJ示例(当然不包括注释),如下示例所示:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
// start stopwatch
val retVal = pjp.proceed()
// stop stopwatch
return pjp.proceed()
}
建议参数
基于模式的声明样式支持完全类型的建议,方式与以下相同
为@AspectJ支持描述——通过按名称匹配点割参数
建议方法参数。详情请参见建议参数。如果你愿意
明确指定建议方法的参数名称(不依赖于
检测策略,你可以通过使用arg-names建议元素的属性,其处理方式与argNames在建议注释中(如判定参数名称中所述)。
以下示例展示了如何在 XML 中指定参数名称:
<aop:before
pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
method="audit"
arg-names="auditable"/>
这arg-names属性接受逗号分隔的参数列表。
以下稍为复杂的XSD方法示例显示 关于建议的一些结合多个强类型参数:
package x.y.service;
public interface PersonService {
Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
public Person getPerson(String name, int age) {
return new Person(name, age);
}
}
package x.y.service
interface PersonService {
fun getPerson(personName: String, age: Int): Person
}
class DefaultPersonService : PersonService {
fun getPerson(name: String, age: Int): Person {
return Person(name, age)
}
}
接下来是相位。注意个人资料(..)方法接受若干
强类型参数,其中第一个恰好是用于
继续执行方法调用。该参数的存在表明个人资料(..)将用于周围建议,正如下面的例子所示:
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)
建议订购
当多个建议需要在同一连接点运行(执行方法)时
排序规则如建议排序中所述。优先级
在方面之间,通过次序属性<aop:aspect>元素或
通过添加以下@Order对支持该方面或的豆子的注释,通过具有
豆子实现了命令接口。
|
与同一中定义的建议方法优先规则不同 例如,给定 一般来说,如果你发现自己有多个建议
同一时期 |
5.5.4. 介绍
引言(在 AspectJ 中称为类型间声明)允许一个体声明 该建议对象实现给定接口,并提供 代表这些对象的接口。
你可以通过以下方式进行介绍aop:声明-父母元素在AOP:方面.
你可以使用aop:声明-父母用来声明匹配类型有新的父(因此得名)。
例如,给定一个名为使用追踪以及该接口的实现默认使用追踪,以下方面声明所有服务的实现者
接口还实现了使用追踪接口。(为了揭示统计学
例如通过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>
支持该类的使用情况追踪豆子则包含以下方法:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
待实现的接口由以下条件确定实现接口属性。这
的值类型匹配attribute 是 AspectJ 类型的模式。任何一颗小子
匹配类型实现了使用追踪接口。注意,在之前
参考前述示例的建议,服务豆可以直接作为
这使用追踪接口。要程序化访问豆子,你可以写
以后:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
val usageTracked = context.getBean("myService") as UsageTracked
5.5.6. 顾问
“顾问”的概念源自春季中定义的AOP支持系统 并且在AspectJ中没有直接对应的。导师就像个小人物 这是一个自成一体的部分,只有一条建议。建议本身是 以豆子表示,必须实现《Spring 的建议类型》中描述的其中一种建议接口。顾问可以利用 AspectJ 的点切割表达式。
Spring 支持 Advisor 概念,包括<aop:advisor>元素。你最
它通常与交易性建议一起使用,而交易性建议也有自己的
春季的命名空间支持。以下示例展示了一位顾问:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<aop:advisor
pointcut-ref="businessService"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
以及点切-参考在前面示例中使用的属性,你也可以使用点切属性以定义内联的点切割表达式。
为了定义顾问的优先级,使该顾问能够参与订购,
使用以下次序属性以定义命令顾问的价值。
5.5.7. AOP模式示例
本节展示了AOP示例中并发锁定失败重试示例在采用模式支持后呈现的样貌。
业务服务的执行有时可能因并发问题而失败(对于
例如,僵局失败者)。如果重试该行动,成功的可能性很大
下次再试。对于适合重新尝试的商业服务
条件(幂冪级作,无需返回用户进行冲突
我们希望透明地重试作,以避免客户端看到PessimisticLockingFailureException.这是一项明确跨越界限的要求
服务层中的多个服务,因此非常适合通过
方面。
因为我们想重试作,需要参考相关建议以便
叫进行多次。以下列表展示了基本的方面实现
(这是一个常规的 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
}
}
注意,该切面实现了命令接口,这样我们就可以设置 的优先级
比交易建议更高的方面(我们希望每次都获得新的交易)
重试)。这maxRetries和次序这两个属性都由 Spring 配置。这
主要作用发生在doConcurrentOperation关于建议的方法。我们尽力了
进行。如果我们用 a 失败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>
请注意,在当时,我们假设所有商业服务都是幂零的。如果
事实并非如此,我们可以细化该方面,使其只真正重试
幂冪等作,通过引入一个幂等注释以及使用
用于注释服务作的实现,如下示例所示:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
// marker annotation
}
这
将重试的面向变更为仅限幂零运算,涉及对
点切割表达式,仅有@Idempotent作匹配如下:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service.*.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
5.6. 选择使用哪种AOP声明样式
一旦你决定了某方面是实现某项的最佳方法 需求,你如何决定是使用Spring AOP还是AspectJ,还是在 是切面语言(代码)样式、@AspectJ注释样式,还是 Spring XML 样式?这些 决策受多种因素影响,包括申请要求, 开发工具,以及团队对AOP的熟悉度。
5.6.1. 春季AOP还是Full AspectJ?
用最简单、最有效的方法。Spring AOP 比使用完整的 AspectJ 更简单,因为 开发过程中无需引入 AspectJ 编译器/织网器 并建立流程。如果你只需要建议春季的作执行 豆子,春季AOP是正确的选择。如果你需要建议未被管理的对象 你需要使用 Spring 容器(通常是域对象) 方面。如果你想建议其他连接点,也需要使用 AspectJ 简单的方法执行(例如字段获取或设置连接点等)。
使用 AspectJ 时,你可以选择 AspectJ 语言语法(也称为 “代码样式”或@AspectJ注释样式。显然,如果你不使用 Java,那就说明了 5+,选择已经为你做好了:使用代码样式。如果方面发挥作用 在你的设计中扮演角色,你可以使用 AspectJ Eclipse 的开发工具(AJDT)插件,AspectJ 语言语法是 首选。它更干净、更简单,因为语言是有意为之 为写作方面设计。如果你不使用Eclipse,或者只有几个方面 如果你在申请中没有主要作用,你可以考虑使用 @AspectJ风格,在你的IDE中坚持常规的Java编译,并添加 构建脚本中一个交织方面阶段。
5.6.2。春季AOP用@AspectJ还是XML?
如果你选择使用 Spring AOP,可以选择 @AspectJ 或 XML 样式。 需要考虑多种权衡。
XML 风格对现有 Spring 用户来说可能最为熟悉,并且得到了 True(真实)的支持 POJO。在使用 AOP 作为配置企业服务的工具时,XML 可以是一个很好的工具 选择(一个好的检验是你是否认为点切表达式是你 配置你可能想独立更改)。用XML风格,是 从你的配置来看,系统中存在哪些方面可能更清晰。
XML 格式有两个缺点。首先,它并未完全封装 在单一地点实现其满足的需求。干性原则是 任何一部分都应有一个单一、明确、权威的代表 系统内的知识。使用 XML 风格时,了解需求如何 实现在支持Beans的声明和XML之间分割。 配置文件。使用@AspectJ样式时,这些信息会被封装 在一个模块中:相位。其次,XML风格在内容上稍微受限一些 它只能表达@AspectJ风格:仅“单例”方面实例化模型 支持,且无法合并在XML中声明的命名点切割。 例如,在@AspectJ风格中,你可以写如下内容:
@Pointcut("execution(* get*())")
public void propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}
@Pointcut("execution(* get*())")
fun propertyAccess() {}
@Pointcut("execution(org.xyz.Account+ *(..))")
fun operationReturningAnAccount() {}
@Pointcut("propertyAccess() && operationReturningAnAccount()")
fun accountPropertyAccess() {}
在XML风格中,你可以声明前两个点割:
<aop:pointcut id="propertyAccess"
expression="execution(* get*())"/>
<aop:pointcut id="operationReturningAnAccount"
expression="execution(org.xyz.Account+ *(..))"/>
XML方法的缺点是你无法定义accountPropertyAccess通过结合这些定义进行点切割。
@AspectJ风格支持额外的实例化模型和更丰富的点切割 组成。它的优点是保持了相块作为模块化单元。它还包括 优点在于,这些@AspectJ方面可以同时理解(从而被吸收)为 春季AOP和AspectJ。所以,如果你后来决定需要AspectJ的功能 要实现额外需求,你可以很容易地迁移到经典的AspectJ配置。 总体来看,春季团队更倾向于采用@AspectJ风格,用于定制方面超越简单 企业服务配置。
5.7. 混合方面类型
完全可以通过自动代理支持混合 @AspectJ 风格方面,
模式定义<aop:aspect>方面<aop:advisor>顾问们甚至代理人都宣布了
以及同配置中其他风格的拦截机。所有这些功能都已实现
通过使用相同的基础支持机制,可以顺利共存。
5.8. 代理机制
Spring AOP 使用 JDK 动态代理或 CGLIB 来创建给定的代理
目标物体。JDK 动态代理内置于 JDK,而 CGLIB 则是常见的
开源类定义库(重新打包为Spring芯).
如果被代理的目标对象至少实现了一个接口,则 JDK 动态 使用代理。目标类型实现的所有接口都是代理的。 如果目标对象不实现任何接口,则创建一个CGLIB代理。
如果你想强制使用 CGLIB 代理(例如,代理所有方法 为目标对象定义,而不仅仅是其接口实现的对象), 你可以这么做。不过,你应该考虑以下几个问题:
-
使用 CGLIB 的话,
最后方法无法被推荐,因为它们无法被覆盖 运行时生成的子类。 -
从春季4.0开始,你的代理对象的构造器不再被调用两次, 因为CGLIB代理实例是通过Objenesis创建的。只有你的JVM有需要 不允许构建器绕过,你可能会看到双重调用和 来自 Spring AOP 支持的对应调试日志条目。
为了强制使用 CGLIB 代理,设置代理目标类属性
关于<aop:config>元素变为真,具体如下:
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
要在使用@AspectJ自动代理支持时强制CGLIB代理,请设置代理目标类属性<aop:aspectj-autoproxy>元素变true,
如下:
<aop:aspectj-autoproxy proxy-target-class="true"/>
|
倍数 明确来说,使用 |
5.8.1. 理解AOP代理
春季AOP是基于代理的。理解 在你写自己的方面或使用任何之前,最后那句话到底是什么意思 以及Spring Framework提供的基于Spring AOP的部分。
首先考虑一个场景,如果你有一个纯普通、无代理的, 没什么特别的,直接的对象引用,如下 代码片段显示:
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()
}
这里需要理解的关键是,客户端代码在主(..)方法
关于主要类对代理的引用。这意味着方法调用了
对象引用是代理上的调用。因此,代理可以委派给所有
与该方法调用相关的拦截器(建议)。然而
一旦调用最终到达目标对象(简单Pojo参考文献
本例中,任何方法调用它自身,例如this.bar()或this.foo(),将被引用针对这参考,而不是代理。
这具有重要意义。这意味着自我祈求不会发生
在与方法调用相关的建议中,有机会运行。
那么,我们该怎么办?最佳方法(即“最佳”一词) 这里松散地说)是重构你的代码,使得自我调用不会发生。 这确实需要你付出一些努力,但这是最好的、最少侵入性的方法。 接下来的做法简直糟糕透顶,我们也不太敢指出来 因为这实在太可怕了。你可以(虽然对我们来说很痛苦)完全理解这个逻辑 在你的班级中,从春季AOP到春季AOP,如下示例所示:
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this works, but... gah!
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
这完全将你的代码与 Spring AOP 绑定,并且让该类本身意识到 而且它被用于AOP语境,这与AOP的做法背道而驰。它 在创建代理时也需要一些额外的配置,因为 以下示例展示了:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
factory.isExposeProxy = true
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
最后,必须注意AspectJ没有这种自我调用问题,因为 它不是一个基于代理的AOP框架。
5.9. 程序化创建@AspectJ代理
除了在配置中声明切面外,还可以通过以下<aop:config>或<aop:aspectj-autoproxy>,也可以编程创建代理
这些目标目标会被指示。关于Spring AOP API的完整细节,请参见下一章。这里,我们想重点介绍自动
利用 @AspectJ 方面创建代理。
你可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory类
为一个目标对象创建一个代理,该代理由一个或多个@AspectJ方面建议。
该类的基本用法非常简单,如下示例所示:
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
// create a factory that can generate a proxy for the given target object
val factory = AspectJProxyFactory(targetObject)
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager::class.java)
// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker)
// now get the proxy object...
val proxy = factory.getProxy<Any>()
更多信息请参见 javadoc。
5.10. 使用 AspectJ 配合 Spring 应用
本章迄今为止我们涵盖的内容都是纯春季AOP。在本节中, 我们会看看你如何用 AspectJ 编译器或编织器代替或在 如果你的需求超出春季AOP提供的设施,可以加上春季AOP的课程 独自。
Spring 自带一个小型 AspectJ aspect 库,可以在你的
分布为spring-aspects.jar.你需要按顺序把它添加到你的类路径中
用它中的面相。使用 AspectJ 对 Spring 及其他 Spring 方面注入依赖域对象,讨论
该库的内容以及如何使用。使用Spring IoC配置AspectJ方面 讨论如何
依赖注入AspectJ的方面,这些方面是通过AspectJ编译器编织的。最后,Spring 框架中的 AspectJ 载时织入介绍了 Spring 应用的载时织入
使用AspectJ的。
5.10.1. 使用 AspectJ 与 Spring 进行依赖注入域对象
Spring 容器实例化并配置你应用中定义的豆子
上下文。也可以要求豆子工厂配置一个已有的
对象,给定一个包含要应用配置的豆子定义名称。spring-aspects.jar包含一个基于注释的特性,利用了这一点
能够允许对任何对象注入依赖。支持旨在
用于创建在任何容器控制之外的对象。域对象
通常属于这一类,因为它们通常是通过程序化创建的新增功能作符或通过数据库查询由 ORM 工具完成。
这@Configurable注释标记该类符合Spring驱动的资格
配置。在最简单的情况下,你可以纯粹将其用作标记标注,因为
以下示例展示了:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable
public class Account {
// ...
}
package com.xyz.myapp.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable
class Account {
// ...
}
当以这种方式作为标记接口使用时,Spring 配置了新的
注释类型(帐户,在此例中),通过使用Beans定义(通常如此)
原型范围)与完全限定的类型名称相同
(com.xyz.myapp.domain.Account).由于豆子的默认名称是
其类型的全限定名称,是一种方便声明原型定义的方式
就是省略身份证属性,如下例所示:
<bean class="com.xyz.myapp.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
如果你想明确指定原型豆定义的名称,你 可以直接在注释中实现,如下示例所示:
package com.xyz.myapp.domain;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("account")
public class Account {
// ...
}
package com.xyz.myapp.domain
import org.springframework.beans.factory.annotation.Configurable
@Configurable("account")
class Account {
// ...
}
春现在寻找一个Beans定义,名为帐户并利用该
定义以配置新帐户实例。
你也可以使用自动接线,避免在
都。要让Spring应用自动接线,可以使用自动线的属性@Configurable注解。你可以指定以下两种@Configurable(autowire=Autowire.BY_TYPE)或@Configurable(autowire=Autowire.BY_NAME对于按类型或名称进行自动布线,
分别。作为替代方案,更倾向于指定显式的注释驱动
依赖注入@Configurable豆子通过@Autowired或@Inject在字段或方法层面(详见基于注释的容器配置)。
最后,你可以启用 Spring 对对象引用进行依赖检查,新
通过使用依赖性检查属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true)).如果该属性为
设置为true,Spring 在配置后验证所有属性(
不是原语或集合)已被设置。
注意,单独使用注释没有任何作用。它是注释BeanConfigurerAspect在spring-aspects.jar该行为基于
注释。本质上,该方面说:“在从初始化中返回之后
一个类型的新对象,注释为@Configurable,配置新创建的对象
根据注释的性质使用Spring“。在此背景下,
“初始化”指的是新实例化的对象(例如,实例化的对象)
其中新增功能算符)以及序列 化正在进行的物体
反序列化(例如通过 readResolve())。
|
上面段落中的关键短语之一是“本质”。在大多数情况下,
“从新对象初始化返回后”的精确语义为
好。在此语境中,“初始化后”意味着依赖关系为
在物体构建完成后注入。这意味着依赖关系
不可用于该类的构造体。如果你想要
依赖关系在构造体运行前注入,因此为
在构造子的正体中可以使用,你需要在 Java
Kotlin
|
为了实现这一点,带注释的类型必须用 AspectJ 织网器编织。 您可以 可以使用构建时的 Ant 或 Maven 任务来实现这一点(例如参见 AspectJ 开发环境指南)或加载时编织(参见 Spring 框架中的 AspectJ 加载时编织)。 这注释BeanConfigurerAspectSpring 本身需要配置(以便获得用于配置新对象的豆子工厂的引用)。如果你使用 Java 配置,你可以添加@EnableSpringConfigured到任何@Configuration类别如下:
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
@Configuration
@EnableSpringConfigured
class AppConfig {
}
如果你更喜欢基于XML的配置,可以选择Spring上下文Namespace定义了 一个方便的上下文:Spring配置你可以以下方式使用元素:
<context:spring-configured/>
实例@Configurable在该切面配置之前创建的对象结果是向调试日志发送消息,且对象没有配置。例如,Spring配置中的一个bean会创建domain对象,当它被Spring初始化时。在这种情况下,你可以使用依赖BEAN 属性 手动指定 bean 依赖于配置方面。以下示例展示了如何使用依赖属性:
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
不要激活@Configurable通过豆配置器部分进行处理,除非你真的打算在运行时依赖它的语义。特别是,确保你不要使用@Configurable在注册为普通春豆的Beans上与容器一起。这样做会导致双重初始化,一次通过容器,一次通过切面。 |
单元测试@Configurable对象
其中一个目标是@Configurable支持是为了实现独立的单元测试域对象,避免硬编码查找带来的困难。 如果@ConfigurableAspectJ 未编织类型,注释不影响单元测试期间。你可以在对象中设置 mock 或 stub 属性引用测试并正常进行。 如果@ConfigurableAspectJ 编织了类型,你仍然可以像往常一样在容器外进行单元测试,但每次构建 a 时都会收到警告消息@Configurable表示该对象尚未被 Spring 配置。
多应用上下文工作
这注释BeanConfigurerAspect用于实现@Configurable支持 是AspectJ的单元素元。单元素元的作用域与作用域相同 之静态的成员:每个类加载器有一个方面实例定义类型。这意味着,如果你在同一类加载器中定义多个应用上下文层级结构,你需要考虑在哪里定义@EnableSpringConfigured豆子和放置位置spring-aspects.jar在阶级路径上。
考虑一个典型的Spring Web应用配置,其中包含一个共享的父应用程序上下文定义了共同的业务服务,以及支持这些服务所需的一切,以及每个servlet的一个子应用上下文(包含特定的定义)针对该servlet。所有这些上下文都存在于同一个类加载器层级结构中,因此注释BeanConfigurerAspect只能对其中一个表示引用。在这种情况下,我们建议定义@EnableSpringConfigured在共享的(父)应用上下文中。这定义了你可能想要注入到域对象中的服务。结果是你无法配置域对象通过使用@Configurable机制来配置域对象通过使用机制(这可能不是你想做的)。
在同一容器内部署多个Web应用时,确保每个Web应用都加载了以下类型spring-aspects.jar通过使用自己的类加载器(例如,通过spring-aspects.jar在'WEB-INF/lib'). 如果spring-aspects.jar只添加到整个容器范围的类路径中(因此由共享的父类加载器加载),所有网页应用共享同一个方面实例(这很可能不是你想要的)。
5.10.2. AspectJ 的其他春季方面
此外@Configurable方面spring-aspects.jar包含一个AspectJ
你可以用来驱动Spring事务管理类型和方法的方面
注释为@Transactional注解。这主要面向
想在 Spring 容器之外使用 Spring Framework 的事务支持。
解释的方面@Transactional注释是注释事务方面.使用该方面时,必须注释
实现类(或该类内的方法,或两者兼有),而不是接口(如果
任何)该类实现的。AspectJ遵循Java的规则,即注释在
接口不是继承的。
一个@Transactional类上的注释指定了 的默认事务语义
执行该类别中任何公开作。
一个@Transactional类内方法的注释覆盖默认
事务语义由类注释(如存在)给出。任何方法
可见性可以通过注释,包括私有方法。注释非公开方法
直接执行此类方法时,只有获得事务分界线。
自 Spring Framework 4.2 起,Spring方面提供了类似的方面,提供
标准版的特征完全相同javax.transaction.事务注解。检查Jta注释事务方面更多细节请阅读。 |
对于想使用 Spring 配置和事务的 AspectJ 程序员
管理层支持但不想(或不能)使用注释,spring-aspects.jar还包含抽象你可以扩展这些方面,提供属于自己的分量
定义。请参阅相关资料来源摘要BeanConfigurerAspect和摘要事务方面相关方面获取更多信息。举例来说,如下
摘录展示了如何写一个切面来配置所有对象实例
在域模型中,使用与
完全合格的班级名称:
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 运行时本身为
负责方面创建,以及配置AspectJ创建的设备
通过 Spring 的 aspects 依赖于 AspectJ 实例化模型(该模型每XXX页条款)
被面向使用。
大多数AspectJ的方面是单元素方面。这些配置
Aspects很简单。你可以创建一个 bean 定义,引用切面类型为
正常值,并包括factory-method=“aspectOf”Beans属性。这确保了
春通过向AspectJ请求,而不是尝试创造,从而获得该面体实例
一个实例本身。以下示例展示了如何使用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”属性 |
非单例方面则更难配置。然而,可以通过以下方式实现
创建原型豆定义并使用@Configurable支持spring-aspects.jar在 Aspect 实例拥有 bean 后配置
AspectJ运行时。
如果你有一些@AspectJ方面想用 AspectJ 编织(例如,
使用加载时编织来处理领域模型类型)以及你想要的其他@AspectJ方面
在 Spring AOP 中使用,这些方面都在 Spring 中配置,你
需要告诉Spring AOP自动代理支持的具体子集@AspectJ
配置中定义的@AspectJ方面应用于自动代理。您可以
通过使用一个或多个来实现<包括/>内部元素<aop:aspectj-autoproxy/>声明。每<包括/>元素指定一个名称模式,且仅指
与至少一个图案匹配的名称用于春季AOP自动代理
配置。以下示例展示了如何使用<包括/>元素:
<aop:aspectj-autoproxy>
<aop:include name="thisBean"/>
<aop:include name="thatBean"/>
</aop:aspectj-autoproxy>
不要被<aop:aspectj-autoproxy/>元素。使用
结果是生成了春季AOP代理。@AspectJ的面相风格
这里使用声明,但不涉及 AspectJ 运行时。 |
5.10.4. 在 Spring 框架中使用 AspectJ 进行加载时编织
加载时织造(LTW)指的是将AspectJ的部分编织成 应用程序的类文件在加载到Java虚拟机(JVM)时。 本节重点是配置和使用LTW在具体情境下 Spring Framework。本节并非对LTW的一般介绍。完整详情 LTW 的具体内容以及仅使用 AspectJ 配置 LTW(不含 Spring) 完全不参与),参见AspectJ的LTW部分 发展环境指南。
Spring 框架为 AspectJ LTW 带来的价值在于实现了许多
对织造过程的更细致控制。“原版”方面 LTW 通过使用
一个Java(5+)代理,通过在启动
JVM。因此,这是一个 JVM 范围的设置,在某些情况下可能没问题,但通常
有点太粗糙了。启用Spring的LTW允许你在
每-ClassLoader基底,粒度更细,可以产生更多
在“单一JVM多应用”环境中(如典型环境中)有意义
应用服务器环境)。
此外,在某些环境中,这种支持能够实现
加载时编织,无需对应用服务器启动做任何修改
需要添加的脚本-javaagent:path/to/aspectjweaver.jar或者(如我们所描述的
本节后面)-javaagent:path/to/spring-instrument.jar.开发者配置
应用上下文中,可以实现加载时编织,而不是依赖管理员
他们通常负责部署配置,比如启动脚本。
现在销售介绍结束了,让我们先快速演示一下AspectJ的例子 LTW使用Spring,随后详细介绍了引入的元素 例。完整示例请参见Petclinic示例申请。
第一个例子
假设你是一名被指派诊断的应用开发人员 系统中某些性能问题的原因。而不是拿出 分析工具,我们将开启一个简单的分析功能,使我们能够使用 赶紧拿一些绩效指标。然后我们可以应用更细致的剖面 紧接着,工具会立即前往该区域。
这里展示的示例使用 XML 配置。你也可以配置和
使用@AspectJ配合Java配置。具体来说,你可以使用@EnableLoadTimeWeaving注释作为替代方案<context:load-time-weaver/>(详情见下文) |
以下示例展示了画像方面,这并不复杂。 它是一种基于时间的分析器,采用@AspectJ风格的体面声明:
package foo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;
@Aspect
public class ProfilingAspect {
@Around("methodsToBeProfiled()")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch(getClass().getSimpleName());
try {
sw.start(pjp.getSignature().getName());
return pjp.proceed();
} finally {
sw.stop();
System.out.println(sw.prettyPrint());
}
}
@Pointcut("execution(public * foo..*.*(..))")
public void methodsToBeProfiled(){}
}
package foo
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Pointcut
import org.springframework.util.StopWatch
import org.springframework.core.annotation.Order
@Aspect
class ProfilingAspect {
@Around("methodsToBeProfiled()")
fun profile(pjp: ProceedingJoinPoint): Any {
val sw = StopWatch(javaClass.simpleName)
try {
sw.start(pjp.getSignature().getName())
return pjp.proceed()
} finally {
sw.stop()
println(sw.prettyPrint())
}
}
@Pointcut("execution(public * foo..*.*(..))")
fun methodsToBeProfiled() {
}
}
我们还需要创建一个元步兵/aop.xmlfile,用以通知 AspectJ 织布者
我们想要编织我们的画像方面进入我们的课堂。这个文件约定,具体来说
Java 类路径上存在一个(或多个文件)称为元步兵/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专属配置部分。我们需要
以配置一个加载时间编织者(稍后解释)这个加载时编织器是
关键组件负责将横体配置编织成一或
更多元步兵/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>
现在所有所需的工件(方面,以及元步兵/aop.xml文件和 Spring 配置)都已到位,我们可以创建以下内容
驾驶组,配备主(..)演示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-上选择性地开启LTW。ClassLoader与Spring的关系,这一点是真的。
不过,在这个例子中,我们使用Spring提供的Java代理来开启LTW。
我们使用以下命令来运行主要前面显示的类别:
java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main
这-javaagent是用于指定和启用代理的标志
连接到运行在JVM上的仪器程序。Spring Framework 自带的
代理人,InstrumentationSavingAgent,该格式被封装在spring-instrument.jar该值被提供为-javaagent论证上述例子。
执行 的输出主要程序看起来和下一个例子差不多。(我引入了Thread.sleep(..)将陈述转化为calculateEntitlement()实现使分析器实际捕获的不是0毫秒(该)01234毫秒并非AOP引入的开销)。以下列表显示了我们运行分析器时得到的输出:
Calculating entitlement StopWatch 'ProfilingAspect': running time (millis) = 1234 ------ ----- ---------------------------- ms % Task name ------ ----- ---------------------------- 01234 100% calculateEntitlement
由于该LTW是通过使用完整的AspectJ实现的,我们并不限于提供建议春季豆。以下是对主要程序结果相同 结果:
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 支持的基本原理在之前的示例中都已介绍,本节其余部分将详细解释每个配置位和使用背后的“为什么”。
这画像方面这个例子中用的虽然很基础,但非常实用。这是一个开发时间方面的一个很好的示例,开发者可以在开发过程中使用然后轻松地从构建中排除正在部署的应用进入 UAT 或生产环境。 |
方面
你在 LTW 中使用的 aspect 必须是 AspectJ 的 aspect。你可以用AspectJ 语言本身写入,或者用 @AspectJ 风格写 aspect。这样你的 aspect 既是有效的 AspectJ 又是 Spring AOP 的 aspect。此外,编译后的 aspect 类必须在类路径上可用。
“元步兵/aop.xml”
AspectJ LTW基础设施通过使用一个或多个配置来配置元步兵/aop.xml这些文件位于 Java 类路径上(直接或更常见的 jar 文件)。
该文件的结构和内容详见 AspectJ 参考文献中的 LTW 部分文档。因为aop.xml文件是100%AspectJ,我们这里不作进一步描述。
必修图书馆(JARS)
至少,你需要以下库来使用 Spring Framework 的支持用于 AspectJ LTW:
-
spring-aop.jar -
aspectjweaver.jar
如果你使用 Spring 提供的代理来启用仪器,你还需要:
-
spring-instrument.jar
Spring配置
Spring LTW 支持的关键组件是加载时间编织者接口(在org.springframework.instrument.classloading以及众多实现随 Spring 发行版一起发布的。 一个加载时间编织者负责添加一个或多个java.lang.instrument.ClassFileTransformers转给ClassLoader在 运行时,这为各种有趣的应用打开了大门,其中之一恰好是方面(aspect of aspects)的 LTW。
如果你不熟悉运行时类文件转换的概念,请参阅javadoc API 文档中的java.lang.instrument在继续之前,先打包。虽然那份文档不够全面,但至少你可以看到关键接口以及类(供你阅读本节时参考)。 |
配置加载时间编织者对于特定的应用上下文可以简单到:添加一行。(注意你几乎肯定需要使用应用上下文作为你的春季容器——通常,一个豆子工厂莫 因为LTW支持人员会豆工厂后处理器.)
要启用 Spring 框架的 LTW 支持,你需要配置一个加载时间编织者, 通常通过使用@EnableLoadTimeWeaving注释如下:
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig {
}
或者,如果你更喜欢基于XML的配置,可以使用<context:load-time-weaver/>元素。 注意,元素定义在上下文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特有的基础设施豆,例如加载时间编织者以及AspectJWeavingEnabler给你的。 默认加载时间编织者是DefaultContextLoadTimeWeaver类别,试图装饰一个自动检测的加载时间编织者. 具体类型加载时间编织者“自动检测”取决于你的运行环境。
下表总结了各种情况加载时间编织者实现:
| 运行环境 | 加载时间编织者实现 |
|---|---|
在Apache Tomcat中运行 |
|
在GlassFish中运行(限于EAR部署) |
|
|
|
在 IBM WebSphere 中运行 |
|
在 Oracle WebLogic 中运行 |
|
JVM始于Spring |
|
后备,期望底层的 ClassLoader 遵循常见的惯例
(具体来说 |
|
请注意,表中仅列出了加载时间编织者当你被自动检测时
使用以下DefaultContextLoadTimeWeaver.你可以具体指定具体哪种加载时间编织者实现到使用。
具体指明加载时间编织者在 Java 配置中,实现LoadTimeWeavingConfigurer接口并覆盖getLoadTimeWeaver()方法。
以下示例指定了反射加载时间织者:
@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的配置,可以指定完全限定的类名
作为韦弗级属性<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
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
</beans>
这加载时间编织者该配置定义并登记,可以更晚
通过使用著名的名称从春季容器中提取,加载时间编织者.
记住加载时间编织者仅作为Spring的LTW机制存在
基础设施以增加一个或多个ClassFileTransformers.实际ClassFileTransformerLTW是类预处理器代理适配器(摘自
这org.aspectj.weaver.loadtimepackage)类别。参见 Javadoc 的类级 javadoc类预处理器代理适配器关于进一步细节,因为具体方法需要说明
实际发生的编织超出了本文件的范围。
配置还有最后一个属性需要讨论:那aspectj织物属性(或aspectj-织造如果你使用XML的话)。该属性控制了LTW是否
是否启用。它接受三种可能的值之一,默认值为自动侦测如果该属性不存在。下表总结了这三者
可能的数值:
| 注释值 | XML 值 | 解释 |
|---|---|---|
|
|
AspectJ编织已启动,且在加载时根据需要编织。 |
|
|
LTW关闭了。加载时没有任何方面被编织。 |
|
|
如果春季LTW基础设施能找到至少一个 |
环境特定配置
最后一节包含你需要的任何额外设置和配置 当你在应用服务器和网页等环境中使用 Spring 的 LTW 支持时 器皿。
Tomcat、JBoss、WebSphere、WebLogic
Tomcat、JBoss/WildFly、IBM WebSphere 应用服务器和 Oracle WebLogic Server 均为
提供一个通用的应用ClassLoader能够进行本地仪器安装。斯普林斯
原生LTW可能利用这些ClassLoader实现来提供AspectJ编织。
你可以像前面描述的那样,直接启用加载时编织。
具体来说,你不需要修改JVM启动脚本来添加-javaagent:path/to/spring-instrument.jar.
注意在JBoss上,你可能需要禁用应用服务器扫描,以防止它被
在应用真正开始之前加载课程。一个快速的变通方法是添加
到你的文物,一个名为的文件网络信息/jboss-scanning.xml内容如下:
<scanning xmlns="urn:jboss:scanning:1.0"/>
通用 Java 应用程序
当在不支持 的环境中需要类仪器时
特定加载时间编织者在实现方面,JVM代理是通用解决方案。
在这种情况下,Spring 提供了InstrumentationLoadTimeWeaver这需要一个
针对Spring专用(但非常通用)的JVM代理,spring-instrument.jar,自动检测
由通用语@EnableLoadTimeWeaving和<context:load-time-weaver/>设置。
要使用它,你必须通过提供 以下JVM选项:
-javaagent:/path/to/spring-instrument.jar
注意,这需要修改JVM启动脚本,可能会阻碍你 在应用服务器环境中使用它(取决于你的服务器和你的 运营方针)。话虽如此,对于每个JVM部署一个应用的独立部署 Spring Boot 应用通常你控制整个 JVM 设置。
5.11. 进一步资源
关于AspectJ的更多信息可在AspectJ官网找到。
《蚀面相J》作者:Adrian Colyer等。al.(Addison-Wesley, 2005)提供了 AspectJ语言的全面介绍与参考。
《AspectJ in Action》第二版,作者Ramnivas Laddad(Manning,2009年)评价极高 推荐。这本书的重点是AspectJ,但很多AOP的通用主题 深入探讨。
6. Spring AOP API
上一章介绍了Spring对基于@AspectJ和基于模式的AOP的支持 体定义。本章讨论了更低级别的 Spring AOP API。对于普通 我们建议使用带有AspectJ点切的Spring AOP,如 上一章。
6.1. Spring 中的 Pointcut API
本节描述了Spring如何处理关键点切概念。
6.1.1. 概念
Spring的点切题模型支持点切入的重复使用,独立于建议类型。您可以 针对不同的建议,但同样的切入点。
这org.springframework.aop.Pointcut接口是中央接口,用于
针对特定课程和方法的建议。完整界面如下:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
interface Pointcut {
fun getClassFilter(): ClassFilter
fun getMethodMatcher(): MethodMatcher
}
分割点切分成两部分的接口允许重复使用类和方法
匹配部件和细粒度合成作(例如执行“并集”
并配有另一个方法匹配器)。
这类过滤器接口用于限制点割对应的目标集合
类。如果比赛()方法总是返回真,所有目标类均为
匹配。以下列表显示了类过滤器界面定义:
public interface ClassFilter {
boolean matches(Class clazz);
}
interface ClassFilter {
fun matches(clazz: Class<*>): Boolean
}
这方法匹配器接口通常更重要。完整界面如下:
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
}
这匹配(方法,类别)用于测试该点割是否曾经
匹配目标类上的给定方法。此评估可在AOP期间进行
代理的创建是为了避免每次方法调用都进行测试。如果
二元论元比赛方法返回true对于给定方法,以及isRuntime()方法匹配器返回true,三参数匹配方法为
每次方法调用都会被调用。这使得点数切割可以查看已通过的论点
在目标建议开始前立即调用方法。
最方法匹配器实现是静态的,意味着他们的isRuntime()方法
返回false.在这种情况下,三元论证比赛方法从未被调用。
| 如果可能,尽量让点切是静态的,这样AOP框架就能缓存 创建AOP代理时的点割评估结果。 |
6.1.2. 点切作
Spring支持点切口的作(尤其是合集和交叉)。
并指任一点切入所匹配的方法。
交点指的是两个点割方法匹配的方法。
联合通常更有用。
你可以用静态方法来组合点割org.springframework.aop.support.Pointcuts类或通过使用可组合点切割同一个包裹里的课程。然而,使用 AspectJ 点切割
表达式通常是一种更简单的方法。
6.1.3. AspectJ表达式点切割
自2.0版本起,Spring 使用的最重要的点切类型是org.springframework.aop.aspectj.AspectJExpressionPointcut.这是一个点切
使用AspectJ提供的库来解析AspectJ的点切割表达式字符串。
关于支持的 AspectJ 点切割原语的讨论,请参见上一章。
6.1.4. 便利点切割实现
Spring 提供了几种方便的点切割实现。你可以用其中一些 径直;其他则旨在通过应用特定的点切割进行子类化。
静态点切
静态点割基于方法和目标类,无法考虑 方法论证。静态点切对于大多数用途来说是最好的。 Spring 只能在方法首次调用时评估静态点切割一次。 之后,每次调用方法时就无需重新评估点切割。
本节其余部分将介绍一些静态点切割实现,包括 随《春季》一起。
正则表达式点割
一种显而易见的指定静态点割方法是正则表达式。多个AOP
除了Spring之外,还有其他框架可以实现这一点。org.springframework.aop.support.JdkRegexpMethodPointcut是一般正则
表达式点切割,使用JDK中正则表达式的支持。
与JdkRegexpMethodPointcut你可以提供一份模式字符串列表。
如果这些中的任何一个匹配,点割算值为true.(因此,
由此产生的点割实际上是指定图案的并集。)
以下示例展示了如何使用JdkRegexpMethodPointcut:
<bean id="settersAndAbsquatulatePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>.*set.*</value>
<value>.*absquatulate</value>
</list>
</property>
</bean>
Spring提供了一个方便类别,名为RegexpMethodPointcutAdvisor,这使得我们能够
另见建议(记住建议可以作为拦截机,在获得建议之前,
投掷建议,以及其他内容)。在幕后,Spring 使用JdkRegexpMethodPointcut.
用RegexpMethodPointcutAdvisor这样可以简化接线,因为一颗豆子同时封装了两者
Pointcut和建议,如下例子所示:
<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对任意建议类型。
6.1.5. Pointcut超级级别
Spring 提供了有用的 pointcut 超类,帮助你实现自己的 pointcut。
因为静态点切最有用,你可能应该选择子职业StaticMethodMatcherPointcut.这只需要实现一个
抽象方法(虽然你可以覆盖其他方法来自定义行为)。这
以下示例展示了如何子类StaticMethodMatcherPointcut:
class TestStaticPointcut extends StaticMethodMatcherPointcut {
public boolean matches(Method m, Class targetClass) {
// return true if custom criteria match
}
}
class TestStaticPointcut : StaticMethodMatcherPointcut() {
override fun matches(method: Method, targetClass: Class<*>): Boolean {
// return true if custom criteria match
}
}
还有用于动态点切的超类。 你可以用自定义的点切来处理任何建议类型。
6.2. Spring 中的建议 API
现在我们可以看看春季AOP如何处理建议。
6.2.1. 建议生命周期
每条建议都是春季的好事。一个建议实例可以在所有被推荐的人群中共享 对象或是对每个推荐对象独一无二。这对应于每个类别或 具体情况建议。
最常用的是每门课的建议。它适用于通用建议,例如: 交易顾问。这些不依赖于代理对象的状态,也不会添加新的 州。他们仅仅根据方法和论点行动。
每实例的建议适合用于介绍,以支持混合。在这种情况下, 该建议会为代理对象添加状态。
你可以在同一个AOP代理中混合使用共享和每个实例的建议。
6.2.2. 春季的建议类型
Spring提供多种建议类型,并且可扩展支持。 各种随意的建议类型。本节介绍基本概念和标准建议类型。
拦截规避建议
春季最基本的建议类型是关于建议的拦截。
Spring符合AOP标准联盟关于使用方法的建议界面
拦截。实现的类拦截方法而实施的建议也应该实现
以下接口:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
interface MethodInterceptor : Interceptor {
fun invoke(invocation: MethodInvocation) : Any
}
这方法调用对invoke()方法暴露了方法的
调用了目标连接点、AOP代理以及方法的参数。这invoke()方法应返回调用结果:连接的返回值
点。
以下示例展示了一个简单的拦截方法实现:
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 方法。
不过,你不想无缘无故这么做。
拦截方法实现可与其他符合AOP联盟的AOP互作性
实现。本节其余部分讨论的其他建议类型
实现常见的AOP概念,但采用Spring特有的方式。虽然有优势
在使用最具体的建议类型时,坚持以下内容拦截方法关于如果的建议
你很可能会想在另一个AOP框架中运行该方面。注意点割
目前框架间不兼容,AOP联盟也不支持
目前定义点切割接口。 |
咨询前
更简单的建议类型是“事前建议”。这不需要方法调用对象,因为它只在进入方法之前调用。
事前建议的主要优点是无需调用继续()因此,没有可能无意中未能继续
拦截链。
以下列表显示了方法前建议接口:
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 设计允许 现场建议,尽管通常的对象适用于现场拦截,且是 Spring 不太可能实现它。)
注意返回类型为无效.Before Avenue 可以插入自定义行为,然后在连接前
点运行但不能更改返回值。如果之前的建议会抛出
例外情况是阻止拦截链的进一步执行。例外
会沿着拦截器链向上传播。如果未被勾选或在签名上
调用的方法直接传递给客户端。否则,就是
被AOP代理包裹在未检查的异常中。
以下示例展示了 Spring 中的 before advice,它统计了所有方法调用:
public class CountingBeforeAdvice implements MethodBeforeAdvice {
private int count;
public void before(Method m, Object[] args, Object target) throws Throwable {
++count;
}
public int getCount() {
return count;
}
}
class CountingBeforeAdvice : MethodBeforeAdvice {
var count: Int = 0
override fun before(m: Method, args: Array<Any>, target: Any?) {
++count
}
}
| 在建议之前,可以用于任何切入点。 |
投掷建议
如果连接点掷出,则在返回连接点后调用投掷建议
异常。Spring提供打字投掷建议。注意,这意味着org.springframework.aop.ThrowsAdvice接口不包含任何方法。它是
标签接口,标识给定对象实现一个或多个类型抛出
建议方法。这些表格应为以下形式:
afterThrowing([Method, args, target], subclassOfThrowable)
只需要最后一个论点。方法签名可以有一个或四个 论证,取决于建议方法是否对该方法感兴趣,且 参数。接下来的两个列表展示了投掷技巧的范例。
如果远程例外是抛出的(包括来自子类):
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
}
}
与前面的建议不同,下一个示例声明了四个参数,以便能够访问调用的方法、方法参数和目标对象。如果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
}
}
最后一个例子说明了这两种方法如何在同一类中使用该类同时处理两者远程例外和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
}
}
| 如果throw-advice方法自己抛出异常,它会覆盖原始异常(即改变抛给用户的异常)。覆盖的异常通常是RuntimeException,兼容任何方法 签名。 然而,如果一个 throws-advice 方法抛出已检验异常,它必须匹配目标方法的声明异常,因此在某种程度上与特定的目标方法签名耦合。不要抛出未声明的检查异常,且该异常与目标方法的签名不兼容! |
| 投掷建议适用于任何点切。 |
回馈建议后
春季回归建议必须实施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)
}
返回后建议(After returning advice)可以访问返回值(但无法修改),调用的方法、方法的参数以及目标值。
返回建议后,以下内容统计所有成功的方法调用未抛出异常:
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将介绍建议视为一种特殊的拦截建议。
引言需要引言顾问以及简介拦截者那 实现以下接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
interface IntroductionInterceptor : MethodInterceptor {
fun implementsInterface(intf: Class<*>): Boolean
}
这invoke()该方法继承自AOP联盟拦截方法接口必须实现引入。也就是说,如果调用的方法位于引入的接口上,引入拦截器负责处理方法调用——它不能调用继续().
引言建议不能适用于任何点切,因为它只适用于类别,而不是方法层面。你只能在引言顾问,具有以下方法:
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<*>>
}
没有方法匹配器因此,不点切与引言相关 建议。 只有类过滤是合乎逻辑的。
这getInterfaces()方法返回该顾问引入的接口。
这validateInterfaces()该方法在内部使用,以判断引入的接口是否可以被配置的简介拦截者.
以Spring测试套件中的一个例子为例,假设我们想为一个或多个对象引入以下接口:
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
interface Lockable {
fun lock()
fun unlock()
fun locked(): Boolean
}
这说明了混合。我们希望能够施放建议对象上锁, 无论它们的类型,并调用锁锁和解锁方法。如果我们调用lock()方法,我们希望所有设定器方法都抛出 a锁定异常. 因此,我们可以添加一个方面能够使对象在不知情的情况下保持不可变:这是一个AOP的好例子。
首先,我们需要一个简介拦截者这就完成了主要的工作。在这种情况下我们扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor方便类。我们可以实现简介拦截者直接,但使用授权引言拦截机对大多数情况下是最好的。
这授权引言拦截机设计用来委托引言给引入接口的实际实现,隐藏拦截的使用以此实现。你可以使用构造子参数将代理设置为任意对象。 这 默认代理(当使用无参数构造函数时)为这. 因此,在下一个例子中,代理是锁混的子类授权引言拦截机. 给定一个代理(默认是代理自身),一个授权引言拦截机实例 查找代理实现的所有接口(除简介拦截者并支持对任何一种的引入。子类如锁混可以称呼suppressInterface(类 intf)抑制不应暴露的接口的方法。然而,无论有多少接口简介拦截者准备支持,引言顾问使用控制哪些接口实际上暴露。一
引入接口隐藏目标对同一接口的任何实现。
因此锁混延伸授权引言拦截机以及工具上锁本身。超职业会自动接收上锁可以支持
引言,所以我们不需要特别说明。我们可以引入任意数量的
以这种方式进行接口。
注意锁实例变量。这实际上增加了额外的状态
与目标物体中保持的联系。
以下示例展示了该示例锁混类:
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()方法。这授权引言拦截机实现(该实现称委托方法 如果
方法被引入,否则通常朝连接点方向前进)
够。在本例中,我们需要加一个检查:不能调用任何设定器方法
如果处于锁定模式。
必要的引言只需有明确的锁混实例和指定引入的接口(此处仅指上锁).一个更复杂的例子可能是引言的引用
拦截机(被定义为原型机)。在这种情况下,没有
与 a 相关的配置锁混,所以我们通过新增功能.
以下示例展示了我们的LockMixinAdvisor类:
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
我们可以非常简单地应用这个顾问,因为它不需要配置。(然而,
无法使用简介拦截者没有引言顾问.)像介绍一样,顾问必须在每个情况下,
因为它具有国家性。我们需要一个不同的实例LockMixinAdvisor,因此锁混,对每个建议对象。顾问是被建议对象的一部分
州。
我们可以通过使用以下方式程序化应用该顾问Advised.addAdvisor()方法或
(推荐的方式)在XML配置中,就像其他顾问一样。所有代理创建
下面讨论的选项,包括“自动代理创建者”,正确地处理了介绍
以及状态混合。
6.3. Advisor 在春季的应用
在春季,顾问是一个只包含单个建议对象的相位 表情尖锐。
除了介绍的特殊情况,任何顾问都可以提供任何建议。org.springframework.aop.support.DefaultPointcutAdvisor是最常用的
顾问班。它可以与拦截方法,建议之前或Throws建议.
Spring Session可以在同一AOP代理中混合顾问和咨询类型。为 例如,你可以用“关于建议”、“扔出建议”和“在建议之前”的拦截 一个代理配置。Spring会自动产生必要的拦截器 链。
6.4. 使用代理工厂豆创建AOP代理
如果你使用Spring IoC容器(一个应用上下文或豆子工厂)
业务对象(你应该这么做!),你想用Spring的AOP之一工厂豆实现。(记住,工厂豆会引入一层间接的,使得
它创造的是不同类型的对象。)
| 春季AOP支持也用原厂豆子盖在被子里。 |
在 Spring 中创建 AOP 代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean.这赋予了对
关于点切、任何适用的建议以及它们的排序。不过,还有更简单的方法
如果你不需要这种控制,这些选项更为可取。
6.4.1. 基础
这代理工厂豆,就像其他Spring一样工厂豆实现,引入了
间接的程度。如果你定义一个代理工厂豆叫福,对象
参考福不要看到代理工厂豆实例本身,但是一个对象
由getObject()在代理工厂豆.这
方法创建一个AOP代理,包裹目标对象。
使用A的最重要优势之一代理工厂豆或者其他IoC知情者
创建AOP代理的类是建议和点切也可以
由国际奥委会管理。这是一个强大的功能,使某些难以实现的方法成为可能
通过其他AOP框架实现。例如,一条建议本身可能引用
应用程序对象(除了目标对象,目标应在任何AOP中都可用)
框架),受益于依赖注入所提供的全部可插拔性。
6.4.2. JavaBean 属性
与大多数人相似工厂豆Spring提供的实现,代理工厂豆类本身就是一个 JavaBean。其性质用于:
-
指定你想代理的目标。
-
指定是否使用 CGLIB(后文介绍,另见基于 JDK 和 CGLIB 的代理)。
一些关键性质继承于org.springframework.aop.framework.ProxyConfig(春季所有AOP代理工厂的超级职业)。这些关键属性包括
以下内容:
-
proxyTargetClass:true如果目标类被代理,而不是 目标职业的接口。如果该属性值被设置为true,然后 CGLIB 代理 创建了(但也参见基于JDK和CGLIB的代理)。 -
优化: 控制是否对代理对象应用激进优化 通过CGLIB创建。除非你完全用过,否则不要轻率使用这个设置 了解相关的AOP代理如何处理优化。目前仍在使用这种方式 仅限于CGLIB代理。它对JDK动态代理没有影响。 -
冷冻:如果代理配置为冷冻,配置的变化为 不再允许。这既是轻微优化,也适用于这些情况 当你不希望呼叫者能够通过建议在代理创建后,接口。该财产的默认值为false,因此允许更改(如添加额外建议)。 -
暴露代理: 决定当前代理是否应在 中暴露ThreadLocal这样目标才能访问它。如果目标需要获得 代理和暴露代理属性设置为true,目标可以使用AopContext.currentProxy()方法。
其他特定性质代理工厂豆包括以下内容:
-
代理接口:一个数组字符串界面名称。如果没有提供,则使用CGLIB 目标类使用代理(但也可参考基于JDK和CGLIB的代理)。 -
拦截机名称:一个字符串数组顾问拦截机或其他建议名称 应用。点餐非常重要,按先到先得原则。也就是说 列表中的第一个拦截器是第一个能够拦截 调用。这些名字在现有工厂中都是豆子名字,包括祖先的豆子名字 工厂。你不能在这里提及豆子引用,因为那样会导致
代理工厂豆忽略建议中单人设置的设定。你可以在拦截机名称后加上星号()。这样做会导致 所有以星号前部分开头名称的顾问豆子的应用 待应用。你可以在“使用”全局顾问“中找到使用该功能的示例。
* -
Singleton:工厂是否应该退回单一物品,无论如何 通常
getObject()称为 方法。几个工厂豆实现方案 这样的方法。默认值为true.如果你想使用状态建议—— 例如,对于有状态混合——使用原型建议和单例值false.
6.4.3. 基于 JDK 和 CGLIB 的代理
本节作为关于代理工厂豆选择为特定目标创建基于JDK的代理或基于CGLIB的代理
对象(该对象将被代理)。
行为代理工厂豆关于创建基于JDK或CGLIB的软件
代理文件在 Spring 1.2.x 和 2.0 版本之间发生了变化。这代理工厂豆现在
在自动检测接口方面表现出与TransactionProxyFactoryBean类。 |
如果目标对象的类(以下简称为
目标类)不实现任何接口,基于CGLIB的代理是
创建。这是最简单的情况,因为 JDK 代理是基于接口的,而不是
这意味着JDK代理甚至不可能。你可以插入目标豆
并通过设置拦截机名称财产。注意
即使proxyTargetClass的属性代理工厂豆已设定为false.(这样做毫无意义,反而是最好的选择
从Beans定义中移除,因为充其量是多余的,最坏的情况是
真让人困惑。)
如果目标类实现了一个(或多个)接口,代理类型为
生成取决于 的配置代理工厂豆.
如果proxyTargetClass的属性代理工厂豆已设定为true,
创建基于CGLIB的代理。这很合理,也符合
最小惊讶原则。即使代理接口的属性代理工厂豆被设置为一个或多个完全限定的接口名称,事实
那个proxyTargetClass属性设置为true导致基于CGLIB的原因
代理生效。
如果代理接口的属性代理工厂豆已设置为一个或多个
对于完全限定的接口名称,会创建一个基于JDK的代理。被创造者
代理实现了在代理接口财产。如果目标类实现了远远多于此的接口
这些规定在代理接口财产,这固然好,但那些
返回的代理不会实现额外的接口。
如果代理接口的属性代理工厂豆尚未设定,但
目标类确实实现了一个(或多个)接口,即代理工厂豆自动检测目标类实际上有
实现至少一个接口,就会创建一个基于JDK的代理。接口
实际上代理的接口是目标类的所有接口
实现。实际上,这等同于提供每一个的列表
目标类实现的接口代理接口财产。然而
工作量显著减少,印刷错误也更少。
6.4.4. 代理接口
考虑一个简单的例子代理工厂豆行动中。这个例子包括:
-
一个被代理的目标豆。这是
个人目标豆子定义 举个例子。 -
一
顾问以及拦截 器用来提供建议。 -
AOP代理豆定义用于指定目标对象(
个人目标豆子), 代理界面和应用建议。
以下列表展示了该示例:
<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>
注意拦截机名称属性取一个列表字符串,该模型包含
现有工厂的拦截机或顾问。你可以用顾问、拦截者、前后
回来了,并投掷了建议物品。顾问的排序非常重要。
你可能会好奇为什么名单里没有Beans的引用。原因是
如果 的单元素属性代理工厂豆设置为false它必须能够
返回独立代理实例。如果任何顾问本身就是原型,那么
独立实例需要返回,因此必须能够获得
工厂原型的实例。仅凭推荐信是不够的。 |
这人前面展示的豆子定义可以替代人实现,作为
遵循:
Person person = (Person) factory.getBean("person");
val person = factory.getBean("person") as Person;
同一IoC上下文中的其他豆子可以表达对其强类型依赖,如 使用一个普通的Java对象。以下示例展示了如何实现:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
这PersonUser本例中,类暴露了一个类型为 的属性人.就
它担心AOP代理可以透明地替代“真实”的人
实现。然而,它的类将是一个动态代理类。这是有可能的
将其抛向建议接口(稍后讨论)。
你可以通过使用匿名工具来隐藏目标和代理之间的区别
内心的豆子。只有代理工厂豆定义不同。这
建议内容仅为完整性而设。以下示例展示了如何使用
匿名内心豆:
<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>
使用匿名内豆的优点是只有一个类型的对象人.如果我们愿意,这很有用
以防止应用上下文用户获取对未建议的引用
或需要避免与Spring IoC自动接线产生任何歧义。还有,
可以说,这也是一种优势,因为代理工厂豆定义是自成体系的。
然而,有时能够从中获得未被建议的目标
工厂实际上可能反而是优势(比如某些测试场景)。
6.4.5. 代理类
如果你需要代理一个类,而不是一个或多个接口怎么办?
想象一下,在我们之前的例子中,没有人接口。我们需要提供建议
一个名为人但没有实现任何业务接口。在这种情况下,你
可以配置 Spring 使用 CGLIB 代理而非动态代理。为此,设置proxyTargetClass属性代理工厂豆之前展示给true.虽然最好
程序对接口而非类进行建议,能够为不提供接口的类提供建议
实现接口在处理遗留代码时非常有用。(总体来说,Spring
不具规定性。虽然这让应用良好实践变得容易,但避免了强制
特别的做法。)
如果你愿意,即使你已经有,也可以强制使用 CGLIB 接口。
CGLIB 代理通过在运行时生成目标类的子类来实现。Spring 配置该生成子类将方法调用委派给原始目标。这 子职业用于实现装饰者模式,并融入了建议。
CGLIB代理通常应对用户透明。不过,也存在一些问题 需要考虑:
-
最后方法无法被覆盖,因此无法被推荐。 -
无需将CGLIB添加到你的类路径中。从春季3.2开始,CGLIB被重新打包 并包含在Spring核JAR中。换句话说,基于CGLIB的AOP工作于“在 盒子“,JDK动态代理也一样。
CGLIB 代理和动态代理之间的性能差别不大。 性能不应成为此案的决定性考量。
6.4.6. 使用“全局”顾问
通过在拦截者名称后加上星号,所有名字相匹配的顾问 星号前的部分被添加到顾问链中。这会很有用 如果你需要添加一套标准的“全球”顾问。以下示例定义 两位全球顾问:
<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 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 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>
注意,在母豆示例中,我们明确标记了母豆定义为
通过设置抽象归属为true,如前所述,因此它实际上可能永远不会
实例。应用上下文(但不包括简单的豆子工厂),默认情况下,
预实例化所有单元素。因此,它很重要(至少对于单粒豆来说)
如果你有一个(父)豆子定义,只打算用作模板,
这个定义指定了一个类,你必须确保设置抽象归属为true.否则,应用上下文实际上会尝试
预实例化它。
6.6. 通过程序创建AOP代理代理工厂
用 Spring 编程创建 AOP 代理很简单。这样你就能使用 春季AOP无依赖于春季IoC。
目标对象实现的接口包括 自动代理。以下列表展示了为目标对象创建代理的过程,其中 拦截者和一名顾问:
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.你可以用目标来创建
对象,如前例所示,或指定替代接口
构造 函数。
你可以添加建议(拦截机作为专门的建议)、顾问,或者两者兼有
并对他们进行控,直到生命的终结代理工厂.如果你添加一个引言拦截环绕顾问,你可以让代理实现额外的
接口。
还有一些方便的方法代理工厂(继承自建议支持(AdvisedSupport))
这样你就可以添加其他建议类型,比如之前和投掷建议。建议支持(AdvisedSupport)是两者的超类代理工厂和代理工厂豆.
| 将AOP代理创建与IoC框架集成是大多数情况下的最佳实践 应用。我们建议你用AOP将Java代码中的配置外部化, 这通常也是应该的。 |
6.7.作建议对象
无论你如何创建AOP代理,你都可以通过使用org.springframework.aop.framework.建议接口。任何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()方法返回顾问对于每个顾问、拦截机,或
工厂中新增的其他建议类型。如果你添加了一个顾问这
在这个索引中返回的顾问就是你添加的对象。如果你添加了一个
拦截机或其他建议类型,Spring用一个顾问包裹了这个
总是返回的点切割true.因此,如果你添加了一个拦截方法, 顾问
该索引返回的是一个默认点cutAdvisor这会返回你的拦截方法以及一个匹配所有类和方法的点割。
这addAdvisor()可以用来添加任意方法顾问.通常,顾问持有
Pointcut 和 Advice 是通用的默认点cutAdvisor,你可以将其与
有什么建议或建议吗(但不是介绍)。
默认情况下,即使代理存在,也可以添加或移除顾问或拦截者 已经被创造出来。唯一的限制是无法添加或删除 介绍顾问,因为工厂现有的代理文件不显示该接口 改变。(你可以从工厂获得新的代理以避免这个问题。)
以下示例展示了将AOP代理投射到以下建议接口与检查
控其建议:
Advised advised = (Advised) myObject;
Advisor[] advisors = advised.getAdvisors();
int oldAdvisorCount = advisors.length;
System.out.println(oldAdvisorCount + " advisors");
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(new DebugInterceptor());
// Add selective advice using a pointcut
advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.getAdvisors().length);
val advised = myObject as Advised
val advisors = advised.advisors
val oldAdvisorCount = advisors.size
println("$oldAdvisorCount advisors")
// Add an advice like an interceptor without a pointcut
// Will match all proxied methods
// Can use for interceptors, before, after returning or throws advice
advised.addAdvice(DebugInterceptor())
// Add selective advice using a pointcut
advised.addAdvisor(DefaultPointcutAdvisor(mySpecialPointcut, myAdvice))
assertEquals("Added two advisors", oldAdvisorCount + 2, advised.advisors.size)
| 是否建议修改关于 生产中的商业对象,尽管无疑存在合法的使用案例。 然而,它在开发中非常有用(例如测试中)。我们有时候会 发现能够以拦截机或其他形式添加测试代码非常有用 建议,进入我们想测试的方法调用。(例如,建议可以 进入为该方法创建的事务,可能用来运行SQL来检查 数据库在标记回滚交易前已正确更新。) |
根据你如何创建代理,通常可以设置冷冻旗。在那个地方
情况,建议 isFrozen()方法返回true,以及任何修改尝试
通过添加或删除获得的建议结果是AopConfigException.该能力
冻结被建议对象的状态在某些情况下是有用的(例如,对
防止调用代码,移除安全拦截器)。
6.8. 使用“自动代理”功能
到目前为止,我们考虑过通过使用代理工厂豆或
类似的工厂豆。
Spring 还允许我们使用“自动代理”豆定义,这可以自动进行 代理选定Beans定义。这基于Spring的“豆后处理”技术 基础设施,允许在容器加载时修改任何Beans定义。
在这个模型中,你需要在XML豆定义文件中设置一些特殊的豆定义
配置自动代理基础设施。这让你可以宣布目标
符合自动代理资格。你不需要使用代理工厂豆.
有两种方式可以实现:
-
通过使用自动代理生成器,指代当前语境中的特定豆子。
-
一个值得单独考虑的自动代理生成的特殊情况: 由源级元数据属性驱动的自动代理创建。
6.8.1. 自动代理豆定义
本节介绍由org.springframework.aop.framework.autoproxy包。
豆名自动代理创作者
这豆名自动代理创作者类是豆子后处理器这会自动产生
AOP代理,表示名字与字面值或万用符匹配的豆子。如下
示例展示了如何创建豆名自动代理创作者豆:
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="jdk*,onlyJdk"/>
<property name="interceptorNames">
<list>
<value>myInterceptor</value>
</list>
</property>
</bean>
如同代理工厂豆,存在一个拦截机名称属性而非列表
拦截机,以便为原型顾问提供正确的行为。命名为“拦截机”
可以是顾问,也可以是任何类型的建议。
就像一般的自动代理一样,使用的主要目的是豆名自动代理创作者是
能够一致地将相同构型应用于多个物体,且体积最小
配置。它是将声明式事务应用于多重交换的热门选择
对象。
Beans定义中名称相符的,例如jdk我的豆子和只有Jdk在前述中
示例,是带有目标类的普通定义。AOP代理是
由豆名自动代理创作者.同样的建议也适用
对所有相配的豆子。注意,如果使用了顾问(而非拦截器),则在
上述例子),这些点切可能适用于不同的豆子。
默认顾问自动代理创建者
更通用且极其强大的自动代理生成器是默认顾问自动代理创建者.这会自动为符合条件的顾问应用
当前上下文,无需在自动代理中包含具体的Beans名称
顾问的Bean定义。它同样提供了稳定配置的优点,
避免重复豆名自动代理创作者.
使用此机制包括:
-
指定一个
默认顾问自动代理创建者豆子的定义。 -
指定在同一或相关情境下的任何数量的顾问。注意这些 必须是顾问,不能是拦截者或其他建议。这是必要的, 因为必须有一个分点来评估,检查每条建议的资格 关于候选豆的定义。
这默认顾问自动代理创建者自动计算所包含的点割
在每个顾问中,查看它应该对每个业务对象适用哪些(如果有的话)建议
(例如:businessObject1和businessObject2在示例中)。
这意味着每个企业都可以自动为任何数量的顾问配备 对象。如果任何顾问的点割与业务对象中的任何方法匹配, 该对象不被代理。随着为新的业务对象添加豆子定义, 如有需要,它们会自动代理。
自动代理的优点是让来电者无法
依赖以获得一个未建议对象。叫getBean(“businessObject1”)关于这个应用上下文返回的是AOP代理,而不是目标业务对象。(“内在”
前面提到的“豆子”习语也带来了这个好处。)
以下示例可生成默认顾问自动代理创建者豆子与另一个
本节讨论的要素:
<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"/>
这默认顾问自动代理创建者如果你想应用同样的建议,这非常有用
与许多业务对象保持一致。一旦基础设施定义确定,
你可以添加新的业务对象,而无需包含特定的代理配置。
你也可以轻松地加入额外的功能(比如描摹或
性能监控方面)配置变化最小。
这默认顾问自动代理创建者支持过滤(通过使用命名方式)
约定中只有特定的顾问会被评估,从而允许使用多个顾问,
配置不同,AdvisorAutoProxyCreators在同一工厂内)并下单。
顾问可以实施org.springframework.core.Ordered接口以确保
如果这是问题,正确的顺序。这交易属性源顾问用于
前述示例具有可配置的顺序值。默认设置是无序。
6.9. 使用目标源实现
Spring提出了一个概念目标源,表示为org.springframework.aop.TargetSource接口。该接口负责
返回实现连接点的“目标对象”。这目标源每次 AOP 代理处理某个方法时,都会请求实现目标实例
调用。
使用 Spring AOP 的开发者通常不需要直接作目标源实现,但
这为支持池化、热插拔等提供了强大的支持方式
复杂的目标。例如,池化目标源可以返回不同的目标
通过使用池管理实例,为每个调用设置实例。
如果你不指定目标源,默认实现用于封装
局部物体。每次召唤返回的目标都是一样(这也在意料之中)。
本节剩余部分将介绍Spring提供的标准目标源及其使用方法。
| 使用自定义目标源时,目标通常需要是原型 而不是单一的Beans定义。这让Spring可以创建一个新的目标 必要时使用。 |
6.9.1. 可热插拔目标源
这org.springframework.aop.target.HotSwappableTargetSource存在以使目标
AOP代理被切换,同时允许呼叫者保留对它的引用。
改变目标源的目标会立即生效。这热插拔目标源是线程安全的。
你可以通过使用swap()HotSwappableTargetSource 上的方法,如下示例所示:
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()调用会改变可交换豆的目标。持有
提到那颗豆子,他们没有察觉变化,但立刻开始攻击
新的目标。
虽然这个例子没有提供任何建议(其实也不需要对
使用一个目标源),任意目标源可以与
随意的建议。
6.9.2. 目标源的池化
使用池化目标源提供了与无状态会话类似的编程模型 EJB,其中维护一个相同的实例池,并带有方法调用 去释放池子里的物品。
春季池化与SLSB池化的一个关键区别是,春季池化 适用于任何POJO。与春季一般一样,这项服务可以应用于 非侵入式方式。
Spring 支持公共资源池 2.2,该方案提供
池化实现相当高效。你需要公共池罐子放在你的
应用程序的类路径以使用此功能。你也可以子职业org.springframework.aop.target.AbstractPoolingTargetSource支持任何其他
池化API。
| Commons Pool 1.5+ 也被支持,但自 Spring Framework 4.2 起已不再支持。 |
以下列表展示了一个示例配置:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
注意目标对象(业务对象目标在上述例子中,必须是
原型。这使得PoolingTargetSource实现 创建新实例
目标根据需要扩大泳池。参见Java doc 的摘要:目标资源集以及你希望用于信息的具体子类
关于它的性质。最大尺寸是最基本的,并且总是保证存在。
在这种情况下,我的拦截者是拦截器的名称,需要
在同一IoC语境下定义。不过,你不必指定拦截器为
使用分池。如果你只想要池化而没有其他建议,就不要设置拦截机名称完全没有财产。
你可以配置 Spring 来将任何池化的对象投射到org.springframework.aop.target.PoolingConfig接口,它会暴露信息
通过介绍介绍池的配置和当前规模。你
需要定义类似以下内容的顾问:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
该顾问通过调用对 的便利方法得到摘要:目标资源集类,因此使用了方法召唤FactoryBean.这
顾问姓名(poolConfigAdvisor, 这里)必须出现在拦截器名称列表中
这代理工厂豆这样就能暴露出聚集的物体。
演员阵容定义如下:
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)
| 通常不需要将无状态服务对象池化。我们认为不应该如此 成为默认选项,因为大多数无状态对象自然对线程安全,实例 如果资源被缓存,池化会有问题。 |
通过自动代理可以实现更简单的池化。你可以设置目标源实现 任何自动代理创建者都会使用。
6.9.3. 原型目标源
建立“原型”目标源类似于设置池化目标源. 在这种情况下每次调用方法都会创建一个新的目标实例。 虽然 在现代JVM中,创建新对象的成本并不高,但布线满足其IoC依赖性的新对象的成本可能更高。因此,你不应在没有充分理由的情况下使用这种方法。
为此,你可以修改poolTargetSource定义如下所示(我们还更改了名称,以便清晰):
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
唯一的属性是目标豆的名称。继承用于目标源实现以确保命名一致。与池化目标来源一样,目标豆必须是原型豆定义。
6.9.4.ThreadLocal目标来源
ThreadLocal如果你需要为每个输入请求(每个线程)创建一个对象,目标源非常有用。目标源的概念ThreadLocal提供一个JDK范围的功能,可以透明地将资源与线程并存。建立线程本地目标源与其他类型目标源的解释基本相同,如下示例所示:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
ThreadLocal实例存在严重问题(可能导致内存泄漏)在多线程和多类加载环境中错误使用实例时, 你 应始终考虑在其他类中封装线程本地,切勿直接使用 这ThreadLocal本身(封装类除外)。此外,你应该始终记得正确设置和取消(后者仅涉及调用ThreadLocal.set(null))即线程本地资源。恢复应在无论如何,因为不重置可能导致问题行为。SpringThreadLocal客服会帮你做到这一点,并且应该始终考虑在使用时给予支持ThreadLocal这些实例没有其他合适的处理代码。 |
6.10. 定义新的建议类型
Spring AOP设计为可扩展性。虽然拦截实施策略目前内部使用,但可以支持任意的建议类型除了关于建议的拦截外,之前、抛出建议和返回建议之后。
这org.springframework.aop.framework.adapterpackage 是一个 SPI 包,允许在不更改核心框架的情况下添加新的自定义建议类型支持。自定义的唯一限制建议类型是它必须实现org.aopalliance.aop.Advice标记界面。
参见org.springframework.aop.framework.adapter更多信息请使用 Javadoc。
7. 零安全
虽然 Java 不允许你用其类型系统表达空安全,但 Spring 框架现在在org.springframework.lang该包允许你声明 API 和字段的空可性:
-
@Nullable:注释表示特定参数、返回值或字段可以是零. -
@NonNull: 注释表示某一特定参数、返回值或字段不能为零(参数/返回值不需)以及字段@NonNullApi和@NonNullFields分别应用。 -
@NonNullApi:包级注释该注释将参数和返回值的默认语义声明为非空。 -
@NonNullFields:注释 包该级别声明非空作为字段默认语义。
Spring 框架本身利用了这些注释,但它们也可以用于任何基于 Spring 的 Java 项目,声明空安全 API 和可选的空安全字段。通用类型参数、vararg 和数组元素的空态尚未被支持,但应会在即将发布的版本中实现,详见 SPR-15942 以获取最新信息。空可言声明预计会在Spring Framework 版本之间进行微调。方法内使用的类型可空性不在本功能范围内。
| 其他常见库如 Reactor 和 Spring Data 提供空安全 API,满足条件 使用类似的空可性安排,提供一致的整体体验 Spring 应用开发者。 |
7.1. 使用场景
除了为 Spring Framework API 的可空性提供显式声明外,
这些注释可以被IDE(如IDEA或Eclipse)用来提供有用的信息
关于零安全(null-safety)的警告,以防避免NullPointerException在运行时。
它们也被用来使 Kotlin 项目中的 Spring API 实现空安全,因为 Kotlin 本身就是原生的 支持零安全。更多细节 可在Kotlin支持文档中找到。
7.2. JSR-305元注释
春季注释通过 JSR 305 注释进行元注释(这是一种休眠但广泛使用的 JSR)。JSR-305 元注释让工具厂商得以实现 像 IDEA 或 Kotlin 以通用方式提供零安全支持,无需 支持Spring注释的硬代码。
不必要也不推荐在项目类路径中添加JSR-305依赖
利用 Spring 的 null-safe API。只有像基于 Spring 的库这样的项目,使用
代码库中的空安全注释应会添加com.google.code.findbugs:jsr305:3.0.2跟仅编译Gradle 配置或 Maven提供避免编译警告的范围。
8. 数据缓冲区和编解码器
Java NIO 提供字节缓冲区但许多库会在其基础上构建自己的字节缓冲 API,
尤其是在网络作中,重用缓冲区和/或直接缓冲区
对性能有益。例如,Netty 有字节Buf层级结构,Undertow的用途
XNIO、Jetty 使用带有回调的池字节缓冲区,等等。
这Spring芯模块提供了一组抽象,用于处理各种字节缓冲区
API如下:
-
数据缓冲工厂抽象了数据缓冲区的创建。 -
DataBufferUtils提供数据缓冲区的实用方法。 -
编解码器将数据缓冲流解码或编码为更高级别的对象。
8.1.数据缓冲工厂
数据缓冲工厂用于创建数据缓冲区,方式有两种:
-
分配一个新的数据缓冲区,如果已知容量,可以选择性地提前指定容量,具体为 即使实现了
数据缓冲区可以根据需求变大或缩小。 -
包裹现有的
字节[]或java.nio.ByteBuffer,将给定数据装饰为 一个数据缓冲区而这并不涉及分配。
注意,WebFlux 应用程序不会创建数据缓冲工厂直接,但却相反
通过ServerHttpResponse或者ClientHttpRequest在客户端。
工厂类型取决于底层客户端或服务器,例如:NettyDataBufferFactory对于反应堆Netty,DefaultDataBufferFactory为其他人。
8.2.数据缓冲区
这数据缓冲区接口提供类似于的作java.nio.ByteBuffer但也
带来了一些额外的好处,其中一些灵感来自Netty。字节Buf.
以下是部分福利列表:
-
读取和写入时位置独立,即不需要调用
flip()自 在读写之间交替。 -
按需扩展容量,如下
java.lang.字符串构建器. -
池化缓冲区和引用计数通过
PooledDataBuffer. -
将缓冲区视为
java.nio.ByteBuffer,输入流或输出流. -
确定给定字节的索引,或最后一个索引。
8.3.PooledDataBuffer
正如 ByteBuffer 的 Javadoc 中所解释的, 字节缓冲区可以是直接的或非直接的。直接缓冲区可能位于 Java 堆之外 这消除了本地I/O作复制的需求。这就产生了直接缓冲 尤其适合通过套接字接收和发送数据,但它们的作用也更多 创建和发布成本高昂,这也引发了缓冲池的想法。
PooledDataBuffer是 的扩展数据缓冲区这有助于参考计数
对于字节缓冲池至关重要。它是如何运作的?当PooledDataBuffer是
分配的参考计数为1。召唤retain()增加计数,而
呼号release()减少它。只要计数大于0,缓冲区为
保证不会被释放。当计数降至0时,合并缓冲区可以为
释放,实际上这可能意味着缓冲区的保留内存被返回到
记忆池。
注意,与其在 上作PooledDataBuffer直接来说,大多数情况下这样更好
使用 方便方法DataBufferUtils对 应用释放或保留数据缓冲区只有当它是 的实例时才PooledDataBuffer.
8.4.DataBufferUtils
DataBufferUtils提供了多种用于数据缓冲区作的工具方法:
-
将数据缓冲流合并为单个缓冲区,可能零副本,例如通过 复合缓冲区,如果底层字节缓冲区API支持的话。
-
转
输入流或NIO渠道到Flux<DataBuffer>,反之亦然 a出版商<DataBuffer>到输出流或NIO渠道. -
释放或保留
数据缓冲区如果缓冲区是 的实例PooledDataBuffer. -
跳过或取取字节流,直到达到特定的字节数。
8.5. 编解码器
这org.springframework.core.codec软件包提供了以下策略接口:
-
编码器编码出版<>进入数据缓冲流。 -
译码器解码出版商<DataBuffer>进入一连串更高层次的对象。
这Spring芯模块提供字节[],字节缓冲区,数据缓冲区,资源和字符串编码器和解码器的实现。这春网模块添加 Jackson JSON,
Jackson Smile、JAXB2、协议缓冲区及其他编码器和解码器。详见WebFlux部分的编解码器。
8.6. 使用数据缓冲区
在使用数据缓冲区时,必须特别注意确保缓冲区被释放 因为它们可能被合并。我们将用编解码器来说明 具体是这样,但这些概念更广泛适用。让我们看看编解码器必须做什么 内部管理数据缓冲区。
一个译码器是最后读取输入数据缓冲区的设备,然后才创建更高层级的
因此必须按如下方式释放它们:
-
如果
译码器只需读取每个输入缓冲区,即可 立即发布,它可以通过以下方式进行DataBufferUtils.release(dataBuffer). -
如果
译码器是使用通量或单算符,如平面地图,减少和 还有一些在内部预取和缓存数据项,或使用诸如Filter,跳以及其他遗漏物品的,那么doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)必须加入 组成链以确保此类缓冲区在丢弃前释放,可能 同时也会产生错误或抵消信号。 -
如果
译码器以任何其他方式保持一个或多个数据缓冲区,必须保持 确保在完全读取后释放,或在出现错误或取消信号时 在缓存数据缓冲区被读取并释放之前发生。
注意DataBufferUtils#join提供了一种安全高效的数据聚合方式
缓冲流进入单一数据缓冲区。同样skipUntilByteCount和takeUntilByteCount是解码器使用的其他安全方法。
一编码器分配数据缓冲区,其他人必须读取(并释放)。所以编码器没什么事可做。然而编码器必须注意释放数据缓冲区
在填充缓冲区时会发生序列化错误。例如:
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
消费者编码器负责释放接收到的数据缓冲区。
在WebFlux应用中,输出编码器用于写入HTTP服务器
响应,或对客户端 HTTP 请求,在这种情况下释放数据缓冲区是
代码对服务器响应或客户端请求的编写责任。
注意,在Netty上运行时,有调试选项用于排查缓冲区泄漏。
9. 附录
9.1. XML 模式
附录的这一部分列出了与核心容器相关的XML模式。
9.1.1. 该实用图式
顾名思义,实用标签处理的是常见的实用配置
问题,比如配置集合、引用常数等。
要在实用Schema,你需要在顶部写下以下序言
你的 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: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/>
请考虑以下Beans定义:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
前述配置使用Spring工厂豆实现(该字段检索FactoryBean)以设定隔离豆子上的性质
到以下值java.sql.Connection.TRANSACTION_SERIALIZABLE不断。这是
这些都很好,但内容冗长,且(不必要地)暴露了斯普林的内心
管道服务于最终用户。
以下基于XML Schema的版本更为简洁,清晰表达了 开发者的意图(“注入这个常数值”),读起来更好:
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
从字段值设置豆属性或构造子参数
字段检索FactoryBean是工厂豆该 得静态的或非静态场值。通常
用于取回公共 静态的 最后常量,然后可以用来设置
另一个豆子的财产价值或构造者论证。
以下示例展示了静态的场的暴露方式是静态场财产:
<bean id="myField"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</bean>
还有一种方便用法,其中静态的字段指定为豆子
名称,如下示例所示:
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>
这意味着现在已经没有选择的余地了身份证是(所以任何其他
指代它的豆子也必须使用这个较长的名称),但这个形式非常
定义简洁,且非常方便用作内层豆子,因为身份证不具备
需要指定为豆子引用,如下示例所示:
<bean id="..." class="...">
<property name="isolation">
<bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
</property>
</bean>
你也可以访问另一个豆子的非静态(实例)字段,像
在 API 文档中描述了字段检索FactoryBean类。
将枚举值注入豆子,作为属性或构造子参数,是
Spring做起来很容易。你其实不需要做什么,也不需要知道什么
Spring内部(甚至关于像字段检索FactoryBean).
以下示例枚举展示了注入枚举值的简单性:
package javax.persistence;
public enum PersistenceContextType {
TRANSACTION,
EXTENDED
}
package javax.persistence
enum class PersistenceContextType {
TRANSACTION,
EXTENDED
}
现在考虑以下类型的设定器PersistenceContextType以及对应的Beans定义:
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"/>
前述配置使用Spring工厂豆实现(该PropertyPathFactoryBean)来生成一个豆子(类型为智力)testBean.age那
其值等于年龄的属性测试豆豆。
现在考虑以下例子,它添加了一个<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"/>
该路径属性<财产路径/>元素遵循 的形式豆名.豆属性.在这种情况下,它会接收到年龄豆子的财产测试豆.它的价值年龄财产是10.
用<util:property-path/>设置豆属性或构造子参数
PropertyPathFactoryBean是工厂豆评估给定性质路径
目标物体。目标对象可以直接指定,也可以用豆子名指定。然后你可以用这个
在另一种豆子定义中,价值作为属性价值或构造子
论点。
以下示例展示了一条路径被用来对付另一个豆子,按名称进行:
<!-- 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>
在下面的例子中,路径是对内层豆子进行评估的:
<!-- 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>
还有一种快捷方式,豆子名即为属性路径。 以下示例展示了快捷方式形式:
<!-- 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,完全没必要提及,正如以下示例所示:
<bean id="..." class="...">
<property name="age">
<bean id="person.age"
class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/>
</property>
</bean>
你可以在实际定义中具体设置结果类型。这并非必要 大多数用例都适用,但有时确实有用。更多信息请参见java文档 这个功能。
用<util:properties/>
请考虑以下例子:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<bean id="jdbcConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:com/foo/jdbc-production.properties"/>
</bean>
前述配置使用Spring工厂豆实现(该PropertiesFactoryBean)以实例化java.util.Properties带有值的实例
从供应中加载资源位置)。
以下示例使用了一个util:properties元素以实现更简洁的表示:
<!-- creates a java.util.Properties instance with values loaded from the supplied location -->
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
用<util:list/>
请考虑以下例子:
<!-- creates a java.util.List instance with values loaded from the supplied 'sourceList' -->
<bean id="emails" class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</list>
</property>
</bean>
前述配置使用Spring工厂豆实现(该ListFactoryBean)以创建java.util.List实例并用取值初始化
从提供的资料列表.
以下示例使用了一个<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>
你也可以明确控制具体的类型列表该实例化为 和
通过使用列表级属性<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>
如果没有列表级提供属性时,容器选择列表实现。
用<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>
前述配置使用Spring工厂豆实现(该MapFactoryBean)以创建java.util.Map实例初始化为键值对
摘自提供的“源地图”.
以下示例使用了一个<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>
你也可以明确控制具体的类型地图该实例化为 和
通过使用“地图类”属性<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>
如果没有“地图类”提供属性时,容器选择地图实现。
用<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>
前述配置使用Spring工厂豆实现(该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>
你也可以明确控制具体的类型设置该实例化为 和
通过使用集合类属性<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>
如果没有集合类提供属性时,容器选择设置实现。
9.1.2. 该AOP图式
这AOP标签涉及在春季中配置所有AOP的内容,包括春季的
自有代理 AOP 框架,以及 Spring 与 AspectJ AOP 框架的集成。
这些标签在名为《面向Aspect with Spring》的章节中有全面介绍。
为了完整性,使用标签AOP你需要有 schema
以下序言位于您的 Spring XML 配置文件顶部(即
Snippet 引用正确的模式,使得标签在AOPNamespace
可供您参考):
<?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. 该上下文图式
这上下文标签涉及应用上下文与管道相关的配置——也就是说,通常不是对终端用户重要的豆子,而是对某个用户重要的豆子
春季的许多“苦力”工作,比如豆工厂后处理器.如下
Snippet 引用正确的模式,使得上下文命名空间为
可供您使用:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- bean definitions here -->
</beans>
用<property-placeholder/用>
该元素激活替换${…}占位符,这些取位符通过
指定属性文件(作为 Spring 资源位置)。这个元素
是一种便利机制,用于设置PropertySourcesPlaceholderConfigurer给你的。如果你需要对具体情况有更多控制PropertySourcesPlaceholderConfigurer设置时,你可以自己明确定义为豆子。
用<annotation-config/>
该元素激活 Spring 基础设施以检测 bean 类中的注释:
-
斯普林斯
@Configuration型 -
@Autowired/@Inject,@Value和@Lookup -
JSR-250
@Resource,@PostConstruct和@PreDestroy(如果有的话) -
JAX-WS
@WebServiceRef以及EJB 3@EJB(如果有的话) -
JPA的
@PersistenceContext和@PersistenceUnit(如果有的话) -
斯普林斯
@EventListener
或者,你也可以选择明确激活该个体豆后处理器关于那些注释。
该元素不会激活Spring的处理@Transactional注解;
你可以使用<tx:注释驱动/>元素就是为了这个目的。同样,Spring的缓存注释也需要明确启用。 |
用<分量扫描/>
该元素详见基于注释的容器配置部分。
用<load-time-weaver/>
该元素在 Spring 框架中关于用 AspectJ 进行加载时织入的章节中有详细说明。
用<Spring配置/>
该元素在使用 AspectJ 与 Spring 注入依赖域对象的部分有详细说明。
用<mbean-export/>
该元素在配置基于注释的MBean导出部分有详细说明。
9.1.4. 豆子模式
最后但同样重要的是,我们有以下元素豆图式。这些元素
自框架诞生之初就已在春季。各种元素的例子
在豆此处未展示模式,因为它们涵盖得相当全面
在依赖关系和配置中详细(实际上,整个章节中也是如此)。
注意,你可以在 中添加零对或更多键值对<豆/>XML定义。
如果能用这些额外的元数据做什么,完全取决于你自己的习惯
逻辑(因此通常只有在你按照描述写自定义元素时才有用
附录中名为 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="foo" class="x.y.Foo">
<meta key="cacheName" value="foo"/> (1)
<property name="name" value="Rick"/>
</bean>
</beans>
| 1 | 这是示例元元素 |
在前面的例子中,你可以假设存在某种逻辑消耗 BEAN 定义并建立一些缓存基础设施,利用提供的元数据。
9.2. XML 模式创作
自2.0版本起,Spring提供了一种机制,可以为 用于定义和配置豆子的基本 Spring XML 格式。本节内容涵盖 如何编写自定义XML豆定义解析器和 将这些解析器集成到 Spring IoC 容器中。
为了方便使用模式识别XML编辑器的配置文件, Spring 的可扩展 XML 配置机制基于 XML Schema。如果你不喜欢 熟悉Spring当前标准自带的XML配置扩展 关于Spring发行版,你应该先阅读之前关于XML Schema的部分。
要创建新的 XML 配置扩展:
举一个统一的例子,我们创建一个
XML 扩展(自定义 XML 元素),允许我们配置 以下类型的对象SimpleDate格式(摘自java.textpackage)。等我们结束后,
我们将能够定义类型的Beans定义SimpleDate格式如下:
<myns:dateformat id="dateFormat"
pattern="yyyy-MM-dd HH:mm"
lenient="true"/>
(我们包含了更详细的内容 后续附录中将举例说明。这个第一个简单例子的目的是让你走路 通过制作自定义扩展的基本步骤。)
9.2.1. 模式的编写
创建用于 Spring IoC 容器的 XML 配置扩展时,首先是
编写一个XML模式来描述扩展。在我们的例子中,我们使用以下模式
配置SimpleDate格式对象:
<!-- myns.xsd (inside package org/springframework/samples/xml) -->
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.mycompany.example/schema/myns"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.mycompany.example/schema/myns"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="dateformat">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType"> (1)
<xsd:attribute name="lenient" type="xsd:boolean"/>
<xsd:attribute name="pattern" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
| 1 | 所示线包含所有可识别标签的扩展底
(意味着他们有身份证我们可以用作豆子标识符
容器)。我们能使用这个属性,是因为我们导入了 Spring 提供的豆Namespace。 |
前面的模式让我们可以配置SimpleDate格式直接在
通过使用<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>
前两个片段中的第二个
在容器中生成豆子(以名称识别)日期格式类型SimpleDate格式)并设置了几个属性。
| 基于模式的配置格式创建方法允许紧密集成 该集成开发环境(IDE)具有模式识别的XML编辑器。通过使用正确编写的模式,你 可以使用自动补全功能,让用户在多个配置选项中选择 定义在枚举中。 |
9.2.2. 编码aNamespaceHandler
除了模式外,我们还需要一个NamespaceHandler解析所有元素
这是 Spring 在解析配置文件时遇到的特定命名空间。在这个例子中,NamespaceHandler应该负责解析Myns:Dateformat元素。
这NamespaceHandler界面具有三种方法:
-
init():允许初始化NamespaceHandler且称为 在使用作器之前先Spring。 -
BeanDefinition 解析法(Element, ParserContext):当Spring遇到 顶层元素(不嵌套在 bean 定义或其他命名空间中)。 该方法本身可以注册豆定义,返回豆定义,或两者兼具。 -
BeanDefinitionHolder decorate(Node, BeanDefinitionHolder, ParserContext):叫 当 Spring 遇到不同命名空间的属性或嵌套元素时。 例如,Spring支持的示波器会装饰一个或多个Beans定义。 我们先用一个简单的例子,不使用装饰,然后再强调 我们展示了一个稍微高级的例子。
虽然你可以自己写代码NamespaceHandler整个
命名空间(因此提供解析命名空间中每个元素的代码),
通常情况下,Spring XML 配置文件中的每个顶层 XML 元素
结果是单豆定义(如我们所见,单一<myns:dateformat/>元素 结果为单一SimpleDate格式豆子定义)。春季有
支持这种情景的便利类数量。在下面的例子中,我们
使用以下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类本身就有
代表团。它支持任意数量的注册BeanDefinition解析器实例,当需要解析其元素时,它会委派给这些实例
Namespace。这种清晰的关注点分离使得NamespaceHandler处理
对命名空间中所有自定义元素的解析进行编排,同时
委派至BeanDefinition解析器负责 XML 解析的繁重工作。这
意味着每个BeanDefinition解析器仅包含解析单个
自定义元素,正如我们在下一步中看到的。
9.2.3. 使用BeanDefinition解析器
一个BeanDefinition解析器如果NamespaceHandler遇到一个XML
该类型的元素被映射到特定的 Bean 定义解析器
(日期格式在这里)。换句话说,BeanDefinition解析器是
负责解析模式中定义的一个独立顶层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提供的摘要SingleBeanDefinition解析器要处理很多
制作单一的基础繁重工作豆子定义. |
| 2 | 我们提供摘要SingleBeanDefinition解析器具有 我们的类型
单豆子定义代表。 |
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提供的摘要SingleBeanDefinition解析器要处理很多
制作单一的基础繁重工作豆子定义. |
| 2 | 我们提供摘要SingleBeanDefinition解析器具有 我们的类型
单豆子定义代表。 |
在这个简单的例子中,这就是我们所需要做的全部。我们单曲的诞生豆子定义由摘要SingleBeanDefinition解析器超类,作为
是提取并设置豆子定义的唯一标识符。
9.2.4. 注册处理器和模式
编码完成了。剩下的就是制作 Spring XML
解析识别我们自定义元素的基础设施。我们通过注册我们的客户来实现namespaceHandler以及两个专用属性文件中的自定义 XSD 文件。这些
属性文件都被放置在元步兵应用程序中的目录 和
例如,可以与二进制类一起分发在JAR文件中。Spring
XML 解析基础设施会自动通过消耗
这些特殊属性文件的格式将在接下来的两节详细说明。
写作META-INF/spring.handlers
属性文件称为Spring。控者包含XML模式URI映射到
命名空间处理类。对于我们的例子,我们需要写以下内容:
http\://www.mycompany.example/schema/myns=org.springframework.samples.xml.MyNamespaceHandler
(:字符在 Java 属性格式中是有效的分隔符,因此:URI中的字符需要用反斜线逃脱。)
键值对的第一部分(键)是与自定义对应的URI
命名空间扩展和需要完全匹配目标命名空间属性,按照你自定义的XSD模式指定。
写作“META-INF/spring.schemas”
属性文件称为spring.schemas包含XML模式位置的映射
(与模式声明一同在使用该模式作为部分的XML文件中被引用
关于xsi:schemaLocation属性)映射到类路径资源。需要这个文件
以防止 Spring 必须使用默认实体解析器这需要
通过互联网访问以获取模式文件。如果你在这里指定映射
属性文件中,Spring 搜索该模式(此处为,myns.xsd在org.springframework.samples.xmlpackage)在类路径上。
以下摘录展示了我们需要为自定义模式添加的行:
http\://www.mycompany.example/schema/myns/myns.xsd=org/springframework/samples/xml/myns.xsd
(记住:必须逃脱角色。)
鼓励你将XSD文件(或文件)与之同时部署
这NamespaceHandler和BeanDefinition解析器课程在Classpath上。
9.2.5. 在 Spring XML 配置中使用自定义扩展
使用你自己实现的自定义扩展和使用没有区别
这是Spring提供的“定制”扩展之一。如下
示例使用了 custom<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 | 我们的定制豆子。 |
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/>元素是元件类别(见下一个示例)。注意元件类不会暴露
设置方法组件财产。这让它变得困难(或者说不可能)
为元件通过使用二传注射来上课。
以下列表显示了元件类:
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
}
}
解决这个问题的典型方法是创建一个自定义工厂豆这会暴露出一个
为组件财产。以下列表展示了这样的习俗工厂豆:
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())
}
}
接下来是习俗BeanDefinition解析器.记住我们在创造
一个豆子定义描述组件工厂豆.如下
列表显示我们的习惯BeanDefinition解析器实现:
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
“法线”元素上的自定义属性
写自己的自定义解析器和相关的产物并不难。然而 有时候这并不是正确的做法。考虑一个情景,你需要 为已有的豆子定义添加元数据。在这种情况下,你确实 我不想自己写整个自定义扩展。相反,你只是 想给现有的豆子定义元素添加一个额外的属性。
再举一个例子,假设你定义一个豆子定义 服务对象(它不知道)访问集群JCache,你要确保 命名为 JCache 实例的实例在周围集群中被迅速启动。 以下列表展示了此类定义:
<bean id="checkingAccountService" class="com.foo.DefaultCheckingAccountService"
jcache:cache-name="checking.account">
<!-- other dependencies here... -->
</bean>
然后我们可以创造另一个豆子定义当'jcache:cache-name'属性被解析。这豆子定义然后初始化
我们的名字叫JCache。我们也可以修改现有的豆子定义对于“查账服务”因此它对这个新的
JCache初始化豆子定义.以下列表展示了我们的JCacheInitializer:
package com.foo;
public class JCacheInitializer {
private String name;
public JCacheInitializer(String name) {
this.name = name;
}
public void initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
package com.foo
class JCacheInitializer(private val name: String) {
fun initialize() {
// lots of JCache API calls to initialize the named cache...
}
}
现在我们可以进入自定义扩展。首先,我们需要写作 描述自定义属性的XSD模式如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://www.foo.example/schema/jcache"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.foo.example/schema/jcache"
elementFormDefault="qualified">
<xsd:attribute name="cache-name" type="xsd:string"/>
</xsd:schema>
接下来,我们需要创建相关的NamespaceHandler如下:
package com.foo;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class JCacheNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
new JCacheInitializingBeanDefinitionDecorator());
}
}
package com.foo
import org.springframework.beans.factory.xml.NamespaceHandlerSupport
class JCacheNamespaceHandler : NamespaceHandlerSupport() {
override fun init() {
super.registerBeanDefinitionDecoratorForAttribute("cache-name",
JCacheInitializingBeanDefinitionDecorator())
}
}
接下来,我们需要创建解析器。注意,在这种情况下,因为我们将解析
一个XML属性,我们写成BeanDefinitionDecorator而不是BeanDefinition解析器.
以下列表展示了我们的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