核心
1. IoC 容器
本章介绍 Spring 的控制反转 (IoC) 容器。
1.1. Spring IoC 容器和 bean 简介
本章介绍了 Inversion of Control 的 Spring Framework 实现 (IoC) 原则。IoC 也称为依赖关系注入 (DI)。这是一个过程 对象只能通过 constructor 参数、工厂方法的参数或在 Object 实例。容器 然后在创建 bean 时注入这些依赖项。这个过程从根本上说是 bean 本身的逆函数(因此得名 Inversion of Control) 使用 Direct 控制其依赖项的实例化或位置 类的构造或机制,例如 Service Locator 模式。
和 packages 是基础
用于 Spring Framework 的 IoC 容器。BeanFactory
接口提供了一种高级配置机制,能够管理任何类型的
对象。ApplicationContext
是 的子接口。它补充说:org.springframework.beans
org.springframework.context
BeanFactory
-
更轻松地与 Spring 的 AOP 功能集成
-
消息资源处理(用于国际化)
-
活动发布
-
特定于应用程序层的上下文,例如 for use in web applications。
WebApplicationContext
简而言之,提供了配置框架和基本的
功能,并添加更多特定于企业的功能。
是 和 的完全超集
仅在本章对 Spring 的 IoC 容器的描述中。了解更多
有关使用 而不是 的信息,请参阅 The BeanFactory
。BeanFactory
ApplicationContext
ApplicationContext
BeanFactory
BeanFactory
ApplicationContext,
在 Spring 中,构成应用程序主干并受管理的对象 被 Spring IoC 容器称为 bean。bean 是一个对象,它是 由 Spring IoC 容器实例化、组装和管理。否则,一个 Bean 只是应用程序中的众多对象之一。Bean 和依赖项 其中,它们反映在容器使用的配置元数据中。
1.2. 容器概述
该接口表示 Spring IoC
容器,并负责实例化、配置和组装
豆。容器获取有关要
通过读取配置元数据来实例化、配置和组装。这
配置元数据以 XML、Java 注释或 Java 代码表示。它让
您表达了组成应用程序的对象和丰富的相互依赖关系
在这些对象之间。org.springframework.context.ApplicationContext
提供了该接口的几种实现
与Spring。在独立应用程序中,通常会创建一个
ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
的实例。
虽然 XML 是定义配置元数据的传统格式,但您可以
指示容器使用 Java 注释或代码作为元数据格式
提供少量的 XML 配置以声明方式启用对这些
其他元数据格式。ApplicationContext
在大多数应用程序场景中,不需要显式用户代码来实例化一个或
Spring IoC 容器的更多实例。例如,在 Web 应用程序场景中,
文件中简单的八行(左右)样板 Web 描述符 XML
通常就足够了(参见 Web 应用程序的便捷 ApplicationContext 实例化)。如果您使用 Spring Tools for Eclipse(一种由 Eclipse 提供支持的开发
环境中),您可以通过单击几下鼠标或
击 键。web.xml
下图显示了 Spring 工作原理的高级视图。您的应用程序类
与配置元数据结合使用,以便在
created 并初始化,则您拥有一个完全配置且可执行的系统,或者
应用。ApplicationContext
1.2.1. 配置元数据
如上图所示, Spring IoC 容器使用一种形式的 配置元数据。此配置元数据表示您作为 application developer,告诉 Spring 容器实例化、配置和组装 应用程序中的对象。
传统上,配置元数据以简单直观的 XML 格式提供。 这是本章大部分用来传达 Spring IoC 容器。
基于 XML 的元数据并不是唯一允许的配置元数据形式。 Spring IoC 容器本身与此格式完全解耦 配置元数据实际上是写入的。如今,许多开发人员为其 Spring 应用程序选择基于 Java 的配置。 |
有关在 Spring 容器中使用其他形式的元数据的信息,请参阅:
-
基于注解的配置:Spring 2.5 推出 支持基于 Annotation 的配置元数据。
-
基于 Java 的配置:从 Spring 3.0 开始,许多功能 由 Spring 提供的 JavaConfig 项目成为核心 Spring Framework 的一部分。 因此,您可以使用 Java 而不是 Java 来定义应用程序类外部的 bean 比 XML 文件。要使用这些新功能,请参阅
@Configuration
、@Bean
、@Import
、 和@DependsOn
注释。
Spring 配置由至少一个 bean 组成,通常由多个 bean 组成
定义。基于 XML 的配置元数据配置这些
bean 作为顶级元素内的元素。Java
配置通常在类中使用 -annotated 方法。<bean/>
<beans/>
@Bean
@Configuration
这些 Bean 定义对应于组成应用程序的实际对象。
通常,您可以定义服务层对象、数据访问对象 (DAO)、表示
对象(如 Struts 实例)、基础结构对象(如 Hibernate、JMS 等)。通常,不配置
fine-grained domain 对象,因为它通常是
的 DAO 和业务逻辑来创建和加载域对象。但是,您可以使用
Spring 与 AspectJ 的集成,用于配置已在外部创建的对象
IoC 容器的控件。参见 使用 AspectJ 来
dependency-inject 域对象。Action
SessionFactories
Queues
以下示例显示了基于 XML 的配置元数据的基本结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> (1) (2)
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
1 | 该属性是标识单个 Bean 定义的字符串。id |
2 | 该属性定义 bean 的类型,并使用完全限定的
classname 的class |
该属性的值是指协作对象。的 XML
此示例中未显示对协作对象的引用。有关更多信息,请参阅依赖项。id
1.2.2. 实例化容器
位置路径或路径
提供给构造函数的是资源字符串,这些字符串让
容器加载来自各种外部资源的配置元数据,例如
作为本地文件系统、Java 等。ApplicationContext
CLASSPATH
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
在了解了 Spring 的 IoC 容器之后,您可能希望更多地了解 Spring 的抽象(如 参考资料 中所述),它提供了一个方便的
从 URI 语法中定义的位置读取 InputStream 的机制。具体而言,路径用于构造应用程序上下文,如应用程序上下文和资源路径中所述。 |
以下示例显示了服务层对象配置文件:(services.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
以下示例显示了数据访问对象文件:daos.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的示例中,服务层由类
以及两个数据类型为 和 (基于
在 JPA Object-Relational Mapping 标准上)。该元素引用
name,并且该元素引用另一个 bean 的名称
定义。和 元素之间的这种联系表达了
协作对象。有关配置对象依赖项的详细信息,请参阅依赖项。PetStoreServiceImpl
JpaAccountDao
JpaItemDao
property name
ref
id
ref
编写基于 XML 的配置元数据
让 Bean 定义跨多个 XML 文件可能很有用。通常,每个个体 XML 配置文件表示体系结构中的逻辑层或模块。
你可以使用应用程序上下文构造函数从所有这些 bean 中加载 bean 定义
XML 片段。此构造函数采用多个位置,如上一节所示。或者,使用一个或多个
从另一个文件加载 Bean 定义的元素的出现次数,或者
文件。以下示例显示了如何执行此操作:Resource
<import/>
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的示例中,外部 Bean 定义是从三个文件加载的:、 、 和 。所有位置路径都是
相对于执行导入的定义文件,因此必须位于
与执行导入的文件相同的目录或 Classpath 位置,而 和 必须位于
导入文件的位置。如您所见,前导斜杠将被忽略。然而,鉴于
这些路径是相对的,最好根本不使用斜杠。这
正在导入的文件的内容(包括 top level 元素)必须
根据 Spring Schema 是有效的 XML bean 定义。services.xml
messageSource.xml
themeSource.xml
services.xml
messageSource.xml
themeSource.xml
resources
<beans/>
可以使用
相对 “../“ 路径。这样做会创建对当前
应用。特别是,不建议将此引用用于 URL(对于
example, ),其中运行时解析进程选择
“nearest” classpath 根目录,然后查看其父目录。类路径
配置更改可能会导致选择不同的错误目录。 您始终可以使用完全限定的资源位置而不是相对路径:对于
example 或 .但是,be
知道您正在将应用程序的配置耦合到特定的 absolute
地点。通常最好为这种绝对的
locations — 例如,通过针对 JVM 解析的 “${...}” 占位符
系统属性。 |
命名空间本身提供了 import 指令功能。进一步
除了普通 Bean 定义之外的配置功能在 SELECTION 中可用
Spring 提供的 XML 命名空间 — 例如,和 命名空间。context
util
Groovy Bean 定义 DSL
作为外部化配置元数据的另一个示例,bean 定义还可以 用 Spring 的 Groovy Bean 定义 DSL 表示,如 Grails 框架所示。 通常,此类配置位于“.groovy”文件中,其结构如 以下示例:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这种配置样式在很大程度上等同于 XML bean 定义,甚至
支持 Spring 的 XML 配置命名空间。它还允许导入 XML
bean 定义文件。importBeans
1.2.3. 使用容器
这是能够维护
不同 bean 及其依赖项的注册表。通过使用 method ,您可以检索 bean 的实例。ApplicationContext
T getBean(String name, Class<T> requiredType)
允许您读取 bean 定义并访问它们,如下所示
示例显示:ApplicationContext
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
import org.springframework.beans.factory.getBean
// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")
// use configured instance
var userList = service.getUsernameList()
使用 Groovy 配置,引导看起来非常相似。它有不同的背景 implementation 类,该类是 Groovy 感知的(但也理解 XML bean 定义)。 以下示例显示了 Groovy 配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
val context = GenericGroovyApplicationContext("services.groovy", "daos.groovy")
最灵活的变体是与阅读器结合使用
delegates — 例如,对于 XML 文件,如下所示
示例显示:GenericApplicationContext
XmlBeanDefinitionReader
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()
您还可以使用 for Groovy 文件,如下所示
示例显示:GroovyBeanDefinitionReader
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()
您可以在同一个 上混合和匹配此类 Reader 委托 。
从不同的配置源读取 bean 定义。ApplicationContext
然后,您可以使用 来检索 bean 的实例。该接口有一些其他方法用于检索 bean,但理想情况下,您的应用程序
代码永远不应该使用它们。事实上,你的应用程序代码应该根本不调用该方法,因此根本不依赖于 Spring API。例如
Spring 与 Web 框架的集成为各种 Web 提供了依赖注入
框架组件(例如控制器和 JSF 托管 Bean),允许您声明
通过元数据(例如自动装配 Comments)对特定 bean 的依赖。getBean
ApplicationContext
getBean()
1.3. Bean 概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用
您提供给容器的配置元数据(例如,以 XML 定义的形式)。<bean/>
在容器本身中,这些 bean 定义表示为对象,其中包含(除其他信息外)以下元数据:BeanDefinition
-
包限定的类名:通常是 bean 被定义。
-
Bean 行为配置元素,这些元素表示 Bean 在 容器(范围、生命周期回调等)。
-
对 Bean 执行其工作所需的其他 Bean 的引用。这些 引用也称为协作者或依赖项。
-
要在新创建的对象中设置的其他配置设置 — 例如,大小 pool 的限制或要在管理 连接池。
此元数据转换为构成每个 Bean 定义的一组属性。 下表描述了这些属性:
财产 | 解释于... |
---|---|
类 |
|
名字 |
|
范围 |
|
构造函数参数 |
|
性能 |
|
自动装配模式 |
|
延迟初始化模式 |
|
初始化方法 |
|
销毁方法 |
除了包含有关如何创建特定
bean,则实现还允许注册现有的
在容器外部创建的对象(由用户创建)。这是通过访问
ApplicationContext 的 BeanFactory,该方法返回
BeanFactory 实现。 支持通过 and 方法进行此注册。但是,典型的应用程序仅使用 bean
通过常规 bean 定义元数据定义。ApplicationContext
getBeanFactory()
DefaultListableBeanFactory
DefaultListableBeanFactory
registerSingleton(..)
registerBeanDefinition(..)
Bean 元数据和手动提供的单例实例需要注册为 early 为了让容器在自动装配期间正确地推断它们 和其他内省步骤。在覆盖现有元数据和现有 在某种程度上支持单例实例,在 运行时(与工厂的实时访问同时)不受官方支持,可能会 导致并发访问异常、Bean 容器中的状态不一致,或两者兼而有之。 |
1.3.1. 命名 Bean
每个 bean 都有一个或多个标识符。这些标识符在 托管 Bean 的容器。一个 bean 通常只有一个标识符。但是,如果它 需要多个,额外的可以被视为别名。
在基于 XML 的配置元数据中,您可以使用 attribute、 attribute 或
both 来指定 bean 标识符。该属性允许您指定
恰好是一个 ID。通常,这些名称是字母数字('myBean'、
'someService' 等),但它们也可以包含特殊字符。如果您想
引入 Bean 的其他别名,您也可以在属性中指定它们,用逗号 () 、分号 () 或空格分隔。作为
历史说明,在 Spring 3.1 之前的版本中,该属性为
定义为类型,用于约束可能的字符。从 3.1 开始,
它被定义为一个类型。请注意,bean 唯一性仍然是
由容器强制执行,但不再由 XML 解析器强制执行。id
name
id
name
,
;
id
xsd:ID
xsd:string
id
您不需要为 bean 提供 a 或 an。如果未显式提供 or,则容器将为该 bean 生成唯一名称。然而
如果要按名称引用该 bean,请使用元素或
Service Locator 样式查找,您必须提供名称。
不提供名称的动机与使用 inner 有关
bean 和 autowiring collaborators。name
id
name
id
ref
通过在 Classpath 中进行组件扫描, Spring 会为 unnamed 生成 bean 名称
组件,遵循前面描述的规则:本质上,采用简单的类名
并将其初始字符转换为小写。然而,在(不寻常的)特别
当有多个字符并且同时具有第一个和第二个字符时
为大写,则保留原始大小写。这些规则与
定义者(Spring 在此处使用)。java.beans.Introspector.decapitalize |
在 Bean 定义之外为 Bean 设置别名
在 bean 定义本身中,您可以通过使用
属性指定的最多一个名称与任意数量的其他名称的组合
names 在属性中。这些名称可以是同一 bean 的等效别名
,并且在某些情况下很有用,例如让应用程序中的每个组件
使用特定于该组件的 Bean 名称来引用公共依赖项
本身。id
name
指定实际定义 bean 的所有别名并不总是足够的,
然而。有时需要为定义的 bean 引入别名
别处。在配置拆分的大型系统中,通常会出现这种情况
在每个子系统中,每个子系统都有自己的一组对象定义。
在基于 XML 的配置元数据中,您可以使用 Element 来完成
这。以下示例显示了如何执行此操作:<alias/>
<alias name="fromName" alias="toName"/>
在这种情况下,名为
使用此别名定义后,称为 。fromName
toName
例如,子系统 A 的配置元数据可以通过
的名称。子系统 B 的配置元数据可以引用
名称为 .编写主应用程序时
使用这两个子系统,则主应用程序通过
的名称。要让所有三个名称都引用同一个对象,您可以
将以下别名定义添加到配置元数据中:subsystemA-dataSource
subsystemB-dataSource
myApp-dataSource
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在每个组件和主应用程序都可以通过名称来引用 dataSource 这是唯一的,并且保证不会与任何其他定义冲突(有效地 创建命名空间),但它们引用同一个 bean。
1.3.2. 实例化 Bean
bean 定义本质上是创建一个或多个对象的配方。这 容器在询问时查看命名 bean 的配方,并使用配置 元数据来创建(或获取)实际对象。
如果使用基于 XML 的配置元数据,请指定对象的类型(或类)
即在 Element 的属性中实例化。此属性(在内部是实例上的属性)通常是必需的。(有关异常,请参见使用实例工厂方法实例化和 Bean 定义继承。
您可以通过以下两种方式之一使用该属性:class
<bean/>
class
Class
BeanDefinition
Class
-
通常,在容器 它本身通过反射性地调用其构造函数来直接创建 Bean 等效于带有运算符的 Java 代码。
new
-
要指定包含工厂方法的实际类,即 invoked 来创建对象,在不太常见的情况下,容器在类上调用工厂方法来创建 Bean。返回的对象类型 从工厂方法的调用可以是同一个类,也可以是另一个类 类。
static
static
static
使用 Constructor 进行实例化
当你通过构造函数方法创建一个 bean 时,所有普通类都可以被 和 与 Spring 兼容。也就是说,正在开发的类不需要实现 任何特定接口或以特定方式编码。只需指定 bean 类应该就足够了。但是,具体取决于您为该特定 bean,则可能需要一个默认的(空的)构造函数。
Spring IoC 容器几乎可以管理您希望它管理的任何类。是的 不限于管理真正的 JavaBeans。大多数 Spring 用户更喜欢带有 仅对默认 (无参数) 构造函数和适当的 setter 和 getter 进行建模 在容器中的属性之后。你也可以有更多异国情调的非 bean 样式 类。例如,如果您需要使用遗留连接池 绝对不遵守 JavaBean 规范,Spring 可以将其管理为 井。
使用基于 XML 的配置元数据,您可以按如下方式指定 Bean 类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
有关向构造函数提供参数的机制的详细信息(如果需要) 以及在构造对象后设置对象实例属性,请参阅注入依赖项。
使用 Static Factory Method 进行实例化
定义使用静态工厂方法创建的 Bean 时,使用该属性指定包含该工厂方法的类和一个属性
named 来指定工厂方法本身的名称。您应该
能够调用此方法(使用可选参数,如下所述)并返回一个实时的
object,该对象随后被视为通过构造函数创建的。
这种 bean 定义的一个用途是在遗留代码中调用工厂。class
static
factory-method
static
以下 Bean 定义指定通过调用
Factory 方法。定义没有指定返回对象的类型(类),
仅包含 Factory 方法的类。在此示例中,该方法必须是静态方法。下面的示例演示如何指定工厂方法:createInstance()
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
下面的示例展示了一个将与前面的 bean 定义一起使用的类:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
class ClientService private constructor() {
companion object {
private val clientService = ClientService()
fun createInstance() = clientService
}
}
有关向工厂方法提供(可选)参数的机制的详细信息 以及在从工厂返回对象后设置对象实例属性, 详见 依赖关系和配置。
使用实例工厂方法进行实例化
类似于通过静态
工厂方法,使用实例工厂方法进行实例化会调用非静态
方法创建一个新的 bean。要使用此功能
机制中,将属性留空,并在属性
在当前 (或 parent) 容器中指定 bean 的名称,其中包含
要调用以创建对象的 instance 方法。设置
factory 方法本身具有 attribute。以下示例显示了
如何配置这样的 bean:class
factory-bean
factory-method
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
}
一个工厂类还可以包含多个工厂方法,如下例所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
以下示例显示了相应的类:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
class DefaultServiceLocator {
companion object {
private val clientService = ClientServiceImpl()
private val accountService = AccountServiceImpl()
}
fun createClientServiceInstance(): ClientService {
return clientService
}
fun createAccountServiceInstance(): AccountService {
return accountService
}
}
这种方法表明,工厂 Bean 本身可以通过 依赖注入 (DI)。请参阅 依赖项 和 配置的详细信息。
在 Spring 文档中,“工厂 Bean”是指在
Spring 容器,它通过实例或静态工厂方法创建对象。相比之下,(注意大写)指的是特定于 Spring 的 FactoryBean 实现类。FactoryBean |
确定 Bean 的运行时类型
确定特定 bean 的运行时类型并非易事。中指定的类
Bean 元数据定义只是一个初始类引用,可能会组合在一起
替换为声明的工厂方法,或者是一个可能导致
bean 的运行时类型不同,或者在实例级别的情况下根本不设置
Factory 方法(通过指定名称解析)。
此外,AOP 代理可以使用基于接口的代理包装 bean 实例,其中
目标 Bean 的实际类型(仅其实现的接口)的有限公开。FactoryBean
factory-bean
了解特定 Bean 的实际运行时类型的推荐方法是
对指定 Bean 名称的调用。这需要以上所有
cases 中,并返回调用的对象类型
将返回相同的 bean 名称。BeanFactory.getType
BeanFactory.getBean
1.4. 依赖项
典型的企业应用程序不是由单个对象(或 Spring 用语)。即使是最简单的应用程序也有一些对象可以协同工作 呈现最终用户看到的连贯应用程序。下一节将介绍如何 您将从定义许多独立的 bean 定义到完全实现 对象协作实现目标的应用程序。
1.4.1. 依赖注入
依赖关系注入 (DI) 是对象定义其依赖关系的过程 (即,它们使用的其他对象)仅通过构造函数参数, 工厂方法的参数,或在 它是从工厂方法构造或返回的。然后,容器会注入这些 dependencies 的 Dependencies 创建。这个过程基本上是相反的(因此 名称 Inversion of Control) 控制实例化的 bean 本身 或通过使用类的直接构造来定位其依赖项,或者 Service Locator 模式。
使用 DI 原则,代码更简洁,当对象 提供它们的依赖项。对象不查找其依赖项,并且 不知道依赖项的位置或类。因此,您的课程变得更加容易 进行测试时,特别是当依赖项位于接口或抽象基类上时, 允许在单元测试中使用 stub 或 mock 实现。
DI 有两种主要变体:基于构造函数 依赖项注入和基于 Setter 的依赖项注入。
基于构造函数的依赖关系注入
基于构造函数的 DI 是通过容器调用带有
个参数,每个参数表示一个依赖项。调用工厂方法
使用特定的参数来构造 bean 几乎是等效的,并且此讨论
将参数视为构造函数和工厂方法。这
以下示例显示了一个只能使用 constructor 进行依赖项注入的类
注射:static
static
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
// business logic that actually uses the injected MovieFinder is omitted...
}
请注意,此类没有什么特别之处。这是一个 POJO 不依赖于特定于容器的接口、基类或注释。
构造函数参数解析
构造函数参数解析匹配通过使用参数的类型进行。如果没有 在 bean 定义的构造函数参数中存在潜在的歧义, 在 Bean 定义中定义构造函数参数的顺序是 Order 其中,当 bean 为 正在实例化。请考虑以下类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
package x.y
class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
假设 和 类没有通过继承相关,则没有
可能存在歧义。因此,以下配置工作正常,而您不会
需要在元素中显式指定 constructor 参数 indexes 或 types。ThingTwo
ThingThree
<constructor-arg/>
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
当引用另一个 bean 时,类型是已知的,并且可以进行匹配(就像
case 与前面的示例一起)。当使用简单类型(如 , Spring 无法确定值的类型)时,因此无法匹配
按类型,无需帮助。请考虑以下类:<value>true</value>
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
package examples
class ExampleBean(
private val years: Int, // Number of years to calculate the Ultimate Answer
private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)
在前面的场景中,容器可以使用与简单类型的类型匹配,如果
您可以使用属性
如下例所示:type
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
你可以使用 attribute 来显式指定构造函数参数的索引。
如下例所示:index
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的歧义外,指定索引 解决构造函数具有两个相同类型的参数时的歧义。
索引从 0 开始。 |
您还可以使用构造函数参数名称来消除值歧义,如下所示 示例显示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,要实现开箱即用的工作,您的代码必须使用 debug 标志,以便 Spring 可以从构造函数中查找参数名称。 如果您不能或不想使用 debug 标志编译代码,则可以使用 @ConstructorProperties JDK 注释显式命名构造函数参数。sample 类将 然后必须查看如下:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
package examples
class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
基于 Setter 的依赖注入
基于 setter 的 DI 是通过容器调用 setter 方法完成的
bean 在调用无参数构造函数或无参数工厂方法后将其更改为
实例化你的 bean。static
以下示例显示了一个只能使用 pure setter 注入。此类是传统的 Java。它是一个没有依赖项的 POJO 在特定于容器的接口、基类或注释上。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
class SimpleMovieLister {
// a late-initialized property so that the Spring container can inject a MovieFinder
lateinit var movieFinder: MovieFinder
// business logic that actually uses the injected MovieFinder is omitted...
}
它支持基于构造函数和基于 setter 的 bean 的 DI
管理。它还支持基于设置程序的 DI,因为某些依赖项已经
通过 constructor 方法注入。您可以以
a ,将其与实例结合使用
将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不工作
直接使用这些类(即以编程方式),而是使用 XML 定义、带注释的组件(即用 、 等注释的类)或基于 Java 的类中的方法。
然后,这些源在内部转换为 的实例并用于
加载整个 Spring IoC 容器实例。ApplicationContext
BeanDefinition
PropertyEditor
bean
@Component
@Controller
@Bean
@Configuration
BeanDefinition
依赖项解析过程
容器按如下方式执行 Bean 依赖关系解析:
-
使用配置元数据创建和初始化 描述所有 Bean 的配置元数据可以通过 XML、Java 代码或 附注。
ApplicationContext
-
对于每个 bean,其依赖项以 properties 的形式表示,constructor arguments,或者 static-factory 方法的参数(如果你使用它而不是 normal 构造函数)。当 Bean 为 实际创建。
-
每个 property 或 constructor 参数都是要设置的值的实际定义,或者 对容器中另一个 bean 的引用。
-
作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以 String 格式提供的值转换为所有内置类型,例如 、 等。
int
long
String
boolean
Spring 容器在创建容器时验证每个 bean 的配置。 但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。 创建 singleton 范围并设置为预实例化(默认值)的 bean 创建容器时。作用域在 Bean Scopes 中定义。否则 仅当请求 Bean 时,才会创建 Bean。创建 Bean 可能会导致 要创建的 bean 图,作为 bean 的依赖项及其依赖项' 创建并分配依赖项(依此类推)。请注意,之间的分辨率不匹配 这些依赖项可能会延迟显示 — 即,在首次创建受影响的 bean 时。
您通常可以相信 Spring 会做正确的事情。它检测配置问题,
例如对不存在的 bean 和循环依赖项的引用,在 Container
load-time 的Spring 会尽可能晚地设置属性并解析依赖关系,当
Bean 实际上是创建的。这意味着已加载的 Spring 容器
如果存在
创建该对象或其依赖项之一时出现问题,例如,Bean 会抛出
异常。这可能会延迟
可见性是 implementation by
默认的 pre-instantiate singleton bean。以一些前期时间和内存为代价
在实际需要这些 bean 之前创建它们,则会发现配置问题
当 创建时,而不是之后。您仍然可以覆盖此默认值
行为,以便单例 bean 惰性初始化,而不是 agerly
预实例化。ApplicationContext
ApplicationContext
如果不存在循环依赖关系,则当一个或多个协作 bean 正在 注入到依赖 bean 中,每个协作 bean 都完全在 注入到依赖 bean 中。这意味着,如果 Bean A 依赖于 bean B 时,Spring IoC 容器会在调用 在 bean A 上执行 setter 方法。换句话说,bean 被实例化(如果它不是 预先实例化的单例),设置其依赖项,以及相关的生命周期 方法(例如配置的 init 方法或 InitializingBean 回调方法) 被调用。
依赖关系注入示例
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。一个小 的一部分指定了一些 bean 定义,如下所示:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例显示了相应的类:ExampleBean
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
class ExampleBean {
lateinit var beanOne: AnotherBean
lateinit var beanTwo: YetAnotherBean
var i: Int = 0
}
在前面的示例中,声明 setter 与指定的属性匹配 在 XML 文件中。以下示例使用基于构造函数的 DI:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例显示了相应的类:ExampleBean
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
class ExampleBean(
private val beanOne: AnotherBean,
private val beanTwo: YetAnotherBean,
private val i: Int)
在 bean 定义中指定的构造函数参数用作
的构造函数。ExampleBean
现在考虑此示例的一个变体,其中 Spring 不是使用构造函数,而是 Spring
告知调用工厂方法以返回对象的实例:static
<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
}
}
}
工厂方法的参数由元素
与实际使用构造函数完全相同。类的类型为
返回的 API API 的 API API 的 API 请求
包含 Factory 方法(尽管在此示例中,它是)。实例
(非静态)Factory 方法可以以基本相同的方式使用(除了
使用 attribute 而不是 attribute),因此我们
不要在这里讨论这些细节。static
<constructor-arg/>
static
factory-bean
class
1.4.2. 依赖项和配置详解
如上一节所述,您可以定义 bean
properties 和构造函数参数作为对其他托管 bean (协作者) 的引用
或作为内联定义的值。Spring 基于 XML 的配置元数据支持
子元素类型
目的。<property/>
<constructor-arg/>
Straight 值(Primitives、Strings 等)
元素的属性指定属性或构造函数
参数作为人类可读的字符串表示形式。Spring 的转换服务用于转换这些
值从 a 更改为属性或参数的实际类型。
以下示例显示了正在设置的各种值:value
<property/>
String
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</bean>
以下示例使用 p-namespace 以获得更简洁的 XML 配置:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
前面的 XML 更简洁。但是,拼写错误是在运行时发现的,而不是 设计时,除非您使用 IDE(例如 IntelliJ IDEA 或 Spring Tools for Eclipse) 支持在创建 Bean 定义时自动完成属性。这样的 IDE 强烈建议您提供帮助。
您还可以配置实例,如下所示:java.util.Properties
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring 容器使用 JavaBeans 机制将元素内的文本转换为实例。这
是一个不错的快捷方式,并且是 Spring 团队确实喜欢使用
Attribute 样式上的嵌套元素。<value/>
java.util.Properties
PropertyEditor
<value/>
value
元素idref
该元素只是一种防错的方式,用于将 (字符串值 - 而不是
一个引用)指向 or 元素。以下示例演示如何使用它:idref
id
<constructor-arg/>
<property/>
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面的 bean 定义代码片段完全等价(在运行时)用于 以下代码段:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式比第二种形式更可取,因为使用标签可以让
容器在部署时验证引用的名为 Bean 的实际
存在。在第二个变体中,不对传递的值执行验证
添加到 Bean 的属性中。拼写错误仅被发现(大多数
可能是致命的结果)。如果 Bean 是原型 Bean,则此拼写错误和结果异常
只有在部署容器很久之后才能被发现。idref
targetName
client
client
client
4.0 bean 中不再支持元素上的属性
XSD 的引用,因为它不再提供优于常规引用的价值。改变
升级到 4.0 架构时对的现有引用。local idref bean idref local idref bean |
一个常见的位置(至少在 Spring 2.0 之前的版本中),其中元素
带来价值的是在 bean 定义中配置 AOP 拦截器。在指定
拦截器 名称 可防止您拼写错误的拦截器 ID。<idref/>
ProxyFactoryBean
<idref/>
对其他 Bean 的引用(协作者)
该元素是 or 定义元素内的最后一个元素。在这里,您将 bean 的指定属性的值设置为
对容器管理的另一个 Bean(协作者)的引用。引用的 Bean
是要设置其属性的 Bean 的依赖项,并且按需初始化
根据需要。(如果协作者是单例 bean,则它可能会
已由容器初始化。所有引用最终都是对
另一个对象。范围界定和验证取决于您是指定
other 对象。ref
<constructor-arg/>
<property/>
bean
parent
通过 tag 的属性指定目标 bean 是最多的
general 形式,并允许创建对同一容器中任何 bean 的引用,或者
父容器,无论它是否在同一个 XML 文件中。该属性的值可以与目标 Bean 的属性相同,也可以相同
作为目标 Bean 属性中的值之一。以下示例
演示如何使用元素:bean
<ref/>
bean
id
name
ref
<ref bean="someBean"/>
通过该属性指定目标 Bean 会创建对 Bean 的引用
即在当前容器的父容器中。该属性的值可以与目标 Bean 的属性相同,也可以与目标 Bean 的
值。目标 Bean 必须位于
当前容器的父容器。您应该主要使用此 bean 引用变体
当您具有容器层次结构并且希望将现有 bean 包装在父 bean 中时
容器,其代理与父 Bean 同名。以下一对
listings 显示了如何使用该属性:parent
parent
id
name
parent
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
4.0 bean 中不再支持元素上的属性
XSD 的引用,因为它不再提供优于常规引用的价值。改变
升级到 4.0 架构时对的现有引用。local ref bean ref local ref bean |
内部 Bean
or 元素中的元素定义了一个
inner bean,如下例所示:<bean/>
<property/>
<constructor-arg/>
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部 Bean 定义不需要定义的 ID 或名称。如果指定,则容器
不使用此类值作为标识符。容器还会忽略
创建,因为内部 bean 始终是匿名的,并且总是使用外部 bean 创建
豆。无法独立访问内部 bean 或将它们注入
将 bean 协作到封闭 bean 中。scope
作为一种极端情况,可以从自定义范围接收销毁回调 — 例如,对于包含在单例 bean 中的请求范围的内部 bean。创作 的 Bean 实例绑定到其包含的 Bean,但销毁回调允许它 参与请求范围的生命周期。这不是常见情况。内豆 通常只是共享其包含的 bean 的 scope。
收集
、、 、 和 元素设置属性
和 Java 类型 、 、 和 的参数
分别。以下示例显示了如何使用它们:<list/>
<set/>
<map/>
<props/>
Collection
List
Set
Map
Properties
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">[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>
map 键或值的值,或者一个 set 值,也可以是 以下元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring 容器还支持合并集合。应用程序
开发人员可以定义父级、 或 元素
并让子、 、 或 元素继承和
覆盖父集合中的值。也就是说,子集合的值为
将父集合和子集合的元素与子集合的
collection 元素覆盖父集合中指定的值。<list/>
<map/>
<set/>
<props/>
<list/>
<map/>
<set/>
<props/>
本节关于合并讨论了父子 Bean 机制。不熟悉的读者 with 父 Bean 和 Child Bean 定义可能希望在继续之前阅读相关部分。
以下示例演示了集合合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>
请注意在 Bean 定义的 property 的 element 上使用 attribute。解析 Bean 时
并由 Container 实例化,则生成的实例具有一个 collection,其中包含将 child 的集合与 parent的集合合并的结果。以下清单
显示结果:merge=true
<props/>
adminEmails
child
child
adminEmails
Properties
adminEmails
adminEmails
子集合的值集从
parent 中,并且该值的 child 值将覆盖
父集合。Properties
<props/>
support
此合并行为类似于 、 和 集合类型。在元素的特定情况下,语义
与集合类型(即值集合的概念)相关联。parent的值位于所有child列表的
值。对于 、 和 集合类型,无排序
存在。因此,对于作为基础的集合类型,没有排序语义
容器
内部使用。<list/>
<map/>
<set/>
<list/>
List
ordered
Map
Set
Properties
Map
Set
Properties
集合合并的限制
您无法合并不同的集合类型(如 a 和 a )。如果你
尝试这样做,则会引发 appropriate 。该属性必须为
在较低的继承子定义上指定。指定 on 属性
父集合定义是冗余的,不会导致所需的合并。Map
List
Exception
merge
merge
强类型集合
在 Java 5 中引入泛型类型后,您可以使用强类型集合。
也就是说,可以声明一个类型,使其只能包含
(例如)元素。如果您使用 Spring 依赖注入
强类型化为 bean,您可以利用 Spring 的
type-conversion 支持,以便在将强类型实例的元素添加到 .
下面的 Java 类和 bean 定义显示了如何做到这一点:Collection
String
Collection
Collection
Collection
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
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>
当 bean 的属性准备好进行注入时,泛型
有关强类型的元素类型的信息为
由 Reflection 提供。因此,Spring 的类型转换基础结构识别
各种 value 元素为 type ,字符串值 (, 和 ) 将转换为 actual 类型。accounts
something
Map<String, Float>
Float
9.99
2.75
3.99
Float
Null 和空字符串值
Spring 将 properties 等的空参数视为 empty 。这
以下基于 XML 的配置元数据代码段将属性设置为空值 (“”)。Strings
email
String
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
前面的示例等效于以下 Java 代码:
exampleBean.setEmail("");
exampleBean.email = ""
元素处理值。下面的清单显示了一个示例:<null/>
null
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等效于以下 Java 代码:
exampleBean.setEmail(null);
exampleBean.email = null
带有 p 命名空间的 XML 快捷方式
p 命名空间允许您使用元素的属性(而不是嵌套元素)来描述您的属性值、协作 bean 或两者。bean
<property/>
Spring 支持带有命名空间的可扩展配置格式,
它们基于 XML 架构定义。中讨论的配置格式
本章在 XML Schema 文档中定义。但是,未定义 p 命名空间
在 XSD 文件中,并且仅存在于 Spring 的核心中。beans
以下示例显示了两个 XML 代码段(第一个使用 标准 XML 格式,第二个使用 p-namespace),它们解析为相同的结果:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</beans>
该示例显示了 bean 定义中调用的 p-namespace 中的一个属性。
这告诉 Spring 包含一个属性声明。如前所述,
p-namespace 没有 schema 定义,因此您可以设置属性的名称
添加到属性名称。email
下一个示例包括另外两个 bean 定义,这两个定义都引用了 另一个 bean:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
此示例不仅包括使用 p-namespace 的属性值
但也使用特殊格式来声明属性引用。而第一个 bean
定义用于创建从 bean 到 bean 的引用,第二个 bean 定义用作
属性来执行完全相同的操作。在本例中,是属性名称
而 part 表示这不是一个直接值,而是一个
引用另一个 bean。<property name="spouse" ref="jane"/>
john
jane
p:spouse-ref="jane"
spouse
-ref
p 命名空间不如标准 XML 格式灵活。例如,格式
用于声明属性引用与以 结尾的属性冲突,而
标准 XML 格式则不需要。我们建议您仔细选择方法,并
将此内容传达给您的团队成员,以避免生成使用全部
同时有三种方法。Ref |
带有 c-namespace 的 XML 快捷方式
类似于 Spring 中引入的带有 p-namespace 的 XML Shortcut、c-namespace
3.1 中,允许内联属性来配置构造函数参数,而不是
然后是嵌套元素。constructor-arg
以下示例使用命名空间执行与 from Constructor-based Dependency Injection 相同的操作: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>
命名空间使用与命名空间相同的约定(
Bean 引用)来按名称设置构造函数参数。同样地
它需要在 XML 文件中声明,即使它未在 XSD 架构中定义
(它存在于 Spring 核心中)。c:
p:
-ref
对于构造函数参数名称不可用的极少数情况(通常如果 字节码在没有调试信息的情况下编译),您可以使用 fallback 到 ARGUMENT 索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
由于 XML 语法的原因,索引表示法需要存在前导 ,
因为 XML 属性名称不能以数字开头(即使某些 IDE 允许)。
相应的索引表示法也可用于元素,但
不常用,因为 plain order of declaration 通常就足够了。_ <constructor-arg> |
在实践中,构造函数解析机制在匹配方面非常有效 参数,因此,除非你真的需要,否则我们建议使用 name 表示法 在整个配置中。
复合属性名称
在设置 Bean 属性时,可以使用复合或嵌套属性名称,只要
除 final property name 之外,path 的所有组件都不是 。考虑一下
遵循 bean 定义:null
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
bean 具有一个 property,该 property 具有一个 property,该 property 又具有一个 property,并且该 final 属性被设置为值 。为了
this 要工作,属性 和 属性 不得
be 在 bean 构建之后。否则,将引发 a。something
fred
bob
sammy
sammy
123
fred
something
bob
fred
null
NullPointerException
1.4.3. 使用depends-on
如果一个 bean 是另一个 bean 的依赖项,这通常意味着一个 bean 被设置为
另一个人的财产。通常,您可以使用基于 XML 的配置元数据中的 <ref/>
元素来实现此目的。但是,有时
豆子就不那么直接了。例如,当类中的 static 初始值设定项需要
triggered,例如用于数据库驱动程序注册。该属性可以
在使用此元素的 bean 之前显式强制初始化一个或多个 bean
已初始化。以下示例使用该属性来表示
对单个 bean 的依赖:depends-on
depends-on
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个 bean 的依赖关系,请提供一个 bean 名称列表作为
属性(逗号、空格和分号有效
delimiters):depends-on
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
该属性可以指定初始化时依赖项和
在仅 singleton bean 的情况下,相应的
销毁时间依赖性。定义关系的依赖 Bean
在给定的 bean 本身被销毁之前,首先销毁给定的 bean。
因此,还可以控制关机顺序。depends-on depends-on depends-on |
1.4.4. 延迟初始化的 Bean
默认情况下,实现在初始化过程中急切地创建和配置所有 singleton bean
过程。通常,这种预实例化是可取的,因为
配置或周围环境会立即被发现,而不是几个小时
甚至几天后。当此行为不可取时,您可以阻止
通过将 Bean 定义标记为
lazy-initialized 初始化的。延迟初始化的 bean 告诉 IoC 容器创建一个 bean
instance,而不是在启动时。ApplicationContext
在 XML 中,此行为由元素上的属性控制,如下例所示:lazy-init
<bean/>
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置被 、 bean 使用
在启动时不会预先实例化,
而 bean 是预先实例化的。ApplicationContext
lazy
ApplicationContext
not.lazy
但是,当延迟初始化的 bean 是单例 bean 的依赖项时,即
不是 lazy-initialized,则在
startup,因为它必须满足单例的依赖项。延迟初始化的 bean
被注入到其他位置未进行延迟初始化的单例 bean 中。ApplicationContext
您还可以通过使用元素上的属性在容器级别控制延迟初始化,如下例所示:default-lazy-init
<beans/>
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 自动装配协作者
Spring 容器可以自动连接协作 bean 之间的关系。您可以
让 Spring 自动解析 bean 的协作者(其他 bean)
正在检查 .Autowiring 具有以下功能
优势:ApplicationContext
-
自动装配可以显著减少指定属性或构造函数的需要 参数。(其他机制(例如本章其他地方讨论的 bean 模板)也很有价值 在这方面。
-
Autowiring 可以随着对象的发展而更新配置。例如,如果您需要 要向类添加依赖项,则可以自动满足该依赖项,而无需 您需要修改配置。因此,自动装配可能特别有用 在开发过程中,在以下情况下不否定切换到显式布线的选项 代码库变得更加稳定。
当使用基于 XML 的配置元数据时(参见 依赖关系注入),您可以
可以使用 Element 的属性为 bean 定义指定 autowire 模式。自动装配功能有四种模式。您指定自动装配
每个 bean,因此可以选择哪些 bean 进行 autowire。下表描述了
四种自动装配模式:autowire
<bean/>
模式 | 解释 |
---|---|
|
(默认)没有自动接线。Bean 引用必须由元素定义。改变
对于较大的部署,不建议使用默认设置,因为指定
collaborators 明确提供了更大的控制权和清晰度。在某种程度上,它
记录系统的结构。 |
|
按属性名称自动装配。Spring 查找与
需要自动装配的属性。例如,如果将 Bean 定义设置为
autowire,并且它包含一个属性(即,它有一个方法),Spring 会查找一个名为 bean 的定义,并使用
it 来设置属性。 |
|
如果 中正好存在一个属性类型的 bean,则允许自动连接属性
容器。如果存在多个异常,则会引发致命异常,该异常指示
你不能对那个 bean 使用 autowired。如果没有匹配项
beans 时,什么都不会发生(属性未设置)。 |
|
类似于构造函数参数,但适用于构造函数参数。如果没有
容器中 constructor 参数类型的一个 bean,则会引发致命错误。 |
使用 或 autowiring 模式,您可以连接数组和
typed collections 的集合。在这种情况下,容器内所有
match 预期的类型来满足依赖项。您可以自动装配
强类型实例(如果预期的键类型为 .自动装配实例的值由与预期类型匹配的所有 bean 实例组成,并且实例的键包含相应的 bean 名称。byType
constructor
Map
String
Map
Map
自动装配的局限性和缺点
当 Autowiring 在整个项目中一致地使用时,它的效果最佳。如果 autowiring 是 通常不使用,则开发人员可能会混淆仅使用它来连接一个 或 两个 bean 定义。
考虑自动装配的限制和缺点:
-
和 settings 中的显式依赖项始终覆盖 autowiring 的您不能自动装配简单属性,例如 primitives, , 和 (以及此类简单属性的数组)。此限制为 设计使然。
property
constructor-arg
Strings
Classes
-
自动装配不如显式装配精确。虽然,如前面的表格所示, Spring 会小心避免猜测,以防出现可能意想不到的歧义 结果。Spring 管理的对象之间的关系不再是 记录下来。
-
连接信息可能不可用于可能从 一个 Spring 容器。
-
容器中的多个 bean 定义可能与 setter 方法或构造函数参数进行自动装配。对于数组、集合或实例,这不一定是问题。但是,对于 expect 单个值,则此歧义不会任意解决。如果没有唯一的 bean 定义可用,则会引发异常。
Map
在后一种情况下,您有以下几种选择:
从 Autowiring 中排除 Bean
在每个 bean 的基础上,你可以从自动装配中排除一个 bean。在 Spring 的 XML 格式中,将
元素的属性 。容器
使该特定 Bean 定义对 Autowiring 基础结构不可用
(包括注释样式配置,如 @Autowired
)。autowire-candidate
<bean/>
false
该属性旨在仅影响基于类型的自动装配。
它不会影响按名称的显式引用,即使
指定的 Bean 未标记为 autowire 候选项。因此,自动装配
尽管如此,如果名称匹配,则 by name 会注入一个 bean。autowire-candidate |
您还可以根据与 Bean 名称的模式匹配来限制自动装配候选项。这
top-level 元素接受其 attribute 中的一个或多个模式。例如,要限制 autowire 候选状态
对于名称以 结尾的任何 bean,请提供值 。自
提供多个模式,在逗号分隔的列表中定义它们。bean 定义属性的显式值 or 始终采用
优先。对于此类 bean,模式匹配规则不适用。<beans/>
default-autowire-candidates
Repository
*Repository
true
false
autowire-candidate
这些技术对于您永远不想注入到其他 bean 中的 bean 非常有用 bean 的 bean 中。这并不意味着被排除的 bean 本身不能由 使用自动装配。相反,bean 本身不是自动装配其他 bean 的候选者。
1.4.6. 方法注入
在大多数应用程序场景中,容器中的大多数 bean 都是单例的。当单例 bean 需要 与另一个单例 bean 协作或非单例 bean 需要协作 对于另一个非单例 bean,通常通过定义一个 bean 作为另一个的属性。当 bean 生命周期为 不同。假设单例 bean A 需要使用非单例(原型)bean B, 可能在 A 上的每个方法调用时。容器仅创建单例 bean A 一次,因此只有一次设置属性的机会。容器不能 每次需要 bean B 时,都会向 bean A 提供一个新的 bean B 实例。
一个解决方案是放弃一些倒置的控制。您可以使
bean A 通过实现接口
并通过对容器进行 getBean(“B”)
调用来请求 (a
通常是 new) bean B 实例。以下示例
显示了这种方法:ApplicationContextAware
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple
// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
class CommandManager : ApplicationContextAware {
private lateinit var applicationContext: ApplicationContext
fun process(commandState: Map<*, *>): Any {
// grab a new instance of the appropriate Command
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.state = commandState
return command.execute()
}
// notice the Spring API dependency!
protected fun createCommand() =
applicationContext.getBean("command", Command::class.java)
override fun setApplicationContext(applicationContext: ApplicationContext) {
this.applicationContext = applicationContext
}
}
前面的内容是不可取的,因为业务代码知道并耦合到 Spring 框架。方法注入,Spring IoC 的一个有点高级的功能 容器,让您能够干净利落地处理此用例。
查找方法注入
Lookup 方法注入是容器覆盖 container-managed bean 中,并返回 容器。查找通常涉及原型 Bean,如所描述的场景所示 在上一节中。Spring 框架 通过使用 CGLIB 库中的字节码生成来实现此方法注入 动态生成覆盖该方法的 subclass。
|
对于上一个代码片段中的类,
Spring 容器动态覆盖该方法的实现。该类没有任何 Spring 依赖项,因为
重新设计的示例显示:CommandManager
createCommand()
CommandManager
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
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
}
在包含要注入的方法的客户端类中(在此
case),则要注入的方法需要以下形式的签名:CommandManager
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是 ,则动态生成的子类实现该方法。
否则,动态生成的子类将覆盖
原始类。请考虑以下示例:abstract
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
标识为 的 bean 调用其自己的方法
每当它需要 bean 的新实例时。您必须小心部署
如果这确实是需要的,那么 bean 作为原型。如果是
一个 singleton,则每次都返回相同的 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
}
或者,更地说,您可以依靠目标 Bean 与 Lookup 方法的 declared return 类型:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
val command = createCommand()
command.state = commandState
return command.execute()
}
@Lookup
protected abstract fun createCommand(): Command
}
请注意,您通常应该使用具体的 stub 实现,以便它们与 Spring 的组件兼容 默认情况下忽略抽象类的扫描规则。此限制不会 应用于显式注册或显式导入的 Bean 类。
访问不同范围的目标 bean 的另一种方法是 / 注入点。请参见将范围限定的 Bean 作为依赖项。 您可能还会发现 (在包中) 很有用。 |
任意方法替换
与查找方法注入相比,方法注入的一种不太有用的形式是能够 将托管 Bean 中的任意方法替换为另一个方法实现。你 可以安全地跳过本节的其余部分,直到您真正需要此功能。
使用基于 XML 的配置元数据,您可以使用
对于已部署的 Bean,将现有方法实现替换为另一个方法实现。考虑
下面的类,它有一个名为 Method 的方法,我们想要覆盖它:replaced-method
computeValue
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
class MyValueCalculator {
fun computeValue(input: String): String {
// some real code...
}
// some other methods...
}
实现接口的类提供新的方法定义,如下例所示:org.springframework.beans.factory.support.MethodReplacer
/**
* 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"/>
您可以在元素中使用一个或多个元素来指示被覆盖的方法的方法签名。签名
仅当方法重载且有多个变体时,才需要参数
存在于类中。为方便起见,参数的类型字符串可以是
完全限定类型名称的 substring 的 substring 中。例如,以下 all match :<arg-type/>
<replaced-method/>
java.lang.String
java.lang.String
String
Str
因为参数的数量通常足以区分每种可能的 选项,此快捷方式可以节省大量键入,因为允许您只键入 与参数类型匹配的最短字符串。
1.5. Bean 作用域
创建 Bean 定义时,将创建用于创建实际实例的配方 由该 Bean 定义定义的类。bean 定义是 recipe 很重要,因为它意味着,与 class 一样,您可以创建多个对象 实例。
您不仅可以控制各种依赖项和配置值,这些依赖项和配置值
插入到从特定 bean 定义创建的对象中,但也插入到控件
从特定 Bean 定义创建的对象的范围。这种方法是
功能强大且灵活,因为您可以选择所创建对象的范围
通过配置,而不必在 Java
类级别。可以将 Bean 定义为部署在多个范围之一中。
Spring 框架支持六个范围,其中四个范围仅在
您使用 Web 感知 .您还可以创建自定义范围。ApplicationContext
下表描述了支持的范围:
范围 | 描述 |
---|---|
(默认)将单个 bean 定义的范围限定为每个 Spring IoC 的单个对象实例 容器。 |
|
将单个 Bean 定义的范围限定为任意数量的对象实例。 |
|
将单个 Bean 定义的范围限定为单个 HTTP 请求的生命周期。那是
每个 HTTP 请求都有自己的 bean 实例,该实例是从单个 bean 的背面创建的
定义。仅在 web-aware Spring 的上下文中有效。 |
|
将单个 bean 定义的范围限定为 HTTP 的生命周期。仅在
Web 感知 Spring 的上下文。 |
|
将单个 Bean 定义的范围限定为 .仅在
Web 感知 Spring 的上下文。 |
|
将单个 Bean 定义的范围限定为 .仅在
Web 感知 Spring 的上下文。 |
从 Spring 3.0 开始,线程范围可用,但默认情况下未注册。为
有关更多信息,请参阅 SimpleThreadScope 的文档。
有关如何注册此 Scope或任何其他自定义 Scope 的说明,请参阅 Using a Custom Scope。 |
1.5.1. 单例范围
仅管理单例 bean 的一个共享实例,以及 bean 的所有请求 替换为与该 Bean 定义的一个或多个 ID 匹配,则生成一个特定的 Bean 实例。
换句话说,当你定义一个 bean 定义并且它被定义为 singleton 时,Spring IoC 容器只创建对象的一个实例 由该 bean 定义定义。这个 single 实例存储在 singleton bean 以及该命名 bean 的所有后续请求和引用 返回缓存的对象。下图显示了单一实例范围的工作原理:
Spring 的单例 bean 概念不同于 Gang of Four (GoF) 模式书。GoF 单例对 Object 中,以便每个 ClassLoader 的Spring 单例的范围最好描述为每个容器 和每 bean 的 bean 中。这意味着,如果您在 单个 Spring 容器,则 Spring 容器将创建一个且仅创建一个实例 由该 Bean 定义定义的类。singleton scope 是默认范围 在Spring。要在 XML 中将 bean 定义为单例,可以定义一个 bean,如 以下示例:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2. 原型范围
bean 部署的非单例原型范围会导致创建一个新的
bean 实例。即 bean
被注入到另一个 bean 中,或者您通过在
容器。通常,您应该对所有有状态 bean 使用 prototype 范围,并且
singleton 作用域。getBean()
下图说明了 Spring 原型范围:
(数据访问对象 (DAO) 通常不配置为原型,因为典型的 DAO 不持有 任何对话状态。我们更容易重用 单例图。
下面的示例将 Bean 定义为 XML 中的原型:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他范围相比, Spring 不管理 prototype bean 的 bean 中。容器实例化、配置和以其他方式组装 prototype 对象并将其交给客户端,不再记录该原型 实例。因此,尽管初始化生命周期回调方法在所有 对象,无论范围如何,在原型的情况下,配置销毁 生命周期回调。客户端代码必须清理 prototype-scoped 对象,并释放原型 bean 所持有的昂贵资源。要获取 Spring 容器来释放原型范围的 bean 持有的资源,请尝试使用 自定义 Bean 后处理器,它包含对 需要清理的豆子。
在某些方面, Spring 容器在原型范围的 bean 中的作用是
替换 Java 运算符。超过该点的所有生命周期管理都必须
由客户端处理。(有关 Spring 中 bean 生命周期的详细信息
容器,请参阅 生命周期回调。new
1.5.3. 具有原型 bean 依赖项的单例 bean
当您使用依赖于原型 bean 的单例作用域 bean 时,请注意 依赖项在实例化时解析。因此,如果你依赖注入一个 原型范围的 bean 转换为单例范围的 bean,则会实例化一个新的原型 bean 然后 dependency-injection 到单例 bean 中。prototype 实例是唯一的 实例。
但是,假设您希望单例范围的 bean 获取 prototype 范围的 bean。您不能依赖注入 prototype 作用域的 bean 添加到你的单例 bean 中,因为该注入只发生 一次,当 Spring 容器实例化单例 bean 并解析 并注入其依赖项。如果您需要原型 bean 的新实例,网址为 运行时,请参阅 Method Injection。
1.5.4. 请求、会话、应用程序和 WebSocket 作用域
、 、 和 范围 仅可用
如果使用 Web 感知的 Spring 实现(例如 )。如果你将这些作用域与常规的 Spring IoC 容器一起使用,
比如 , an 抱怨
关于未知的 bean 范围。request
session
application
websocket
ApplicationContext
XmlWebApplicationContext
ClassPathXmlApplicationContext
IllegalStateException
初始 Web 配置
为了支持在 、 和 级别(Web 范围的 bean)界定 bean 的范围,一些次要的初始配置是
在定义 bean 之前是必需的。(此初始设置不是必需的
对于标准范围:和 。request
session
application
websocket
singleton
prototype
如何完成此初始设置取决于特定的 Servlet 环境。
如果您在 Spring Web MVC 中访问范围限定的 bean,则实际上,在
由 Spring 处理,无需特殊设置。 already 公开所有相关状态。DispatcherServlet
DispatcherServlet
如果使用 Servlet 2.5 Web 容器,并且请求在 Spring 之外处理(例如,使用 JSF 或 Struts 时),则需要注册 .
对于 Servlet 3.0+,这可以通过使用接口以编程方式完成。或者,或者对于较旧的容器,将以下声明添加到
您的 Web 应用程序的文件:DispatcherServlet
org.springframework.web.context.request.RequestContextListener
ServletRequestListener
WebApplicationInitializer
web.xml
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果您的侦听器设置存在问题,请考虑使用 Spring 的 .过滤器映射取决于周围的 Web
application configuration 的 app 配置,因此您必须根据需要对其进行更改。以下清单
显示了 Web 应用程序的 Filter 部分:RequestContextFilter
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet
、 和 all 都完全执行
同样的事情,即将 HTTP 请求对象绑定到正在服务的
那个请求。这使得请求和会话范围的 bean 进一步可用
沿着调用链向下。RequestContextListener
RequestContextFilter
Thread
请求范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring 容器通过对每个 HTTP 请求使用 bean 定义来创建 bean 的新实例。也就是说,Bean 的范围限定在 HTTP 请求级别。您可以将内部的
state (状态),因为其他实例
从同一 bean 定义创建的 state 中看不到这些更改。
它们特定于单个请求。当请求完成处理时,
范围限定为请求的 bean 将被丢弃。LoginAction
loginAction
loginAction
loginAction
当使用注解驱动的组件或 Java 配置时,注解
可用于将组件分配给范围。以下示例显示了如何操作
为此,请执行以下操作:@RequestScope
request
@RequestScope
@Component
public class LoginAction {
// ...
}
@RequestScope
@Component
class LoginAction {
// ...
}
会话范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring 容器在单个 HTTP 的生命周期中使用 bean 定义来创建 bean 的新实例。在其他
words,则 bean 的作用域实际上是在 HTTP 级别。如
使用请求范围的 bean,您可以更改实例的内部状态,即
根据需要创建尽可能多的 HTTP 实例,要知道其他 HTTP 实例也是
使用从同一 bean 定义创建的实例不会看到这些
状态更改,因为它们特定于单个 HTTP 。当
HTTP 最终被丢弃,范围限定为该特定 HTTP 的 bean 也被丢弃。UserPreferences
userPreferences
Session
userPreferences
Session
Session
userPreferences
Session
Session
Session
使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。@SessionScope
session
@SessionScope
@Component
public class UserPreferences {
// ...
}
@SessionScope
@Component
class UserPreferences {
// ...
}
应用范围
对于 Bean 定义,请考虑以下 XML 配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring 容器通过对整个 Web 应用程序使用一次 Bean 定义来创建 Bean 的新实例。也就是说,Bean 的范围限定在 level 上,并存储为 regular 属性。这有点类似于 Spring 单例 bean,但
在两个重要方面有所不同:它是 per , 而不是 per Spring 的单例(在任何给定的 Web 应用程序中都可能有多个),
它实际上是公开的,因此作为一个属性可见。AppPreferences
appPreferences
appPreferences
ServletContext
ServletContext
ServletContext
ApplicationContext
ServletContext
使用注释驱动的组件或 Java 配置时,可以使用注释将组件分配给范围。这
以下示例显示了如何执行此操作:@ApplicationScope
application
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
@ApplicationScope
@Component
class AppPreferences {
// ...
}
作为依赖项的作用域 Bean
Spring IoC 容器不仅管理对象 (bean) 的实例化, 但也包括合作者(或依赖项)的联系。如果要注入 (对于 example) 将一个 HTTP 请求范围的 bean 转换为另一个寿命较长的 bean 中,您可以 选择注入 AOP 代理来代替作用域 Bean。也就是说,您需要注入 一个代理对象,它公开与 Scoped 对象相同的公共接口,但可以 还可以从相关范围(如 HTTP 请求)中检索真正的目标对象 并将方法调用委托到真实对象上。
您还可以在范围为 、
然后,引用通过可序列化的中间代理
因此能够在反序列化时重新获取目标 singleton bean。 当针对 scope 的 bean 进行声明时,每个方法
调用会导致创建一个新的目标实例,该实例的
然后,呼叫将被转发。 此外,作用域代理并不是从较短的作用域访问 bean 的唯一方法
生命周期安全时尚。您还可以声明注入点(即
constructor 或 setter 参数或 autowired 字段)设置为 ,
允许调用以按需检索当前实例
时间 — 无需保留实例或单独存储实例。 作为扩展变体,您可以声明 which deliver
其他几种访问变体,包括 和 . 此的 JSR-330 变体被调用,并与声明和每次检索尝试的相应调用一起使用。
有关 JSR-330 的更多详细信息,请参阅此处。 |
以下示例中的配置只有一行,但对于 了解其背后的 “为什么” 和 “如何”:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/> (1)
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
1 | 定义代理的行。 |
要创建这样的代理,请将子元素插入到作用域
Bean 定义(请参见选择要创建的代理类型和基于 XML 架构的配置)。
为什么 bean 的定义范围在 、 和 custom-scope
级别需要元素吗?
考虑以下单例 bean 定义,并将其与
您需要为上述范围定义什么(请注意,以下 bean 定义并不完整):<aop:scoped-proxy/>
request
session
<aop:scoped-proxy/>
userPreferences
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,单例 bean () 注入了一个引用
添加到 HTTP 范围的 Bean () 中。这里的要点是 bean 是一个单例:它每个
container 及其依赖项(在本例中只有一个 bean)是
也只注射了一次。这意味着 Bean 仅在
完全相同的对象(即最初注入的对象)。userManager
Session
userPreferences
userManager
userPreferences
userManager
userPreferences
当将生存期较短的作用域 bean 注入
生存期较长的作用域 bean(例如,注入 HTTP 作用域的协作
bean 作为单例 bean 的依赖项)。相反,您需要一个对象,并且在 HTTP 的生命周期中,您需要一个对象
特定于 HTTP .因此,容器会创建一个对象,该对象
公开与类完全相同的公共接口(理想情况下是
对象,它是一个实例),它可以从范围机制(HTTP 请求、 等
forth)。容器将此代理对象注入到 bean 中,即
不知道此引用是代理。在此示例中,当实例在依赖项注入的对象上调用方法时,它实际上是在代理上调用方法。然后,代理从 HTTP(在本例中)获取真实对象,并将
方法调用到检索到的 Real Object 上。Session
userManager
Session
userPreferences
Session
UserPreferences
UserPreferences
UserPreferences
Session
userManager
UserPreferences
UserManager
UserPreferences
UserPreferences
Session
UserPreferences
因此,在将 bean 注入协作对象时,您需要以下(正确和完整的)配置,如下例所示
显示:request-
session-scoped
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理类型
默认情况下,当 Spring 容器为标记为
元素,将创建基于 CGLIB 的类代理。<aop:scoped-proxy/>
CGLIB 代理仅拦截公共方法调用!不要调用非公共方法 在这样的代理上。它们不会委托给实际的 Scoped 目标对象。 |
或者,您可以将 Spring 容器配置为创建标准 JDK
基于接口的代理,通过为
元素的属性。使用 JDK
基于接口的代理意味着您的
application classpath 来影响此类代理。但是,这也意味着
作用域 Bean 必须至少实现一个接口,并且所有协作者
作用域 bean 注入到其中,必须通过其
接口。以下示例显示了基于接口的代理:false
proxy-target-class
<aop:scoped-proxy/>
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
有关选择基于类或基于接口的代理的更多详细信息, 请参见代理机制。
1.5.5. 自定义范围
Bean 范围机制是可扩展的。您可以定义自己的
范围,甚至重新定义现有范围,尽管后者被认为是不好的做法
并且你不能覆盖 built-in 和 scopes。singleton
prototype
创建自定义范围
要将自定义范围集成到 Spring 容器中,您需要实现接口,如下所述
部分。有关如何实现自己的范围的想法,请参阅 Spring 框架本身提供的实现和 Scope
javadoc。
其中更详细地解释了您需要实现的方法。org.springframework.beans.factory.config.Scope
Scope
该接口有四种方法从 scope 中获取对象,从
范围,然后销毁它们。Scope
例如,会话范围的实现返回会话范围的 Bean(如果它 不存在,则该方法在将 Bean 绑定到 会话以供将来参考)。以下方法从 底层范围:
Object get(String name, ObjectFactory<?> objectFactory)
fun get(name: String, objectFactory: ObjectFactory<*>): Any
例如,会话范围实现从
基础会话。应该返回该对象,但如果
找不到具有指定名称的对象。以下方法从
底层范围:null
Object remove(String name)
fun remove(name: String): Any
以下方法注册一个回调,范围应在 destroyed 或当 scope 中的指定对象被销毁时:
void registerDestructionCallback(String name, Runnable destructionCallback)
fun registerDestructionCallback(name: String, destructionCallback: Runnable)
有关销毁回调的更多信息,请参见 javadoc 或 Spring 范围实现。
以下方法获取基础范围的聊天标识符:
String getConversationId()
fun getConversationId(): String
此标识符对于每个范围都不同。对于会话范围的实现,此 identifier 可以是会话标识符。
使用自定义范围
在编写和测试一个或多个自定义实现后,您需要将
Spring 容器知道您的新范围。以下方法是中心
向 Spring 容器注册 new 的方法:Scope
Scope
void registerScope(String scopeName, Scope scope);
fun registerScope(scopeName: String, scope: Scope)
此方法在接口上声明,该接口可用
通过 Spring 附带的大多数具体实现上的属性。ConfigurableBeanFactory
BeanFactory
ApplicationContext
该方法的第一个参数是与
一个范围。Spring 容器本身中此类名称的示例是 和 。该方法的第二个参数是实际实例
。registerScope(..)
singleton
prototype
registerScope(..)
Scope
假设您编写了自定义实现,然后按所示注册它
在下一个示例中。Scope
下一个示例使用 Spring 中包含的 ,但不是
registered (默认)。对于您自己的自定义实现,说明是相同的。SimpleThreadScope Scope |
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
val threadScope = SimpleThreadScope()
beanFactory.registerScope("thread", threadScope)
然后,您可以创建符合 custom 的 scopeping rules 的 bean 定义,如下所示:Scope
<bean id="..." class="..." scope="thread">
使用自定义实现,您不仅限于编程注册
的范围。您还可以使用类以声明方式进行注册,如下例所示:Scope
Scope
CustomScopeConfigurer
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
当你放置在实现的声明中时,确定范围的是工厂 Bean 本身,而不是对象
从 返回。<aop:scoped-proxy/> <bean> FactoryBean getObject() |
1.6. 自定义 bean 的性质
Spring Framework 提供了许多接口,您可以使用这些接口来自定义 豆子。本节将它们分组如下:
1.6.1. 生命周期回调
要与容器对 bean 生命周期的 Management 进行交互,您可以实现
Spring 和接口。容器调用前者,而后者则让 bean
在初始化和销毁 bean 时执行某些操作。InitializingBean
DisposableBean
afterPropertiesSet()
destroy()
JSR-250 和注解通常被认为是最好的
在现代 Spring 应用程序中接收生命周期回调的实践。使用这些
annotations 意味着你的 bean 没有耦合到特定于 Spring 的接口。
有关详细信息,请参阅使用 如果您不想使用 JSR-250 注解,但仍希望删除
coupling、consider 和 bean 定义元数据。 |
在内部, Spring 框架使用 implementation 来处理任何
callback 接口,它可以找到并调用适当的方法。如果您需要自定义
功能或其他生命周期行为 Spring 默认不提供,您可以
实施 YOURSELF。有关更多信息,请参阅容器扩展点。BeanPostProcessor
BeanPostProcessor
除了初始化和销毁回调之外, Spring Management 的对象还可以
此外,实现接口,以便这些对象可以参与
启动和关闭过程,由容器自身的生命周期驱动。Lifecycle
生命周期回调接口将在 此部分 中介绍。
初始化回调
该接口允许 bean
在容器在
豆。该接口指定单个方法:org.springframework.beans.factory.InitializingBean
InitializingBean
void afterPropertiesSet() throws Exception;
我们建议您不要使用该接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
@PostConstruct
注解或
指定 POJO 初始化方法。对于基于 XML 的配置元数据,
您可以使用该属性指定具有 void 的方法的名称
no-argument 签名。对于 Java 配置,您可以使用 .请参阅 接收生命周期回调。请考虑以下示例:InitializingBean
init-method
initMethod
@Bean
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
class ExampleBean {
fun init() {
// do some initialization work
}
}
前面的示例与以下示例的效果几乎完全相同 (由两个列表组成):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// do some initialization work
}
}
但是,前面两个示例中的第一个示例并没有将代码耦合到 Spring。
销毁回调
实现该接口后,可以让
bean 在包含它的容器被销毁时获取回调。该接口指定单个方法:org.springframework.beans.factory.DisposableBean
DisposableBean
void destroy() throws Exception;
我们建议您不要使用回调接口,因为它
不必要地将代码耦合到 Spring。或者,我们建议使用
@PreDestroy
注解或
指定 Bean 定义支持的泛型方法。使用基于 XML 的
configuration 元数据中,您可以使用 .
对于 Java 配置,您可以使用 .请参阅 接收生命周期回调。请考虑以下定义:DisposableBean
destroy-method
<bean/>
destroyMethod
@Bean
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
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。
你可以为元素的属性分配一个特殊值,该值指示 Spring 自动检测特定 bean 类上的 public 或 method。(实现或因此匹配的任何类。您还可以设置
this 特殊值应用于此行为,以将此行为应用于整个 bean 集(请参见默认初始化和销毁方法)。请注意,这是
default behavior 与 Java 配置一起使用。destroy-method <bean> (inferred) close shutdown java.lang.AutoCloseable java.io.Closeable (inferred) default-destroy-method <beans> |
默认 Initialization 和 Destroy 方法
当您编写不使用
特定于 Spring 的接口和回调接口,您
通常编写名称为 、 等
上。理想情况下,此类生命周期回调方法的名称在
project 中,以便所有开发人员都使用相同的方法名称并确保一致性。InitializingBean
DisposableBean
init()
initialize()
dispose()
你可以将 Spring 容器配置为“查找”命名初始化和销毁
回调方法名称。这意味着您作为应用程序
开发人员可以编写应用程序类并使用名为 的初始化回调,而不必为每个 bean 配置属性
定义。Spring IoC 容器在创建 Bean 时调用该方法(在
根据前面描述的标准生命周期回调协定)。此功能还对
initialization 和 destroy 方法回调。init()
init-method="init"
假设您的初始化回调方法已命名,并且您的 destroy
回调方法命名为 。然后,您的类类似于
以下示例:init()
destroy()
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// this is (unsurprisingly) the initialization callback method
fun init() {
if (blogDao == null) {
throw IllegalStateException("The [blogDao] property must be set.")
}
}
}
然后,您可以在类似于以下内容的 bean 中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级元素上是否存在该属性
属性使 Spring IoC 容器识别在 Bean 上调用的方法
class 作为初始化方法回调。在创建和组装 bean 时,如果
Bean 类具有这样一个方法,则会在适当的时间调用它。default-init-method
<beans/>
init
您可以通过使用 top-level 元素上的属性以类似方式(即在 XML 中)配置 destroy 方法回调。default-destroy-method
<beans/>
现有 Bean 类已经具有以 Variance 命名的回调方法
使用约定,您可以通过指定(在 XML 中,即)来覆盖默认值
method name 来使用本身的 and 属性。init-method
destroy-method
<bean/>
Spring 容器保证调用配置的初始化回调
紧接着为 bean 提供所有依赖项。因此,初始化
callback 在原始 bean 引用上调用,这意味着 AOP 拦截器等
forth 尚未应用于 Bean。首先完全创建目标 Bean,然后
然后应用 AOP 代理(例如)及其拦截器链。如果目标
bean 和 proxy 是单独定义的,你的代码甚至可以与原始的
target bean,绕过代理。因此,应用
interceptor 添加到该方法中,因为这样做会耦合
将 bean 绑定到其代理或拦截器,并在您的代码
直接与原始目标 Bean 交互。init
组合生命周期机制
从 Spring 2.5 开始,您有三个选项来控制 bean 生命周期行为:
-
InitializingBean
和DisposableBean
回调接口 -
自定义和方法
init()
destroy()
-
@PostConstruct
和@PreDestroy
注释。您可以组合这些机制来控制给定的 bean。
如果为 Bean 配置了多个生命周期机制,并且每个机制都是
配置了不同的方法名称,则每个配置的方法都会在
在此说明之后列出的顺序。但是,如果为这些生命周期机制中的多个配置了相同的方法名称(例如,对于初始化方法),则
该方法运行一次,如上一节所述。init() |
为同一 bean 配置了多个生命周期机制,具有不同的 初始化方法的调用方式如下:
-
注释有
@PostConstruct
-
afterPropertiesSet()
由回调接口定义InitializingBean
-
自定义配置的方法
init()
Destroy 方法的调用顺序相同:
-
注释有
@PreDestroy
-
destroy()
由回调接口定义DisposableBean
-
自定义配置的方法
destroy()
启动和关闭回调
该接口为任何具有自己的
生命周期要求(例如启动和停止某些后台进程):Lifecycle
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring Management 的对象都可以实现该接口。然后,当 本身收到启动和停止信号(例如,对于停止/重启
scenario 的 intent 中),它会将这些调用级联到所有实现
在该上下文中定义。它通过委托给 , 所示
在下面的清单中:Lifecycle
ApplicationContext
Lifecycle
LifecycleProcessor
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,它本身是接口的扩展。它还添加了另外两种方法来响应正在刷新的上下文
并关闭。LifecycleProcessor
Lifecycle
请注意,常规接口是普通的
显式启动和停止通知的协定,并不意味着在上下文中自动启动
刷新时间。为了对特定 bean 的自动启动(包括启动阶段)进行细粒度控制,
请考虑改为实施。 另外,请注意,不保证在销毁之前收到停止通知。
在常规关闭时,所有 bean 首先收到停止通知
正在传播常规销毁回调。但是,在
context 的生命周期或停止的刷新尝试时,仅调用 destroy 方法。 |
启动和关闭调用的顺序可能很重要。如果 “depends-on”
关系存在于任意两个对象之间,则依赖端在其
依赖项,并且它在依赖项之前停止。然而,有时,直接的
依赖项是未知的。您可能只知道特定类型的对象应该启动
在其他类型的对象之前。在这些情况下,接口定义
另一个选项,即在其超级接口 .下面的清单显示了接口的定义:SmartLifecycle
getPhase()
Phased
Phased
public interface Phased {
int getPhase();
}
下面的清单显示了接口的定义:SmartLifecycle
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
开始时,阶段最低的对象首先启动。停止时,
遵循 Reverse Order。因此,实现 和
其方法 returns 将是第一个开始的
也是最后一个停下来的。在频谱的另一端,相位值 表示对象应最后启动并停止
first (可能是因为它依赖于其他进程运行)。在考虑
phase 值,同样重要的是要知道,任何未实现的 “normal” 对象的默认 phase 是 。因此,任何
负相位值表示对象应在这些标准之前开始
组件(并在它们之后停止)。对于任何正相位值,情况正好相反。SmartLifecycle
getPhase()
Integer.MIN_VALUE
Integer.MAX_VALUE
Lifecycle
SmartLifecycle
0
定义的 stop 方法接受回调。任何
实现必须在该实现的
shutdown 过程完成。这将在必要时启用异步关闭,因为
接口的默认实现 , 等待对象组的超时值
在每个阶段中调用该回调。默认的每阶段超时为 30 秒。
您可以通过在上下文中定义名为 的 bean 来覆盖默认生命周期处理器实例。如果只想修改超时,
定义以下内容就足够了:SmartLifecycle
run()
LifecycleProcessor
DefaultLifecycleProcessor
lifecycleProcessor
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,该接口为
刷新和关闭上下文。后者驱动关闭
进程,就好像已经被显式调用一样,但它发生在上下文为
关闭。另一方面,'refresh' 回调启用了 bean 的另一个功能。刷新上下文时(在所有对象都已
instantiated 和 initialized),则会调用该回调。此时,
default lifecycle processor 检查每个对象的方法返回的布尔值。如果 ,则该对象为
启动,而不是等待显式调用上下文的 OR
它自己的方法(与上下文刷新不同,上下文启动不会发生
automatically 用于标准上下文实现)。值和任何
“depends-on” 关系确定 Startup Sequence,如前所述。LifecycleProcessor
stop()
SmartLifecycle
SmartLifecycle
isAutoStartup()
true
start()
phase
在非 Web 应用程序中正常关闭 Spring IoC 容器
本节仅适用于非 Web 应用程序。Spring 基于 Web 的实现已经有代码可以正常关闭
当相关 Web 应用程序关闭时,Spring IoC 容器。 |
如果你在非 Web 应用程序环境中使用 Spring 的 IoC 容器(对于 例如,在富客户端桌面环境中),使用 JVM 的 JVM 中。这样做可以确保正常关闭,并在 singleton bean 的实例,以便释放所有资源。您仍必须配置 并正确实现这些 destroy 回调。
要注册 shutdown 钩子,请调用
在接口上声明,如下例所示:registerShutdownHook()
ConfigurableApplicationContext
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
// add a shutdown hook for the above context...
ctx.registerShutdownHook()
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
1.6.2. 和ApplicationContextAware
BeanNameAware
当 an 创建实现该接口的对象实例时,将提供该实例
并引用该 .下面的清单显示了定义
的界面中:ApplicationContext
org.springframework.context.ApplicationContextAware
ApplicationContext
ApplicationContextAware
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean 可以通过编程方式操作创建它们
通过接口或将引用强制转换为已知的
子类(例如 ,它公开了
附加功能)。一种用途是以编程方式检索其他 bean。
有时此功能很有用。但是,一般来说,您应该避免使用它,因为
它将代码耦合到 Spring,并且不遵循 Inversion of Control 风格,
其中,协作者作为属性提供给 bean。的其他方法提供对文件资源的访问、发布应用程序事件、
并访问 .这些附加功能在 ApplicationContext
的附加功能中进行了描述。ApplicationContext
ApplicationContext
ConfigurableApplicationContext
ApplicationContext
MessageSource
自动装配是获取对 .传统模式和自动装配模式
(如 Autowiring Collaborators 中所述)可以为构造函数参数或 setter 方法参数提供 type 的依赖项,
分别。为了提高灵活性,包括自动装配字段和
多个参数方法,使用基于注释的自动装配功能。如果这样做,
自动连接到字段、构造函数参数或方法
参数,如果字段、构造函数或
有问题的方法带有注释。有关更多信息,请参阅使用 @Autowired
。ApplicationContext
constructor
byType
ApplicationContext
ApplicationContext
ApplicationContext
@Autowired
当 an 创建实现该接口的类时,该类会附带
对其关联对象定义中定义的名称的引用。以下清单
显示了 BeanNameAware 接口的定义:ApplicationContext
org.springframework.beans.factory.BeanNameAware
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
该回调在填充普通 bean 属性之后但在
初始化回调,例如 OR 自定义
init-method 的InitializingBean.afterPropertiesSet()
1.6.3. 其他接口Aware
除了 and (前面讨论过),
Spring 提供了广泛的回调接口,让 bean 向容器指示
它们需要一定的基础设施依赖性。作为一般规则,该名称表示
dependency 类型。下表总结了最重要的接口:ApplicationContextAware
BeanNameAware
Aware
Aware
名字 | 注入的依赖项 | 解释于... |
---|---|---|
|
声明。 |
|
|
封闭 的事件发布者 . |
|
|
用于加载 bean 类的类加载器。 |
|
|
声明。 |
|
|
声明 Bean 的名称。 |
|
|
容器运行的资源适配器。通常仅在
JCA 感知实例。 |
|
|
定义了 weaver,用于在加载时处理类定义。 |
|
|
配置的消息解析策略(支持参数化和 国际化)。 |
|
|
Spring JMX 通知发布者。 |
|
|
配置了 loader ,用于对资源的低级访问。 |
|
|
容器运行的当前情况。仅在 web-aware Spring 中有效。 |
|
|
容器运行的当前情况。仅在 web-aware Spring 中有效。 |
再次注意,使用这些接口会将您的代码绑定到 Spring API,并且不会 遵循 Inversion of Control 样式。因此,我们建议将它们用于基础设施 需要以编程方式访问容器的 bean。
1.7. Bean 定义继承
一个 bean 定义可以包含很多配置信息,包括构造函数 参数、属性值和特定于容器的信息,例如初始化 method、静态工厂方法名称等。子 Bean 定义继承 来自父定义的 configuration 数据。子定义可以覆盖一些 值或根据需要添加其他值。使用父 Bean 定义和子 Bean 定义可以节省很多 的打字。实际上,这是一种模板形式。
如果以编程方式使用接口,则子 Bean
定义由 class 表示。大多数用户不工作
与他们在这个层面上。相反,它们在类中以声明方式配置 bean 定义
例如 .使用基于 XML 的配置时
metadata,您可以使用属性
指定父 Bean 作为此属性的值。以下示例显示了如何操作
为此,请执行以下操作:ApplicationContext
ChildBeanDefinition
ClassPathXmlApplicationContext
parent
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize"> (1)
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
1 | 请注意该属性。parent |
子 Bean 定义使用父定义中的 Bean 类(如果没有 指定,但也可以覆盖它。在后一种情况下,子 Bean 类必须是 与 Parent 兼容(即,它必须接受 Parent的 Property 值)。
子 Bean 定义继承 scope、constructor argument 值、property 值和
method 覆盖父级,并可选择添加新值。任何范围、初始化
您指定的 method、destroy method 或 factory method 设置
覆盖相应的父设置。static
其余设置始终取自子定义:depends on, autowire 模式、依赖关系检查、Singleton 和 Lazy init。
前面的示例通过使用
属性。如果父定义未指定类,则显式
根据需要标记父 Bean 定义,如下例所示
显示:abstract
abstract
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父 Bean 不能单独实例化,因为它不完整,并且是
也明确标记为 .当定义为 时,它是
只能用作纯模板 bean 定义,用作
子定义。尝试单独使用这样的父 Bean,通过引用
作为另一个 bean 的 ref 属性,或者使用
父 Bean ID 返回错误。同样,容器的内部方法会忽略定义为
抽象。abstract
abstract
abstract
getBean()
preInstantiateSingletons()
ApplicationContext 默认情况下,预实例化所有单例。因此,它是
重要的是(至少对于单例 bean),如果你有一个(父)bean 定义
它仅打算用作模板,并且此定义指定了一个类,则
必须确保将 abstract 属性设置为 true,否则应用程序
context 实际上将(尝试)预先实例化 bean。abstract |
1.8. 容器扩展点
通常,应用程序开发人员不需要对实现类进行子类化。相反,Spring IoC 容器可以通过插入来扩展
特殊集成接口的实现。接下来的几节将介绍这些
集成接口。ApplicationContext
1.8.1. 使用BeanPostProcessor
该接口定义了您可以实现的回调方法
提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项
resolution logic,依此类推。如果你想在
Spring 容器完成实例化、配置和初始化 bean 后,您可以
插入一个或多个自定义实现。BeanPostProcessor
BeanPostProcessor
您可以配置多个实例,并且可以控制顺序
其中这些实例通过设置属性
仅当 实现接口时,才能设置此属性。如果您编写自己的 ,您应该考虑实现
界面也是如此。有关更多详细信息,请参阅 BeanPostProcessor
和 Ordered
接口的 javadoc。另见注释
在程序化
BeanPostProcessor
实例的注册。BeanPostProcessor
BeanPostProcessor
order
BeanPostProcessor
Ordered
BeanPostProcessor
Ordered
要更改实际的 Bean 定义(即定义 Bean 的 Blueprint),请执行以下操作:
相反,你需要使用一个 ,如使用 |
该接口包括
恰好是两个回调方法。当此类注册为后处理器时,使用
容器中,对于容器创建的每个 bean 实例,
后处理器在容器之前从容器获取回调
初始化方法(例如 OR
declared method),并在任何 bean 初始化回调之后调用。
后处理器可以对 bean 实例执行任何操作,包括忽略
callback 的 Quin 函数。bean 后处理器通常会检查回调接口,
或者它可能用代理包装 bean。一些 Spring AOP 基础设施类是
作为 Bean 后处理器实现,以提供代理包装逻辑。org.springframework.beans.factory.config.BeanPostProcessor
InitializingBean.afterPropertiesSet()
init
An 会自动检测在
实现接口的配置元数据。将这些 bean 注册为后处理器,以便可以调用它们
稍后,在 bean 创建时。Bean 后处理器可以部署在容器中的
与任何其他Beans一样时尚。ApplicationContext
BeanPostProcessor
ApplicationContext
请注意,当在
configuration 类,则工厂方法的返回类型应为 implementation
类本身或至少是接口,清楚地表明该 bean 的后处理器性质。否则,在完全创建它之前,无法按类型自动检测它。
由于 a 需要提前实例化才能应用于
初始化上下文中的其他 bean,这种早期类型检测至关重要。BeanPostProcessor
@Bean
org.springframework.beans.factory.config.BeanPostProcessor
ApplicationContext
BeanPostProcessor
以编程方式注册实例 虽然推荐的注册方法是通过自动检测(如前所述),但您可以注册它们
使用 该方法以编程方式对 a 进行攻击。当您需要在
注册,甚至用于跨层次结构中的上下文复制 Bean 后处理器。
但请注意,以编程方式添加的实例不遵循
界面。在这里,是注册顺序决定了顺序
的执行。另请注意,实例以编程方式注册
始终在通过自动检测注册的 URL 之前处理,无论
显式排序。BeanPostProcessor BeanPostProcessor ApplicationContext ConfigurableBeanFactory addBeanPostProcessor BeanPostProcessor Ordered BeanPostProcessor |
BeanPostProcessor 实例和 AOP 自动代理实现该接口的类是特殊的,并被视为
容器不同。它们
直接引用在启动时实例化,作为特殊启动阶段的一部分
的 .接下来,注册所有实例
以排序的方式,并应用于容器中的所有其他 bean。因为 AOP
自动代理是作为自身实现的,实例和它们直接引用的 bean 都没有资格进行自动代理,并且,
因此,不要将 aspects 编织到其中。 对于任何此类 Bean,您应该会看到一条信息性日志消息:。 如果你通过使用自动装配或(可能会回退到自动装配)将 bean 连接到你的 bean 中,Spring 可能会访问意外的 bean
在搜索类型匹配的依赖项候选项时,因此,请将其
不符合自动代理或其他类型的 bean 后处理的条件。例如,如果你
具有 Comments 的依赖项,其中字段或 setter 名称没有
直接对应 bean 的声明名称,不使用 name 属性,
Spring 访问其他 bean 以按类型匹配它们。 |
以下示例演示如何编写、注册和使用实例
在 .BeanPostProcessor
ApplicationContext
示例:Hello World、-styleBeanPostProcessor
第一个示例说明了基本用法。该示例显示了一个自定义实现,该实现将每个 bean 的方法作为
它由容器创建,并将生成的字符串打印到系统控制台。BeanPostProcessor
toString()
下面的清单显示了自定义实现类定义:BeanPostProcessor
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
import org.springframework.beans.factory.config.BeanPostProcessor
class InstantiationTracingBeanPostProcessor : BeanPostProcessor {
// simply return the instantiated bean as-is
override fun postProcessBeforeInitialization(bean: Any, beanName: String): Any? {
return bean // we could potentially return any object reference here...
}
override fun postProcessAfterInitialization(bean: Any, beanName: String): Any? {
println("Bean '$beanName' created : $bean")
return bean
}
}
以下元素使用 :beans
InstantiationTracingBeanPostProcessor
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean (messenger) is instantiated, this custom
BeanPostProcessor implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
请注意 the 是如何被定义的。它没有
甚至有一个名称,并且,因为它是一个 bean,所以它可以像任何 bean 一样被依赖注入
其他豆子。(前面的配置还定义了一个由 Groovy 支持的 bean
脚本。Spring 动态语言支持在标题为“动态语言支持”的章节中进行了详细介绍。InstantiationTracingBeanPostProcessor
以下 Java 应用程序运行上述代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("scripting/beans.xml")
val messenger = ctx.getBean<Messenger>("messenger")
println(messenger)
}
上述应用程序的输出类似于以下内容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961 org.springframework.scripting.groovy.GroovyMessenger@272961
1.8.2. 使用BeanFactoryPostProcessor
我们要看的下一个扩展点是 .的语义
此接口类似于 的 ,具有一个 Major
差异:对 Bean 配置元数据进行操作。
也就是说,Spring IoC 容器允许读取
配置元数据,并可能在容器实例化之前对其进行更改
除实例以外的任何 bean。org.springframework.beans.factory.config.BeanFactoryPostProcessor
BeanPostProcessor
BeanFactoryPostProcessor
BeanFactoryPostProcessor
BeanFactoryPostProcessor
您可以配置多个实例,并且可以控制
这些实例通过设置属性来运行。
但是,只有在 实现接口时,才能设置此属性。如果你自己写 ,你应该
也请考虑实现接口。有关更多详细信息,请参见BeanFactoryPostProcessor
和Ordered
接口的javadoc。BeanFactoryPostProcessor
BeanFactoryPostProcessor
order
BeanFactoryPostProcessor
Ordered
BeanFactoryPostProcessor
Ordered
如果要更改实际的 Bean 实例(即创建的对象
),那么你需要改用一个(在前面的使用 此外,实例的范围是按容器划分的。这仅相关
如果您使用容器层次结构。如果您定义 a 合一
container,则它仅适用于该容器中的 bean 定义。Bean 定义
不被另一个容器中的实例进行后处理
容器,即使两个容器属于同一层次结构。 |
当 bean 工厂后处理器在 ,当它在 中声明时,它会自动运行,以便将更改应用于配置元数据
定义容器。Spring 包含许多预定义的 bean factory
后处理器,例如 和 .您还可以使用自定义 — 例如,注册自定义属性编辑器。ApplicationContext
PropertyOverrideConfigurer
PropertySourcesPlaceholderConfigurer
BeanFactoryPostProcessor
An 会自动检测部署到其中的任何 bean
实现接口。它将这些 bean 用作 bean factory
后处理器。您可以将这些后处理器 bean 部署为
你会喜欢任何其他豆子。ApplicationContext
BeanFactoryPostProcessor
与 s 一样,您通常不希望为 s 延迟初始化配置 s。如果没有其他 bean 引用 a ,则该后处理器将根本不被实例化。
因此,将其标记为延迟初始化将被忽略,并且即使您在元素的声明上将属性设置为 ,也会急切地实例化 。BeanPostProcessor BeanFactoryPostProcessor Bean(Factory)PostProcessor Bean(Factory)PostProcessor default-lazy-init true <beans /> |
示例:类名替换PropertySourcesPlaceholderConfigurer
您可以使用 to externalize 属性值
使用标准 Java 格式从单独文件中的 bean 定义。
这样做使部署应用程序的人员能够自定义特定于环境的
属性(例如数据库 URL 和密码),而不会产生
修改容器的一个或多个主 XML 定义文件。PropertySourcesPlaceholderConfigurer
Properties
请考虑以下基于 XML 的配置元数据片段,其中定义了 a with placeholder 值:DataSource
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
该示例显示了从外部文件配置的属性。在运行时,
a 应用于元数据,该元数据将替换某些
属性。要替换的值指定为
form ,它遵循 Ant 和 log4j 以及 JSP EL 样式。Properties
PropertySourcesPlaceholderConfigurer
${property-name}
实际值来自标准 Java 格式的另一个文件:Properties
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
因此,字符串在运行时被替换为值 'sa' 和
这同样适用于与 Properties 文件中的键匹配的其他占位符值。
检查大多数属性中的占位符,以及
bean 定义的属性。此外,您还可以自定义占位符前缀和后缀。${jdbc.username}
PropertySourcesPlaceholderConfigurer
使用 Spring 2.5 中引入的名称空间,您可以配置属性占位符
替换为专用的配置元素。您可以将一个或多个位置作为
逗号分隔的列表,如下例所示:context
location
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
不仅在指定的文件中查找属性。默认情况下,如果在指定的属性文件中找不到属性,则
它检查 Spring 属性和常规 Java 属性。PropertySourcesPlaceholderConfigurer
Properties
Environment
System
您可以使用 to 替换类名,该类名
当您必须在运行时选择特定的实现类时,有时很有用。
以下示例显示了如何执行此操作:
如果该类在运行时无法解析为有效类,则 Bean 的解析
在即将创建时失败,这是在非惰性初始化 bean 的阶段。 |
示例:PropertyOverrideConfigurer
的 ,另一个 bean factory 后处理器类似于 ,但与后者不同的是,原始定义
可以为 Bean 属性设置默认值或根本没有值。如果覆盖文件没有某个 Bean 属性的条目,则默认的
上下文定义。PropertyOverrideConfigurer
PropertySourcesPlaceholderConfigurer
Properties
请注意,bean 定义不知道被覆盖,因此它不是
从 XML 定义文件中可以立即明显看出 override configurer 正在
使用。如果多个实例定义了不同的
值,由于覆盖机制,最后一个 Bean 属性优先。PropertyOverrideConfigurer
Properties 文件配置行采用以下格式:
beanName.property=value
下面的清单显示了格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
此示例文件可以与容器定义一起使用,该容器定义包含名为 has 和 properties 的 bean。dataSource
driver
url
还支持复合属性名称,只要路径的每个组件
除了被覆盖的最终属性已经是非 null 的(大概是初始化的
由构造函数)。在以下示例中,bean 的 property 的 property 的
设置为 scalar 值 :sammy
bob
fred
tom
123
tom.fred.bob.sammy=123
指定的覆盖值始终是文本值。它们不会被翻译成 bean 引用。当 XML Bean 中的原始值 definition 指定 bean 引用。 |
使用 Spring 2.5 中引入的名称空间,可以配置
属性替换为专用的 configuration 元素,如下例所示:context
<context:property-override location="classpath:override.properties"/>
1.8.3. 使用FactoryBean
您可以为
本身就是工厂。org.springframework.beans.factory.FactoryBean
该接口是 Spring IoC 容器的
实例化逻辑。如果你有复杂的初始化代码,最好用
Java 而不是(可能)冗长的 XML,您可以创建自己的 ,在该类中编写复杂的初始化,然后将
custom 添加到容器中。FactoryBean
FactoryBean
FactoryBean
该接口提供了三种方法:FactoryBean<T>
-
T getObject()
:返回此工厂创建的对象的实例。这 实例可以共享,具体取决于此工厂是否返回单例 或原型。 -
boolean isSingleton()
: 如果返回单例或其他,则返回。此方法的默认实现返回 。true
FactoryBean
false
true
-
Class<?> getObjectType()
:返回方法返回的对象类型 或者如果事先不知道类型。getObject()
null
概念和接口在 Spring 中的许多地方使用
框架。Spring 附带了 50 多个接口实现
本身。FactoryBean
FactoryBean
当您需要向容器请求实际实例本身而不是
它生成的 bean 在 bean 前面加上 & 符号 () 时
调用 .因此,对于具有 of 的给定,在容器上调用将返回
product 的 ,而调用则返回实例本身。FactoryBean
id
&
getBean()
ApplicationContext
FactoryBean
id
myBean
getBean("myBean")
FactoryBean
getBean("&myBean")
FactoryBean
1.9. 基于注解的容器配置
XML 设置的替代方案由基于 annotation 的配置提供,它依赖于
用于连接组件的字节码元数据,而不是尖括号声明。
开发人员没有使用 XML 来描述 Bean 连接,而是移动了配置
到组件类本身中,方法是在相关的类、方法或
field 声明。如示例中所述:AutowiredAnnotationBeanPostProcessor
,使用
a 与注解结合使用是扩展
Spring IoC 容器。例如,Spring 2.0 引入了强制执行
required 属性替换为 @Required
注解。Spring
2.5 使得遵循相同的通用方法来驱动 Spring 的依赖项成为可能
注射。从本质上讲,注解提供了与
在 Autowiring Collaborators 中描述,但具有更精细的控制和更广泛的
适用性。Spring 2.5 还添加了对 JSR-250 注释的支持,例如 和 。Spring 3.0 增加了对 JSR-330(依赖性
Injection for Java) 注释(如 和 .有关这些注释的详细信息,请参阅相关部分。BeanPostProcessor
@Autowired
@PostConstruct
@PreDestroy
javax.inject
@Inject
@Named
注释注入在 XML 注入之前执行。因此,XML 配置 覆盖通过这两种方法连接的属性的注释。 |
与往常一样,您可以将后处理器注册为单独的 bean 定义,但是它们
也可以通过在基于 XML 的 Spring 中包含以下标记来隐式注册
configuration(注意包含命名空间):context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
该元素隐式注册以下后处理器:<context:annotation-config/>
|
1.9.1. @Required
该注释适用于 Bean 属性 setter 方法,如下所示
例:@Required
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Required
lateinit var movieFinder: MovieFinder
// ...
}
此注释指示受影响的 Bean 属性必须在
配置时间,通过 Bean 定义中的显式属性值或通过
autowiring 的如果受影响的 Bean 属性尚未
填充。这允许 Aged 和 explicit 失败,避免以后出现实例或类似情况。我们仍然建议您将断言放入
Bean 类本身(例如,放入 init 方法中)。这样做会强制执行那些必需的
引用和值,即使您在容器外部使用类也是如此。NullPointerException
|
注释 和 are 正式
从 Spring Framework 5.1 开始弃用,取而代之的是 使用 constructor injection for
必需的设置(或者自定义实现或自定义方法以及 Bean 属性 setter 方法)。 |
1.9.2. 使用@Autowired
JSR 330 的注解可以代替 Spring 的注解。
本节中包含的示例。有关更多详细信息,请参阅此处。 |
您可以将注释应用于构造函数,如下例所示:@Autowired
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao)
从 Spring Framework 4.3 开始,这种构造函数上的 Comments 不再是
如果目标 Bean 一开始只定义了一个构造函数,则为 necessary。但是,如果
有几个构造函数可用,至少没有主/默认构造函数
必须对其中一个构造函数进行注释,才能指示
container 使用哪一个。有关详细信息,请参阅有关构造函数解析的讨论。 |
您还可以将 Comments 应用于传统的 setter 方法,
如下例所示:@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
// ...
}
确保您的目标组件(例如,或 )
始终由您用于 -annotated 的类型声明
注射点。否则,注入可能会因运行时的 “no type match found” 错误而失败。 对于通过 Classpath 扫描找到的 XML 定义的 bean 或组件类,容器
通常预先知道混凝土类型。但是,对于工厂方法,您需要
以确保 Declared 的 return 类型具有足够的表现力。对于组件
实现多个接口,或者对于可能由其
implementation 类型,请考虑在 Factory 上声明最具体的返回类型
方法(至少与引用 bean 的注入点所要求一样具体)。 |
你还可以通过将 Comments 添加到字段或方法中来指示 Spring 提供特定类型的所有 bean,该字段或方法如下:
需要该类型的数组,如下例所示:ApplicationContext
@Autowired
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalogs: Array<MovieCatalog>
// ...
}
这同样适用于类型化集合,如下例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Set<MovieCatalog>
// ...
}
您的目标 bean 可以实现接口或使用
的 OR 标准注解(如果您希望数组或列表中的项目)
以按特定顺序排序。否则,他们的顺序将遵循注册
容器中相应目标 bean 定义的顺序。 您可以在目标类级别和方法上声明注释。
可能对于单个 bean 定义(在多个定义的情况下,
使用相同的 bean 类)。 值可能会影响注入点的优先级,
但请注意,它们不会影响单例启动顺序,即
由依赖关系和声明确定的正交关注点。 请注意,标准注释在 level 上不可用,因为它不能在方法上声明。它的语义可以建模
through 值与 on each type 的单个 bean 的组合。 |
只要预期的键类型是 ,即使是类型化实例也可以自动装配。
map 值包含预期类型的所有 bean,键包含
相应的 bean 名称,如下例所示:Map
String
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Map<String, MovieCatalog>
// ...
}
默认情况下,当给定的 bean 没有匹配的候选 bean 可用时,自动装配将失败 注射点。对于声明的数组、集合或 map,至少有一个 匹配元素。
默认行为是将带注释的方法和字段视为指示必需的
依赖。您可以更改此行为,如以下示例所示,
使框架能够跳过无法满足的注入点,方法是将其标记为
非必需的(即,通过将属性设置为 ):required
@Autowired
false
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
class SimpleMovieLister {
@Autowired(required = false)
var movieFinder: MovieFinder? = null
// ...
}
如果非必需方法的依赖项(或其 dependencies)不可用。非必填字段将 在这种情况下,根本不填充,保留其默认值。
注入的构造函数和工厂方法参数是一种特殊情况,因为由于 Spring 的构造函数,属性 in 的含义略有不同
Resolution 算法,该算法可能会处理多个构造函数。构造 函数
和工厂方法参数在默认情况下实际上是必需的,但有一些特殊的
单构造函数方案中的规则,例如多元素注入点(数组、
collections, map) 解析为空实例。这
允许使用通用的实现模式,其中所有依赖项都可以在
unique 多参数构造函数 — 例如,声明为单个公共构造函数
没有注释。required
@Autowired
@Autowired
任何给定 bean 类只有一个构造函数可以声明属性设置为 ,指示构造函数在用作 Spring 时要自动装配
豆。因此,如果该属性保留为其默认值,
只有一个构造函数可以用 .如果多个构造函数
declare 注解,它们都必须声明才能
被视为 autowiring 的候选者(类似于 XML 中)。
通过匹配可以满足的依赖项数量最多的构造函数
将选择 Spring 容器中的 bean。如果所有候选人都不能满足,
然后将使用 primary/default 构造函数(如果存在)。同样,如果类
声明了多个构造函数,但没有一个用 进行注释,那么
primary/default 构造函数(如果存在)。如果一个类只声明一个
constructor 开头,它将始终被使用,即使没有注解。请注意,
带注释的构造函数不必是 public。 在 setter 方法上,建议使用 of 属性而不是已弃用的 Comments。将属性设置为表示
该属性对于自动装配不是必需的,如果该属性
不能自动装配。,则更强大,因为它强制执行
属性,如果未定义值,则
引发相应的异常。 |
或者,您可以表示特定依赖项的非必需性质
通过 Java 8 的 ,如下例所示:java.util.Optional
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从 Spring Framework 5.0 开始,您还可以使用 Comments(任何类型的
在任何包中 — 例如,来自 JSR-305)或仅利用
Kotlin 内置 null-safety 支持:@Nullable
javax.annotation.Nullable
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
class SimpleMovieLister {
@Autowired
var movieFinder: MovieFinder? = null
// ...
}
您还可以用于众所周知的 resolvable 接口
依赖项:、、、、 和 .这些接口及其扩展
接口(如 或 )是
自动解析,无需特殊设置。以下示例 autowires
一个对象:@Autowired
BeanFactory
ApplicationContext
Environment
ResourceLoader
ApplicationEventPublisher
MessageSource
ConfigurableApplicationContext
ResourcePatternResolver
ApplicationContext
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
class MovieRecommender {
@Autowired
lateinit var context: ApplicationContext
// ...
}
, , , 和 Comments 由 Spring 实现处理。这意味着您无法应用这些注释
在您自己的 OR 类型(如果有)中。
这些类型必须使用 XML 或 Spring 方法显式地“连接”。 |
1.9.3. 微调基于 Annotation 的自动装配@Primary
因为按类型自动装配可能会导致多个候选者,所以通常需要
对选择过程的更多控制。实现此目的的一种方法是使用 Spring 的 Comments。 指示应给定一个特定的 bean
当多个 bean 是自动连接到单值的候选者时,首选项
Dependency。如果候选者中正好存在一个主 bean,则它将成为
autowired 值。@Primary
@Primary
考虑以下配置为
主要:firstMovieCatalog
MovieCatalog
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
@Configuration
class MovieConfiguration {
@Bean
@Primary
fun firstMovieCatalog(): MovieCatalog { ... }
@Bean
fun secondMovieCatalog(): MovieCatalog { ... }
// ...
}
在前面的配置中,以下内容使用 :MovieRecommender
firstMovieCatalog
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
相应的 bean 定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 使用限定符微调基于 Annotation 的自动装配
@Primary
是在多个实例中使用 autowiring by type 的有效方法,当一个实例
可以确定主要候选人。当您需要对选择过程进行更多控制时,
你可以使用 Spring 的 Comments。您可以关联限定符值
使用特定参数时,缩小类型匹配的集合,以便特定的 bean 为
为每个参数选择。在最简单的情况下,这可以是一个简单的描述性值,如
如以下示例所示:@Qualifier
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
您还可以在单个构造函数参数上指定注释,或者
method 参数,如以下示例所示:@Qualifier
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
以下示例显示了相应的 Bean 定义。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/> (2)
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1 | 具有 qualifier 值的 bean 与 constructor 参数连接,该 constructor 参数
使用相同的值进行限定。main |
2 | 具有 qualifier 值的 bean 与 constructor 参数连接,该 constructor 参数
使用相同的值进行限定。action |
对于回退匹配,Bean 名称被视为默认限定符值。因此,您
可以使用 of 而不是嵌套的限定符元素来定义 bean,将
更改为相同的匹配结果。但是,尽管您可以使用此约定来引用
特定的 bean 从根本上讲是关于类型驱动的注入的
可选的语义限定符。这意味着限定符值,即使 bean 名称为
fallback 在类型匹配集中始终具有缩小语义。他们没有
在语义上表示对唯一 bean 的引用。好的限定符值为 or 或 ,表示特定组件的特征,这些
独立于 bean ,在匿名 bean 的情况下可以自动生成
定义,例如前面示例中的定义。id
main
@Autowired
id
main
EMEA
persistent
id
如前所述,限定符也适用于类型化集合 — 例如,to .在这种情况下,所有匹配的 bean 都会根据声明的
限定符作为集合注入。这意味着限定符不必是
独特。相反,它们构成了筛选标准。例如,您可以定义
具有相同限定符值 “action” 的多个 bean,所有这些 bean 都是
注入到带有 .Set<MovieCatalog>
MovieCatalog
Set<MovieCatalog>
@Qualifier("action")
在类型匹配中,让限定符值根据目标 Bean 名称进行选择
candidates)不需要在注入点进行注释。
如果没有其他分辨率指示符(例如限定符或主标记),
对于非唯一依赖项情况, Spring 匹配注入点名称
(即字段名称或参数名称),并选择
同名候选人(如果有)。 |
也就是说,如果您打算按名称表示注解驱动的注入,请不要
主要使用 ,即使它能够在其中按 bean 名称进行选择
类型匹配的候选项。相反,请使用 JSR-250 注解,即
语义上定义以通过其唯一名称标识特定的目标组件,其中
声明的类型与匹配过程无关。 有
不同的语义:按类型选择候选 bean 后,仅在那些类型选定的候选 bean 中考虑指定的限定符值(例如,
将限定符与标有相同限定符标签的 bean 匹配)。@Autowired
@Resource
@Autowired
String
account
对于本身定义为集合、 、 或数组类型的 bean ,是一个很好的解决方案,通过唯一名称引用特定的集合或数组 bean。
也就是说,从 4.3 开始,你也可以通过 Spring 的类型匹配算法来匹配 collection、 和 array 类型,只要元素类型信息是
保留在返回类型签名或集合继承层次结构中。
在这种情况下,你可以使用限定符值在相同类型的集合中进行选择。
如上一段所述。Map
@Resource
Map
@Autowired
@Bean
从 4.3 开始,还会考虑用于注入的自引用(即引用
返回到当前注入的 bean)。请注意,自注入是一种后备。
对其他组件的常规依赖项始终具有优先权。从这个意义上说,自我
参考文献不参与常规的候选选择,因此位于
特别 从 不 主要。相反,它们总是以最低优先级结束。
在实践中,您应该仅将自引用作为最后的手段(例如,对于
通过 Bean 的事务代理调用同一实例上的其他方法)。
在这种情况下,请考虑将受影响的方法分解为单独的委托 Bean。
或者,您可以使用 ,这可能会获得返回到当前 bean 的代理
通过其唯一名称。@Autowired
@Resource
尝试从同一配置类上的方法注入结果是
实际上也是一种自引用场景。要么延迟解析此类引用
在实际需要它的方法签名中(而不是 autowired 字段
)或将受影响的方法声明为 ,
将它们与包含的 Configuration 类实例及其生命周期分离。
否则,仅在回退阶段考虑此类 bean,并具有匹配的 bean
在选择为主要候选项的其他配置类上(如果可用)。 |
@Autowired
应用于字段、构造函数和多参数方法,允许
通过参数级别的限定符注释缩小范围。相反,仅支持具有单个参数的字段和 bean 属性 setter 方法。
因此,如果您的注入目标是
constructor 或多参数方法。@Resource
您可以创建自己的自定义限定符注释。为此,请定义一个 annotation 和
在定义中提供注释,如下例所示:@Qualifier
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
然后,您可以在自动装配的字段和参数上提供自定义限定符,如 以下示例显示:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
class MovieRecommender {
@Autowired
@Genre("Action")
private lateinit var actionCatalog: MovieCatalog
private lateinit var comedyCatalog: MovieCatalog
@Autowired
fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
this.comedyCatalog = comedyCatalog
}
// ...
}
接下来,您可以提供候选 Bean 定义的信息。您可以将标记添加为标记的子元素,然后指定 and 以匹配您的自定义限定符注释。该类型与
注解的完全限定类名。或者,为了方便,如果没有
存在冲突的名称,则可以使用短类名。以下示例
演示了这两种方法:<qualifier/>
<bean/>
type
value
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="Genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="example.Genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在 Classpath Scanning 和 Managed Components 中,您可以看到 在 XML 中提供限定符元数据。具体而言,请参阅提供带有注释的限定符元数据。
在某些情况下,使用没有值的 Comments 可能就足够了。这可以是 当注释用于更通用的用途并且可以应用于 几种不同类型的依赖项。例如,您可以提供离线 目录,当没有 Internet 连接可用时可以搜索。首先,定义 简单注释,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
然后将注释添加到要自动装配的字段或属性中,如 以下示例:
public class MovieRecommender {
@Autowired
@Offline (1)
private MovieCatalog offlineCatalog;
// ...
}
1 | 此行添加注释。@Offline |
class MovieRecommender {
@Autowired
@Offline (1)
private lateinit var offlineCatalog: MovieCatalog
// ...
}
1 | 此行添加注释。@Offline |
现在 bean 定义只需要一个 qualifier ,如以下示例所示:type
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> (1)
<!-- inject any dependencies required by this bean -->
</bean>
1 | 此元素指定限定符。 |
您还可以定义自定义限定符注释,这些注释接受
addition 或 instead to the simple 属性。如果多个属性值为
然后在要自动装配的字段或参数上指定,则 Bean 定义必须匹配
所有此类属性值都被视为 Autowire 候选项。例如,
请考虑以下注释定义:value
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
在本例中是一个枚举,定义如下:Format
public enum Format {
VHS, DVD, BLURAY
}
enum class Format {
VHS, DVD, BLURAY
}
要自动装配的字段使用 custom 限定符进行批注,并包含值
对于这两个属性:和 ,如下例所示:genre
format
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
class MovieRecommender {
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Action")
private lateinit var actionVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.VHS, genre = "Comedy")
private lateinit var comedyVhsCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.DVD, genre = "Action")
private lateinit var actionDvdCatalog: MovieCatalog
@Autowired
@MovieQualifier(format = Format.BLURAY, genre = "Comedy")
private lateinit var comedyBluRayCatalog: MovieCatalog
// ...
}
最后,bean 定义应包含匹配的限定符值。这个例子
还演示了您可以使用 Bean 元属性而不是元素。如果可用,则元素及其属性采用
优先级,但是如果不存在这样的限定符,则自动装配机制会回退到标记中提供的值,就像
以下示例:<qualifier/>
<qualifier/>
<meta/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Action"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier type="MovieQualifier">
<attribute key="format" value="VHS"/>
<attribute key="genre" value="Comedy"/>
</qualifier>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="DVD"/>
<meta key="genre" value="Action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<meta key="format" value="BLURAY"/>
<meta key="genre" value="Comedy"/>
<!-- inject any dependencies required by this bean -->
</bean>
</beans>
1.9.5. 使用泛型作为自动装配限定符
除了 Comments 之外,您还可以使用 Java 泛型类型
作为限定的隐含形式。例如,假设您有以下
配置:@Qualifier
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Configuration
class MyConfiguration {
@Bean
fun stringStore() = StringStore()
@Bean
fun integerStore() = IntegerStore()
}
假设前面的 bean 实现了一个泛型接口(即 and),你可以将接口和泛型接口
用作限定符,如下例所示:Store<String>
Store<Integer>
@Autowire
Store
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean
@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean
泛型限定符也适用于自动装配列表、实例和数组。这
以下示例自动装配一个类属 :Map
List
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>
1.9.6. 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一个允许您注册自己的自定义限定符
Comments 类型,即使它们没有使用 Spring 的 Comments 进行 Comments。
以下示例演示如何使用:BeanFactoryPostProcessor
@Qualifier
CustomAutowireConfigurer
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
通过以下方式确定 autowire 候选项:AutowireCandidateResolver
-
每个 bean 定义的值
autowire-candidate
-
元素上可用的任何模式
default-autowire-candidates
<beans/>
-
存在注释和注册的任何自定义注释 使用
@Qualifier
CustomAutowireConfigurer
当多个 bean 符合自动装配候选者的条件时,“主要”的确定是
如下所示:如果候选者中只有一个 bean 定义将属性设置为 ,则将其选中。primary
true
1.9.7. 使用@Resource
Spring 还通过使用 JSR-250 注解来支持注入
() 在 fields 或 bean 属性 setter 方法上。
这是 Java EE 中的一种常见模式:例如,在 JSF 管理的 bean 和 JAX-WS 中
端点。Spring 也支持 Spring Management 的对象使用这种模式。@Resource
javax.annotation.Resource
@Resource
接受 name 属性。默认情况下,Spring 将该值解释为
要注入的 bean 名称。换句话说,它遵循 by-name 语义,
如以下示例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") (1)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
1 | 此行注入 .@Resource |
class SimpleMovieLister {
@Resource(name="myMovieFinder") (1)
private lateinit var movieFinder:MovieFinder
}
1 | 此行注入 .@Resource |
如果未明确指定名称,则默认名称派生自字段名称或
setter 方法。如果是字段,则采用字段名称。对于 setter 方法,
它采用 Bean 属性 name。以下示例将具有 bean
named 注入到其 setter 方法中:movieFinder
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
class SimpleMovieLister {
@Resource
private lateinit var movieFinder: MovieFinder
}
随 Comments 提供的名称被解析为 bean 名称,由该名称 知道。
如果你显式地配置了 Spring 的 SimpleJndiBeanFactory ,那么可以通过 JNDI 解析这些名称。但是,我们建议您依赖默认行为和
使用 Spring 的 JNDI 查找功能来保持间接级别。ApplicationContext CommonAnnotationBeanPostProcessor |
在未指定显式名称的 usage 和类似情况下
to 查找主要类型匹配项,而不是特定的命名 bean
并解析众所周知的可解析依赖项:、、、 和 接口。@Resource
@Autowired
@Resource
BeanFactory
ApplicationContext
ResourceLoader
ApplicationEventPublisher
MessageSource
因此,在下面的示例中,该字段首先查找 bean
命名为 “customerPreferenceDao”,然后回退到该类型的主要类型匹配项:customerPreferenceDao
CustomerPreferenceDao
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; (1)
public MovieRecommender() {
}
// ...
}
1 | 该字段根据已知的可解析依赖项类型注入: 。context ApplicationContext |
class MovieRecommender {
@Resource
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Resource
private lateinit var context: ApplicationContext (1)
// ...
}
1 | 该字段根据已知的可解析依赖项类型注入: 。context ApplicationContext |
1.9.8. 使用@Value
@Value
通常用于注入外部化属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig
以及以下文件:application.properties
catalog.name=MovieCatalog
在这种情况下,parameter 和 field 将等于该值。catalog
MovieCatalog
Spring 提供了默认的宽松嵌入值解析器。它将尝试解析
属性值,如果无法解析,则为属性名称(例如 )
将作为值注入。如果您想对不存在的
值,您应该声明一个 Bean,如下所示
示例显示:${catalog.name}
PropertySourcesPlaceholderConfigurer
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Configuration
class AppConfig {
@Bean
fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
使用 JavaConfig 配置 时,方法必须为 .PropertySourcesPlaceholderConfigurer @Bean static |
如果无法解析任何占位符,则使用上述配置可确保 Spring 初始化失败。也可以使用 , 等方法或进行自定义
占位符。${}
setPlaceholderPrefix
setPlaceholderSuffix
setValueSeparator
Spring Boot 默认配置一个 bean,该 bean
将从 和 文件中获取属性。PropertySourcesPlaceholderConfigurer application.properties application.yml |
Spring 提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为或)。多个逗号分隔值可以是
无需额外操作即可自动转换为 Array。Integer
int
String
可以按如下方式提供默认值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
Spring 在后台使用
process 将值转换为 target 类型。如果您想
为您自己的自定义类型提供转换支持,您可以提供自己的 bean 实例,如下例所示:BeanPostProcessor
ConversionService
String
@Value
ConversionService
@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())
}
}
}
当包含 SPEL
表达式时,该值将是动态的
在运行时计算,如下例所示:@Value
@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
不仅识别 annotation
以及 JSR-250 生命周期注释:和 .在 Spring 2.5 中引入,对这些
annotations 提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方案。前提是 在 Spring 中注册,
带有这些 Comments 之一的方法在生命周期的同一点被调用
作为相应的 Spring 生命周期接口方法或显式声明的回调
方法。在以下示例中,缓存在初始化时预先填充,并且
销毁时清除:CommonAnnotationBeanPostProcessor
@Resource
javax.annotation.PostConstruct
javax.annotation.PreDestroy
CommonAnnotationBeanPostProcessor
ApplicationContext
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...
}
}
有关组合各种生命周期机制的效果的详细信息,请参阅组合生命周期机制。
与 一样,和 注释类型是
的标准 Java 库从 JDK 6 到 8。但是,整个包与 JDK 9 中的核心 Java 模块分离,并最终在
JDK 11 的。如果需要,需要通过 Maven 获取工件
Central 现在,只需像任何其他库一样添加到应用程序的 Classpath 中即可。 |
1.10. Classpath 扫描和托管组件
本章中的大多数示例都使用 XML 来指定生成
每个都包含在 Spring 容器中。上一节
(基于注释的容器配置)演示了如何提供大量配置
元数据。然而,即使在这些例子中,“基础”
Bean 定义在 XML 文件中显式定义,而 Comments 仅驱动
依赖项注入。本节介绍用于隐式检测
candidate 组件。候选组件是
匹配过滤条件,并在
容器。这样就不需要使用 XML 来执行 Bean 注册。相反,您
可以使用注解(例如)、AspectJ 类型表达式或您自己的
自定义过滤条件,用于选择哪些类注册了 Bean 定义
容器。BeanDefinition
@Component
从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能包括
核心 Spring Framework 的一部分。这允许您使用 Java 而不是 Java 定义 bean
而不是使用传统的 XML 文件。查看 、 、 和 注释 ,了解如何使用这些新功能的示例。 |
1.10.1. 和进一步的 Stereotype 注解@Component
注解是满足角色或
存储库的构造型(也称为数据访问对象或 DAO)。用途
的标记是异常的自动转换,如 异常转换中所述。@Repository
Spring 提供了进一步的构造型 Comments: , , , 和 。 是任何 Spring Management 组件的通用构造型。、 和 是 的特化
更具体的使用案例(在 Persistence、Service 和 Presentation 中
层)。因此,您可以使用 , 来注释组件类,但是,通过使用 , , 来注释它们,或者,您的类更适合通过工具或关联进行处理
与方面。例如,这些原型注释是
切入点。、 和 也可以
在 Spring Framework 的未来版本中携带其他语义。因此,如果你是
在使用 或 for your service layer 之间进行选择是
显然是更好的选择。同样,如前所述,已经
支持作为持久层中自动异常转换的标记。@Component
@Service
@Controller
@Component
@Repository
@Service
@Controller
@Component
@Component
@Repository
@Service
@Controller
@Repository
@Service
@Controller
@Component
@Service
@Service
@Repository
1.10.2. 使用元注解和组合注解
Spring 提供的许多 Comments 都可以用作
自己的代码。元注释是可以应用于另一个注释的注释。
例如,前面提到的 Comments 使用 进行元注释,如下例所示:@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 |
您还可以组合元注释来创建 “组合注释”。例如
Spring MVC 的 Comments 由 和 组成。@RestController
@Controller
@ResponseBody
此外,组合注释可以选择从
meta-annotations 允许自定义。当您
希望仅公开 meta-annotation 属性的子集。例如,Spring 的注解将范围名称硬编码为,但仍允许
的自定义 .下面的清单显示了 annotation 的定义:@SessionScope
session
proxyMode
SessionScope
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
@get:AliasFor(annotation = Scope::class)
val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
然后,无需声明 the 即可使用,如下所示:@SessionScope
proxyMode
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope
class SessionScopedService {
// ...
}
您还可以覆盖 的值,如下例所示:proxyMode
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
有关更多详细信息,请参阅 Spring Annotation Programming Model wiki 页面。
1.10.3. 自动检测类并注册 bean 定义
Spring 可以自动检测构造型类并使用 .例如,以下两个类
符合此类自动检测的条件:BeanDefinition
ApplicationContext
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的 bean,您需要在类中添加其中的
是这两个类的公共父包。(或者,您可以指定
包含每个类的父包的逗号或分号或空格分隔的列表。@ComponentScan
@Configuration
basePackages
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
为简洁起见,前面的示例可以使用
注释(即 )。value @ComponentScan("org.example") |
以下替代方法使用 XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
使用 隐式启用 的功能。使用 .<context:component-scan> <context:annotation-config> <context:annotation-config> <context:component-scan> |
扫描 classpath 包需要存在相应的目录 条目。使用 Ant 构建 JAR 时,请确保不要 激活 JAR 任务的 files-only 开关。此外,类路径目录可能不是 在某些环境中根据安全策略公开,例如,独立应用程序 JDK 1.7.0_45 及更高版本(需要在清单中设置“Trusted-Library”——请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。
但是,请确保在 Descriptors 中导出组件类。如果你希望 Spring 调用类的非公共成员,请将
确保它们已“打开”(即,它们在描述符中使用声明而不是声明)。 |
此外,当您使用
component-scan 元素。这意味着这两个组件是自动检测的,并且
连接在一起 — 所有这些都没有以 XML 形式提供任何 bean 配置元数据。AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
您可以通过添加
的值为 .AutowiredAnnotationBeanPostProcessor CommonAnnotationBeanPostProcessor annotation-config false |
1.10.4. 使用过滤器自定义扫描
默认情况下,使用 、 、 、 或 本身使用 Comments 的自定义 Comments 的类是
唯一检测到的候选组件。但是,您可以修改和扩展此行为
通过应用自定义筛选器。将它们添加为 或 属性
注释(或 AS 或元素的子元素
XML 配置)。每个 filter 元素都需要 and 属性。
下表描述了筛选选项:@Component
@Repository
@Service
@Controller
@Configuration
@Component
includeFilters
excludeFilters
@ComponentScan
<context:include-filter />
<context:exclude-filter />
<context:component-scan>
type
expression
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
annotation (默认) |
|
目标组件中类型级别存在或元存在的 Annotation。 |
可分配的 |
|
目标组件可分配给 (扩展或实现) 的类 (或接口) 。 |
AspectJ |
|
要由目标组件匹配的 AspectJ 类型表达式。 |
正则表达式 |
|
要与目标组件的类名称匹配的正则表达式。 |
习惯 |
|
接口的自定义实现。 |
以下示例显示了忽略所有注释的配置
并使用 “stub” 存储库:@Repository
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
class AppConfig {
// ...
}
下面的清单显示了等效的 XML:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
您还可以通过在
注解或通过作为元素的属性提供。这实际上禁用了类的自动检测
使用 、、、、 或 进行批注或元批注。useDefaultFilters=false use-default-filters="false" <component-scan/> @Component @Repository @Service @Controller @RestController @Configuration |
1.10.5. 在组件中定义 Bean 元数据
Spring 组件还可以向容器提供 bean 定义元数据。你可以做
这与用于定义 Comments 类中的 Bean 元数据的 Comments 相同。以下示例显示了如何执行此操作:@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 组件,其方法中包含特定于应用程序的代码。但是,它还提供了一个 bean 定义,该定义具有 factory
方法引用方法 。注释标识
Factory 方法和其他 Bean 定义属性,例如通过
注释。可以指定的其他方法级注释包括 、 和 自定义限定符注释。doWork()
publicInstance()
@Bean
@Qualifier
@Scope
@Lazy
除了其在组件初始化中的作用外,您还可以将注释放置在标有 或 的注入点上。在此上下文中,
它会导致注入延迟分辨率代理。但是,这种代理方法
相当有限。用于复杂的惰互,特别是组合
对于可选依赖项,我们建议改用。@Lazy @Autowired @Inject ObjectProvider<MyTargetBean> |
如前所述,支持自动装配的字段和方法,并带有额外的
支持方法的自动装配。以下示例显示了如何执行此操作:@Bean
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
该示例将 method 参数自动连接到另一个名为 .Spring Expression Language 元素
通过表示法定义属性的值 。对于 Comments,表达式解析器被预先配置为在
解析表达式文本。String
country
age
privateInstance
#{ <expression> }
@Value
从 Spring Framework 4.3 开始,您还可以将类型(或其更具体的子类:)的工厂方法参数声明为
访问触发当前 Bean 创建的请求注入点。
请注意,这仅适用于 bean 实例的实际创建,而不适用于
注入现有实例。因此,此功能最适合
prototype 范围的 bean。对于其他作用域,工厂方法只看到
触发在给定范围内创建新 bean 实例的注入点
(例如,触发创建惰性单例 Bean 的依赖项)。
在这种情况下,您可以谨慎使用提供的注入点元数据。
以下示例演示如何使用:InjectionPoint
DependencyDescriptor
InjectionPoint
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
常规 Spring 组件中的方法的处理方式与它们的
对应的 Spring 类中。区别在于,类没有使用 CGLIB 进行增强以拦截方法和字段的调用。
CGLIB 代理是调用方法或方法中的字段的方法
IN CLASSES 创建对协作对象的 Bean 元数据引用。
这些方法不是用普通的 Java 语义调用的,而是通过
container 来提供 Spring 的通常生命周期管理和代理
bean,即使通过对方法的编程调用引用其他 bean 也是如此。
相比之下,在普通类中调用方法中的方法或字段具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他
约束适用。@Bean
@Configuration
@Component
@Bean
@Configuration
@Bean
@Bean
@Component
您可以将方法声明为 ,允许在没有
创建其包含的 Configuration 类作为实例。这使得特别
sense 在定义后处理器 bean(例如,type 或 )时,因为这样的 bean 在容器的早期就被初始化了
生命周期,并且应避免在此时触发配置的其他部分。 由于技术原因,对静态方法的调用永远不会被容器拦截,即使在类中也不会(如本节前面所述)。
限制: CGLIB 子类化只能覆盖非静态方法。因此,
对另一个方法的直接调用具有标准的 Java 语义,从而
在直接从工厂方法本身返回的独立实例中。 方法的 Java 语言可见性不会对
Spring 容器中生成的 bean 定义。您可以自由地声明您的
你认为适合 non- classes 和 static 的 factory 方法
方法。但是,类中的常规方法需要
才能被覆盖 — 也就是说,它们不能被声明为 或 。
最后,单个类可以包含多个方法
bean,作为多个工厂方法的安排,根据可用情况使用
运行时的依赖项。这与选择“最贪婪”的算法相同
constructor 或 factory 方法:具有
在构造时选择最大数量的 satisfiable dependencies,
类似于容器在多个构造函数之间进行选择的方式。 |
1.10.6. 命名自动检测到的分量
当组件在扫描过程中被自动检测时,其 Bean 名称为
由该扫描程序已知的策略生成。默认情况下,任何
Spring 构造型注释(、和)因此将该名称提供给
相应的 bean 定义。BeanNameGenerator
@Component
@Repository
@Service
@Controller
value
如果此类注释不包含名称或包含检测到的任何其他组件
(例如由自定义过滤器发现的那些),默认的 Bean 名称生成器返回
未大写的非限定类名。例如,如果以下组件
类,则名称为 和 :value
myMovieLister
movieFinderImpl
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果您不想依赖默认的 bean 命名策略,则可以提供自定义的
bean 命名策略。首先,实现 BeanNameGenerator
接口,并确保包含一个默认的 no-arg 构造函数。然后,提供完整的
限定的类名,如以下示例注释
和 bean 定义显示。
如果由于多个自动检测到的组件具有
相同的非限定类名(即,具有相同名称但驻留在
不同的软件包),你可能需要配置一个,默认为
生成的 Bean 名称的完全限定类名。从 Spring Framework 5.2.3 开始,位于 package 中可用于此类目的。BeanNameGenerator FullyQualifiedAnnotationBeanNameGenerator org.springframework.context.annotation |
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作为一般规则,请考虑在使用 Comments 指定名称时,每当其他 组件可能会显式引用它。另一方面, 每当容器负责 wiring 时,自动生成的名称就足够了。
1.10.7. 为自动检测的组件提供范围
与一般的 Spring 管理组件一样,默认和最常见的
autodetected components 为 。但是,有时您需要不同的范围
这可以通过 Annotation 指定。您可以提供
范围,如下例所示:singleton
@Scope
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 注解仅在具体的 Bean 类上被内省(对于带注解的
组件)或工厂方法(用于方法)。与 XML Bean 相比
定义,没有 bean 定义继承的概念,而继承
类级别的层次结构与元数据目的无关。@Bean |
有关 Spring 上下文中特定于 Web 的范围(例如“request”或“session”)的详细信息,
请参阅 Request、Session、Application 和 WebSocket 范围。与这些范围的预构建注释一样,
您还可以使用 Spring 的元注释编写自己的范围注释
方法:例如,使用 、
也可能声明自定义范围代理模式。@Scope("prototype")
要为范围解析提供自定义策略,而不是依赖
基于注释的方法,您可以实现 ScopeMetadataResolver 接口。请务必包含默认的 no-arg 构造函数。然后,您可以提供
完全限定的类名,如以下示例所示
注释和 bean 定义显示: |
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使用某些非单例作用域时,可能需要为
scoped 对象。原因在 Scoped Bean as Dependencies 中进行了描述。
为此,component-scan 上提供了 scoped-proxy 属性
元素。三个可能的值是:、 和 。例如
以下配置将生成标准 JDK 动态代理:no
interfaces
targetClass
@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. 提供带有注解的限定符元数据
Fine-tuning Annotation-based Autowiring with Qualifiers中讨论了该注解。
该部分中的示例演示了 annotation 和
自定义限定符注释,用于在解析 autowire 时提供精细控制
候选人。因为这些示例是基于 XML Bean 定义的,所以限定符
通过使用 XML 中元素的 or 子元素,在候选 Bean 定义上提供元数据。当依赖 Classpath 扫描
自动检测组件,则可以提供类型级
Candidate 类的注释。以下三个示例演示了这一点
技术:@Qualifier
@Qualifier
qualifier
meta
bean
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
与大多数基于 Comments 的替代方案一样,请记住,Comments 元数据是 绑定到类定义本身,而 XML 的使用允许多个 bean 的 SAME 类型在其限定符元数据中提供变体,因为 元数据是按实例而不是按类提供的。 |
1.10.9. 生成候选组件的索引
虽然 Classpath 扫描非常快,但可以提高启动性能 通过在编译时创建静态候选列表来获取大型应用程序。在这个 模式下,作为组件扫描目标的所有模块都必须使用此机制。
您的现有 or 指令必须保留
unchanged 请求上下文以扫描某些包中的候选项。当检测到此类索引时,它会自动使用它,而不是扫描
类路径。@ComponentScan <context:component-scan/> ApplicationContext |
要生成索引,请向包含 作为组件 Scan 指令目标的组件。以下示例显示了 如何使用 Maven 执行此操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.25.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
对于 Gradle 4.5 及更早版本,应在配置中声明依赖项,如以下示例所示:compileOnly
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.25.RELEASE"
}
对于 Gradle 4.6 及更高版本,应在配置中声明依赖项,如以下示例所示:annotationProcessor
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.2.25.RELEASE"
}
工件会生成一个文件,该文件
包含在 jar 文件中。spring-context-indexer
META-INF/spring.components
在 IDE 中使用此模式时,必须为
注册为注释处理器,以确保索引在以下时间是最新的
候选组件已更新。spring-context-indexer |
找到文件时,将自动启用索引
在 Classpath 上。如果索引部分可用于某些库(或用例)
但无法为整个应用程序构建,则可以回退到常规的 Classpath
排列(就好像根本没有索引一样),作为 JVM 系统属性或通过 SpringProperties 机制。META-INF/spring.components spring.index.ignore true |
1.11. 使用 JSR 330 标准注解
从 Spring 3.0 开始, Spring 提供对 JSR-330 标准注释的支持 (依赖关系注入)。这些 Comments 的扫描方式与 Spring 相同 附注。要使用它们,您需要在 Classpath 中包含相关的 jar。
如果您使用 Maven,则工件在标准 Maven 中可用
存储库 ( https://repo1.maven.org/maven2/javax/inject/javax.inject/1/)。
您可以将以下依赖项添加到文件pom.xml:
|
1.11.1. 使用 和 的依赖注入@Inject
@Named
您可以按如下方式使用 ,而不是 :@Autowired
@javax.inject.Inject
import javax.inject.Inject;
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(...)
// ...
}
}
与 一样,您可以在字段级别、方法级别使用
和 constructor-argument 级别。此外,您可以将注入点声明为 ,允许按需访问范围较短的 bean 或延迟访问
其他 bean 通过调用。以下示例提供了
前面的示例:@Autowired
@Inject
Provider
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
}
// ...
}
与 一样,也可以与 或 一起使用。这在这里更适用,因为没有
一个属性。以下一对示例演示如何使用 and :@Autowired
@Inject
java.util.Optional
@Nullable
@Inject
required
@Inject
@Nullable
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
class SimpleMovieLister {
@Inject
var movieFinder: MovieFinder? = null
}
1.11.2. 和:注解的标准等效物@Named
@ManagedBean
@Component
您可以使用 或 ,而不是 ,
如下例所示:@Component
@javax.inject.Named
javax.annotation.ManagedBean
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
在不指定组件名称的情况下使用是很常见的。 可以以类似的方式使用,如下例所示:@Component
@Named
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
import javax.inject.Inject
import javax.inject.Named
@Named
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
当您使用 或 时,您可以在
与使用 Spring 注释时的方式完全相同,如下例所示:@Named
@ManagedBean
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
与 相反,JSR-330 和 JSR-250 注释是不可组合的。您应该使用 Spring 的 stereotype 模型来构建
自定义组件注释。@Component @Named ManagedBean |
1.11.3. JSR-330 标准注解的限制
当您使用标准注释时,您应该知道一些重要的 功能不可用,如下表所示:
Spring | javax.inject.* | javax.inject 限制 / 注释 |
---|---|---|
@Autowired |
@Inject |
|
@Component |
@Named / @ManagedBean |
JSR-330 不提供可组合模型,只提供一种标识命名组件的方法。 |
@Scope(“单例”) |
@Singleton |
JSR-330 的默认范围类似于 Spring 的 .但是,为了保持它
与 Spring 的一般默认值一致,在 Spring 中声明的 JSR-330 bean
container 默认为 a。为了使用除 之外的作用域 ,
你应该使用 Spring 的 Comments。 还提供 @Scope 注释。
不过,这个 API 仅用于创建您自己的 Comments。 |
@Qualifier |
@Qualifier / @Named |
|
@Value |
- |
无等效项 |
@Required |
- |
无等效项 |
@Lazy |
- |
无等效项 |
对象工厂 |
供应商 |
|
1.12. 基于 Java 的容器配置
本节介绍如何在 Java 代码中使用 Comments 来配置 Spring 容器。它包括以下主题:
1.12.1. 基本概念:和@Bean
@Configuration
Spring 的新 Java 配置支持中的核心工件是 -annotated 类和 -annotated 方法。@Configuration
@Bean
注解用于指示方法 instantites、configures 和
初始化一个要由 Spring IoC 容器管理的新对象。对于熟悉的人
使用 Spring 的 XML 配置,注解起着与
元素。您可以将 -annotated 方法与任何 Spring 一起使用。然而,它们最常与Beans一起使用。@Bean
<beans/>
@Bean
<bean/>
@Bean
@Component
@Configuration
对类进行注释表示其主要目的是作为
bean 定义的来源。此外,类允许 inter-bean
依赖项通过调用同一类中的其他方法来定义。
最简单的类如下所示:@Configuration
@Configuration
@Bean
@Configuration
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun myService(): MyService {
return MyServiceImpl()
}
}
前面的类等价于下面的 Spring XML:AppConfig
<beans/>
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
以下部分将深入讨论 and 注释。
但是,首先,我们介绍了使用
基于 Java 的配置。@Bean
@Configuration
1.12.2. 使用 实例化 Spring 容器AnnotationConfigApplicationContext
以下各节记录了 Spring 中引入的 Spring
3.0. 这个多功能的实现不仅能够接受类作为输入,还能够接受普通类和类
使用 JSR-330 元数据进行注释。AnnotationConfigApplicationContext
ApplicationContext
@Configuration
@Component
当类作为输入提供时,类本身
注册为 bean 定义和类中所有声明的方法
也注册为 Bean 定义。@Configuration
@Configuration
@Bean
当提供 JSR-330 类时,它们将被注册为 Bean
定义,并假定 DI 元数据(如 或
在必要时使用这些类。@Component
@Autowired
@Inject
结构简单
与在实例化 a 时将 Spring XML 文件用作 input 的方式大致相同,您可以在
实例化一个 .这允许完全
Spring 容器的无 XML 用法,如下例所示:ClassPathXmlApplicationContext
@Configuration
AnnotationConfigApplicationContext
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()
}
如前所述,不仅限于工作
与类。可以提供任何带 JSR-330 注释的类
作为构造函数的 input,如下例所示:AnnotationConfigApplicationContext
@Configuration
@Component
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()
}
前面的示例假定 , , 和 使用 Spring
依赖项注入注释,例如 .MyServiceImpl
Dependency1
Dependency2
@Autowired
使用 以编程方式构建容器register(Class<?>…)
您可以使用 no-arg 构造函数实例化
,然后使用该方法进行配置。这种方法特别有用
以编程方式构建 .以下内容
示例展示了如何做到这一点:AnnotationConfigApplicationContext
register()
AnnotationConfigApplicationContext
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.register(AppConfig::class.java, OtherConfig::class.java)
ctx.register(AdditionalConfig::class.java)
ctx.refresh()
val myService = ctx.getBean<MyService>()
myService.doStuff()
}
启用 Component Scanningscan(String…)
要启用组件扫描,您可以按如下方式对类进行注释:@Configuration
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig {
// ...
}
1 | 此注释启用组件扫描。 |
有经验的 Spring 用户可能熟悉 XML 声明等价物
Spring 的命名空间,如以下示例所示:
|
在前面的示例中,扫描包以查找任何带 -ancomments 的类,并且这些类被注册为 Spring bean
定义。 公开了该方法以允许相同的组件扫描功能,因为
以下示例显示:com.acme
@Component
AnnotationConfigApplicationContext
scan(String…)
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean<MyService>()
}
请记住,类是元注释的,因此它们是组件扫描的候选者。在前面的示例中,
假设 在 package(或任何 package)中声明
),则会在调用 期间选取它。在 上,它的所有方法都将被处理并注册为容器中的 Bean 定义。@Configuration @Component AppConfig com.acme scan() refresh() @Bean |
支持 Web 应用程序AnnotationConfigWebApplicationContext
的变体 可用
跟。在以下情况下,可以使用此实现
配置 Spring servlet 侦听器、 Spring MVC 等。以下代码段配置了一个典型的
Spring MVC Web 应用程序(注意 context-param 和
init-param) 的WebApplicationContext
AnnotationConfigApplicationContext
AnnotationConfigWebApplicationContext
ContextLoaderListener
DispatcherServlet
web.xml
contextClass
<web-app>
<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!-- Configuration locations must consist of one or more comma- or space-delimited
fully-qualified @Configuration classes. Fully-qualified packages may also be
specified for component-scanning -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.AppConfig</param-value>
</context-param>
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Declare a Spring MVC DispatcherServlet as usual -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
instead of the default XmlWebApplicationContext -->
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or space-delimited
and fully-qualified @Configuration classes -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
1.12.3. 使用 Annotation@Bean
@Bean
是方法级注释,是 XML 元素的直接模拟。
该注释支持 提供的一些属性,例如:<bean/>
<bean/>
-
name
.
可以在 -annotated 或 -annotated 类中使用注释。@Bean
@Configuration
@Component
声明一个 Bean
要声明 Bean,可以使用注释对方法进行注释。您使用此
方法在
指定为方法的返回值。默认情况下,bean 名称与
方法名称。下面的示例演示方法声明:@Bean
ApplicationContext
@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>
这两个声明都使名为 的 bean 在 中可用,绑定到 类型的对象实例 ,因为
以下文本图像显示:transferService
ApplicationContext
TransferServiceImpl
transferService -> com.acme.TransferServiceImpl
您还可以使用接口(或基类)声明方法
return 类型,如下例所示:@Bean
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(): TransferService {
return TransferServiceImpl()
}
}
但是,这会将高级类型预测的可见性限制为指定的
接口类型 ()。然后,使用完整类型 ()
仅在实例化受影响的 singleton bean 后,容器才知道。
非惰性单例 bean 根据它们的声明顺序进行实例化,
因此,您可能会看到不同的类型匹配结果,具体取决于另一个组件的时间
尝试通过未声明的类型(如 ,
只有在 bean 实例化后才会解析)。TransferService
TransferServiceImpl
@Autowired TransferServiceImpl
transferService
如果您始终通过声明的服务接口引用您的类型,则您的返回类型可以安全地加入该设计决策。但是,对于组件
实现多个接口,或者对于可能由其
implementation 类型,则声明最具体的返回类型会更安全
(至少与引用 bean 的注入点所要求一样具体)。@Bean |
Bean 依赖项
带注释的方法可以具有任意数量的参数,用于描述
构建该 bean 所需的依赖项。例如,如果我们需要一个 ,我们可以使用一个
参数,如下例所示:@Bean
TransferService
AccountRepository
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
解析机制与基于构造函数的依赖项几乎相同 注射。有关更多详细信息,请参阅相关部分。
接收生命周期回调
使用 Comments 定义的任何类都支持常规生命周期回调
并且可以使用 JSR-250 中的 and 注解。有关详细信息,请参阅 JSR-250 注释
详。@Bean
@PostConstruct
@PreDestroy
常规的 Spring 生命周期回调完全支持为
井。如果 Bean 实现 、 、 或 、 则
容器调用相应的方法。InitializingBean
DisposableBean
Lifecycle
标准接口集(例如 BeanFactoryAware、BeanNameAware、MessageSourceAware、ApplicationContext Aware 等)也完全受支持。*Aware
注解支持指定任意初始化和析构
回调方法,很像 Spring XML 的 and attributes
在元素上,如下例所示:@Bean
init-method
destroy-method
bean
public class BeanOne {
public void init() {
// initialization logic
}
}
public class BeanTwo {
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init")
public BeanOne beanOne() {
return new BeanOne();
}
@Bean(destroyMethod = "cleanup")
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
class BeanOne {
fun init() {
// initialization logic
}
}
class BeanTwo {
fun cleanup() {
// destruction logic
}
}
@Configuration
class AppConfig {
@Bean(initMethod = "init")
fun beanOne() = BeanOne()
@Bean(destroyMethod = "cleanup")
fun beanTwo() = BeanTwo()
}
默认情况下,使用 Java 配置定义的具有 public 或 method 的 bean 会自动使用销毁回调进行登记。如果你有一个 public 或 方法,并且你不希望在容器
关闭时,您可以添加到 Bean 定义中以禁用
default 模式。 默认情况下,您可能希望对使用 JNDI 获取的资源执行此操作,因为它的
生命周期在应用程序外部进行管理。特别是,确保始终这样做
,因为已知它在 Java EE 应用程序服务器上存在问题。 以下示例说明如何防止 : Java
Kotlin
此外,对于方法,您通常使用编程式 JNDI 查找,或者
使用 Spring 的 or helpers 或直接的 JNDI 用法,但不使用 variant(这将强制
u 将返回类型声明为类型,而不是实际目标
type,使其更难用于其他方法中的交叉引用调用
打算在此处引用提供的资源)。 |
在上面的示例(前面的说明)中,在构造期间直接调用该方法同样有效,如下例所示:BeanOne
init()
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
当您直接在 Java 中工作时,您可以对对象执行任何您喜欢的操作,并执行 并不总是需要依赖容器生命周期。 |
指定 Bean 范围
Spring 包含 Comments,以便您可以指定 bean 的范围。@Scope
使用注释@Scope
您可以指定使用 Comments 定义的 bean 应该具有
特定范围。您可以使用 Bean Scopes 部分中指定的任何标准范围。@Bean
默认范围是 ,但您可以使用注解
如下例所示:singleton
@Scope
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
@Scope
和scoped-proxy
Spring 提供了一种通过作用域代理处理作用域依赖项的便捷方法。最简单的创建
使用 XML 配置时,此类代理是 element。
使用 Comments 在 Java 中配置 bean 可提供等效的支持
替换为属性。默认值为 ,它
通常表示不应创建作用域代理,除非使用不同的默认值
已在 component-scan 指令级别进行配置。您可以指定 、 或 .<aop:scoped-proxy/>
@Scope
proxyMode
ScopedProxyMode.DEFAULT
ScopedProxyMode.TARGET_CLASS
ScopedProxyMode.INTERFACES
ScopedProxyMode.NO
如果您将 XML 参考文档(请参阅范围代理)中的范围代理示例移植到我们的 using Java,
它类似于以下内容:@Bean
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean
service.setUserPreferences(userPreferences());
return service;
}
// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
return SimpleUserService().apply {
// a reference to the proxied userPreferences bean
setUserPreferences(userPreferences())
}
}
自定义 Bean 命名
默认情况下,配置类使用方法的名称作为
结果 bean。但是,可以使用属性
如下例所示:@Bean
name
@Configuration
public class AppConfig {
@Bean("myThing")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean 别名
正如 命名 Bean 中所讨论的,有时需要给出一个 bean
多个名称,也称为 Bean 别名。为此,注释的属性接受 String 数组。以下示例演示如何设置
bean 的多个别名:name
@Bean
@Configuration
public class AppConfig {
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
public DataSource dataSource() {
// instantiate, configure and return DataSource bean...
}
}
@Configuration
class AppConfig {
@Bean("dataSource", "subsystemA-dataSource", "subsystemB-dataSource")
fun dataSource(): DataSource {
// instantiate, configure and return DataSource bean...
}
}
Bean 描述
有时,提供更详细的 bean 文本描述会很有帮助。这可以 当 bean 公开(可能通过 JMX)以进行监视时,特别有用。
要向 添加描述 ,可以使用 @Description
注释,如下例所示:@Bean
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
public Thing thing() {
return new Thing();
}
}
@Configuration
class AppConfig {
@Bean
@Description("Provides a basic example of a bean")
fun thing() = Thing()
}
1.12.4. 使用注解@Configuration
@Configuration
是类级注解,指示对象是
bean 定义。 类通过 -annotated 声明 bean
方法。对类的方法的调用也可用于定义
bean 间依赖关系。有关一般介绍,请参阅基本概念:@Bean
和@Configuration
。@Configuration
@Bean
@Bean
@Configuration
注入 bean 间依赖关系
当 bean 彼此依赖时,表达这种依赖性就很简单 就像让一个 bean 方法调用另一个 bean 方法一样,如下例所示:
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() {
return new BeanTwo();
}
}
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne(beanTwo())
@Bean
fun beanTwo() = BeanTwo()
}
在前面的示例中,接收对 through 构造函数
注射。beanOne
beanTwo
这种声明 bean 间依赖关系的方法仅在
在类中声明。不能声明 bean 间依赖关系
通过使用普通类。@Bean @Configuration @Component |
查找方法注入
如前所述,查找方法注入是一个 您应该很少使用的高级功能。它在 singleton 范围的 bean 依赖于原型范围的 bean。使用 Java 实现此目的 type 配置为实现此模式提供了一种自然的方法。这 以下示例显示了如何使用 Lookup 方法注入:
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface
val command = createCommand()
// set the state on the (hopefully brand new) Command instance
command.setState(commandState)
return command.execute()
}
// okay... but where is the implementation of this method?
protected abstract fun createCommand(): Command
}
通过使用 Java 配置,您可以创建 where
abstract 方法被覆盖,以便它查找新的
(prototype) 命令对象。以下示例显示了如何执行此操作:CommandManager
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 的配置如何在内部工作的更多信息
请考虑以下示例,该示例显示了一个被调用两次的带 Comments 的方法:@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()
已被调用一次 和 一次。
由于此方法会创建一个新实例并返回它,因此您将
通常期望有两个实例(每个服务一个)。那肯定是
problematic:在 Spring 中,实例化的 bean 默认有一个范围。这是
神奇之处:所有类在启动时都是子类化的
跟。在子类中,子方法首先检查容器是否有任何
在调用 Parent 方法并创建新实例之前缓存 (作用域) bean。clientService1()
clientService2()
ClientDaoImpl
singleton
@Configuration
CGLIB
根据 Bean 的范围,行为可能会有所不同。我们正在交谈 关于单例 这里. |
从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的类路径中,因为 CGLIB
类已重新打包并直接包含在下
在 spring-core JAR 中。 |
由于 CGLIB 在
startup-time 的 Startup-time 中。特别是,配置类不能是 final。但是,由于
在 4.3 中,允许在 Configuration 类上使用任何构造函数,包括使用或单个非 default 构造函数声明进行 default 注入。 如果您希望避免任何 CGLIB 施加的限制,请考虑在非类上声明您的方法(例如,在普通类上)。
方法之间的跨方法调用不会被拦截,因此你有
完全依赖于构造函数或方法级别的依赖注入。 |
1.12.5. 编写基于 Java 的配置
Spring 基于 Java 的配置功能允许您编写 Comments,这可以减少 配置的复杂程度。
使用注释@Import
就像在 Spring XML 文件中使用元素来帮助模块化一样
配置,该注解允许从
另一个 Configuration 类,如下例所示:<import/>
@Import
@Bean
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B b() {
return new B();
}
}
@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>()
}
这种方法简化了容器实例化,因为只需要处理一个类
替换为 Like,而不是要求您在构造过程中记住可能大量的类。@Configuration
从 Spring Framework 4.2 开始,还支持对常规组件的引用
类,类似于 method。
如果您想通过使用一些
configuration 类作为入口点来显式定义所有组件。@Import AnnotationConfigApplicationContext.register |
注入对导入定义的依赖关系@Bean
前面的示例有效,但过于简单。在大多数实际场景中,bean 具有
跨配置类彼此依赖。使用 XML 时,这不是
问题,因为不涉及编译器,你可以声明并信任 Spring 在容器初始化期间解决它。
使用类时,Java 编译器对
配置模型,因为对其他 bean 的引用必须是有效的 Java 语法。ref="someBean"
@Configuration
幸运的是,解决这个问题很简单。正如我们已经讨论过的,
一个方法可以有任意数量的描述 Bean 的参数
依赖。考虑以下更真实的场景,其中包含多个类,每个类都依赖于其他类中声明的 bean:@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")
}
还有另一种方法可以达到相同的结果。请记住,类是
最终容器中只有另一个 bean:这意味着它们可以利用 and injection 和其他功能,就像任何其他 bean 一样。@Configuration
@Autowired
@Value
确保您以这种方式注入的依赖项只是最简单的类型。 类在上下文初始化期间很早就被处理,并强制依赖项
以这种方式注入可能会导致意外的提前初始化。只要有可能,就求助于
基于参数的注入,如前面的示例所示。 此外,要特别小心 和 定义
通过。这些通常应该声明为方法,而不是触发
实例化其包含的配置类。否则,并且可能不会
在配置类本身上工作,因为可以将其创建为早于 |
下面的示例展示了如何将一个 bean 自动连接到另一个 bean:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
private final DataSource dataSource;
public RepositoryConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
// everything wires up across configuration classes...
TransferService transferService = ctx.getBean(TransferService.class);
transferService.transfer(100.00, "A123", "C456");
}
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
lateinit var accountRepository: AccountRepository
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class, RepositoryConfig::class)
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
类中的构造函数注入仅从 Spring 开始受支持
框架 4.3.另请注意,如果 target
Bean 只定义了一个构造函数。@Configuration @Autowired |
在前面的场景中,使用 效果很好,并提供所需的
模块化,但确定自动装配的 bean 定义的确切声明位置是
仍然有点模棱两可。例如,作为开发人员查看 ,如何
您确切知道 bean 的声明位置吗?事实并非如此
explicit 的 intent 函数,这可能就好了。请记住,Spring Tools for Eclipse 提供了以下工具
可以渲染显示所有内容是如何连接的图表,这可能就是您所需要的。也
您的 Java IDE 可以轻松找到
并快速显示返回该类型的方法的位置。@Autowired
ServiceConfig
@Autowired AccountRepository
AccountRepository
@Bean
如果这种歧义是不可接受的,并且您希望进行直接导航
在 IDE 中从一个类到另一个类,考虑自动装配
configuration 类本身。以下示例显示了如何执行此操作:@Configuration
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
在上述情况下,where is defined 是完全显式的。
但是,现在与 紧密耦合。那就是
权衡。这种紧密耦合可以通过使用基于接口的 或
抽象基于类的类。请考虑以下示例: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")
}
Now 相对于具体来说是松散耦合的,内置的 IDE 工具仍然很有用:您可以轻松地
获取实现的类型层次结构。在这个
方式,导航类及其依赖项也没有什么不同
而不是导航基于接口的代码的通常过程。ServiceConfig
DefaultRepositoryConfig
RepositoryConfig
@Configuration
如果要影响某些 bean 的启动创建顺序,请考虑
将其中一些声明为 (用于在首次访问时创建,而不是在启动时创建)
或作为某些其他 bean (确保特定的其他 bean 是
在当前 bean 之前创建,超出了后者的直接依赖关系所暗示的范围)。@Lazy @DependsOn |
有条件地包含类或方法@Configuration
@Bean
有条件地启用或禁用完整类通常很有用
甚至是基于某个任意系统状态的单个方法。一个普通
这方面的示例是,仅在特定的
profile 已在 Spring 中启用(有关详细信息,请参见 Bean Definition Profiles)。@Configuration
@Bean
@Profile
Environment
该 Comments 实际上是通过使用更灵活的 Comments 来实现的
称为 @Conditional
。
该注解指示了应该
在注册 A 之前咨询。@Profile
@Conditional
org.springframework.context.annotation.Condition
@Bean
接口的实现提供了一个返回 或 的方法。例如,下面的清单显示了用于:Condition
matches(…)
true
false
Condition
@Profile
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
// Read the @Profile annotation attributes
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
return true
}
}
return false
}
return true
}
有关更多详细信息,请参阅 @Conditional
javadoc。
组合 Java 和 XML 配置
Spring 的类支持并不旨在成为 100% 的完全替代品
用于 Spring XML。一些工具(例如 Spring XML 名称空间)仍然是
配置容器。在 XML 方便或必要的情况下,您有一个
选择:要么以“以 XML 为中心”的方式实例化容器,例如使用 和 注释导入 XML,要么以“以 Java 为中心”的方式实例化容器
根据需要。@Configuration
ClassPathXmlApplicationContext
AnnotationConfigApplicationContext
@ImportResource
以 XML 为中心的类使用@Configuration
最好从 XML 引导 Spring 容器,并以 ad-hoc 方式包含类。例如,在大型现有代码库中
,则在
根据需要,并从现有 XML 文件中包含它们。在本节的后面部分,我们将介绍
在这种 “以 XML 为中心” 的情况下使用类的选项。@Configuration
@Configuration
@Configuration
请记住,类最终是
容器。在本系列示例中,我们创建了一个名为 和
将其作为定义包含在 中。因为是打开的,所以容器会识别 Comments 并正确处理 Declaration 中声明的方法。@Configuration
@Configuration
AppConfig
system-test-config.xml
<bean/>
<context:annotation-config/>
@Configuration
@Bean
AppConfig
以下示例显示了 Java 中的普通配置类:
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
@Configuration
class AppConfig {
@Autowired
private lateinit var dataSource: DataSource
@Bean
fun accountRepository(): AccountRepository {
return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService() = TransferService(accountRepository())
}
以下示例显示了示例文件的一部分:system-test-config.xml
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
以下示例显示了一个可能的文件:jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
在 file 中,它不会声明一个元素。虽然这样做是可以接受的,但这是不必要的,因为没有其他 bean
Ever 引用它,并且不太可能按名称从容器中显式获取它。
同样,bean 仅按类型自动装配,因此不严格要求显式 bean。system-test-config.xml AppConfig <bean/> id DataSource id |
因为 is 元注释 , -annotated
类会自动成为组件扫描的候选对象。使用与
describe 在前面的示例中,我们可以重新定义以利用组件扫描。
请注意,在这种情况下,我们不需要显式声明 ,因为启用相同的
功能性。@Configuration
@Component
@Configuration
system-test-config.xml
<context:annotation-config/>
<context:component-scan/>
以下示例显示了修改后的文件:system-test-config.xml
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
@Configuration
以类为中心 将 XML 与@ImportResource
在类是配置
容器,则可能仍然需要至少使用一些 XML。在这些
方案中,您只能根据需要使用和定义任意数量的 XML。行为
因此,实现了一种“以 Java 为中心”的容器配置方法,并将 XML 保持为
最低 限度。以下示例(包括一个配置类、一个 XML 文件
,它定义了一个 bean、一个属性文件和类)展示了如何使用
用于实现使用 XML 的“以 Java 为中心”配置的注释
根据需要:@Configuration
@ImportResource
main
@ImportResource
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
class AppConfig {
@Value("\${jdbc.url}")
private lateinit var url: String
@Value("\${jdbc.username}")
private lateinit var username: String
@Value("\${jdbc.password}")
private lateinit var password: String
@Bean
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password=
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
TransferService transferService = ctx.getBean(TransferService.class);
// ...
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val transferService = ctx.getBean<TransferService>()
// ...
}
1.13. 环境抽象
Environment
界面
是集成在容器中的抽象,它对两个键进行建模
应用程序环境的各个方面:配置文件和属性。
配置文件是要注册到
容器。可以将 Bean 分配给配置文件
无论是在 XML 中定义还是使用注释定义。对象的角色
relation to profiles 用于确定当前处于活动状态的用户档案(如果有),
以及默认情况下应处于活动状态的配置文件(如果有)。Environment
属性在几乎所有应用程序中都起着重要作用,可能源自
多种来源:属性文件、JVM 系统属性、系统环境
变量、JNDI、servlet 上下文参数、临时对象、对象等
上。对象与属性相关的角色是提供
具有便捷服务界面的用户,用于配置属性源和解析
属性。Properties
Map
Environment
1.13.1. Bean 定义配置文件
Bean 定义配置文件在核心容器中提供了一种机制,该机制允许 在不同环境中注册不同的 bean。“环境”这个词 对不同的用户可能意味着不同的事情,而此功能可以帮助解决许多问题 使用案例,包括:
-
在开发中处理内存中数据源与查找相同的数据源 datasource 的 JNDI 的 QA 或生产环境。
-
仅在将应用程序部署到 性能环境。
-
为客户 A 与客户注册 bean 的自定义实现 B 部署。
考虑实际应用程序中的第一个用例,它需要一个 .在测试环境中,配置可能类似于以下内容:DataSource
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
现在考虑如何将此应用程序部署到 QA 或生产环境中
环境中,假设应用程序的数据源已注册
替换为生产应用程序服务器的 JNDI 目录。我们的 bean
现在如下面的清单所示:dataSource
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
问题在于如何根据
当前环境。随着时间的推移,Spring 用户已经设计出了许多方法来
完成此操作,通常依赖于系统环境变量的组合
以及包含解析
添加到正确的配置文件路径,具体取决于环境的值
变量。Bean 定义配置文件是一个核心容器功能,它提供了一个
解决这个问题。<import/>
${placeholder}
如果我们概括前面特定于环境的 bean 示例中显示的用例 定义,我们最终需要在 某些上下文,但在其他上下文中没有。您可以说您想要注册一个 情况 A 中 bean 定义的某个配置文件和 情况 B.我们首先更新配置以反映此需求。
用@Profile
@Profile
注释允许您指示组件符合注册条件
当一个或多个指定的配置文件处于活动状态时。使用前面的示例,我们
可以重写配置,如下所示:dataSource
@Configuration
@Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
@Configuration
@Profile("development")
class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
@Configuration
@Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
@Configuration
@Profile("production")
class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
如前所述,对于方法,您通常会选择使用 Programmatic
JNDI 查找,通过使用 Spring 的 / helpers 或
直接 JNDI 用法,而不是变体,这将强制您将返回类型声明为类型。@Bean JndiTemplate JndiLocatorDelegate InitialContext JndiObjectFactoryBean FactoryBean |
配置文件字符串可以包含简单的配置文件名称(例如,)或
profile 表达式。配置文件表达式允许更复杂的配置文件逻辑
表示(例如,)。支持以下运算符
配置文件表达式:production
production & us-east
-
!
:配置文件的逻辑 “not” -
&
:配置文件的逻辑 “and” -
|
:配置文件的逻辑 “or”
不能在不使用括号的情况下混合 and 运算符。例如,不是有效的表达式。它必须表示为 。& | production & us-east | eu-central production & (us-east | eu-central) |
您可以用作元注释
创建自定义组合注释。以下示例定义了一个自定义注释,您可以将其用作 :@Profile
@Production
@Profile("production")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
如果一个类标有 ,则与该类关联的所有方法和注释都将被绕过,除非一个或多个
指定的配置文件处于活动状态。如果 a 或 类被标记为
使用 ,则不会注册或处理该类,除非
配置文件“P1”或“P2”已激活。如果给定配置文件的前缀为
NOT 运算符 (),则仅当配置文件未
积极。例如,给定 ,如果配置文件
“p1”处于活动状态,或者配置文件“p2”未处于活动状态。@Configuration @Profile @Bean @Import @Component @Configuration @Profile({"p1", "p2"}) ! @Profile({"p1", "!p2"}) |
@Profile
也可以在方法级别声明以仅包含一个特定的 bean
配置类中(例如,对于特定 Bean 的替代变体),作为
以下示例显示:
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production") (2)
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
1 | 该方法仅在配置文件中可用。standaloneDataSource development |
2 | 该方法仅在配置文件中可用。jndiDataSource production |
@Configuration
class AppConfig {
@Bean("dataSource")
@Profile("development") (1)
fun standaloneDataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
@Bean("dataSource")
@Profile("production") (2)
fun jndiDataSource() =
InitialContext().lookup("java:comp/env/jdbc/datasource") as DataSource
}
1 | 该方法仅在配置文件中可用。standaloneDataSource development |
2 | 该方法仅在配置文件中可用。jndiDataSource production |
使用 on 方法时,可能会应用特殊情况:如果
重载方法(类似于构造函数
overloading),则需要在 all 上一致地声明一个 condition
重载方法。如果条件不一致,则仅
重载方法中的第一个声明很重要。因此,可以
不用于选择具有特定参数签名的重载方法
另一个。同一 bean 的所有工厂方法之间的解析遵循 Spring 的
构造函数解析算法。 如果要定义具有不同性能分析条件的替代 bean,
使用指向同一 bean 名称的不同 Java 方法名称,方法是使用名称
属性,如前面的示例所示。如果参数签名全部为
相同(例如,所有变体都有 no-arg 工厂方法),这是唯一的
首先在有效的 Java 类中表示这种排列的方式
(因为只能有一个特定名称和参数签名的方法)。 |
XML Bean 定义配置文件
XML 对应项是元素的属性。我们前面的示例
配置可以重写为两个 XML 文件,如下所示:profile
<beans>
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
也可以避免在同一个文件中拆分和嵌套元素,
如下例所示:<beans/>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
已限制为仅允许
文件中的最后一个。这应该有助于提供灵活性,而不会产生
XML 文件中的混乱。spring-bean.xsd
XML 对应项不支持前面描述的配置文件表达式。有可能,
但是,要使用运算符 .也可以应用逻辑
“and” 嵌套配置文件,如下例所示:
在前面的示例中,如果 和 配置文件都处于活动状态,则会公开 Bean。 |
激活配置文件
现在我们已经更新了配置,我们仍然需要指示 Spring 哪个
配置文件处于活动状态。如果我们现在启动示例应用程序,我们将看到
a thrown,因为容器找不到
名为 的 Spring Bean 。NoSuchBeanDefinitionException
dataSource
可以通过多种方式激活配置文件,但最直接的是
它以编程方式针对 API,该 API 可通过 .以下示例显示了如何执行此操作:Environment
ApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
此外,您还可以通过属性以声明方式激活用户档案,该属性可通过系统环境指定
变量、JVM 系统属性、servlet 上下文参数,甚至作为
条目(请参阅 PropertySource
Abstraction)。在集成测试中,active
可以使用模块中的 Comments 来声明配置文件(请参阅使用环境配置文件的上下文配置)。spring.profiles.active
web.xml
@ActiveProfiles
spring-test
请注意,用户档案不是“非此即彼”的命题。您可以激活多个
配置文件。以编程方式,您可以向接受 varargs 的方法提供多个配置文件名称。以下示例
激活多个配置文件:setActiveProfiles()
String…
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
声明性地,可以接受逗号分隔的配置文件名称列表,
如下例所示:spring.profiles.active
-Dspring.profiles.active="profile1,profile2"
默认配置文件
default 配置文件表示默认启用的配置文件。考虑一下 以下示例:
@Configuration
@Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
如果没有配置文件处于活动状态,则会创建 。你可以看到这个
作为为一个或多个 bean 提供默认定义的一种方式。如果有
profile 时,默认配置文件不适用。dataSource
您可以使用 on 更改默认配置文件的名称
或,以声明方式,通过使用属性。setDefaultProfiles()
Environment
spring.profiles.default
1.13.2. 抽象PropertySource
Spring 的抽象通过可配置的
属性源的层次结构。请考虑以下清单:Environment
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsMyProperty = env.containsProperty("my-property");
System.out.println("Does my environment contain the 'my-property' property? " + containsMyProperty);
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
在前面的代码片段中,我们看到了一种高级方式,即询问 Spring 属性是否为
为当前环境定义。为了回答这个问题,对象执行
搜索一组 PropertySource
对象。A 是对任何键值对源的简单抽象,而
Spring 的 StandardEnvironment
配置了两个 PropertySource 对象——一个表示 JVM 系统属性集
() 和一个表示系统环境变量集
().my-property
Environment
PropertySource
System.getProperties()
System.getenv()
这些默认属性源可用于 ,以便在独立环境中使用
应用。StandardServletEnvironment 中填充了其他默认属性源,包括 servlet config 和 servlet
context 参数。它可以选择启用 JndiPropertySource 。
有关详细信息,请参阅 javadoc。StandardEnvironment |
具体来说,当您使用 时,如果系统属性或环境变量位于
运行。StandardEnvironment
env.containsProperty("my-property")
my-property
my-property
执行的搜索是分层的。默认情况下,系统属性优先于
环境变量。因此,如果在
对 的调用,系统属性值 “wins” 并返回。
请注意,属性值不会合并
而是完全被前面的条目覆盖。 对于通用 ,完整的层次结构如下,其中
topest-precedence 条目:
|
最重要的是,整个机制是可配置的。也许您有一个自定义源
要集成到此搜索中的属性。为此,请实现
并实例化你自己的 Git,并将其添加到
当前。以下示例显示了如何执行此操作:PropertySource
PropertySources
Environment
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
在前面的代码中,已在
搜索。如果它包含属性,则检测并返回该属性,优先使用
任何其他 .MutablePropertySources
API 公开了许多方法,这些方法允许对
property 源。MyPropertySource
my-property
my-property
PropertySource
1.13.3. 使用@PropertySource
@PropertySource
注解提供了一种方便的声明性机制,用于将 a 添加到 Spring 的 .PropertySource
Environment
给定一个包含键值对的 called 文件 ,则
下面的类以
对 return 的调用 :app.properties
testbean.name=myTestBean
@Configuration
@PropertySource
testBean.getName()
myTestBean
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
资源位置中存在的任何占位符都是
针对已针对
environment,如下例所示:${…}
@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")!!
}
}
假设 它已经存在于其中一个属性源中
registered (例如,系统属性或环境变量),则占位符为
resolved 的值。如果不是,则使用
作为默认值。如果未指定 default 且无法解析属性,则会引发 an。my.placeholder
default/path
IllegalArgumentException
根据 Java 8 约定,注释是可重复的。
但是,所有这些注解都需要在同一
级别,可以直接在配置类上,也可以作为
相同的自定义注释。混合直接注释和元注释不是
推荐,因为直接注释可以有效地覆盖元注释。@PropertySource @PropertySource |
1.13.4. 语句中的占位符解析
从历史上看,元素中占位符的值只能针对
JVM 系统属性或环境变量。现在情况已不再如此。因为
抽象集成在整个容器中,很容易
通过它的 route resolution of placeholders 进行路由解析。这意味着您可以配置
解决过程。您可以更改搜索的优先级
系统属性和环境变量,或者完全删除它们。您还可以添加
自己的属性源添加到组合中。Environment
具体来说,无论在何处定义属性,只要它在 :customer
Environment
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
1.14. 注册LoadTimeWeaver
Spring 使用 该 来动态转换类
加载到 Java 虚拟机 (JVM) 中。LoadTimeWeaver
要启用加载时编织,您可以将 添加到其中一个类中,如下例所示:@EnableLoadTimeWeaving
@Configuration
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
@Configuration
@EnableLoadTimeWeaving
class AppConfig
或者,对于 XML 配置,您可以使用以下元素:context:load-time-weaver
<beans>
<context:load-time-weaver/>
</beans>
一旦为 配置了 ,其中的任何 bean 都可以实现 ,从而接收对加载时间的引用
Weaver 实例。这与 Spring 的 JPA 支持结合使用时特别有用,其中加载时编织可能是
对于 JPA 类转换是必需的。
有关更多详细信息,请查阅LocalContainerEntityManagerFactoryBean
javadoc。有关 AspectJ 加载时编织的更多信息,请参见 Spring 框架中的使用 AspectJ 进行加载时编织。ApplicationContext
ApplicationContext
LoadTimeWeaverAware
1.15. 其他功能ApplicationContext
如章节介绍中所述,该包提供了用于管理和操作 bean 的基本功能,包括
编程方式。该包添加了 ApplicationContext
接口,该接口除了扩展了其他
接口,以便在更多应用程序中提供额外的功能
面向框架的样式。许多人完全使用
声明式方式,甚至不是以编程方式创建它,而是依赖于
支持类,例如在 Java EE Web 应用程序的正常启动过程中自动实例化 an 的一部分。org.springframework.beans.factory
org.springframework.context
BeanFactory
ApplicationContext
ContextLoader
ApplicationContext
为了以更面向框架的样式增强功能,上下文
package 还提供以下功能:BeanFactory
-
通过界面访问 i18n 风格的消息。
MessageSource
-
通过界面访问资源,例如 URL 和文件。
ResourceLoader
-
事件发布,即向实现接口 通过使用界面。
ApplicationListener
ApplicationEventPublisher
-
加载多个 (分层) 上下文,让每个上下文都专注于一个 特定层,例如应用程序的 Web 层,通过接口。
HierarchicalBeanFactory
1.15.1. 国际化使用MessageSource
该接口扩展了一个名为 and 的接口。
因此,提供国际化 (“i18n”) 功能。Spring 还提供了接口,该接口可以分层解析消息。
这些接口共同为 Spring effects 消息提供了基础
分辨率。在这些接口上定义的方法包括:ApplicationContext
MessageSource
HierarchicalMessageSource
-
String getMessage(String code, Object[] args, String default, Locale loc)
: 基本 用于从 .未找到消息时 对于指定的区域设置,将使用 default message。传入的任何参数都将变为 替换值,使用标准 图书馆。MessageSource
MessageFormat
-
String getMessage(String code, Object[] args, Locale loc)
:基本相同 前一种方法,但有一个区别:不能指定默认消息。如果 找不到消息,将引发 A。NoSuchMessageException
-
String getMessage(MessageSourceResolvable resolvable, Locale locale)
:所有属性 在上述方法中也包装在名为 的类中,您可以将其与此方法一起使用。MessageSourceResolvable
加载 an 时,它会自动搜索在上下文中定义的 bean。Bean 必须具有名称 。如果这样的 bean
,则所有对上述方法的调用都会委托给消息源。如果没有
message source 时,尝试查找包含
bean 的 bean 的 intent如果是这样,它将使用该 bean 作为 .如果找不到任何消息源,则会实例化一个空函数,以便能够接受对
方法。ApplicationContext
MessageSource
messageSource
ApplicationContext
MessageSource
ApplicationContext
DelegatingMessageSource
Spring 提供了三种实现 、 和 。他们都为了实现嵌套
消息。这很少使用,但提供了编程方式
将消息添加到源。以下示例显示:MessageSource
ResourceBundleMessageSource
ReloadableResourceBundleMessageSource
StaticMessageSource
HierarchicalMessageSource
StaticMessageSource
ResourceBundleMessageSource
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
该示例假定您有三个名为 的资源包,并在 Classpath 中定义。任何解决消息的请求都是
以 JDK 标准方式通过对象解析消息进行处理。对于
本示例的目的,假设上述两个资源包文件的内容
如下:format
exceptions
windows
ResourceBundle
# in format.properties message=Alligators rock!
# in exceptions.properties argument.required=The {0} argument is required.
下一个示例显示了一个运行该功能的程序。
请记住,所有 implementations 也是 implementations,因此可以强制转换为 interface。MessageSource
ApplicationContext
MessageSource
MessageSource
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH)
println(message)
}
上述程序的结果输出如下:
Alligators rock!
总而言之,它在一个名为 的文件中定义,该文件
存在于 Classpath 的根目录中。bean 定义引用
通过其属性的资源包数。这三个文件是
在列表中传递给属性的 exists 作为文件位于
Classpath 和 分别称为 、 和 。MessageSource
beans.xml
messageSource
basenames
basenames
format.properties
exceptions.properties
windows.properties
下一个示例显示了传递给消息查找的参数。这些参数是
转换为 Objects 并插入到 lookup 消息的占位符中。String
<beans>
<!-- this MessageSource is being used in a web application -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="exceptions"/>
</bean>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
public class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
class Example {
lateinit var messages: MessageSource
fun execute() {
val message = messages.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.ENGLISH)
println(message)
}
}
调用该方法的结果输出如下:execute()
The userDao argument is required.
关于国际化(“i18n”),Spring 的各种实现遵循与标准 JDK 相同的语言环境解析和回退规则。简而言之,继续定义示例
以前,如果要针对英国 () 区域设置解析邮件,请使用
将分别创建名为 、 和 、 的文件。MessageSource
ResourceBundle
messageSource
en-GB
format_en_GB.properties
exceptions_en_GB.properties
windows_en_GB.properties
通常,区域设置解析由 应用。在以下示例中,(英国)消息所针对的区域设置 resolved 手动指定:
# in exceptions_en_GB.properties argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("argument.required",
arrayOf("userDao"), "Required", Locale.UK)
println(message)
}
运行上述程序的结果输出如下:
Ebagum lad, the 'userDao' argument is required, I say, required.
您还可以使用该接口获取对已定义的任何内容的引用。在实现该接口的 an 中定义的任何 bean 都会注入
应用程序和配置 bean 时的应用程序上下文。MessageSourceAware
MessageSource
ApplicationContext
MessageSourceAware
MessageSource
因为 Spring 的基于 Java 的,所以它不会合并
具有相同基本名称的捆绑包,但将仅使用找到的第一个捆绑包。
具有相同基本名称的后续消息包将被忽略。MessageSource ResourceBundle |
作为 的替代方法,Spring 提供了一个类。此变体支持相同的捆绑包
文件格式,但比基于 JDK 的标准实现更灵活。特别是,它允许读取
文件(不仅来自 Classpath),并支持热
重新加载 bundle 属性文件(同时在两者之间有效地缓存它们)。
有关详细信息,请参见ReloadableResourceBundleMessageSource javadoc。ResourceBundleMessageSource ReloadableResourceBundleMessageSource ResourceBundleMessageSource |
1.15.2. 标准事件和自定义事件
中的事件处理是通过类和接口提供的。如果将实现该接口的 bean 部署到上下文中,则每次将 发布到 时,都会通知该 bean。
从本质上讲,这是标准的 Observer 设计模式。ApplicationContext
ApplicationEvent
ApplicationListener
ApplicationListener
ApplicationEvent
ApplicationContext
从 Spring 4.2 开始,活动基础设施得到了显著改进,并提供
基于注释的模型以及
能够发布任何任意事件(即,不一定
extend from (扩展自 )。当这样的对象被发布时,我们将其包装在
活动。ApplicationEvent |
下表描述了 Spring 提供的标准事件:
事件 | 解释 |
---|---|
|
在初始化或刷新时发布(例如,通过
使用界面上的方法)。
这里,“initialized” 表示加载所有 bean,检测到后处理器 bean
和 activated,则单例是预先实例化的,并且对象是
随时可用。只要上下文尚未关闭,就可以触发刷新
多次,前提是 chosen 实际上支持这样的
“hot” 刷新。例如,支持热刷新,但不支持。 |
|
使用接口上的方法启动时发布。这里,“started” 表示所有 bean 都接收到显式的 start 信号。通常,此信号用于重新启动 bean
,但它也可用于启动尚未
配置为自动启动(例如,尚未在
初始化)。 |
|
使用接口上的方法停止时发布。这里,“stopped”意味着所有 bean 都接收到显式的停止信号。可以通过调用重新启动已停止的上下文。 |
|
使用方法关闭 时发布
在接口上或通过 JVM 关闭钩子。这里
“closed” 表示所有单例 bean 都将被销毁。关闭上下文后,
它已达到其生命周期的终点,无法刷新或重新启动。 |
|
一个特定于 Web 的事件,告诉所有 bean HTTP 请求已得到处理。这
事件在请求完成后发布。此活动仅适用于
使用 Spring 的 . |
|
的子类添加特定于 Servlet 的上下文信息。 |
您还可以创建和发布自己的自定义事件。以下示例显示了
简单的类来扩展 Spring 的基类:ApplicationEvent
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
要发布自定义 ,请在 .通常,这是通过创建一个实现的类并将其注册为 Spring bean 来完成的。以下内容
example 显示了这样的类:ApplicationEvent
publishEvent()
ApplicationEventPublisher
ApplicationEventPublisherAware
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 容器检测到 implements 并自动调用 .实际上,传入的参数是 Spring
容器本身。您正在通过其界面与应用程序上下文进行交互。EmailService
ApplicationEventPublisherAware
setApplicationEventPublisher()
ApplicationEventPublisher
要接收 custom ,您可以创建一个实现的类并将其注册为 Spring bean。以下示例
显示了这样的类: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...
}
}
请注意,它通常使用 your
自定义事件(在前面的示例中)。这意味着该方法可以保持类型安全,无需进行向下转换。
您可以根据需要注册任意数量的事件侦听器,但请注意,默认情况下,event
侦听器同步接收事件。这意味着
块,直到所有侦听器都处理完事件。这样做的一个优势
同步和单线程方法是,当侦听器接收到事件时,它会
在发布者的事务上下文中运行(如果事务上下文为
可用。如果需要其他事件发布策略,请参阅 javadoc
对于 Spring 的 ApplicationEventMulticaster
接口
和 SimpleApplicationEventMulticaster
implementation 来获取配置选项。ApplicationListener
BlockedListEvent
onApplicationEvent()
publishEvent()
以下示例显示了用于注册和配置每个 上面的类:
<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>
把它们放在一起,当 bean 的方法是
调用,如果存在任何应阻止的电子邮件,则发布 Custom Event 类型。bean 注册为 an 并接收 ,此时它可以
通知相关方。sendEmail()
emailService
BlockedListEvent
blockedListNotifier
ApplicationListener
BlockedListEvent
Spring 的事件机制是为 Spring bean 之间的简单通信而设计的 在同一应用程序上下文中。但是,对于更复杂的企业 集成需求,单独维护的 Spring 集成项目提供了 完全支持构建轻量级、面向模式、事件驱动的 构建在众所周知的 Spring 编程模型之上的架构。 |
基于注释的事件侦听器
您可以使用注释在托管 Bean 的任何方法上注册事件侦听器。可以按如下方式重写:@EventListener
BlockedListNotifier
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
方法签名再次声明它监听的事件类型, 但是,这一次,使用灵活的名称,并且没有实现特定的侦听器接口。 事件类型也可以通过泛型缩小范围,只要实际的事件类型 在其 implementation hierarchy 中解析泛型参数。
如果你的方法应该监听多个事件,或者你想用 no 参数,也可以在 Annotation 本身上指定事件类型。这 以下示例显示了如何执行此操作:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
还可以使用属性
定义 SPEL
表达式的注释中,该表达式应匹配
以实际调用特定事件的方法。condition
以下示例显示了如何重写我们的通知器,以便仅在事件的属性等于 :content
my-event
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
每个表达式都根据专用上下文进行计算。下表列出了
项,以便您可以将它们用于条件事件处理:SpEL
名字 | 位置 | 描述 | 例 |
---|---|---|---|
事件 |
root 对象 |
实际的 . |
|
Arguments 数组 |
root 对象 |
用于调用方法的参数(作为对象数组)。 |
|
参数名称 |
评估上下文 |
任何方法参数的名称。如果由于某种原因,名称不可用
(例如,因为编译后的字节码中没有调试信息),单个
参数也可以使用语法 where 表示
argument index (从 0 开始)。 |
|
请注意,这允许您访问基础事件,即使您的方法
signature 实际上是指已发布的任意对象。#root.event
如果您需要发布事件作为处理其他事件的结果,则可以更改 method signature 返回应发布的事件,如下例所示:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
异步侦听器不支持此功能。 |
该方法为它处理的每个 FOR EACH 发布一个 new。如果需要发布多个事件,可以返回
a 或事件数组。handleBlockedListEvent()
ListUpdateEvent
BlockedListEvent
Collection
异步侦听器
如果您希望特定侦听器异步处理事件,则可以重用常规@Async
支持。
以下示例显示了如何执行此操作:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时,请注意以下限制:
-
如果异步事件侦听器抛出 ,则不会传播到 访客。有关更多详细信息,请参阅
AsyncUncaughtExceptionHandler
。Exception
-
异步事件侦听器方法无法通过返回 价值。如果需要发布另一个事件作为处理结果,请注入
ApplicationEventPublisher
以手动发布事件。
对侦听器进行排序
如果需要在调用另一个侦听器之前调用另一个侦听器,则可以将 Comments 添加到方法声明中,如下例所示:@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...
}
泛型事件
您还可以使用泛型来进一步定义事件的结构。考虑使用 where 是创建的实际实体的类型。例如,您
可以创建以下侦听器定义以仅接收 :EntityCreatedEvent<T>
T
EntityCreatedEvent
Person
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
// ...
}
由于类型擦除,仅当触发的事件解析泛型
事件侦听器过滤的参数(即类似 )。class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }
在某些情况下,如果所有事件都遵循相同的
结构(如上例中的事件所示)。在这种情况下,
您可以实现以指导框架超越运行时
环境提供。以下事件演示如何执行此操作:ResolvableTypeProvider
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {
override fun getResolvableType(): ResolvableType? {
return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
}
}
这不仅适用于您作为
一个事件。ApplicationEvent |
1.15.3. 方便地访问低级资源
为了最佳地使用和理解应用程序上下文,您应该熟悉
自己使用 Spring 的抽象,如 参考资料.Resource
应用程序上下文是 ,可用于加载对象。
A 实质上是 JDK 类的功能更丰富的版本。
实际上,包装的实现是 , 其中
适当。A 可以从
透明方式,包括从 Classpath、文件系统位置、任何位置
decpreable 替换为标准 URL 和其他一些变体。如果资源位置
string 是一个没有任何特殊前缀的简单路径,这些资源的来源是
特定于实际的应用程序上下文类型。ResourceLoader
Resource
Resource
java.net.URL
Resource
java.net.URL
Resource
您可以配置部署到应用程序上下文中的 Bean 来实现特殊的
回调接口 ,在
初始化时间,应用程序上下文本身作为 .
您还可以公开 type 的属性 ,以用于访问静态资源。
它们像任何其他属性一样被注入其中。您可以将这些属性指定为简单路径,并依赖于这些文本的自动转换
strings 添加到实际对象中。ResourceLoaderAware
ResourceLoader
Resource
Resource
String
Resource
提供给构造函数的一个或多个位置路径实际上是
资源字符串,并且以简单形式,根据特定的
context 实现。例如,将简单的
location path 作为 Classpath 位置。您还可以使用位置路径(资源字符串)
替换为特殊前缀来强制从 Classpath 或 URL 加载定义,
无论实际的上下文类型如何。ApplicationContext
ClassPathXmlApplicationContext
1.15.4. Web 应用程序的便捷 ApplicationContext 实例化
例如,您可以使用 .当然,您也可以创建实例
使用其中一个实现以编程方式。ApplicationContext
ContextLoader
ApplicationContext
ApplicationContext
您可以使用 注册 ,因为
以下示例显示:ApplicationContext
ContextLoaderListener
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
侦听器检查参数。如果参数不
存在,则侦听器将其用作默认值。当
参数确实存在,则侦听器会使用 predefined
分隔符(逗号、分号和空格),并将值用作其中
搜索应用程序上下文。还支持 Ant 样式的路径模式。
示例包括 (对于名称以 结尾且位于目录中的所有文件) 和 (对于任何子目录中的所有此类文件)。contextConfigLocation
/WEB-INF/applicationContext.xml
String
/WEB-INF/*Context.xml
Context.xml
WEB-INF
/WEB-INF/**/*Context.xml
WEB-INF
1.15.5. 将 Spring 部署为 Java EE RAR 文件ApplicationContext
可以将 Spring 部署为 RAR 文件,将
context 及其所有必需的 bean 类和库 JAR 在 Java EE RAR 部署中
单位。这相当于引导一个独立的(仅托管的
在 Java EE 环境中)能够访问 Java EE 服务器工具。RAR 部署
是部署无头 WAR 文件方案的更自然的替代方案 — 实际上,
一个没有任何 HTTP 入口点的 WAR 文件,仅用于在 Java EE 环境中引导 Spring。ApplicationContext
ApplicationContext
ApplicationContext
RAR 部署非常适合不需要 HTTP 入口点但
而是由消息终端节点和计划作业组成。在这种情况下,bean 可以
使用应用程序服务器资源,例如 JTA 事务管理器和 JNDI 绑定的 JDBC 实例和 JMS 实例,并且还可以注册
平台的 JMX 服务器 — 全部通过 Spring 的标准事务管理和 JNDI
和 JMX 支持工具。应用程序组件还可以与应用程序交互
server 的 JCA 通过 Spring 的抽象。DataSource
ConnectionFactory
WorkManager
TaskExecutor
有关 RAR 部署中涉及的配置详细信息,请参见SpringContextResourceAdapter
类的 javadoc。
要将 Spring ApplicationContext 简单部署为 Java EE RAR 文件:
-
包 所有应用程序类都合并到一个 RAR 文件(这是一个标准、JAR 文件,具有不同的 文件扩展名)。
-
将所有必需的库 JAR 添加到 RAR 存档的根目录中。
-
添加部署描述符(如
SpringContextResourceAdapter
的 javadoc 中所示) 和相应的 Spring XML bean 定义文件(通常)。META-INF/ra.xml
META-INF/applicationContext.xml
-
将生成的 RAR 文件拖放到 Application Server 的部署目录。
此类 RAR 部署单元通常是独立的。它们不暴露组件
对外界,甚至对同一应用程序的其他模块也不例外。与
基于 RAR 的 RAR 通常通过与之共享的 JMS 目标进行
其他模块。例如,基于 RAR 的 RAR 还可以安排一些作业
或对文件系统中的新文件(或类似文件)做出反应。如果需要允许同步
从外部访问,它可以(例如)导出 RMI 端点,这些端点可以使用
通过同一台计算机上的其他应用程序模块。ApplicationContext ApplicationContext |
1.16. 使用BeanFactory
API 为 Spring 的 IoC 功能提供了基础。
它的特定 Contract 主要用于与 Spring 和
相关的第三方框架及其实现
是更高级别容器中的关键委托。BeanFactory
DefaultListableBeanFactory
GenericApplicationContext
BeanFactory
和相关接口(如 、 、 )是其他框架组件的重要集成点。
通过不需要任何注释甚至反射,它们允许非常高效
容器与其组件之间的交互。应用程序级 bean 可以
使用相同的回调接口,但通常更喜欢声明性依赖项
注入,而是通过 Comments 或编程配置。BeanFactoryAware
InitializingBean
DisposableBean
请注意,核心 API 级别及其实现不会对配置格式或任何
要使用的组件注释。所有这些风格都来自扩展
(例如 和 )和
对共享库进行操作,将其作为核心元数据表示形式。
这就是 Spring 的容器如此灵活和可扩展的本质。BeanFactory
DefaultListableBeanFactory
XmlBeanDefinitionReader
AutowiredAnnotationBeanPostProcessor
BeanDefinition
1.16.1. 或 ?BeanFactory
ApplicationContext
本节介绍 和 容器级别之间的差异以及对引导程序的影响。BeanFactory
ApplicationContext
除非你有充分的理由不这样做,否则你应该使用 an 及其子类作为自定义引导的常见实现。这些是主要条目
指向 Spring 的核心容器,用于所有常见目的:加载配置
文件, 触发类路径扫描, 以编程方式注册 Bean 定义
和带 Comments 的类,以及(从 5.0 开始)注册函数式 bean 定义。ApplicationContext
GenericApplicationContext
AnnotationConfigApplicationContext
因为 an 包括 的所有功能 ,所以它是
一般推荐 平原 ,但 full
需要控制 Bean 处理。在 an(例如实现)中,检测到多种 bean
按约定(即按 Bean 名称或 Bean 类型 — 特别是后处理器),
而 Plain 对任何特殊的豆子都是不可知的。ApplicationContext
BeanFactory
BeanFactory
ApplicationContext
GenericApplicationContext
DefaultListableBeanFactory
对于许多扩展容器功能,例如注释处理和 AOP 代理,
BeanPostProcessor
扩展点是必不可少的。
如果仅使用 plain ,则此类后处理器不会
默认情况下被检测并激活。这种情况可能会令人困惑,因为
您的 bean 配置实际上没有任何问题。相反,在这种情况下,
容器需要通过其他设置完全引导。DefaultListableBeanFactory
下表列出了 和 interfaces 和实现提供的功能。BeanFactory
ApplicationContext
特征 | BeanFactory |
ApplicationContext |
---|---|---|
Bean 实例化/连接 |
是的 |
是的 |
集成的生命周期管理 |
不 |
是的 |
自动注册 |
不 |
是的 |
自动注册 |
不 |
是的 |
便捷访问(用于国际化) |
不 |
是的 |
内置发布机制 |
不 |
是的 |
要使用 ,
您需要以编程方式调用 ,如下例所示:DefaultListableBeanFactory
addBeanPostProcessor
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());
// now start using the factory
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(MyBeanPostProcessor())
// now start using the factory
要将 a 应用于普通 ,
您需要调用其方法,如下例所示:BeanFactoryPostProcessor
DefaultListableBeanFactory
postProcessBeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))
// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))
// now actually do the replacement
cfg.postProcessBeanFactory(factory)
在这两种情况下,显式注册步骤都很不方便,即
为什么在 Spring 支持的应用程序中,各种变体比 plain 更受欢迎,尤其是当
依赖 和 实例进行扩展
典型企业设置中的容器功能。ApplicationContext
DefaultListableBeanFactory
BeanFactoryPostProcessor
BeanPostProcessor
An 具有所有常见的 Annotation 后处理器
已注册,并可能在
覆盖配置注释,例如 .
在 Spring 基于 Comments 的配置模型的抽象层,
Bean 后处理器的概念变成了一个纯粹的内部容器细节。 |
2. 资源
本章介绍了 Spring 如何处理资源以及如何使用 Spring。它包括以下主题:
2.1. 简介
Java 的标准类和各种 URL 前缀的标准处理程序,
不幸的是,对于所有对低级资源的访问来说,这还不够。为
示例中,没有可用于访问
需要从 Classpath 获取的资源或相对于 .虽然可以为专用前缀注册新的处理程序(类似于前缀的现有处理程序,例如 ),这通常是
相当复杂,并且界面仍然缺乏一些理想的功能,
例如,用于检查所指向的资源是否存在的方法。java.net.URL
URL
ServletContext
URL
http:
URL
2.2. 资源接口
Spring 的接口旨在成为一个功能更强大的抽象接口
访问低级资源。下面的清单显示了接口
定义:Resource
Resource
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
interface Resource : InputStreamSource {
fun exists(): Boolean
val isOpen: Boolean
val url: URL
val file: File
@Throws(IOException::class)
fun createRelative(relativePath: String): Resource
val filename: String
val description: String
}
如接口的定义所示,它扩展了接口。下面的清单显示了接口的定义:Resource
InputStreamSource
InputStreamSource
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
interface InputStreamSource {
val inputStream: InputStream
}
界面中一些最重要的方法是:Resource
-
getInputStream()
:查找并打开资源,返回 for 从资源中读取。预计每次调用都会返回一个新的 .调用方负责关闭流。InputStream
InputStream
-
exists()
:返回一个 物理形式。boolean
-
isOpen()
:返回一个 tag,指示此资源是否表示句柄 与开放的流。如果 ,则不能多次读取 ,并且 必须只读取一次,然后关闭以避免资源泄漏。的返回 所有常用的资源实现,除了 .boolean
true
InputStream
false
InputStreamResource
-
getDescription()
:返回此资源的说明,用于错误 output 来执行。这通常是完全限定的文件名或 资源的实际 URL。
其他方法允许您获取表示
资源(如果底层实现兼容并支持
功能)。URL
File
Spring 本身广泛使用抽象,作为
需要资源时有许多方法签名。某些 Spring API 中的其他方法
(例如各种实现的构造函数)采用一个 THAT,它以朴素或简单的形式用于创建适当的
该上下文实现,或者通过路径上的特殊前缀,让
caller 指定必须创建和使用特定实现。Resource
ApplicationContext
String
Resource
String
Resource
虽然该接口与 Spring 和 Spring 一起使用很多,但它实际上是
在您自己的代码中单独用作通用工具类非常有用,用于访问
资源,即使您的代码不知道或不关心 Spring 的任何其他部分。
虽然这会将您的代码耦合到 Spring,但它实际上只将其耦合到这一小群
实用程序类,这些类可以作为
被认为等同于您用于此目的的任何其他库。Resource
URL
抽象不会取代功能。
它会尽可能地包装它。例如,a 包装一个 URL 并使用
wrapped 来执行其工作。Resource UrlResource URL |
2.3. 内置资源实现
Spring 包括以下实现:Resource
2.3.1.UrlResource
UrlResource
包装 a 并可用于访问任何
通常可通过 URL 访问,例如文件、HTTP 目标、FTP 目标等。都
URL 具有标准化的表示形式,因此适当的标准化
前缀用于指示一种 URL 类型与另一种 URL 类型。这包括
访问文件系统路径、通过 HTTP 协议访问资源、通过 FTP 访问资源等。java.net.URL
String
file:
http:
ftp:
A 由 Java 代码显式使用构造函数创建
但通常在调用采用用于表示路径的参数的 API 方法时隐式创建。对于后一种情况,JavaBeans 最终决定创建哪种类型。如果路径
string 包含众所周知的(对它来说,即)前缀(例如 ),它
为该前缀创建适当的专用。但是,如果它没有
识别前缀,则假定该字符串是标准 URL 字符串,并且
创建一个 .UrlResource
UrlResource
String
PropertyEditor
Resource
classpath:
Resource
UrlResource
2.3.2.ClassPathResource
此类表示应从 Classpath 获取的资源。它使用 线程上下文类加载器、给定类加载器或 loading resources.
此实现支持解析,就像类路径
资源驻留在文件系统中,但不适用于驻留在
jar 中,并且尚未扩展(通过 servlet 引擎或任何环境)
添加到文件系统中。为了解决这个问题,各种 implementations 始终支持
分辨率设置为 .Resource
java.io.File
Resource
java.net.URL
A 是由 Java 代码通过显式使用构造函数创建的,但通常是在调用采用用于表示路径的参数的 API 方法时隐式创建的。对于后一种情况,JavaBeans 识别字符串路径上的特殊前缀 , 和
在这种情况下创建一个。ClassPathResource
ClassPathResource
String
PropertyEditor
classpath:
ClassPathResource
2.3.3.FileSystemResource
这是 和 handles 的实现。
它支持分辨率为 a 和 .Resource
java.io.File
java.nio.file.Path
File
URL
2.3.4.ServletContextResource
这是解释
相关 Web 应用程序根目录中的相对路径。Resource
ServletContext
它始终支持流访问和 URL 访问,但仅允许访问
当 Web 应用程序存档扩展并且资源物理位于
文件系统。无论它是否被扩展、在文件系统上或被访问
直接从 JAR 或其他位置(如数据库)实际上是
依赖于 Servlet 容器。java.io.File
2.4. 使用ResourceLoader
该接口旨在由可以返回
(即 load)实例。下面的清单显示了接口定义:ResourceLoader
Resource
ResourceLoader
public interface ResourceLoader {
Resource getResource(String location);
}
interface ResourceLoader {
fun getResource(location: String): Resource
}
所有应用程序上下文都实现该接口。因此,所有
应用程序上下文可用于获取实例。ResourceLoader
Resource
当您调用特定的应用程序上下文时,位置路径
specified 没有特定的前缀,则返回一个
适合该特定应用程序上下文。例如,假设以下内容
针对实例运行的代码片段:getResource()
Resource
ClassPathXmlApplicationContext
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
val template = ctx.getResource("some/resource/path/myTemplate.txt")
对于 ,该代码返回一个 .如果运行相同的方法
对于实例,它将返回一个 .对于 a ,它将返回一个 .它同样会为每个上下文返回适当的对象。ClassPathXmlApplicationContext
ClassPathResource
FileSystemXmlApplicationContext
FileSystemResource
WebApplicationContext
ServletContextResource
因此,您可以以适合特定应用程序的方式加载资源 上下文。
另一方面,您也可以强制使用
application context 类型,通过指定特殊前缀,如下所示
示例显示:ClassPathResource
classpath:
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
同样,您可以通过指定任何标准前缀来强制使用 a。以下一对示例使用 and 前缀:UrlResource
java.net.URL
file
http
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
下表总结了将对象转换为对象的策略:String
Resource
前缀 | 例 | 解释 |
---|---|---|
类路径: |
|
从 Classpath 加载。 |
文件: |
作为 从文件系统加载。另请参阅 |
|
http: |
加载为 . |
|
(无) |
|
取决于底层 . |
2.5. 界面ResourceLoaderAware
该接口是一个特殊的回调接口,用于标识
组件。以下内容
清单显示了接口的定义:ResourceLoaderAware
ResourceLoader
ResourceLoaderAware
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
interface ResourceLoaderAware {
fun setResourceLoader(resourceLoader: ResourceLoader)
}
当类实现并部署到应用程序上下文中时
(作为 Spring 管理的 bean)中,它被应用程序识别为
上下文。然后,应用程序上下文调用 ,
将自身作为参数提供(请记住,Spring 中的所有应用程序上下文都实现了
界面)。ResourceLoaderAware
ResourceLoaderAware
setResourceLoader(ResourceLoader)
ResourceLoader
由于 an 是 ,因此 bean 还可以实现接口并使用提供的应用程序上下文直接
load 资源。但是,一般来说,如果这就是您所需要的,最好使用专用界面。该代码将仅与资源加载耦合
接口(可以被认为是一个 Util 接口),而不是整个 Spring 接口。ApplicationContext
ResourceLoader
ApplicationContextAware
ResourceLoader
ApplicationContext
在应用程序组件中,您还可以依赖 as 的自动装配
实现接口的替代方法。“传统”模式和自动装配模式(如自动装配协作者中所述)
能够为 constructor 参数或
setter 方法参数。为了获得更大的灵活性(包括
autowire fields 和多个参数方法),请考虑使用基于注释的
自动装配功能。在这种情况下,它被自动连接到一个字段
constructor 参数或期望类型为 long 的方法参数
因为有问题的 field、constructor 或 method 带有 Annotation。
有关更多信息,请参阅使用 @Autowired
。ResourceLoader
ResourceLoaderAware
constructor
byType
ResourceLoader
ResourceLoader
ResourceLoader
@Autowired
2.6. 资源作为依赖项
如果 Bean 本身将通过某种排序来确定和提供资源路径
动态进程中,bean 使用该接口来加载资源可能是有意义的。例如,考虑加载一些
sort,其中所需的特定资源取决于用户的角色。如果
资源是静态的,因此完全消除接口的使用是有意义的,让 bean 公开它需要的属性,
并期望他们被注入其中。ResourceLoader
ResourceLoader
Resource
然后注入这些属性变得微不足道的是,所有应用程序上下文
注册并使用一个特殊的 JavaBeans ,它可以转换路径
到对象。因此,如果具有 type 为 的 template 属性,则可以
为该资源配置一个简单的字符串,如下例所示:PropertyEditor
String
Resource
myBean
Resource
<bean id="myBean" class="...">
<property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>
请注意,资源路径没有前缀。因此,因为应用程序上下文本身是
将用作 ,资源本身通过 、 、 或 、 加载 、
取决于上下文的确切类型。ResourceLoader
ClassPathResource
FileSystemResource
ServletContextResource
如果需要强制使用特定类型,可以使用前缀。
以下两个示例显示了如何强制 a 和 a (后者用于访问文件系统文件):Resource
ClassPathResource
UrlResource
<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>
2.7. 应用程序上下文和资源路径
本节介绍如何使用资源(包括快捷方式)创建应用程序上下文 ,以及如何使用 XML、如何使用通配符和其他详细信息。
2.7.1. 构造应用程序上下文
应用程序上下文构造函数(针对特定的应用程序上下文类型) 将字符串或字符串数组作为资源的位置路径,例如 构成上下文定义的 XML 文件。
当此类位置路径没有前缀时,从
该路径 和 用于加载 bean 定义的路径取决于 并且适合于
特定的应用程序上下文。例如,请考虑以下示例,该示例创建一个 :Resource
ClassPathXmlApplicationContext
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
Bean 定义是从 Classpath 加载的,因为
使用。但是,请考虑以下示例,该示例创建一个 :ClassPathResource
FileSystemXmlApplicationContext
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
现在,bean 定义是从文件系统位置加载的(在本例中,相对于 当前工作目录)。
请注意,在
location path 会覆盖 created 的默认类型,以加载
定义。请考虑以下示例:Resource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
Using 从 Classpath 中加载 bean 定义。但是,它仍然是 .如果它随后用作 ,则任何
无前缀的路径仍被视为文件系统路径。FileSystemXmlApplicationContext
FileSystemXmlApplicationContext
ResourceLoader
构造实例 — 快捷方式ClassPathXmlApplicationContext
这公开了许多构造函数以启用
方便的实例化。基本思想是,您可以只提供一个字符串数组
,仅包含 XML 文件本身的文件名(没有前导路径
信息),并且还提供 .then 从提供的类中派生路径信息。ClassPathXmlApplicationContext
Class
ClassPathXmlApplicationContext
请考虑以下目录布局:
com/ foo/ services.xml daos.xml MessengerService.class
下面的示例展示了由
名为 and 的文件(在 Classpath 上)可以实例化: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. 应用程序上下文构造函数资源路径中的通配符
应用程序上下文构造函数值中的资源路径可以是简单路径(如
前面所示),每个 Target(或可能)都有
包含特殊的 “classpath*:” 前缀或内部 Ant 样式的正则表达式
(通过使用 Spring 的 Util 进行匹配)。后者两者都是有效的
通配符。Resource
PathMatcher
此机制的一个用途是当您需要执行组件样式的应用程序组装时。都
组件可以将上下文定义片段“发布”到已知的位置路径,并且
当使用前缀为 的相同路径创建最终应用程序上下文时,会自动选取所有组件片段。classpath*:
请注意,此通配符特定于应用程序上下文中资源路径的使用
构造函数(或者直接使用实用程序类层次结构时),并且是
在构建时解决。它与类型本身无关。
您不能使用前缀来构造实际的 ,因为
一个资源一次只指向一个资源。PathMatcher
Resource
classpath*:
Resource
Ant-style 模式
路径位置可以包含 Ant 样式模式,如下例所示:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
当路径位置包含 Ant 样式模式时,解析程序会遵循更复杂的过程来尝试解析
通配符。它会为直到最后一个非通配符分段的路径生成一个
从中获取 URL。如果此 URL 不是 URL 或特定于容器的变体
(例如在 WebLogic、WebSphere 等中),则 a 是
从中获取,并用于通过遍历文件系统来解析通配符。在
如果是 jar URL,解析器要么从它那里获得 a 要么
手动解析 jar URL,然后遍历 jar 文件的内容以解析
通配符。Resource
jar:
zip:
wsjar
java.io.File
java.net.JarURLConnection
对可移植性的影响
如果指定的路径已经是文件 URL(隐式,因为基是文件系统 URL,或者显式地),则通配符保证为
以完全便携的方式工作。ResourceLoader
如果指定的路径是 Classpath 位置,则解析程序必须获取最后一个
非通配符路径段 URL。由于这个
只是路径的一个节点(而不是末尾的文件),实际上它未定义(在 javadoc 中)在这种情况下返回的 URL 类型。在实践中,
它始终表示目录(其中 Classpath 资源
解析为文件系统位置)或某种类型的 jar URL(其中 Classpath 资源
解析为 jar 位置)。尽管如此,此操作仍然存在可移植性问题。Classloader.getResource()
ClassLoader
java.io.File
如果获取了最后一个非通配符段的 jar URL,则解析程序必须能够
从中获取 a 或手动解析 jar URL,以便能够
遍历 jar 的内容并解析通配符。这在大多数环境中都有效
但在其他 API 中失败,我们强烈建议 resources 的通配符解析
来自 jars 在您依赖它之前,请在您的特定环境中对其进行全面测试。java.net.JarURLConnection
前缀classpath*:
在构建基于 XML 的应用程序上下文时,位置字符串可以使用
特殊前缀,如下例所示:classpath*:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
此特殊前缀指定与给定名称匹配的所有 Classpath 资源
必须获取(在内部,这基本上是通过调用 发生 ),然后合并以形成最终应用程序
context 定义。ClassLoader.getResources(…)
通配符类路径依赖于底层
classloader 中。由于现在大多数应用程序服务器都提供自己的类加载器
实现时,行为可能会有所不同,尤其是在处理 JAR 文件时。一个
检查是否有效的简单测试是使用 Classloader 从
在 Classpath 上的 jar 中: .尝试此测试
具有相同名称但放置在两个不同位置的文件。如果
不适当的结果,请查看 Application Server 文档以获取
可能影响 ClassLoader 行为的设置。getResources() classpath* getClass().getClassLoader().getResources("<someFileInsideTheJar>") |
您还可以将前缀与
位置路径的其余部分(例如 )。在这个
的情况下,解析策略相当简单:调用
用于获取
类加载器层次结构,然后从每个资源中使用相同的分辨率
前面描述的策略用于通配符子路径。classpath*:
PathMatcher
classpath*:META-INF/*-beans.xml
ClassLoader.getResources()
PathMatcher
与通配符相关的其他说明
请注意,当与 Ant 样式模式结合使用时,仅
可靠地使用至少一个根目录,除非实际的
目标文件驻留在文件系统中。这意味着诸如 jar 文件的根目录之类的模式可能不会从 jar 文件的根目录中检索文件,而只会从
从扩展目录的根目录。classpath*:
classpath*:*.xml
Spring 检索 Classpath 条目的能力源自 JDK 的方法,该方法仅返回
空字符串(指示要搜索的潜在根)。Spring 评估运行时配置和 jar 文件中的清单
,但不能保证这会导致可移植行为。ClassLoader.getResources()
URLClassLoader
java.class.path
扫描 classpath 包需要存在相应的目录 条目。使用 Ant 构建 JAR 时,不要仅激活文件 switch 的 jar 任务。此外,Classpath 目录可能不会根据安全性公开 某些环境中的策略 — 例如,JDK 1.7.0_45 上的独立应用程序 和更高级别(这需要在您的清单中设置 'Trusted-Library')。请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在 JDK 9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常按预期工作。 在这里,强烈建议将资源放入专用目录, 避免了上述搜索 jar 文件根级别的可移植性问题。 |
不保证包含资源的 ant 样式模式能够找到匹配项
resources(如果要搜索的根包在多个类路径位置中可用)。
请考虑以下资源位置示例:classpath:
com/mycompany/package1/service-context.xml
现在考虑一个 Ant 样式的路径,有人可能会使用它来尝试查找该文件:
classpath:com/mycompany/**/service-context.xml
此类资源可能只位于一个位置,但是当路径(如前面的示例)
用于尝试解析它,则解析程序会处理 .如果此基础包节点存在于多个
ClassLoader 位置,则实际的最终资源可能不存在。因此,在这种情况下
您应该更喜欢使用相同的 Ant 样式模式,该模式
搜索包含根包的所有类路径位置。getResource("com/mycompany");
classpath*:
2.7.3. 注意事项FileSystemResource
A 未附加到 (该
is,当 a 不是实际的 ) 处理
绝对路径和相对路径。相对路径是相对于
当前工作目录,而绝对路径是相对于
文件系统。FileSystemResource
FileSystemApplicationContext
FileSystemApplicationContext
ResourceLoader
但是,出于向后兼容性(历史)原因,当 为 .强制所有附加的实例
将所有位置路径视为相对路径,无论它们是否以前导斜杠开头。
在实践中,这意味着以下示例是等效的:FileSystemApplicationContext
ResourceLoader
FileSystemApplicationContext
FileSystemResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
以下示例也是等效的(即使它们不同是有意义的,但作为一个 case 是相对的,另一个是绝对的):
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
在实践中,如果你需要真正的绝对文件系统路径,你应该避免使用
带有 OR 和
通过使用 URL 前缀强制使用 a。以下示例
演示如何执行此操作:FileSystemResource
FileSystemXmlApplicationContext
UrlResource
file:
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
3. 验证、数据绑定和类型转换
将验证视为业务逻辑有利有弊,Spring 提供了
不排除其中任何一个的验证 (和数据绑定) 设计。
具体来说,验证不应该与 Web 层相关联,并且应该易于本地化。
并且应该可以插入任何可用的验证器。考虑到这些担忧,
Spring 提供了一个既基本又非常有用的 Contract
在应用程序的每一层中。Validator
数据绑定对于将用户输入动态绑定到域非常有用
应用程序模型(或用于处理用户输入的任何对象)的Spring
提供了恰当的命名来做到这一点。和 组成了包,它主要用于但不用于
仅限于 Web 层。DataBinder
Validator
DataBinder
validation
这是 Spring Framework 中的一个基本概念,被广泛使用
的地方。但是,您可能不需要直接使用 。因为这是参考文档,所以我们觉得有一些解释
可能是有序的。我们将在本章中解释,因为,如果你是
打算使用它,你很可能在尝试将数据绑定到对象时这样做。BeanWrapper
BeanWrapper
BeanWrapper
Spring 的和较低级别的都使用 implementations 来解析和格式化属性值。and 类型是 JavaBeans 规范的一部分,也是
在本章中解释。Spring 3 引入了一个包,它提供了一个
通用类型转换工具,以及用于
格式化 UI 字段值。您可以将这些包用作实现的更简单替代方案。本章还将讨论它们。DataBinder
BeanWrapper
PropertyEditorSupport
PropertyEditor
PropertyEditorSupport
core.convert
PropertyEditorSupport
Spring 通过设置基础设施和适配器支持 Java Bean 验证
Spring 自己的合同。应用程序可以全局启用 Bean 验证一次,
如 Java Bean 验证中所述,并将其专门用于所有验证
需要。在 Web 层中,应用程序可以进一步注册控制器本地 Spring 实例,如 配置 DataBinder
中所述,它可以
对于插入自定义验证逻辑很有用。Validator
Validator
DataBinder
3.1. 使用 Spring 的 Validator 接口进行验证
Spring 具有一个可用于验证对象的接口。该接口通过使用对象来工作,因此,在验证时,
验证程序可以向对象报告验证失败。Validator
Validator
Errors
Errors
请考虑以下小型数据对象示例:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
class Person(val name: String, val age: Int)
下一个示例通过实现
接口的两种方式如下:Person
org.springframework.validation.Validator
-
supports(Class)
:这能否验证提供的实例 ?Validator
Class
-
validate(Object, org.springframework.validation.Errors)
:验证给定的对象 并且,如果出现验证错误,则将它们注册到给定的对象。Errors
实现 a 相当简单,尤其是当您知道 Spring Framework 也提供的帮助程序类时。以下内容
实例的示例实现:Validator
ValidationUtils
Validator
Person
public class PersonValidator implements Validator {
/**
* This Validator validates only Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
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")
}
}
}
类上的方法用于
如果是 Reject 属性,则拒绝该属性或空字符串。请查看 ValidationUtils
javadoc
以查看除了前面显示的示例之外,它还提供了哪些功能。static
rejectIfEmpty(..)
ValidationUtils
name
null
虽然当然可以实现单个类来验证每个
的嵌套对象中,最好将验证
logic 中每个嵌套的 Object 类在其自己的 implementation中。一个简单的
“丰富”对象的示例是由两个属性(第一个和第二个名称)和一个复杂对象组成的 A。 对象
可以独立于对象使用,因此已实现 Distinct。如果您希望重用包含的 logic
在类中,无需使用复制和粘贴,就可以
dependency-inject 或实例化 an
如下例所示:Validator
Validator
Customer
String
Address
Address
Customer
AddressValidator
CustomerValidator
AddressValidator
AddressValidator
CustomerValidator
public class CustomerValidator implements Validator {
private final Validator addressValidator;
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException("The supplied [Validator] is " +
"required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException("The supplied [Validator] must " +
"support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
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()
}
}
}
验证错误将报告给传递给验证器的对象。在这种情况下
中,你可以使用 tag 来检查错误消息,但是
您也可以自己检查对象。有关
它提供的方法可以在 Javadoc 中找到。Errors
<spring:bind/>
Errors
3.2. 将代码解析为错误消息
我们介绍了数据绑定和验证。本节介绍如何输出对应的消息
验证错误。在上一节所示的示例中,
我们拒绝了 and 字段。如果我们想使用 a 输出错误消息,可以使用我们在拒绝字段时提供的错误代码来实现
(在本例中为 'name' 和 'age')。当您调用 (直接或间接, 通过使用
例如,类)或其他方法之一
从接口中,底层实现不仅会注册
传入,但还会注册许多其他错误代码。这决定了接口注册的错误代码。默认情况下,使用 the ,它(例如)不仅注册消息
使用您提供的代码,但还会注册包含您传递的字段名称的消息
添加到 reject 方法中。因此,如果您使用 拒绝字段 ,
除了代码之外, Spring 还注册了 and(第一个包含字段名称,第二个包含类型
的字段)。这样做是为了方便开发人员在定位错误消息时提供帮助。name
age
MessageSource
ValidationUtils
rejectValue
reject
Errors
MessageCodesResolver
Errors
DefaultMessageCodesResolver
rejectValue("age", "too.darn.old")
too.darn.old
too.darn.old.age
too.darn.old.age.int
有关 和 default 策略的更多信息,请参见
在 MessageCodesResolver
和 DefaultMessageCodesResolver
的 javadoc 中,
分别。MessageCodesResolver
3.3. Bean 操作和BeanWrapper
该软件包遵循 JavaBeans 标准。
JavaBean 是一个具有默认无参数构造函数的类,它遵循
命名约定,其中(例如)名为 would 的属性
具有 setter 方法和 getter 方法。为
有关 JavaBeans 和规范的更多信息,请参阅 JavaBeans。org.springframework.beans
bingoMadness
setBingoMadness(..)
getBingoMadness()
bean 包中一个非常重要的类是接口及其
相应的实现 ()。正如 javadoc 中引用的那样,提供了设置和获取属性值(单独或在
bulk)、获取属性描述符和查询属性以确定它们是否为
readable 或 writable。此外,它还提供对嵌套属性
将 子属性 的 属性设置为 无限深度 。还支持添加标准 JavaBeans 和 的功能,而无需在目标类中支持代码。
最后但并非最不重要的一点是,它支持设置索引属性。
通常不由应用程序代码直接使用,但由 和 使用 。BeanWrapper
BeanWrapperImpl
BeanWrapper
BeanWrapper
BeanWrapper
PropertyChangeListeners
VetoableChangeListeners
BeanWrapper
BeanWrapper
DataBinder
BeanFactory
它的工作方式部分由它的名称表示:它将一个 bean 包装到
对该 Bean 执行操作,例如设置和检索属性。BeanWrapper
3.3.1. 设置和获取 Basic 和 Nested 属性
设置和获取属性是通过 的 和 重载方法变体完成的。请参阅他们的 Javadoc 以获取
详。下表显示了这些约定的一些示例:setPropertyValue
getPropertyValue
BeanWrapper
表达 | 解释 |
---|---|
|
指示与 or 和 方法对应的属性。 |
|
指示对应于
(例如)OR 方法。 |
|
指示 indexed property 的第三个元素 。索引属性
可以是 , , 或其他自然有序的集合。 |
|
指示由属性的键编制索引的映射条目的值。 |
(如果您不打算使用
的 直接 。如果您只使用 和 以及它们的默认实现,您应该跳到 PropertyEditor
部分。BeanWrapper
DataBinder
BeanFactory
以下两个示例类使用 to get 和 set
性能:BeanWrapper
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
class Company {
var name: String? = null
var managingDirector: Employee? = null
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
class Employee {
var name: String? = null
var salary: Float? = null
}
以下代码片段显示了如何检索和操作某些
instantiated 和 :Companies
Employees
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)
// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)
// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?
3.3.2. 内置实现PropertyEditor
Spring 使用 a 的概念来实现 an 和 a 之间的转换。它可以很方便
以不同于对象本身的方式表示属性。例如,a 可以用人类可读的方式表示(如 :),而
我们仍然可以将人类可读的形式转换回原始日期(或者,甚至
更好的是,将以人类可读形式输入的任何日期转换回对象)。这
可以通过注册 类型的自定义编辑器来实现行为。在 or 上注册自定义编辑器,
或者,在特定的 IoC 容器中(如上一章所述),会给出
了解如何将属性转换为所需的类型。有关 的更多信息,请参阅 Oracle 的 java.beans
软件包的 javadoc。PropertyEditor
Object
String
Date
String
'2007-14-09'
Date
java.beans.PropertyEditor
BeanWrapper
PropertyEditor
在 Spring 中使用属性编辑的几个示例:
-
在 bean 上设置属性是通过使用实现来完成的。 当你将声明的某个 bean 的值用作 在 XML 文件中,Spring (如果相应属性的 setter 具有参数)用于尝试将参数解析为对象。
PropertyEditor
String
Class
ClassEditor
Class
-
在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种来完成的 的实现,您可以在 .
PropertyEditor
CommandController
Spring 具有许多内置实现,使生活变得轻松。
它们都位于包中。默认情况下,大多数(但不是全部,如下表所示)由 注册。如果属性编辑器可以以某种方式进行配置,则可以
仍然注册你自己的变体来覆盖默认的变体。下表描述
Spring 提供的各种实现:PropertyEditor
org.springframework.beans.propertyeditors
BeanWrapperImpl
PropertyEditor
类 | 解释 |
---|---|
|
字节数组的编辑器。将字符串转换为相应的字节
交涉。默认情况下由 注册。 |
|
将表示类的 String 解析为实际的类,反之亦然。当
class 的 git 中,则会引发 an。默认情况下,由 注册。 |
|
用于属性的可自定义属性编辑器。默认情况下,由 registered by 但可以通过将其自定义实例注册为
自定义编辑器。 |
|
集合的 Property 编辑器,将任何源转换为给定的目标类型。 |
|
的可自定义属性编辑器 ,支持自定义 .不
registered (默认)。必须根据需要使用适当的格式进行用户注册。 |
|
任何子类的可自定义属性编辑器,例如 、 、 或 。默认情况下,由 registered by 但可以被
将其自定义实例注册为 Custom Editor。 |
|
将字符串解析为对象。默认情况下,由 注册。 |
|
单向属性编辑器,可以接受一个字符串并生成(通过
intermediate 和 ) an,以便可以直接将属性设置为字符串。请注意,默认用法不会关闭
的为你。默认情况下,由 注册。 |
|
可以将字符串解析为对象,反之亦然(字符串格式为 ,与 的方法相同)。默认情况下,由 注册。 |
|
可以将字符串解析为对象,反之亦然。 |
|
可以将字符串(使用类的 javadoc 中定义的格式进行格式化)转换为对象。默认情况下,已注册
由。 |
|
修剪字符串的 Property editor。(可选)允许转换空字符串
转换为值。默认情况下未注册 — 必须由用户注册。 |
|
可以将 URL 的字符串表示形式解析为实际对象。
默认情况下,由 注册。 |
Spring 使用 来设置 属性的搜索路径
可能需要的编辑器。搜索路径还包括 ,其中
包括类型(如 、 和大多数
原始类型。另请注意,标准的 JavaBeans 基础结构
自动发现类(无需注册它们
显式地)如果它们与它们处理的类位于同一 package 中,并且具有相同的
name 作为该类,并附加例如,可以有以下内容
class 和 package 结构,这足以使 class 为
识别并用作 for -typed 属性。java.beans.PropertyEditorManager
sun.bean.editors
PropertyEditor
Font
Color
PropertyEditor
Editor
SomethingEditor
PropertyEditor
Something
com chank pop Something SomethingEditor // the PropertyEditor for the Something class
请注意,您也可以在此处使用标准的 JavaBeans 机制
(在这里有一定程度的描述)。以下示例使用该机制
使用
关联类:BeanInfo
BeanInfo
PropertyEditor
com chank pop Something SomethingBeanInfo // the BeanInfo for the Something class
引用类的以下 Java 源代码
将 a 与类的属性相关联:SomethingBeanInfo
CustomNumberEditor
age
Something
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
class SomethingBeanInfo : SimpleBeanInfo() {
override fun getPropertyDescriptors(): Array<PropertyDescriptor> {
try {
val numberPE = CustomNumberEditor(Int::class.java, true)
val ageDescriptor = object : PropertyDescriptor("age", Something::class.java) {
override fun createPropertyEditor(bean: Any): PropertyEditor {
return numberPE
}
}
return arrayOf(ageDescriptor)
} catch (ex: IntrospectionException) {
throw Error(ex.toString())
}
}
}
注册其他自定义实现PropertyEditor
当将 Bean 属性设置为字符串值时,Spring IoC 容器最终会使用
标准 JavaBeans 实现将这些字符串转换为
财产。Spring 预先注册了许多自定义实现(例如,将
将表示为 String 的类名转换为 Object)。此外
Java 的标准 JavaBeans 查找机制允许对类进行适当命名,并将其与类放在同一个包中
为此,它提供支持,以便可以自动找到它。PropertyEditor
PropertyEditor
Class
PropertyEditor
PropertyEditor
如果需要注册其他自定义,有几种机制是
可用。最手动的方法,通常不方便或
推荐,就是使用接口的方法,假设你有个参考。
另一种(稍微方便一点)机制是使用特殊的咖啡豆工厂
名为 .虽然您可以使用 Bean Factory 后处理器
对于实现,它有一个
nested 属性设置,因此我们强烈建议您将它与 一起使用,在那里您可以以与任何其他 bean 类似的方式部署它,并且
可以自动检测和应用。PropertyEditors
registerCustomEditor()
ConfigurableBeanFactory
BeanFactory
CustomEditorConfigurer
BeanFactory
CustomEditorConfigurer
ApplicationContext
请注意,所有 bean 工厂和应用程序上下文都会自动使用一些
内置属性编辑器,通过使用 A to
处理属性转换。寄存器的标准属性编辑器在上一节中列出。
此外,还可以覆盖或添加其他编辑器来处理
资源查找。BeanWrapper
BeanWrapper
ApplicationContexts
标准 JavaBeans 实例用于转换属性值
表示为属性的实际复杂类型的字符串。您可以使用 ,一个 bean factory 后处理器,方便地添加
支持将其他实例复制到 .PropertyEditor
CustomEditorConfigurer
PropertyEditor
ApplicationContext
请考虑以下示例,该示例定义了一个名为 和
另一个名为 的类,需要将其设置为属性:ExoticType
DependsOnExoticType
ExoticType
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
package example
class ExoticType(val name: String)
class DependsOnExoticType {
var type: ExoticType? = null
}
当事情设置正确时,我们希望能够将 type 属性分配为
string,将其转换为实际实例。以下 Bean 定义显示了如何设置此关系:PropertyEditor
ExoticType
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
实现可能类似于以下内容:PropertyEditor
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
// converts string representation to ExoticType object
package example
import java.beans.PropertyEditorSupport
class ExoticTypeEditor : PropertyEditorSupport() {
override fun setAsText(text: String) {
value = ExoticType(text.toUpperCase())
}
}
最后,以下示例显示了如何使用 向 注册 new,然后 将能够根据需要使用它:CustomEditorConfigurer
PropertyEditor
ApplicationContext
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
用PropertyEditorRegistrar
向 Spring 容器注册属性编辑器的另一种机制是
创建并使用 .此接口在以下情况下特别有用
您需要在几种不同情况下使用同一组属性编辑器。
您可以编写相应的注册商并在每种情况下重复使用它。 实例与一个名为 的接口一起工作,该接口由 Spring(和 )实现。 实例特别方便
当与 (在此处描述) 结合使用时,它会公开一个属性
叫。 已添加的实例
以这种方式可以轻松共享给 和
Spring MVC 控制器。此外,它还避免了在自定义时进行同步的需要
editors:A 应为每次 bean 创建尝试创建新的实例。PropertyEditorRegistrar
PropertyEditorRegistrar
PropertyEditorRegistry
BeanWrapper
DataBinder
PropertyEditorRegistrar
CustomEditorConfigurer
setPropertyEditorRegistrars(..)
PropertyEditorRegistrar
CustomEditorConfigurer
DataBinder
PropertyEditorRegistrar
PropertyEditor
以下示例显示如何创建自己的实现:PropertyEditorRegistrar
package com.foo.editors.spring;
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...
}
}
另请参阅 for an example implementation.请注意,在该方法的实现中,它是如何创建每个属性编辑器的新实例。org.springframework.beans.support.ResourceEditorRegistrar
PropertyEditorRegistrar
registerCustomEditors(..)
下一个示例展示了如何配置 a 并将 our 的实例注入其中:CustomEditorConfigurer
CustomPropertyEditorRegistrar
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后(对于你们这些人来说,这与本章的重点有点不同
使用 Spring 的 MVC Web 框架),在
与数据绑定(如 )结合使用可以非常
方便。以下示例在
方法的实现:PropertyEditorRegistrars
Controllers
SimpleFormController
PropertyEditorRegistrar
initBinder(..)
public final class RegisterUserController extends SimpleFormController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods to do with registering a User
}
class RegisterUserController(
private val customPropertyEditorRegistrar: PropertyEditorRegistrar) : SimpleFormController() {
protected fun initBinder(request: HttpServletRequest,
binder: ServletRequestDataBinder) {
this.customPropertyEditorRegistrar.registerCustomEditors(binder)
}
// other methods to do with registering a User
}
这种注册样式可以产生简洁的代码(实现
of 只有一行长),并允许将公共注册码封装在一个类中,然后根据需要在任意数量的类之间共享。PropertyEditor
initBinder(..)
PropertyEditor
Controllers
3.4. Spring Type Conversion
Spring 3 引入了一个包,它提供了一个通用的类型转换
系统。系统定义了一个 SPI 来实现类型转换逻辑和一个 API
在运行时执行类型转换。在 Spring 容器中,您可以使用此系统
作为转换外部化 bean 属性值的实现的替代方法
strings 设置为所需的属性类型。您还可以在
需要类型转换的应用程序。core.convert
PropertyEditor
3.4.1. 转换器 SPI
实现类型转换逻辑的 SPI 简单且强类型,如下所示 接口定义显示:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
package org.springframework.core.convert.converter
interface Converter<S, T> {
fun convert(source: S): T
}
要创建自己的转换器,请实现接口并参数化为要转换的源类型和要转换的类型。您还可以透明地应用这样的
converter (如果需要
转换为 的数组或集合,前提是委托数组或集合
converter 也已注册(默认情况下已注册)。Converter
S
T
S
T
DefaultConversionService
对于对 的每次调用,保证 source 参数不为 null。如果转换失败,您可以抛出任何未经检查的异常。具体来说,它应该抛出一个 an 来报告一个无效的源值。
请注意确保您的实现是线程安全的。convert(S)
Converter
IllegalArgumentException
Converter
包中提供了几种转换器实现,如
一种便利。其中包括从字符串到数字和其他常见类型的转换器。
下面的清单显示了该类,这是一个典型的实现:core.convert.support
StringToInteger
Converter
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
package org.springframework.core.convert.support
import org.springframework.core.convert.converter.Converter
internal class StringToInteger : Converter<String, Int> {
override fun convert(source: String): Int? {
return Integer.valueOf(source)
}
}
3.4.2. 使用ConverterFactory
当您需要集中整个类层次结构的转换逻辑时
(例如,在 Convert from to Objects 时),您可以实施 ,如下例所示:String
Enum
ConverterFactory
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
package org.springframework.core.convert.converter
interface ConverterFactory<S, R> {
fun <T : R> getConverter(targetType: Class<T>): Converter<S, T>
}
将 S 参数化为要转换的起始类型,将 R 参数化为定义基本类型
您可以转换为的类的范围。然后实施 ,
其中 T 是 R 的子类。getConverter(Class<T>)
以 为例:StringToEnumConverterFactory
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
3.4.3. 使用GenericConverter
当您需要复杂的实现时,请考虑使用接口。使用更灵活但类型不太强的签名
than ,a 支持在多个源和
目标类型。此外,还提供了 source 和 target 字段
context 中,您可以在实施转化逻辑时使用。这样的上下文允许
类型转换由字段注释或在
字段签名。以下清单显示了 的接口定义:Converter
GenericConverter
Converter
GenericConverter
GenericConverter
GenericConverter
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
package org.springframework.core.convert.converter
interface GenericConverter {
fun getConvertibleTypes(): Set<ConvertiblePair>?
fun convert(@Nullable source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any?
}
要实现 , have 返回支持的
source→target 类型对。然后 implement 来包含您的转换逻辑。源提供
访问保存要转换的值的 source 字段。目标提供对要设置转换值的目标字段的访问。GenericConverter
getConvertibleTypes()
convert(Object, TypeDescriptor,
TypeDescriptor)
TypeDescriptor
TypeDescriptor
一个很好的例子是在 Java 数组之间转换的转换器
和一个集合。这样的 an 内省了声明
目标集合类型,用于解析集合的元素类型。这样,每个
元素转换为 collection 元素类型,然后再将
collection 在 target 字段上设置。GenericConverter
ArrayToCollectionConverter
因为是一个更复杂的 SPI 接口,所以你应该使用
它只在你需要的时候。偏爱或基本型
转换需求。GenericConverter Converter ConverterFactory |
用ConditionalGenericConverter
有时,您希望 a 仅在特定条件成立时运行 。为
例如,您可能希望仅在存在特定注释时运行
在 target 字段上,或者您可能希望仅在特定方法时运行
(例如方法)在 Target 类上定义。 是 和 接口的联合,允许您定义此类自定义匹配条件:Converter
Converter
Converter
static valueOf
ConditionalGenericConverter
GenericConverter
ConditionalConverter
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
interface ConditionalConverter {
fun matches(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean
}
interface ConditionalGenericConverter : GenericConverter, ConditionalConverter
a 的一个很好的例子是 that convert
在持久实体标识符和实体引用之间。仅当目标实体类型声明静态查找器方法(例如,)时,此类 才可能匹配。您可以在 的实现中执行此类 finder 方法检查。ConditionalGenericConverter
IdToEntityConverter
IdToEntityConverter
findAccount(Long)
matches(TypeDescriptor, TypeDescriptor)
3.4.4. APIConversionService
ConversionService
定义一个统一的 API,用于在
运行。转换器通常在以下 Facade 接口后面运行:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
package org.springframework.core.convert
interface ConversionService {
fun canConvert(sourceType: Class<*>, targetType: Class<*>): Boolean
fun <T> convert(source: Any, targetType: Class<T>): T
fun canConvert(sourceType: TypeDescriptor, targetType: TypeDescriptor): Boolean
fun convert(source: Any, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any
}
大多数实现还实现了 ,该
提供用于注册转换器的 SPI。在内部,实现委托其注册的转换器来执行类型转换逻辑。ConversionService
ConverterRegistry
ConversionService
包中提供了健壮的实现。 通用实现是否适合
在大多数环境中使用。 提供便捷的工厂
创建通用配置。ConversionService
core.convert.support
GenericConversionService
ConversionServiceFactory
ConversionService
3.4.5. 配置ConversionService
A 是无状态对象,旨在在 application 中实例化
startup 的 URL,然后在多个线程之间共享。在 Spring 应用程序中,您通常
为每个 Spring 容器(或 )配置一个实例。
Spring 会拾取它,并在 type
转换需要由框架执行。您还可以将其注入到任何 bean 中并直接调用它。ConversionService
ConversionService
ApplicationContext
ConversionService
ConversionService
如果 no 注册到 Spring,则基于原始 -
系统。ConversionService PropertyEditor |
要向 Spring 注册默认值,请添加以下 bean 定义
其中 为 :ConversionService
id
conversionService
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认值可以在字符串、数字、枚举、集合、
映射和其他常见类型。要使用
own' custom converters,请设置属性。属性值可以实现
任何 、 或 接口。ConversionService
converters
Converter
ConverterFactory
GenericConverter
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
在 Spring MVC 应用程序中使用 a 也很常见。参见 Spring MVC 一章中的转换和格式化。ConversionService
在某些情况下,您可能希望在转换过程中应用格式。有关使用 的详细信息,请参阅 FormatterRegistry
SPI 。FormattingConversionServiceFactoryBean
3.4.6. 以编程方式使用ConversionService
要以编程方式使用实例,您可以注入对
就像你对任何其他豆子所做的那样。以下示例显示了如何执行此操作:ConversionService
@Service
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(...)
}
}
对于大多数使用案例,您可以使用指定 , 但
不适用于更复杂的类型,例如参数化元素的集合。
例如,如果要以编程方式将 a of 转换为 a of,
您需要提供源类型和目标类型的正式定义。convert
targetType
List
Integer
List
String
幸运的是,提供了各种选项来简化操作,
如下例所示:TypeDescriptor
DefaultConversionService cs = new DefaultConversionService();
List<Integer> input = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
val cs = DefaultConversionService()
val input: List<Integer> = ...
cs.convert(input,
TypeDescriptor.forObject(input), // List<Integer> type descriptor
TypeDescriptor.collection(List::class.java, TypeDescriptor.valueOf(String::class.java)))
请注意,会自动注册
适用于大多数环境。这包括集合转换器、标量
转换器和基本 -to- 转换器。您可以注册相同的转换器
with any 通过对类使用 static 方法。DefaultConversionService
Object
String
ConverterRegistry
addDefaultConverters
DefaultConversionService
值类型的转换器被重新用于数组和集合,因此有
无需创建特定的转换器即可从 a of 转换为 of ,前提是标准集合处理是合适的。Collection
S
Collection
T
3.5. Spring Field 格式化
如上一节所述,core.convert
是一个
通用型转换系统。它提供了一个统一的 API,如
以及一个强类型 SPI,用于实现一种类型的转换逻辑
到另一个。Spring 容器使用此系统来绑定 bean 属性值。在
此外,Spring 表达式语言 (SpEL) 和使用此系统都可以
bind 字段值。例如,当 SPEL 需要强制 a 到 a 到
完成 an attempt,系统执行强制转换。ConversionService
Converter
DataBinder
Short
Long
expression.setValue(Object bean, Object value)
core.convert
现在考虑典型客户端环境的类型转换要求,例如
Web 或桌面应用程序。在此类环境中,您通常会从 转换为 以支持客户端回发过程,以及转换回以支持
View 渲染过程。此外,您通常需要本地化值。越多
通用 SPI 不满足此类格式要求
径直。为了直接解决这些问题,Spring 3 引入了一个方便的 SPI,它
为客户端环境的实现提供了一种简单而强大的替代方案。String
String
String
core.convert
Converter
Formatter
PropertyEditor
一般来说,当你需要实现通用类型时,你可以使用 SPI
转换逻辑 — 例如,用于在 a 和 a 之间进行转换。
当您在客户端环境(例如 Web
application),并且需要解析和打印本地化的字段值。它为两个 SPI 提供了一个统一的类型转换 API。Converter
java.util.Date
Long
Formatter
ConversionService
3.5.1. SPIFormatter
用于实现字段格式化逻辑的 SPI 很简单,并且是强类型的。这
以下清单显示了接口定义:Formatter
Formatter
package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
从 和 Building Block 接口扩展。这
下面的清单显示了这两个接口的定义:Printer
Parser
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
interface Printer<T> {
fun print(fieldValue: T, locale: Locale): String
}
import java.text.ParseException;
public interface Parser<T> {
T parse(String clientValue, Locale locale) throws ParseException;
}
interface Parser<T> {
@Throws(ParseException::class)
fun parse(clientValue: String, locale: Locale): T
}
要创建自己的 ,请实现前面显示的接口。
参数化为要格式化的对象类型,例如 .实现打印 for 实例的操作
display in client locale 中。实现该操作以从客户端区域设置返回的格式化表示形式解析 的实例。如果解析尝试失败,您应该抛出 a 或 an。拿
注意确保您的实现是线程安全的。Formatter
Formatter
T
java.util.Date
print()
T
parse()
T
Formatter
ParseException
IllegalArgumentException
Formatter
为方便起见,子包提供了多种实现。
该软件包提供 、 和 格式使用 .
该软件包提供了一个用于格式化对象的
一个。该软件包提供了全面的日期时间
基于 Joda-Time 库的格式设置支持。format
Formatter
number
NumberStyleFormatter
CurrencyStyleFormatter
PercentStyleFormatter
Number
java.text.NumberFormat
datetime
DateFormatter
java.util.Date
java.text.DateFormat
datetime.joda
下面是一个示例实现:DateFormatter
Formatter
package org.springframework.format.datetime;
public final class DateFormatter implements Formatter<Date> {
private String pattern;
public DateFormatter(String pattern) {
this.pattern = pattern;
}
public String print(Date date, Locale locale) {
if (date == null) {
return "";
}
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
protected DateFormat getDateFormat(Locale locale) {
DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
dateFormat.setLenient(false);
return dateFormat;
}
}
class DateFormatter(private val pattern: String) : Formatter<Date> {
override fun print(date: Date, locale: Locale)
= getDateFormat(locale).format(date)
@Throws(ParseException::class)
override fun parse(formatted: String, locale: Locale)
= getDateFormat(locale).parse(formatted)
protected fun getDateFormat(locale: Locale): DateFormat {
val dateFormat = SimpleDateFormat(this.pattern, locale)
dateFormat.isLenient = false
return dateFormat
}
}
Spring 团队欢迎社区驱动的贡献。请参阅 GitHub Issues to contribute。Formatter
3.5.2. 注解驱动的格式化
字段格式可以按字段类型或注释进行配置。绑定
对 , implement 的注释。以下内容
清单显示了接口的定义:Formatter
AnnotationFormatterFactory
AnnotationFormatterFactory
package org.springframework.format;
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
package org.springframework.format
interface AnnotationFormatterFactory<A : Annotation> {
val fieldTypes: Set<Class<*>>
fun getPrinter(annotation: A, fieldType: Class<*>): Printer<*>
fun getParser(annotation: A, fieldType: Class<*>): Parser<*>
}
要创建实施:
.将 A 参数化为要关联的字段
格式设置逻辑 — 例如 .
.Have 返回可以使用注释的字段类型。
.Have return a 打印带注释的字段的值。
.Have return a 解析带注释字段的 a。annotationType
org.springframework.format.annotation.DateTimeFormat
getFieldTypes()
getPrinter()
Printer
getParser()
Parser
clientValue
以下示例实现将 annotation 绑定到格式化程序,以使数字样式或模式为
指定:AnnotationFormatterFactory
@NumberFormat
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
Short.class, Integer.class, Long.class, Float.class,
Double.class, BigDecimal.class, BigInteger.class }));
}
public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
return configureFormatterFrom(annotation, fieldType);
}
private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
if (!annotation.pattern().isEmpty()) {
return new NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
}
}
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
override fun getFieldTypes(): Set<Class<*>> {
return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
}
override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
return configureFormatterFrom(annotation, fieldType)
}
override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
return configureFormatterFrom(annotation, fieldType)
}
private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
return if (annotation.pattern.isNotEmpty()) {
NumberStyleFormatter(annotation.pattern)
} else {
val style = annotation.style
when {
style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
else -> NumberStyleFormatter()
}
}
}
}
要触发格式设置,您可以使用 @NumberFormat 注释字段,如下所示 示例显示:
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
格式注释 API
包中存在可移植格式注释 API。您可以用于设置字段的格式,例如 and ,以及设置、 (用于毫秒时间戳) 以及 JSR-310 和 Joda-Time 值类型的格式。org.springframework.format.annotation
@NumberFormat
Number
Double
Long
@DateTimeFormat
java.util.Date
java.util.Calendar
Long
java.time
以下示例用于将 a 格式化为 ISO 日期
(yyyy-MM-dd):@DateTimeFormat
java.util.Date
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
class MyModel(
@DateTimeFormat(iso= ISO.DATE) private val date: Date
)
3.5.3. SPIFormatterRegistry
这是一个用于注册格式化程序和转换器的 SPI。 是 适用于
大多数环境。您可以以编程方式或声明方式配置此变体
作为 Spring bean 进行设置,例如通过使用 .因为这个
implementation 也实现了,可以直接配置
用于 Spring 和 Spring 表达式语言 (SpEL)。FormatterRegistry
FormattingConversionService
FormatterRegistry
FormattingConversionServiceFactoryBean
ConversionService
DataBinder
下面的清单显示了 SPI:FormatterRegistry
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<*>)
}
如前面的清单所示,您可以按字段类型或注释注册格式化程序。
SPI 允许您集中配置格式规则,而不是
在您的控制器之间复制此类配置。例如,您可能希望
强制所有日期字段都以某种方式格式化,或者强制字段具有特定的
annotation 以某种方式格式化。使用共享的 ,您可以定义
这些规则一次,每当需要格式化时都会应用它们。FormatterRegistry
FormatterRegistry
3.5.4. SPIFormatterRegistrar
FormatterRegistrar
是一个 SPI,用于通过
FormatterRegistry 的下面的清单显示了它的接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
package org.springframework.format
interface FormatterRegistrar {
fun registerFormatters(registry: FormatterRegistry)
}
A 在注册多个相关转换器时很有用,并且
给定格式类别的格式化程序,例如日期格式。它也可以是
在声明式注册不足时很有用 — 例如,当格式化程序
需要在不同于其自身的特定字段类型下编制索引,或者
注册 / 对。下一节提供了有关
转换器和格式化程序注册。FormatterRegistrar
<T>
Printer
Parser
3.5.5. 在 Spring MVC 中配置格式化
参见 Spring MVC 一章中的转换和格式化。
3.6. 配置全局日期和时间格式
默认情况下,未注释的日期和时间字段将从
strings 的 Strings。如果您愿意,可以通过以下方式更改此设置
定义您自己的全局格式。@DateTimeFormat
DateFormat.SHORT
为此,请确保 Spring 不注册默认格式化程序。相反,请注册 格式化程序:
-
org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
-
org.springframework.format.datetime.DateFormatterRegistrar
或 Joda-Time 的 Joda-Time 的 Torg.springframework.format.datetime.joda.JodaTimeFormatterRegistrar
例如,以下 Java 配置注册了全局格式:yyyyMMdd
@Configuration
public class AppConfig {
@Bean
public FormattingConversionService conversionService() {
// Use the DefaultFormattingConversionService but do not register defaults
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
// Ensure @NumberFormat is still supported
conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Register JSR-310 date conversion with a specific global format
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"));
registrar.registerFormatters(conversionService);
// Register date conversion with a specific global format
DateFormatterRegistrar registrar = new DateFormatterRegistrar();
registrar.setFormatter(new DateFormatter("yyyyMMdd"));
registrar.registerFormatters(conversionService);
return conversionService;
}
}
@Configuration
class AppConfig {
@Bean
fun conversionService(): FormattingConversionService {
// Use the DefaultFormattingConversionService but do not register defaults
return DefaultFormattingConversionService(false).apply {
// Ensure @NumberFormat is still supported
addFormatterForFieldAnnotation(NumberFormatAnnotationFormatterFactory())
// Register JSR-310 date conversion with a specific global format
val registrar = DateTimeFormatterRegistrar()
registrar.setDateFormatter(DateTimeFormatter.ofPattern("yyyyMMdd"))
registrar.registerFormatters(this)
// Register date conversion with a specific global format
val registrar = DateFormatterRegistrar()
registrar.setFormatter(DateFormatter("yyyyMMdd"))
registrar.registerFormatters(this)
}
}
}
如果您更喜欢基于 XML 的配置,可以使用 .以下示例显示了如何执行此操作(这次使用 Joda
时间):FormattingConversionServiceFactoryBean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="registerDefaultFormatters" value="false" />
<property name="formatters">
<set>
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory" />
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar">
<property name="dateFormatter">
<bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
<property name="pattern" value="yyyyMMdd"/>
</bean>
</property>
</bean>
</set>
</property>
</bean>
</beans>
请注意,在 Web 中配置日期和时间格式时,还需要注意一些额外的注意事项 应用。请参阅 WebMVC 转换和格式化 或 WebFlux 转换和格式化。
3.7. Java Bean 验证
Spring Framework 提供了对 Java Bean 验证 API 的支持。
3.7.1. Bean 验证概述
Bean Validation 提供了一种通用的验证方法,通过 constraint declaration 和 元数据。要使用它,您可以使用 声明性验证约束,然后由运行时强制执行。有 built-in constraints,您还可以定义自己的自定义 constraints。
请考虑以下示例,该示例显示了一个具有两个属性的简单模型:PersonForm
public class PersonForm {
private String name;
private int age;
}
class PersonForm(
private val name: String,
private val age: Int
)
Bean 验证允许您声明约束,如下例所示:
public class PersonForm {
@NotNull
@Size(max=64)
private String name;
@Min(0)
private int age;
}
class PersonForm(
@get:NotNull @get:Size(max=64)
private val name: String,
@get:Min(0)
private val age: Int
)
然后,Bean 验证器根据声明的 约束。有关 API 的 API 创建。请参阅 Hibernate Validator 文档 特定约束。了解如何将 Bean 验证提供程序设置为 Spring Bean,请继续阅读。
3.7.2. 配置 Bean 验证提供程序
Spring 提供了对 Bean 验证 API 的全面支持,包括
Bean Validation 提供程序作为 Spring Bean。这允许您注入 or 验证所在的
在您的应用程序中需要。javax.validation.ValidatorFactory
javax.validation.Validator
你可以使用 将默认的 Validator 配置为 Spring
bean,如下例所示:LocalValidatorFactoryBean
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@Configuration
public class AppConfig {
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
前面示例中的基本配置触发 bean 验证以通过以下方式初始化 使用其默认的引导机制。Bean Validation 提供程序,例如 Hibernate Validator 应存在于 Classpath 中,并被自动检测。
注入验证器
LocalValidatorFactoryBean
实现 和 ,以及 Spring 的 。
您可以将对这些接口中任一接口的引用注入到需要调用
验证逻辑。javax.validation.ValidatorFactory
javax.validation.Validator
org.springframework.validation.Validator
如果你更喜欢使用 Bean,可以注入一个引用
验证 API,如下例所示:javax.validation.Validator
import javax.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import javax.validation.Validator;
@Service
class MyService(@Autowired private val validator: Validator)
您可以注入对 bean 的引用
需要 Spring Validation API,如下例所示:org.springframework.validation.Validator
import org.springframework.validation.Validator;
@Service
public class MyService {
@Autowired
private Validator validator;
}
import org.springframework.validation.Validator
@Service
class MyService(@Autowired private val validator: Validator)
配置自定义约束
每个 bean 验证约束由两部分组成:
-
声明约束及其可配置属性的注释。
@Constraint
-
实现 约束的行为。
javax.validation.ConstraintValidator
为了将声明与实现相关联,每个注解
引用相应的实现类。在运行时,当
约束注释。@Constraint
ConstraintValidator
ConstraintValidatorFactory
默认情况下,配置使用 Spring 创建实例。这使您的自定义可以像任何其他 Spring bean 一样从依赖关系注入中受益。LocalValidatorFactoryBean
SpringConstraintValidatorFactory
ConstraintValidator
ConstraintValidators
以下示例显示了一个自定义声明,后跟一个使用 Spring 进行依赖项注入的关联实现:@Constraint
ConstraintValidator
@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 {
// ...
}
如前面的示例所示,实现可以像任何其他 Spring bean 一样具有其依赖项。ConstraintValidator
@Autowired
Spring 驱动的方法验证
您可以集成 Bean Validation 1.1 支持的方法验证功能(并且,作为
自定义扩展,也是由 Hibernate Validator 4.3 提供的)通过 bean 定义进入 Spring 上下文:MethodValidationPostProcessor
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
@Configuration
public class AppConfig {
@Bean
public MethodValidationPostProcessor validationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
要获得 Spring 驱动的方法验证的资格,所有目标类都需要被注释
替换为 Spring 的注解,它也可以选择性地声明验证
要使用的组。有关 Hibernate Validator 和 Bean Validation 1.1 提供程序的设置详细信息,请参见MethodValidationPostProcessor
。@Validated
其他配置选项
默认配置足以满足大多数
例。各种 Bean 验证有许多配置选项
构造,从消息插值到遍历解析。有关这些选项的更多信息,请参见LocalValidatorFactoryBean
javadoc。LocalValidatorFactoryBean
3.7.3. 配置DataBinder
从 Spring 3 开始,您可以使用 .一次
配置后,您可以通过调用 来调用 。任何验证都会自动添加到 Binder 的 .DataBinder
Validator
Validator
binder.validate()
Errors
BindingResult
以下示例演示如何以编程方式使用 a 调用验证
绑定到目标对象后的逻辑: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
您还可以通过 和 配置具有多个实例的 。这在以下情况下很有用
将全局配置的 bean 验证与配置的 Spring 相结合
本地的 DataBinder 实例。参见 Spring MVC 验证配置。DataBinder
Validator
dataBinder.addValidators
dataBinder.replaceValidators
Validator
3.7.4. Spring MVC 3 验证
参见 Spring MVC 一章中的 验证。
4. Spring 表达式语言 (SpEL)
Spring 表达式语言(简称“SPEL”)是一种强大的表达式语言,它 支持在运行时查询和操作对象图。语言语法为 类似于 Unified EL,但提供了额外的功能,最明显的是方法调用和 基本的字符串模板功能。
虽然还有其他几种可用的 Java 表达式语言 — OGNL、MVEL 和 JBoss EL,仅举几例 — Spring 表达式语言的创建是为了提供 Spring 社区,该社区具有一种受支持的表达式语言,可用于所有 Spring 产品组合中的产品。它的语言功能由 Spring 产品组合中项目的要求,包括工具要求 以获取 Spring Tools for Eclipse 中的代码完成支持。 也就是说,SPEL 基于一个与技术无关的 API,它允许其他表达式语言 如果需要,可以集成 implementations。
虽然 SpEL 是 Spring 中表达式评估的基础 portfolio 中,它不直接与 Spring 绑定,可以独立使用。自 是自包含的,本章中的许多示例都使用 SpEL,就好像它是一个 独立的表达式语言。这需要创建一些引导 infrastructure 类,例如 parser 的 Parser 等。大多数 Spring 用户不需要处理 此基础结构,而是只能创作表达式字符串进行评估。 这种典型用途的一个示例是将 SPEL 集成到创建 XML 或 基于注释的 Bean 定义,如定义 Bean 定义的表达式支持中所示。
本章介绍表达式语言、其 API 及其语言的功能
语法。在多个地方,类被用作目标
对象进行表达式计算。这些类声明和用于
填充它们列在本章末尾。Inventor
Society
表达式语言支持以下功能:
-
文本表达式
-
布尔运算符和关系运算符
-
正则表达式
-
类表达式
-
访问属性、数组、列表和映射
-
方法调用
-
关系运算符
-
分配
-
调用构造函数
-
Bean 引用
-
数组构造
-
内联列表
-
内联映射
-
三元运算符
-
变量
-
用户定义的函数
-
集合投影
-
集合选择
-
模板化表达式
4.1. 评估
本节介绍 SpEL 接口及其表达式语言的简单用法。 完整的语言参考可以在 语言参考 中找到。
以下代码介绍了 SpEL API 来评估文本字符串表达式 .Hello World
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
1 | message 变量的值为 .'Hello World' |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'") (1)
val message = exp.value as String
1 | message 变量的值为 .'Hello World' |
您最有可能使用的 SPEL 类和接口位于包及其子包中,例如 .org.springframework.expression
spel.support
该接口负责解析表达式字符串。在
前面的示例,表达式 String 是由周围的 single 表示的字符串文本
引号。该接口负责评估先前定义的
expression 字符串。可以引发的两个异常,在调用 和 时,调用 和 ,
分别。ExpressionParser
Expression
ParseException
EvaluationException
parser.parseExpression
exp.getValue
SPEL 支持广泛的功能,例如调用方法、访问属性、 并调用构造函数。
在以下方法调用示例中,我们在字符串 Literals 上调用该方法:concat
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
1 | 的值现在是 'Hello World!'。message |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("'Hello World'.concat('!')") (1)
val message = exp.value as String
1 | 的值现在是 'Hello World!'。message |
下面调用 JavaBean 属性的示例调用了该属性:String
Bytes
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
1 | 此行将 Literals 转换为字节数组。 |
val parser = SpelExpressionParser()
// invokes 'getBytes()'
val exp = parser.parseExpression("'Hello World'.bytes") (1)
val bytes = exp.value as ByteArray
1 | 此行将 Literals 转换为字节数组。 |
SPEL 还通过使用标准点表示法(例如 )以及相应的属性值设置来支持嵌套属性。
还可以访问 Public 字段。prop1.prop2.prop3
以下示例演示如何使用点表示法获取文本的长度:
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();
1 | 'Hello World'.bytes.length 给出文本的长度。 |
val parser = SpelExpressionParser()
// invokes 'getBytes().length'
val exp = parser.parseExpression("'Hello World'.bytes.length") (1)
val length = exp.value as Int
1 | 'Hello World'.bytes.length 给出文本的长度。 |
可以调用 String 的构造函数,而不是使用字符串文本,如下所示 示例显示:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);
1 | 从文本构造一个 new 并将其设置为大写。String |
val parser = SpelExpressionParser()
val exp = parser.parseExpression("new String('hello world').toUpperCase()") (1)
val message = exp.getValue(String::class.java)
1 | 从文本构造一个 new 并将其设置为大写。String |
请注意泛型方法的使用:.
使用此方法无需将表达式的值强制转换为所需的值
result 类型。如果值无法强制转换为
type 或 converted 使用已注册的类型转换器。public <T> T getValue(Class<T> desiredResultType)
EvaluationException
T
SPEL 更常见的用法是提供一个经过评估的表达式字符串
针对特定对象实例 (称为根对象) 。以下示例显示了
如何从类的实例中检索属性,或者
创建一个布尔条件:name
Inventor
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
// Create and set a calendar
val c = GregorianCalendar()
c.set(1856, 7, 9)
// The constructor arguments are name, birthday, and nationality.
val tesla = Inventor("Nikola Tesla", c.time, "Serbian")
val parser = SpelExpressionParser()
var exp = parser.parseExpression("name") // Parse name as an expression
val name = exp.getValue(tesla) as String
// name == "Nikola Tesla"
exp = parser.parseExpression("name == 'Nikola Tesla'")
val result = exp.getValue(tesla, Boolean::class.java)
// result == true
4.1.1. 理解EvaluationContext
在计算表达式以解析时使用该接口
属性、方法或字段,并帮助执行类型转换。Spring 提供两个
实现。EvaluationContext
-
SimpleEvaluationContext
:公开基本 SPEL 语言功能的子集,以及 configuration options(配置选项),用于不需要 full extent 的表达式类别 ,并且应该进行有意义的限制。示例包括 but 不限于数据绑定表达式和基于属性的过滤器。 -
StandardEvaluationContext
:公开了全套 SPEL 语言功能,并且 配置选项。您可以使用它来指定默认根对象并配置 所有可用的评估相关策略。
SimpleEvaluationContext
旨在仅支持 SPEL 语言语法的子集。
它不包括 Java 类型引用、构造函数和 Bean 引用。它还要求
U 显式选择对表达式中属性和方法的支持级别。
默认情况下,static 工厂方法仅允许对属性进行读取访问。
您还可以获取构建器来配置所需的确切支持级别,并针对
以下一项或多项组合:create()
-
仅自定义(无反射)
PropertyAccessor
-
用于只读访问的数据绑定属性
-
用于读取和写入的数据绑定属性
类型转换
默认情况下,SPEL 使用 Spring 核心中提供的转换服务
().此转换服务随之而来
具有许多用于常见转换的内置转换器,但也完全可扩展,因此
您可以在类型之间添加自定义转换。此外,它是
generics-aware。这意味着,当您在
表达式中,SPEL 会尝试转换以保持任何对象的类型正确性
它相遇。org.springframework.core.convert.ConversionService
这在实践中意味着什么?假设正在使用 的赋值 ,
以设置属性。属性的类型实际上是 。斯佩尔
识别出列表的元素需要转换为 before
被放置在其中。以下示例显示了如何执行此操作:setValue()
List
List<Boolean>
Boolean
class Simple {
public List<Boolean> booleanList = new ArrayList<Boolean>();
}
Simple simple = new Simple();
simple.booleanList.add(true);
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");
// b is false
Boolean b = simple.booleanList.get(0);
class Simple {
var booleanList: MutableList<Boolean> = ArrayList()
}
val simple = Simple()
simple.booleanList.add(true)
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false")
// b is false
val b = simple.booleanList[0]
4.1.2. 解析器配置
可以使用解析器配置对象配置 SpEL 表达式解析器
().配置
object 控制某些表达式组件的行为。例如,如果你
index 添加到数组或集合中,并且指定索引处的元素为 ,
您可以自动创建元素。当使用由
属性引用链。如果您索引到数组或列表中
并指定超出数组当前大小末尾的索引,或者
list 中,您可以自动增加数组或列表以容纳该索引。以下内容
示例演示如何自动增加列表:org.springframework.expression.spel.SpelParserConfiguration
null
class Demo {
public List<String> list;
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true,true);
ExpressionParser parser = new SpelExpressionParser(config);
Expression expression = parser.parseExpression("list[3]");
Demo demo = new Demo();
Object o = expression.getValue(demo);
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
class Demo {
var list: List<String>? = null
}
// Turn on:
// - auto null reference initialization
// - auto collection growing
val config = SpelParserConfiguration(true, true)
val parser = SpelExpressionParser(config)
val expression = parser.parseExpression("list[3]")
val demo = Demo()
val o = expression.getValue(demo)
// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String
4.1.3. SPEL 编译
Spring Framework 4.1 包括一个基本的表达式编译器。表达式通常是 interpreted,这在评估过程中提供了很大的动态灵活性,但 不提供最佳性能。对于偶尔的表达式使用, 这很好,但是,当被其他组件(如 Spring Integration)使用时, 性能可能非常重要,并且没有真正需要动态性。
SPEL 编译器旨在满足这一需求。在评估期间,编译器 生成一个 Java 类,该类在运行时体现表达式行为,并使用该类 类来实现更快的表达式计算。由于周围缺乏打字 expressions,编译器会使用在解释的评估期间收集的信息 的表达式。例如,它不知道类型 的属性引用,但在第一次解释的 evaluation 时,它会找出它是什么。当然,基于这样的派生 如果各种表达式元素的类型 随时间变化。因此,编译最适合于其 type information 不会在重复计算时发生变化。
请考虑以下基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1
因为前面的表达式涉及数组访问,所以一些属性取消引用 和数值运算,则性能提升可能非常明显。在示例中 Micro Benchmark 运行 50000 次迭代,使用 interpreter 的 Expression,并且仅使用 3ms 的编译版本。
编译器配置
默认情况下,编译器未打开,但您可以通过以下两种方式之一打开它 不同的方式。您可以使用解析器配置过程来打开它 (前面讨论过)或使用系统 当 SPEL 用法嵌入到另一个组件中时,属性。本节 讨论这两个选项。
编译器可以在枚举中捕获的三种模式之一运行。模式如下:org.springframework.expression.spel.SpelCompilerMode
-
OFF
(默认):编译器已关闭。 -
IMMEDIATE
:在即时模式下,表达式会尽快编译。这 通常在第一次解释的评估之后。如果编译的表达式失败 (通常是由于类型更改,如前所述),表达式的调用方 evaluation 收到异常。 -
MIXED
:在混合模式下,表达式在已解释和已编译之间静默切换 模式。经过一定数量的解释运行后,它们会切换到 compiled 表单,如果编译后的表单出现问题(例如类型更改,如 如前所述),表达式会自动切换回解释形式 再。稍后,它可能会生成另一个编译的表单并切换到它。基本上 用户在 mode 中获得的异常则在内部处理。IMMEDIATE
IMMEDIATE
mode 存在,因为 mode 可能会导致
有副作用。如果编译的表达式在部分成功后崩溃,则
可能已经做了一些影响系统状态的事情。如果此
已发生,调用方可能不希望它在解释模式下静默重新运行。
因为表达式的一部分可能运行两次。MIXED
选择模式后,使用 配置解析器。这
以下示例显示了如何执行此操作:SpelParserConfiguration
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression expr = parser.parseExpression("payload");
MyMessage message = new MyMessage();
Object payload = expr.getValue(message);
val config = SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
this.javaClass.classLoader)
val parser = SpelExpressionParser(config)
val expr = parser.parseExpression("payload")
val message = MyMessage()
val payload = expr.getValue(message)
指定编译器模式时,还可以指定类加载器(允许传递 null)。 编译的表达式在提供的 any 下创建的子类加载器中定义。 请务必确保,如果指定了类加载器,则它可以看到 表达式求值过程。如果未指定类加载器,则使用默认类加载器 (通常是在表达式计算期间运行的线程的上下文类加载器)。
配置编译器的第二种方法是在 SpEL 嵌入到其他一些
组件,并且可能无法通过配置对象对其进行配置。在这些
情况下,可以使用 system 属性。您可以将该属性设置为枚举值(、 或 )之一。spring.expression.compiler.mode
SpelCompilerMode
off
immediate
mixed
4.2. Bean 定义中的表达式
您可以将 SPEL 表达式与基于 XML 或基于注释的配置元数据一起使用,以便
定义实例。在这两种情况下,定义表达式的语法都是
形式。BeanDefinition
#{ <expression string> }
4.2.1. XML配置
可以使用表达式设置属性或构造函数参数值,如下所示 示例显示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
应用程序上下文中的所有 bean 都可以作为预定义的变量使用,其
通用 bean 名称。这包括用于访问运行时环境的标准上下文 bean,例如 (type ) 和 and (type )。environment
org.springframework.core.env.Environment
systemProperties
systemEnvironment
Map<String, Object>
下面的示例将对 Bean 的访问作为 SPEL 变量显示:systemProperties
<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
<!-- other properties -->
</bean>
请注意,您不必在此处为预定义变量加上 symbol 前缀。#
您还可以按名称引用其他 Bean 属性,如下例所示:
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
<!-- other properties -->
</bean>
<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>
<!-- other properties -->
</bean>
4.2.2. 注解配置
要指定默认值,您可以将注释放在字段、方法、
以及方法或构造函数参数。@Value
以下示例设置字段变量的默认值:
public class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
private String defaultLocale;
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class FieldValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
以下示例显示了等效的 but on a property setter 方法:
public class PropertyValueTestBean {
private String defaultLocale;
@Value("#{ systemProperties['user.region'] }")
public void setDefaultLocale(String defaultLocale) {
this.defaultLocale = defaultLocale;
}
public String getDefaultLocale() {
return this.defaultLocale;
}
}
class PropertyValueTestBean {
@Value("#{ systemProperties['user.region'] }")
var defaultLocale: String? = null
}
自动装配的方法和构造函数也可以使用 Comments,如下所示
示例显示:@Value
public class SimpleMovieLister {
private MovieFinder movieFinder;
private String defaultLocale;
@Autowired
public void configure(MovieFinder movieFinder,
@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
this.movieFinder = movieFinder;
this.defaultLocale = defaultLocale;
}
// ...
}
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
private lateinit var defaultLocale: String
@Autowired
fun configure(movieFinder: MovieFinder,
@Value("#{ systemProperties['user.region'] }") defaultLocale: String) {
this.movieFinder = movieFinder
this.defaultLocale = defaultLocale
}
// ...
}
public class MovieRecommender {
private String defaultLocale;
private CustomerPreferenceDao customerPreferenceDao;
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
@Value("#{systemProperties['user.country']}") String defaultLocale) {
this.customerPreferenceDao = customerPreferenceDao;
this.defaultLocale = defaultLocale;
}
// ...
}
class MovieRecommender(private val customerPreferenceDao: CustomerPreferenceDao,
@Value("#{systemProperties['user.country']}") private val defaultLocale: String) {
// ...
}
4.3. 语言参考
本节描述了 Spring 表达式语言的工作原理。它涵盖以下内容 主题:
4.3.1. 文字表达式
支持的文字表达式类型包括字符串、数值(int、real、hex)、 boolean 和 null。字符串由单引号分隔。放置单引号本身 在字符串中,使用两个单引号字符。
下面的清单显示了 Literals 的简单用法。通常,它们不会被使用 像这样孤立地进行,而是作为更复杂的表达式的一部分——例如, 在逻辑比较运算符的一侧使用 Literals。
ExpressionParser parser = new SpelExpressionParser();
// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
Object nullValue = parser.parseExpression("null").getValue();
val parser = SpelExpressionParser()
// evals to "Hello World"
val helloWorld = parser.parseExpression("'Hello World'").value as String
val avogadrosNumber = parser.parseExpression("6.0221415E+23").value as Double
// evals to 2147483647
val maxValue = parser.parseExpression("0x7FFFFFFF").value as Int
val trueValue = parser.parseExpression("true").value as Boolean
val nullValue = parser.parseExpression("null").value
数字支持使用负号、指数表示法和小数点。 默认情况下,使用 Double.parseDouble() 解析实数。
4.3.2. 属性、数组、列表、映射和索引器
使用属性引用进行导航非常简单。为此,请使用句点来表示嵌套的
property 值。类的实例 和 中填充了
数据列在 Classes used in the examples 部分中。
要导航“向下”并获取 Tesla 的出生年份和 Pupin 的出生城市,我们使用以下命令
表达 式:Inventor
pupin
tesla
// 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)
映射的内容是通过在
括弧。在下面的示例中,由于 map 的键是字符串,因此我们可以指定
字符串:Officers
// Officer's Dictionary
Inventor pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor.class);
// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String.class);
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia");
// Officer's Dictionary
val pupin = parser.parseExpression("Officers['president']").getValue(
societyContext, Inventor::class.java)
// evaluates to "Idvor"
val city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
societyContext, String::class.java)
// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
societyContext, "Croatia")
4.3.3. 内联列表
您可以使用表示法直接在表达式中表示列表。{}
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);
// evaluates to a Java list containing the four numbers
val numbers = parser.parseExpression("{1,2,3,4}").getValue(context) as List<*>
val listOfLists = parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context) as List<*>
{}
本身意味着一个空列表。出于性能原因,如果列表本身是
完全由固定文本组成,创建一个常量列表来表示
expression (而不是在每次评估时构建一个新列表)。
4.3.4. 内联映射
您还可以使用 notation 直接在表达式中表示映射。这
以下示例显示了如何执行此操作:{key:value}
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);
// evaluates to a Java map containing the two entries
val inventorInfo = parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context) as Map<*, >
val mapOfMaps = parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context) as Map<, *>
{:}
本身意味着一张空地图。出于性能原因,如果映射本身是由
的固定文本或其他嵌套常量结构(列表或映射)中,将创建一个常量映射
来表示表达式(而不是在每次求值时构建新 map)。引用 map 键
是可选的。上面的示例不使用带引号的键。
4.3.5. 数组构造
您可以使用熟悉的 Java 语法构建数组,并可选择提供初始化器 以在构造时填充数组。以下示例显示了如何执行此操作:
int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);
val numbers1 = parser.parseExpression("new int[4]").getValue(context) as IntArray
// Array with initializer
val numbers2 = parser.parseExpression("new int[]{1,2,3}").getValue(context) as IntArray
// Multi dimensional array
val numbers3 = parser.parseExpression("new int[4][5]").getValue(context) as Array<IntArray>
当前,在构造 Multi-dimensional 数组。
4.3.6. 方法
您可以使用典型的 Java 编程语法来调用方法。您还可以调用方法 在 Literals 上。还支持变量参数。以下示例说明如何 invoke 方法:
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean.class);
// string literal, evaluates to "bc"
val bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String::class.java)
// evaluates to true
val isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
societyContext, Boolean::class.java)
4.3.7. 运算符
Spring 表达式语言支持以下类型的运算符:
关系运算符
关系运算符(等于、不等于、小于、小于或等于、大于、 和大于或等于)使用标准运算符表示法来支持。这 下面的清单显示了一些 Operators 示例:
// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
// evaluates to true
val trueValue = parser.parseExpression("2 == 2").getValue(Boolean::class.java)
// evaluates to false
val falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean::class.java)
大于和小于比较遵循一个简单的规则:被视为
nothing(不是零)。因此,任何其他值总是更大
than ( is always ) 并且没有其他值永远小于 none
( 总是 )。 如果您更喜欢数字比较,请避免基于数字的比较
赞成与 Zero 进行比较(例如 或 )。 |
除了标准关系运算符之外,SPEL 还支持 和 regular
基于表达式的运算符。下面的清单显示了这两种方法的示例:instanceof
matches
// evaluates to false
boolean falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean.class);
// evaluates to true
boolean trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
//evaluates to false
boolean falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
// evaluates to false
val falseValue = parser.parseExpression(
"'xyz' instanceof T(Integer)").getValue(Boolean::class.java)
// evaluates to true
val trueValue = parser.parseExpression(
"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
//evaluates to false
val falseValue = parser.parseExpression(
"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean::class.java)
小心原始类型,因为它们会立即被装箱到 wrapper 类型。
so 的计算结果为 ,而 则计算结果为 ,正如预期的那样。1 instanceof T(int) false 1 instanceof T(Integer) true |
每个符号运算符也可以指定为纯字母等效运算符。这 避免了所使用的符号对 表达式嵌入的 (,例如在 XML 文档中)。文本等价物是:
-
lt
(<
) -
gt
(>
) -
le
(<=
) -
ge
(>=
) -
eq
(==
) -
ne
(!=
) -
div
(/
) -
mod
(%
) -
not
(!
).
所有文本运算符都不区分大小写。
逻辑运算符
SPEL 支持以下逻辑运算符:
-
and
(&&
) -
or
(||
) -
not
(!
)
以下示例演示如何使用逻辑运算符
// -- AND --
// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- OR --
// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- NOT --
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
// -- AND --
// evaluates to false
val falseValue = parser.parseExpression("true and false").getValue(Boolean::class.java)
// evaluates to true
val expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- OR --
// evaluates to true
val trueValue = parser.parseExpression("true or false").getValue(Boolean::class.java)
// evaluates to true
val expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')"
val trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
// -- NOT --
// evaluates to false
val falseValue = parser.parseExpression("!true").getValue(Boolean::class.java)
// -- AND and NOT --
val expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')"
val falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean::class.java)
数学运算符
您可以对数字和字符串使用加号运算符。您可以使用减法、乘法、 和除法运算符仅对数字。您还可以使用 模数 (%) 和指数幂 (^) 运算符。强制实施标准运算符优先级。这 以下示例显示了正在使用的数学运算符:
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
String testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String.class); // 'test string'
// Subtraction
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
// Multiplication
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
// Division
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
// Modulus
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21
// Addition
val two = parser.parseExpression("1 + 1").getValue(Int::class.java) // 2
val testString = parser.parseExpression(
"'test' + ' ' + 'string'").getValue(String::class.java) // 'test string'
// Subtraction
val four = parser.parseExpression("1 - -3").getValue(Int::class.java) // 4
val d = parser.parseExpression("1000.00 - 1e4").getValue(Double::class.java) // -9000
// Multiplication
val six = parser.parseExpression("-2 * -3").getValue(Int::class.java) // 6
val twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double::class.java) // 24.0
// Division
val minusTwo = parser.parseExpression("6 / -3").getValue(Int::class.java) // -2
val one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double::class.java) // 1.0
// Modulus
val three = parser.parseExpression("7 % 4").getValue(Int::class.java) // 3
val one = parser.parseExpression("8 / 5 % 2").getValue(Int::class.java) // 1
// Operator precedence
val minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Int::class.java) // -21
赋值运算符
要设置属性,请使用赋值运算符 ()。这通常是
在对 的调用中完成,但也可以在对 的调用中完成。这
下面的清单显示了使用 ASSIGNMENT 运算符的两种方法:=
setValue
getValue
Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");
// alternatively
String aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
val inventor = Inventor()
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic")
// alternatively
val aleks = parser.parseExpression(
"Name = 'Aleksandar Seovic'").getValue(context, inventor, String::class.java)
4.3.8. 类型
您可以使用特殊运算符指定 (
type) 的 intent 的 intent静态方法也是使用此运算符调用的。使用 a 来查找类型,而 (可以替换) 是在了解包的基础上构建的。这意味着对 中的类型的引用不需要是
完全限定,但所有其他类型的引用都必须是。以下示例显示了如何操作
要使用运算符:T
java.lang.Class
StandardEvaluationContext
TypeLocator
StandardTypeLocator
java.lang
T()
java.lang
T
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
boolean trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean.class);
val dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class::class.java)
val stringClass = parser.parseExpression("T(String)").getValue(Class::class.java)
val trueValue = parser.parseExpression(
"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
.getValue(Boolean::class.java)
4.3.9. 构造函数
您可以使用 operator 调用构造函数。您应该使用完全限定的类名
对于除基元类型(、 、 等)和 String 之外的所有类型。以下内容
示例演示如何使用 Operator 调用构造函数:new
int
float
new
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. 变量
您可以使用语法在表达式中引用变量。变量
通过使用 implementation 上的方法进行设置。#variableName
setVariable
EvaluationContext
有效的变量名称必须由以下一个或多个受支持的变量组成 字符。
|
以下示例演示如何使用变量。
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");
parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName()) // "Mike Tesla"
val tesla = Inventor("Nikola Tesla", "Serbian")
val context = SimpleEvaluationContext.forReadWriteDataBinding().build()
context.setVariable("newName", "Mike Tesla")
parser.parseExpression("Name = #newName").getValue(context, tesla)
println(tesla.name) // "Mike Tesla"
和 变量#this
#root
变量始终是定义的,并引用当前评估对象
(根据这些引用解析不合格的引用)。变量始终为
定义并引用根上下文对象。虽然
表达式,始终引用根。以下示例
演示如何使用 AND 变量:#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. 函数
您可以通过注册可在
expression 字符串。该函数通过 注册 .这
以下示例演示如何注册用户定义的函数:EvaluationContext
Method method = ...;
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
val method: Method = ...
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("myFunction", method)
例如,请考虑以下反转字符串的实用程序方法:
public abstract class StringUtils {
public static String reverseString(String input) {
StringBuilder backwards = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i++) {
backwards.append(input.charAt(input.length() - 1 - i));
}
return backwards.toString();
}
}
fun reverseString(input: String): String {
val backwards = StringBuilder(input.length)
for (i in 0 until input.length) {
backwards.append(input[input.length - 1 - i])
}
return backwards.toString()
}
然后,您可以注册并使用上述方法,如下例所示:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
StringUtils.class.getDeclaredMethod("reverseString", String.class));
String helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
context.setVariable("reverseString", ::reverseString::javaMethod)
val helloWorldReversed = parser.parseExpression(
"#reverseString('hello')").getValue(context, String::class.java)
4.3.12. Bean 引用
如果评估上下文已经配置了 bean 解析器,则可以
使用 symbol 从表达式中查找 bean。以下示例显示了如何操作
为此,请执行以下操作:@
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
val bean = parser.parseExpression("@something").getValue(context)
要访问工厂 Bean 本身,您应该在 Bean 名称前加上一个符号。
以下示例显示了如何执行此操作:&
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);
val parser = SpelExpressionParser()
val context = StandardEvaluationContext()
context.setBeanResolver(MyBeanResolver())
// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
val bean = parser.parseExpression("&foo").getValue(context)
4.3.13. 三元运算符 (If-Then-Else)
您可以使用三元运算符在 表达式。下面的清单显示了一个最小示例:
String falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String.class);
val falseString = parser.parseExpression(
"false ? 'trueExp' : 'falseExp'").getValue(String::class.java)
在这种情况下,布尔值会导致返回字符串值 。一个 更多
实际示例如下:false
'falseExp'
parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
String queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
parser.parseExpression("Name").setValue(societyContext, "IEEE")
societyContext.setVariable("queryName", "Nikola Tesla")
expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'"
val queryResultString = parser.parseExpression(expression)
.getValue(societyContext, String::class.java)
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
请参阅 Elvis 运算符的下一节,了解 三元运算符。
4.3.14. Elvis 运算符
Elvis 运算符是三元运算符语法的缩写,用于 Groovy 语言。 使用三元运算符语法时,通常必须将变量重复两次,因为 以下示例显示:
String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");
相反,您可以使用 Elvis 运算符(因与 Elvis 的发型相似而命名)。 以下示例演示如何使用 Elvis 运算符:
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name); // 'Unknown'
val parser = SpelExpressionParser()
val name = parser.parseExpression("name?:'Unknown'").getValue(Inventor(), String::class.java)
println(name) // 'Unknown'
下面的清单显示了一个更复杂的示例:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Nikola Tesla
tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name); // Elvis Presley
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
var name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Nikola Tesla
tesla.setName(null)
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String::class.java)
println(name) // Elvis Presley
您可以使用 Elvis 运算符在表达式中应用默认值。以下内容
example 演示如何在表达式中使用 Elvis 运算符:
如果已定义,这将注入一个系统属性,如果未定义,则注入 25。 |
4.3.15. Safe Navigation 操作符
安全导航运算符用于避免 a 和 comes from
Groovy 语言。通常,当您引用某个对象时,可能需要验证
在访问对象的方法或属性之前,它不为 null。为避免这种情况,
Safe Navigation 运算符返回 null,而不是引发异常。以下内容
示例演示如何使用 Safe Navigation 运算符:NullPointerException
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
var city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java)
println(city) // Smiljan
tesla.setPlaceOfBirth(null)
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String::class.java)
println(city) // null - does not throw NullPointerException!!!
4.3.16. 集合选择
选择项是一项强大的表达式语言功能,可用于转换 source 集合添加到另一个集合中。
选择使用 .它会过滤集合和
返回包含原始元素子集的新集合。例如
选择让我们轻松获得塞尔维亚发明家的列表,如下例所示:.?[selectionExpression]
List<Inventor> list = (List<Inventor>) parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext);
val list = parser.parseExpression(
"Members.?[Nationality == 'Serbian']").getValue(societyContext) as List<Inventor>
可以在列表和地图上进行选择。对于列表,选择
criteria 根据每个单独的 list 元素进行评估。对于地图,
根据每个映射条目(Java 类型的对象)评估选择标准 。每个映射条目都有其键和值,可作为属性访问
选择。Map.Entry
以下表达式返回一个由原始 map 的那些元素组成的新 map 其中 entry 值小于 27:
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
val newMap = parser.parseExpression("map.?[value<27]").getValue()
除了返回所有选定的元素外,您还可以仅检索
first 或 last 值。要获取与所选内容匹配的第一个条目,语法为 。要获取最后一个匹配的选择,语法为 。.^[selectionExpression]
.$[selectionExpression]
4.3.17. 集合投影
Projection 允许集合驱动子表达式的计算,而
result 是一个新集合。projection 的语法为 。为
例如,假设我们有一个发明人列表,但希望
他们出生的城市。实际上,我们想评估 'placeOfBirth.city'
Inventor 列表中的每个条目。以下示例使用 projection 来执行此操作:.![projectionExpression]
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
// returns ['Smiljan', 'Idvor' ]
val placesOfBirth = parser.parseExpression("Members.![placeOfBirth.city]") as List<*>
您还可以使用地图来驱动投影,在这种情况下,投影表达式为
根据 Map 中的每个条目进行评估(表示为 Java )。结果
的 of a projection across a map 是一个列表,其中包含对投影的评估
expression 来触发每个 Map 条目。Map.Entry
4.3.18. 表达式模板
表达式模板允许将文本文本与一个或多个评估块混合。
每个评估块都用前缀和后缀字符分隔,您可以
定义。常见的选择是用作分隔符,如下例所示
显示:#{ }
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
val randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
TemplateParserContext()).getValue(String::class.java)
// evaluates to "random number is 0.7038186818312008"
通过将文本文本与
在分隔符内计算表达式的结果(在本例中为结果
调用该方法)。该方法的第二个参数
的类型为 。该接口用于影响
解析表达式以支持表达式模板化功能。
的定义如下:'random number is '
#{ }
random()
parseExpression()
ParserContext
ParserContext
TemplateParserContext
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
class TemplateParserContext : ParserContext {
override fun getExpressionPrefix(): String {
return "#{"
}
override fun getExpressionSuffix(): String {
return "}"
}
override fun isTemplate(): Boolean {
return true
}
}
4.4. 示例中使用的类
本节列出了本章中示例中使用的类。
package org.spring.samples.spel.inventor;
import java.util.Date;
import java.util.GregorianCalendar;
public class Inventor {
private String name;
private String nationality;
private String[] inventions;
private Date birthdate;
private PlaceOfBirth placeOfBirth;
public Inventor(String name, String nationality) {
GregorianCalendar c= new GregorianCalendar();
this.name = name;
this.nationality = nationality;
this.birthdate = c.getTime();
}
public Inventor(String name, Date birthdate, String nationality) {
this.name = name;
this.nationality = nationality;
this.birthdate = birthdate;
}
public Inventor() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNationality() {
return nationality;
}
public void setNationality(String nationality) {
this.nationality = nationality;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public PlaceOfBirth getPlaceOfBirth() {
return placeOfBirth;
}
public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
this.placeOfBirth = placeOfBirth;
}
public void setInventions(String[] inventions) {
this.inventions = inventions;
}
public String[] getInventions() {
return inventions;
}
}
class Inventor(
va