Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
```
Kotlin
```
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
@get:AliasFor(annotation = Scope::class)
val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
```
然后,你可以使用`@SessionScope`,而不必声明`proxyMode`,如下所示:
Java
```
@Service
@SessionScope
public class SessionScopedService {
// ...
}
```
Kotlin
```
@Service
@SessionScope
class SessionScopedService {
// ...
}
```
还可以重写`proxyMode`的值,如下例所示:
Java
```
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
```
Kotlin
```
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
```
有关更多详细信息,请参见[Spring Annotation Programming Model](https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model)维基页面。
#### 1.10.3.自动检测类并注册 Bean 定义
Spring 可以自动检测原型类并将与`BeanDefinition`实例相对应的`ApplicationContext`实例注册到`ApplicationContext`中。例如,以下两个类可以进行这种自动检测:
Java
```
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
```
Kotlin
```
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
```
Java
```
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
```
Kotlin
```
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
```
要自动检测这些类并注册相应的 bean,你需要将`@ComponentScan`添加到`@Configuration`类中,其中`basePackages`属性是这两个类的公共父包。(或者,你可以指定一个逗号或分号或空格分隔的列表,其中包括每个类的父包。
Java
```
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
```
| |为了简洁起见,前面的示例可以使用`value`注释(即`@ComponentScan("org.example")`)的`value`属性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------|
以下替代方案使用 XML:
```
条目。当你使用 Ant 构建 JAR 时,请确保没有
激活 jar 任务的仅文件开关。另外,在某些环境中,基于安全策略, Classpath 目录可能不会
公开,例如,在
jdk1.7.0\_45 或更高版本上的独立应用程序(这需要在你的清单中设置“信任库”——参见[https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources))。,
在 JDK9 的模块路径(拼图)上
, Spring 的 Classpath 扫描通常按预期工作。
但是,请确保你的组件类在`module-info`描述符中导出。如果你希望 Spring 调用类的非公共成员,请确保
它们是“打开的”(即它们在你的`opens`描述符中使用`exports`声明,而不是`module-info`声明)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
此外,当你使用组件扫描元素时,`AutowiredAnnotationBeanPostProcessor`和`CommonAnnotationBeanPostProcessor`都是隐式包含的。这意味着这两个组件是自动检测和连接在一起的——所有这些都没有 XML 提供的任何 Bean 配置元数据。
| |你可以禁用`AutowiredAnnotationBeanPostProcessor`和`CommonAnnotationBeanPostProcessor`的注册,方法是使用`annotation-config`属性
,其值为`false`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.4.使用筛选器自定义扫描
默认情况下,用`@Component`、`@Repository`、`@Service`、`@Controller`、`@Configuration`注释的类,或本身用`@Component`注释的自定义注释是唯一检测到的候选组件。但是,你可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为`includeFilters`或`excludeFilters`注释的属性(或作为`
注释上设置`useDefaultFilters=false`或通过提供`use-default-filters="false"`作为`
注释或用`@Component`、`@Repository`、`@Service`、`@Controller`、`@RestController`注释或`@Configuration`注释的类的自动检测。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.5.在组件中定义 Bean 元数据
Spring 组件还可以向容器贡献 Bean 定义元数据。你可以使用与在`@Configuration`注释类中定义 Bean 元数据相同的`@Bean`注释来实现此目的。下面的示例展示了如何做到这一点:
爪哇
```
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
```
Kotlin
```
@Component
class FactoryMethodComponent {
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
fun doWork() {
// Component method implementation omitted
}
}
```
前面的类是一个 Spring 组件,在其`doWork()`方法中具有特定于应用程序的代码。然而,它还贡献了 Bean 定义,该定义具有引用方法`publicInstance()`的工厂方法。`@Bean`注释标识了工厂方法和其他 Bean 定义属性,例如通过`@Qualifier`注释的限定符值。可以指定的其他方法级注释是`@Scope`、`@Lazy`和自定义限定符注释。
| |除了组件初始化的作用外,还可以将`@Lazy`注释放置在标记有`@Autowired`或`@Inject`的注入点上。在此上下文中,
将导致注入一个延迟分辨率代理。然而,这样的代理方法
是相当有限的。对于复杂的惰性交互,特别是结合
和可选依赖项的情况,我们建议使用`ObjectProvider
具有特殊意义,因为这样的 bean 在容器
生命周期的早期就被初始化了,并且应该避免在那一点上触发配置的其他部分。,
调用静态`@Bean`方法永远不会被容器拦截,甚至在`@Configuration`类(如本节前面所述)中也不会,由于技术
的限制:CGLIB 子类只能覆盖非静态方法。因此,
直接调用另一个`@Bean`方法具有标准的 爪哇 语义,结果
在一个独立的实例中被直接从工厂方法本身返回。
`@Bean`方法的 爪哇 语言可见性对
Spring 容器中的结果 Bean 定义没有立即的影响。你可以自由地声明你的
工厂方法,就像你在非-`@Configuration`类中看到的那样,也可以在任何地方声明静态
方法。但是,`@Bean`类中的常规`@Configuration`方法需要
才能被重写——也就是说,它们不能被声明为`private`或
。
方法也可以在给定组件的基类上发现,或者
配置类,以及在 爪哇8 上由组件或配置类实现的接口
中声明的默认方法。这允许在组成复杂的配置安排时有很多
的灵活性,即使是多个
继承也可以通过 爪哇8 的默认方法实现,截至 Spring 4.2,
最后,对于相同的
Bean,单个类可以容纳多个`@Bean`方法,根据运行时可用的
依赖关系,作为使用的多个工厂方法的安排。这与在其他配置场景中选择“greediest”
构造函数或工厂方法的算法相同:具有
最大数量的可满足依赖项是在构造时选择的,
类似于容器如何在多个`@Autowired`构造函数之间进行选择。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.6.命名自动检测到的组件
当组件作为扫描过程的一部分被自动检测时,其 Bean 名称由该扫描仪已知的`BeanNameGenerator`策略生成。默认情况下,任何 Spring 原型注释(`@Component`,`@Repository`,`@Service`,和`@Controller`)中包含一个名称`value`,从而为相应的 Bean 定义提供了该名称。
如果这样的注释不包含名称`value`或任何其他检测到的组件(例如由自定义过滤器发现的组件),则默认的 Bean Name Generator 返回未大写的非限定类名称。例如,如果检测到以下组件类,则名称将为`myMovieLister`和`movieFinderImpl`:
爪哇
```
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
```
Kotlin
```
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
```
爪哇
```
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
```
Kotlin
```
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
```
如果你不想依赖默认的 Bean 命名策略,那么可以提供自定义的 Bean 命名策略。首先,实现[`BeanNameGenerator`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/beans/factory/support/beannamegenerator.html)接口,并确保包含一个默认的无 arg 构造函数。然后,在配置扫描仪时提供完全限定的类名,如下面的示例注释和 Bean 定义所示。
| |如果由于多个具有
相同的非限定类名(即名称相同但位于
不同包中的类)的自动检测组件而导致命名冲突,则可能需要为生成的 Bean 名称配置一个`BeanNameGenerator`默认为
完全限定类名的`BeanNameGenerator`类。截至 Spring 框架 5.2.3,位于包`FullyQualifiedAnnotationBeanNameGenerator`中的`org.springframework.context.annotation`可以用于这样的目的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
爪哇
```
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
```
```
组件)或工厂方法(对于`@Bean`方法)上进行内省。与 XML Bean
定义相反,没有 Bean 定义继承的概念,并且在类级别上的继承
层次结构对于元数据目的是无关的。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
有关特定于 Web 的作用域(例如 Spring 上下文中的“请求”或“会话”)的详细信息,请参见[Request, Session, Application, and WebSocket Scopes](#beans-factory-scopes-other)。与这些作用域的预构建注释一样,你也可以使用 Spring 的元注释方法来编写自己的范围注释:例如,使用`@Scope("prototype")`进行自定义注释的元注释,也可能声明自定义范围代理模式。
| |要为范围解析提供自定义策略,而不是依赖于
基于注释的方法,你可以实现[`ScopeMetadataResolver`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/scopemetadataresolver.html)接口。一定要包含一个默认的无参数构造函数。然后可以在配置扫描仪时提供
完全限定的类名,如下面的
注释和 Bean 定义的示例所示:|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
爪哇
```
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
```
```
绑定到类定义本身的,而使用 XML 允许多个相同类型的 bean
在其限定符元数据中提供变体,因为
元数据是按实例而不是按类提供的。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.9.生成候选组件的索引
Classpath 尽管扫描速度非常快,但通过在编译时创建候选的静态列表,可以提高大型应用程序的启动性能。在这种模式下,所有组件扫描的目标模块都必须使用这种机制。
| |你现有的`@ComponentScan`或`
不变,以请求上下文扫描某些包中的候选项。当`ApplicationContext`检测到这样的索引时,它会自动使用它,而不是扫描
Classpath。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
要生成索引,请向每个包含组件的模块添加一个附加依赖项,这些组件是组件扫描指令的目标。下面的示例展示了如何使用 Maven 来实现这一点:
```
注册为注释处理器,以确保在更新
候选组件时索引是最新的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |当在 Classpath 上找到`META-INF/spring.components`文件
时,将自动启用索引。如果对于某些库(或用例)
,索引部分可用,但无法为整个应用程序构建索引,则可以通过将
设置为`true`,返回到常规的 Classpath
安排(就好像根本没有索引),可以作为 JVM 系统属性,也可以通过[`SpringProperties`](acception.html#acception- Spring-properties)机制。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 1.11.使用 JSR330 标准注释
从 Spring 3.0 开始, Spring 提供了对 JSR-330 标准注释(依赖注入)的支持。以与 Spring 注释相同的方式扫描这些注释。要使用它们,你需要在你的 Classpath 中有相关的罐子。
| |如果使用 Maven,则`javax.inject`工件在标准 Maven
存储库([https://repo1.maven.org/maven2/javax/inject/javax.inject/1/](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(...);
// ...
}
}
```
Kotlin
```
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
```
与`@Autowired`一样,你可以在字段级、方法级和构造函数参数级使用`@Inject`。此外,你可以将注入点声明为`Provider`,从而允许按需访问较短范围的 bean,或者通过`Provider.get()`调用对其他 bean 进行惰性访问。下面的示例提供了前面示例的一个变体:
爪哇
```
import javax.inject.Inject;
import javax.inject.Provider;
public class SimpleMovieLister {
private Provider
自定义组件注释。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.11.3.JSR-330 标准注释的局限性
当你使用标准注释时,你应该知道一些重要的特性是不可用的,如下表所示:
| Spring | javax.inject.\* |javax.inject 限制/注释|
|-------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| @Autowired | @Inject |`@Inject`没有“required”属性。可以与 爪哇8 的`Optional`一起使用。|
| @Component |@Named / @ManagedBean|JSR-330 不提供可组合的模型,只提供一种识别命名组件的方法。|
|@Scope("singleton")| @Singleton |JSR-330 缺省作用域类似于 Spring 的`prototype`。然而,为了使其
与 Spring 的一般默认值保持一致,在 Spring
容器中声明的 JSR-330 Bean 默认为`singleton`。为了使用`singleton`、
以外的作用域,你应该使用 Spring 的`@Scope`注释。`javax.inject`还提供了[@Scope](https://download.oracle.com/javaee/6/api/javax/inject/Scope.html)注释。
不过,这个注释只用于创建你自己的注释。|
| @Qualifier | @Qualifier / @Named |`javax.inject.Qualifier`只是构建自定义限定符的元注释。
具体`String`限定符(就像 Spring 的`@Qualifier`与一个值)可以关联
到`javax.inject.Named`。|
| @Value | \- |没有等效的|
| @Required | \- |没有等效的|
| @Lazy | \- |没有等效的|
| ObjectFactory | Provider |`javax.inject.Provider`是 Spring 的`ObjectFactory`,
的直接替代方法,只使用更短的`get()`方法名。它也可以与
Spring 的`@Autowired`结合使用,或者与非注释的构造函数和 setter 方法结合使用。|
### 1.12.基于 爪哇 的容器配置
本节介绍如何在 爪哇 代码中使用注释来配置 Spring 容器。它包括以下主题:
* [基本概念:`@Bean`和`@Configuration`](#beans-java-basic-concepts)
* [通过使用`AnnotationConfigApplicationContext`实例化 Spring 容器]
* [使用`@Bean`注释](#beans-java- Bean-注释)
* [使用`@Configuration`注释](#beans-java-configuration-annotation)
* [编写基于 爪哇 的配置](#beans-java-composing-configuration-classes)
* [Bean Definition Profiles](#beans-definition-profiles)
* [`PropertySource`抽象]
* [using`@PropertySource`](#beans-using-propertysource)
* [语句中的占位符解析](#beans-placeholder-resolution-in-statements)
#### 1.12.1.基本概念:`@Bean`和`@Configuration`
Spring 新的 爪哇 配置支持中的核心构件是`@Configuration`-带注释的类和`@Bean`-带注释的方法。
`@Bean`注释用于指示方法实例化、配置和初始化一个新对象,该对象将由 Spring IOC 容器管理。对于那些熟悉 Spring 的`
Spring 的`context:`名称空间的等效 XML 声明,如以下示例所示:
```
```|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
在前面的示例中,扫描`com.acme`包以查找任何`@Component`-注释的类,并且这些类被注册为容器内的 Spring Bean 定义。`AnnotationConfigApplicationContext`公开了`scan(String…)`方法,以允许相同的组件扫描功能,如下例所示:
爪哇
```
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
```
Kotlin
```
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean
在`AppConfig`包(或下面的任何包
)中声明了`AppConfig`,则在调用`scan()`时将其拾取。在`refresh()`时,它的所有`@Bean`方法都被处理并注册为容器内的 Bean 定义。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### 对`AnnotationConfigWebApplicationContext`#### 的 Web 应用程序的支持
一个`WebApplicationContext`的`AnnotationConfigApplicationContext`变种可与`AnnotationConfigWebApplicationContext`一起使用。可以在配置 Spring `ContextLoaderListener` Servlet 侦听器、 Spring MVC`DispatcherServlet`等时使用该实现。下面的`web.xml`片段配置了典型的 Spring MVC Web 应用程序(请注意使用`contextClass`context-param 和 init-param):
```
的`AnnotationConfigWebApplicationContext`的替代项。详情见[`GenericWebApplicationContext`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/context/support/genericwebapplicationcontext.html)爪哇doc。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.12.3.使用`@Bean`注释
`@Bean`是一个方法级的注释,是 XML`
,或者对于由其
实现类型可能引用的组件,更安全的做法是声明尽可能具体的返回类型
(至少与引用你的 Bean 的注入点所要求的特定类型一样)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### Bean 相依性
带注释的`@Bean`方法可以具有任意数量的参数,这些参数描述了构建 Bean 所需的依赖关系。例如,如果我们的`TransferService`需要`AccountRepository`,则可以使用方法参数实现该依赖关系,如下例所示:
爪哇
```
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
}
```
Kotlin
```
@Configuration
class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService {
return TransferServiceImpl(accountRepository)
}
}
```
解析机制与基于构造函数的依赖注入几乎相同。有关更多详细信息,请参见[有关部分](#beans-constructor-injection)。
##### 接收生命周期回调
使用`@Bean`注释定义的任何类都支持常规的生命周期回调,并且可以使用 JSR-250 中的`@PostConstruct`和`@PreDestroy`注释。有关更多详细信息,请参见[JSR-250 注解](#beans-postconstruct-and-predestroy-annotations)。
常规的 Spring [lifecycle](#beans-factory-nature)回调也完全支持。如果 Bean 实现了`InitializingBean`、`DisposableBean`或`Lifecycle`,则容器调用它们各自的方法。
标准的`*Aware`接口集合(如[BeanFactoryAware](#beans-beanfactory),[Beannameaware](#beans-factory-aware),[MessagesourceAware](#context-functionality-messagesource),[应用情境软件](#beans-factory-aware),等等)也是完全支持的。
`@Bean`注释支持指定任意的初始化和销毁回调方法,很像 Spring XML 的`init-method`和`destroy-method`元素上的属性,如下例所示:
爪哇
```
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();
}
}
```
Kotlin
```
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()
}
```
| |默认情况下,使用 爪哇 配置定义的具有公共`close`或`shutdown`方法的 bean 将自动加入销毁回调。如果你有一个公共的`close`或`shutdown`方法,并且不希望在容器
关闭时调用它,你可以将`@Bean(destroyMethod="")`添加到你的 Bean 定义中,以禁用
默认`(inferred)`模式。
对于你通过 JNDI 获得的资源,你可能希望在默认情况下这样做,因为其
生命周期是在应用程序之外管理的。特别是,对于`DataSource`,要确保始终执行
,下面的示例显示了如何防止`DataSource`的自动销毁回调:
在 爪哇 EE 应用服务器上是有问题的:
java
```
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
return (DataSource) jndiTemplate.lookup("MyDS");
}
```
Kotlin gt r=“/48”/>“><2917"/>><>>>>><<>>>>>>>>>>>>>>>>>>><<2946>>>>>>>>>>>>
使用 Spring 的`JndiTemplate`或`JndiLocatorDelegate`助手或直接的 jndi`InitialContext`使用但不使用`JndiObjectFactoryBean`变体(这将强制
你将返回类型声明为`FactoryBean`类型,而不是实际的目标
类型,使得在其他`@Bean`方法中的交叉引用调用更难使用,这些方法
打算在此引用所提供的资源)。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
在上面的例子`BeanOne`的情况下,在构造过程中直接调用`init()`方法将同样有效,如下例所示:
爪哇
```
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne();
beanOne.init();
return beanOne;
}
// ...
}
```
Kotlin
```
@Configuration
class AppConfig {
@Bean
fun beanOne() = BeanOne().apply {
init()
}
// ...
}
```
| |当你直接在 爪哇 中工作时,你可以对对象执行任何你喜欢的操作,并且执行
,并不总是需要依赖容器生命周期。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------|
##### 指定 Bean 范围
Spring 包括`@Scope`注释,以便可以指定 Bean 的作用域。
###### 使用`@Scope`注释
你可以指定使用`@Bean`注释定义的 bean 应该具有特定的作用域。你可以使用[Bean Scopes](#beans-factory-scopes)部分中指定的任何标准作用域。
默认的作用域是`singleton`,但是你可以使用`@Scope`注释来覆盖此范围,如下例所示:
爪哇
```
@Configuration
public class MyConfiguration {
@Bean
@Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
```
Kotlin
```
@Configuration
class MyConfiguration {
@Bean
@Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
```
###### `@Scope`和`scoped-proxy`
Spring 通过[作用域代理](#beans-factory-scopes-other-injection)提供了一种处理作用域依赖关系的方便方法。在使用 XML 配置时,创建这样的代理的最简单的方法是`
在`@Configuration`类中声明时才起作用。不能使用普通的`@Component`类来声明 Bean 之间的依赖关系
。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### 查找方法注入
如前所述,[查找方法注入](#beans-factory-method-injection)是一个很少使用的高级特性。在单作用域 Bean 依赖于原型作用域 Bean 的情况下,它是有用的。对这种类型的配置使用 爪哇 为实现这种模式提供了一种自然的方法。下面的示例展示了如何使用查找方法注入:
爪哇
```
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();
}
```
Kotlin
```
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
}
```
通过使用 爪哇 配置,你可以创建`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();
}
}
}
```
Kotlin
```
@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()
}
}
}
```
##### 有关基于 爪哇 的配置如何在内部工作的更多信息 #####
考虑以下示例,该示例显示了一个`@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();
}
}
```
Kotlin
```
@Configuration
class AppConfig {
@Bean
fun clientService1(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientService2(): ClientService {
return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientDao(): ClientDao {
return ClientDaoImpl()
}
}
```
`clientDao()`在`clientService1()`中调用过一次,在`clientService2()`中调用过一次。由于此方法创建了`ClientDaoImpl`的一个新实例并返回它,因此你通常希望有两个实例(每个服务有一个实例)。这肯定会有问题:在 Spring 中,实例化的 bean 默认具有`singleton`作用域。这就是神奇之处:所有`@Configuration`类在启动时都用`CGLIB`子类。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器中是否有任何缓存的(作用域)bean。
| |根据你的 Bean 的范围,行为可能会有所不同。我们在这里讨论的是
关于单身汉的问题。|
|---|--------------------------------------------------------------------------------------------------------------|
| |从 Spring 3.2 开始,不再需要将 CGLIB 添加到你的 Classpath 中,因为 CGLIB类已经在下重新打包,并直接包含在 Spring-core jar 中。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |由于 CGlib 在
启动时动态添加特性,因此有一些限制。特别是,配置类不能是最终的。但是,正如
of4.3,在配置类上允许使用任何构造函数,包括使用`@Autowired`或为默认注入使用单个非默认构造函数声明。
如果你希望避免任何 CGLIB 施加的限制,请考虑在非-`@Bean`类上声明你的`@Bean`方法(例如,在普通的`@Component`类上)。
方法之间的跨方法调用不会被截获,因此你有
在构造函数或方法级别完全依赖于依赖注入。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.12.5.编写基于 爪哇 的配置
Spring 的基于 爪哇 的配置特性允许你编写注释,这可以降低配置的复杂性。
##### 使用`@Import`注释
正如在 Spring XML 文件中使用`
类的引用,类似于`AnnotationConfigApplicationContext.register`方法。
如果你想要避免组件扫描,通过使用几个
配置类作为切入点来显式地定义所有组件,这是特别有用的。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
###### 对导入的`@Bean`定义注入依赖项
前面的例子是可行的,但过于简单化了。在大多数实际场景中,跨配置类的 bean 彼此具有依赖性。在使用 XML 时,这不是一个问题,因为不涉及编译器,并且你可以声明`ref="someBean"`并信任 Spring 在容器初始化期间将其计算出来。当使用`@Configuration`类时,Java 编译器对配置模型施加约束,因为对其他 bean 的引用必须是有效的 Java 语法。
幸运的是,解决这个问题很简单。作为[我们已经讨论过了](#beans-java-dependencies),`@Bean`方法可以具有任意数量的描述 Bean 依赖关系的参数。考虑以下更现实的场景,其中包含几个`@Configuration`类,每个类取决于在其他类中声明的 bean:
Java
```
@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");
}
```
Kotlin
```
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
可能会导致意外的早期初始化。只要有可能,就求助于
基于参数的注入,就像在前面的示例中一样。
此外,要特别小心`BeanPostProcessor`和`BeanFactoryPostProcessor`定义
通过`@Bean`。这些方法通常应该声明为`static @Bean`方法,而不是触发其包含的配置类的
实例化。否则,`@Autowired`和`@Value`可能不会在配置类本身上工作,因为有可能在[`AutowiredAnnotationBeanPostProcessor`]之前将其创建为 Bean 实例(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/org/SpringFramework/beans/factory/factory/annotation/autoidannotationbeanpostpostchlor.html)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
下面的示例显示了一个 Bean 如何能够自动连接到另一个 Bean:
Java
```
@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");
}
```
Kotlin
```
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
框架 4.3 支持。还请注意,如果目标
Bean 只定义了一个构造函数,则不需要指定`@Autowired`。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
[]()完全合格的导入 bean,便于导航
在前面的场景中,使用`@Autowired`可以很好地工作并提供所需的模块化,但是确定 AutoWired Bean 定义的确切声明位置仍然有些模棱两可。例如,作为查看`ServiceConfig`的开发人员,你如何确切地知道在哪里声明了`@Autowired AccountRepository` Bean?它在代码中不是显式的,这可能就很好了。请记住,[Spring Tools for Eclipse](https://spring.io/tools)提供了工具,可以呈现显示所有事物如何连接的图形,这可能就是你所需要的。另外,你的 Java IDE 可以很容易地找到`AccountRepository`类型的所有声明和用法,并快速向你显示返回该类型的`@Bean`方法的位置。
如果这种歧义是不可接受的,并且你希望在 IDE 中从一个`@Configuration`类直接导航到另一个类,请考虑自动连接配置类本身。下面的示例展示了如何做到这一点:
Java
```
@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());
}
}
```
Kotlin
```
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method!
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
```
在前面的情况中,其中`AccountRepository`是完全显式定义的。然而,`ServiceConfig`现在与`RepositoryConfig`紧密耦合。这是一种权衡。通过使用基于接口或基于抽象类的`@Configuration`类,可以在一定程度上减轻这种紧密耦合。考虑以下示例:
Java
```
@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");
}
```
Kotlin
```
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
将其中一些声明为`@Lazy`(用于在首次访问而不是在启动时创建)
或将其声明为`@DependsOn`某些其他 bean(确保特定的其他 bean 是在当前 Bean 之前创建的
,而后者的直接依赖所隐含的意义则更大)。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### 有条件地包括`@Configuration`类或`@Bean`方法
通常有条件地启用或禁用一个完整的`@Configuration`类,甚至基于某个任意系统状态的单个`@Bean`方法是有用的。一个常见的例子是,只有在 Spring `Environment`中启用了特定配置文件时,才使用`@Profile`注释来激活 bean(有关详细信息,请参见[Bean Definition Profiles](#beans-definition-profiles))。
`@Profile`注释实际上是通过使用一种更灵活的注释[`@Conditional`]来实现的(https://DOCS. Spring.io/ Spring-Framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/conditional.html)。`@Conditional`注释表示在注册`@Bean`之前应该参考的特定`org.springframework.context.annotation.Condition`实现。
`Condition`接口的实现提供了一个`matches(…)`方法,该方法返回`true`或`false`。例如,下面的清单显示了用于`Condition`的实际`@Profile`实现:
Java
```
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes
MultiValueMap
引用过它,并且不太可能通过名称从容器中显式地获取它,同样,
Bean 也只能通过类型自动连线,所以显式 Bean `id`并不是严格要求的。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
[]()使用 \
JNDI 查找,方法是使用 Spring 的`JndiTemplate`/`JndiLocatorDelegate`helpers 或
直接的 JNDI`InitialContext`用法,但不使用前面显示的`JndiObjectFactoryBean`变体,这将迫使你将返回类型声明为`FactoryBean`类型。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
配置文件字符串可以包含一个简单的配置文件名(例如,`production`)或一个配置文件表达式。配置文件表达式允许表达更复杂的配置文件逻辑(例如,`production & us-east`)。配置文件表达式中支持以下操作符:
* `!`:配置文件的逻辑“not”
* `&`:配置文件的逻辑“和”
* `|`:配置文件的逻辑“或”
| |You cannot mix the `&` and `|` operators without using parentheses. For example,`production & us-east |EU-Central` is not a valid expression. It must be expressed as`Production&(美国东部)| eu-central)`.|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
你可以使用`@Profile`作为[元注释](#beans-meta-annotations)来创建自定义组合注释。下面的示例定义了一个自定义的`@Production`注释,你可以将其用作`@Profile("production")`的插入替换:
Java
```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}
```
Kotlin
```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Profile("production")
annotation class Production
```
| |如果一个`@Configuration`类被标记为`@Profile`,则所有与该类相关的`@Bean`方法和`@Import`注释都将被绕过,除非一个或多个
指定的配置文件处于活动状态。如果一个`@Component`或`@Configuration`类被标记为
并带有`@Profile({"p1", "p2"})`,则除非
配置文件“p1”或“p2”已被激活,否则该类不会被注册或处理。如果给定概要文件的前缀是
not 运算符(`!`),则只有在概要文件不是
活动的情况下,才会注册带注释的元素。例如,给定`@Profile({"p1", "!p2"})`,如果配置文件
’P1’是活动的,或者如果配置文件’P2’不是活动的,就会发生注册。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
`@Profile`还可以在方法级别上声明仅包括一个特定的 Bean 配置类(例如,对于特定的 Bean 的可选变体),如以下示例所示:
Java
```
@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`配置文件中可用。|
Kotlin
```
@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`配置文件中可用。|
| |对于`@Profile`on`@Bean`方法,可能会应用一个特殊的场景:在
重载`@Bean`相同 Java 方法名的方法的情况下(类似于构造函数
重载),需要在所有`@Profile`重载方法上一致地声明`@Profile`条件。如果条件不一致,在重载的方法中,只有
第一个声明上的条件才重要。因此,`@Profile`可以不使用
在
上选择具有特定参数签名的重载方法。相同 Bean 的所有工厂方法之间的解析遵循 Spring 的
构造函数在创建时的解析算法。
如果你想定义具有不同配置文件条件的替代 bean,
使用不同的 Java 方法名称,通过使用`@Bean`名称
属性指向相同的 Bean 名称,如前面的例子所示。如果参数签名都是
相同的(例如,所有变量都有 no-arg 工厂方法),这是唯一的
在有效的 Java 类中表示这样的安排的方式,放在第一个位置
(因为对于特定的名称和参数签名,只能有一种方法)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### XML Bean 定义配置文件
XML 对应的是`profile`元素的`
可以使用`!`操作符来否定配置文件。也可以通过嵌套配置文件来应用逻辑
“and”,如下例所示:
```
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
```
在前面的示例中,如果`dataSource` Bean 和`us-east`配置文件都处于活动状态,则将暴露`dataSource`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### 激活配置文件
既然我们已经更新了配置,我们仍然需要指示 Spring 哪个配置文件是活动的。如果我们现在开始示例应用程序,我们将看到一个`NoSuchBeanDefinitionException`抛出,因为容器找不到名为`dataSource`的 Spring Bean。
激活配置文件可以通过几种方式完成,但最直接的方法是通过`Environment`API 以编程方式完成它,该 API 可通过`ApplicationContext`获得。下面的示例展示了如何做到这一点:
Java
```
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
```
Kotlin
```
val ctx = AnnotationConfigApplicationContext().apply {
environment.setActiveProfiles("development")
register(SomeConfig::class.java, StandaloneDataConfig::class.java, JndiDataConfig::class.java)
refresh()
}
```
此外,你还可以通过`spring.profiles.active`属性声明性地激活配置文件,该属性可以通过系统环境变量、JVM 系统属性、`web.xml`中的 Servlet 上下文参数、甚至作为 JNDI 中的条目来指定(参见[`PropertySource`Abstraction](#beans-property-source-abstraction))。在集成测试中,可以通过在`spring-test`模块中使用`@ActiveProfiles`注释来声明活动配置文件(参见[具有环境配置文件的上下文配置](testing.html#testcontext-ctx-management-env-profiles))。
请注意,配置文件不是一个“非此即彼”的命题。你可以一次激活多个配置文件。通过编程,你可以为`setActiveProfiles()`方法提供多个配置文件名,该方法接受`String…`vargs。以下示例激活多个配置文件:
Java
```
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
```
Kotlin
```
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
```
声明地,`spring.profiles.active`可以接受以逗号分隔的配置文件名称列表,如下例所示:
```
-Dspring.profiles.active="profile1,profile2"
```
##### 默认配置文件
默认配置文件表示默认启用的配置文件。考虑以下示例:
Java
```
@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();
}
}
```
Kotlin
```
@Configuration
@Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
```
如果没有活动的配置文件,则创建`dataSource`。你可以将此视为为一个或多个 bean 提供默认定义的一种方式。如果启用了任何配置文件,则默认配置文件不适用。
你可以通过在`Environment`上使用`setDefaultProfiles()`来更改默认配置文件的名称,或者以声明性的方式使用`spring.profiles.default`属性来更改默认配置文件的名称。
#### 1.13.2.`PropertySource`抽象
Spring 的`Environment`抽象提供了对属性源的可配置层次结构的搜索操作。考虑一下以下清单:
Java
```
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);
```
Kotlin
```
val ctx = GenericApplicationContext()
val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
```
在前面的代码片段中,我们看到了一种高级的方式来询问 Spring 是否为当前环境定义了`my-property`属性。为了回答这个问题,`Environment`对象在一组[`PropertySource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/propertysource.html)对象上执行搜索。a`PropertySource`是对任何键值对的源的简单抽象, Spring 的[`StandardEnvironment`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/standardenvironment.html)配置了两个 PropertySource 对象——一个代表 JVM 系统属性集(`System.getProperties()`),一个代表系统环境变量集(
应用程序。[`StandardServletEnvironment`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/web/context/support/standardservletenvironment.html)填充了其他默认属性源,包括 Servlet config 和 Servlet
context 参数。它可以任选地启用一个[`JndiPropertySource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/jndi/jndipropertysource.html)。
环境变量。因此,如果`my-property`属性在
调用`env.getProperty("my-property")`的过程中恰好在这两个地方都设置了,则系统属性值“wins”并被返回。,
注意,属性值不是合并
,而是完全被前面的一个条目覆盖,
对于一个常见的`StandardServletEnvironment`,完整的层次结构如下,
优先级最高的条目位于顶部:
1。ServletConfig 参数(如果适用——例如,在`DispatcherServlet`上下文的情况下)
2。ServletContext 参数
3。JNDI 环境变量(`java:comp/env/`条目)
4。JVM 系统属性(`-D`命令行参数)
5。JVM 系统环境(操作系统环境变量)|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
最重要的是,整个机制是可配置的。也许你有希望集成到此搜索中的自定义属性源。要做到这一点,实现并实例化你自己的`PropertySource`,并将其添加到当前`PropertySources`的`Environment`的集合中。下面的示例展示了如何做到这一点:
Java
```
ConfigurableApplicationContext ctx = new GenericApplicationContext();
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
sources.addFirst(new MyPropertySource());
```
Kotlin
```
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources
sources.addFirst(MyPropertySource())
```
在前面的代码中,`MyPropertySource`已被添加到搜索中具有最高优先级的位置。如果它包含一个`my-property`属性,则检测并返回该属性,以有利于任何其他`my-property`中的任何`PropertySource`属性。[`MutablePropertySources`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/ENV/mutablepropertysources.html)API 公开了许多允许精确操作属性源集合的方法。
#### 1.13.3.使用`@PropertySource`
[`@PropertySource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/annotation/propertysource.html)注释提供了一种方便的声明性机制,用于将`PropertySource`添加到 Spring 的`Environment`。
给定一个名为`app.properties`的文件,该文件包含键-值对`testbean.name=myTestBean`,下面的`@Configuration`类使用`@PropertySource`,使得对`testBean.getName()`的调用返回`myTestBean`:
Java
```
@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;
}
}
```
Kotlin
```
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
```
在`@PropertySource`资源位置中存在的任何`${…}`占位符都将根据已经针对该环境注册的一组属性源进行解析,如下例所示:
Java
```
@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;
}
}
```
Kotlin
```
@Configuration
@PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties")
class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
```
假设`my.placeholder`存在于已经注册的一个属性源中(例如,系统属性或环境变量),则将占位符解析为相应的值。如果不是,则将`default/path`作为默认值。如果没有指定默认值,并且无法解析某个属性,则抛出一个`IllegalArgumentException`。
| |根据 Java8 约定,`@PropertySource`注释是可重复的。
然而,所有此类`@PropertySource`注释都需要在相同的
级别上声明,可以直接在配置类上声明,也可以在
相同的自定义注释中声明元注释。混合直接注释和元注释不是
推荐的,因为直接注释有效地覆盖了元注释。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.13.4.语句中的占位符解析
从历史上看,元素中占位符的值只能根据 JVM 系统属性或环境变量来解析。现在已经不是这样了。因为`Environment`抽象集成在整个容器中,所以很容易通过它来路由占位符的解析。这意味着你可以以你喜欢的任何方式配置解析过程。你可以更改通过系统属性和环境变量进行搜索的优先级,或者完全删除它们。你也可以将自己的属性源添加到该组合中,视情况而定。
具体地说,无论`customer`属性在哪里定义,只要它在`Environment`中可用,以下语句都可以工作:
```
bundle,而只会使用找到的第一个 bundle。
具有相同基名的后续消息 bundle 将被忽略。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |作为`ResourceBundleMessageSource`的替代方案, Spring 提供了一个`ReloadableResourceBundleMessageSource`类。这个变体支持相同的 bundle
文件格式,但比基于`ResourceBundleMessageSource`实现的标准 JDK 更灵活。特别是,它允许从任何 Spring 资源位置读取
文件(不仅是从 Classpath),并支持 hot
重新加载捆绑包属性文件(同时在两者之间有效地缓存它们)。
参见[`ReloadableResourceBundleMessageSource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-context/org/javadoFramework/org/javadoFramework/org/reloadableResourceource.html)|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.15.2.标准和自定义事件
`ApplicationContext`中的事件处理是通过`ApplicationEvent`类和`ApplicationListener`接口提供的。如果将实现`ApplicationListener`接口的 Bean 部署到上下文中,则每次将`ApplicationEvent`发布到`ApplicationContext`时,都会通知 Bean 该接口。本质上,这是标准的观察者设计模式。
| |截至 Spring 4.2,事件基础结构已经得到了显著的改进,并且提供了
和[基于注释的模型](#context-functionality-events-annotation)以及
发布任意事件的能力(即,不一定从
扩展的对象`ApplicationEvent`)。当这样的对象被发布时,我们将它包装为
事件。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
下表描述了 Spring 提供的标准事件:
| Event |解释|
|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ContextRefreshedEvent` |当`ApplicationContext`被初始化或刷新时发布(例如,通过
使用`refresh()`接口上的`ConfigurableApplicationContext`方法)。
在这里,“初始化”意味着加载所有 bean,检测到后处理器 bean
并激活,预先实例化单例,`ApplicationContext`对象
刷新,前提是所选的`ApplicationContext`实际上支持这样的
“热”刷新。例如,`XmlWebApplicationContext`支持热刷新,但`GenericApplicationContext`不支持。|
| `ContextStartedEvent` |当`ApplicationContext`通过在`ConfigurableApplicationContext`接口上使用`start()`方法启动`ApplicationContext`时发布。在这里,“started”表示所有`Lifecycle`bean 都接收到一个显式的开始信号。通常,此信号用于在显式停止后重新启动 bean
,但也可以用于启动未被
配置为自动启动的组件(例如,在
初始化上尚未启动的组件)。|
| `ContextStoppedEvent` |当`ApplicationContext`通过在`ConfigurableApplicationContext`接口上使用`stop()`方法停止`ApplicationContext`时发布。这里,“stopped”表示所有`Lifecycle`bean 都接收到一个明确的停止信号。可以通过`start()`调用重新启动已停止的上下文。|
| `ContextClosedEvent` |当`ApplicationContext`通过`close()`接口上的
方法
关闭`ApplicationContext`时或通过 JVM 关机钩子关闭时发布。在这里,
“closed”表示所有单例 bean 都将被销毁。一旦上下文被关闭,
它就到达了生命周期的终点,不能刷新或重新启动。|
| `RequestHandledEvent` |一个特定于 Web 的事件,告诉所有 bean 一个 HTTP 请求已被服务。此
事件将在请求完成后发布。此事件仅适用于使用 Spring 的
的`DispatcherServlet`的 Web 应用程序。|
|`ServletRequestHandledEvent`|添加 Servlet 特定上下文信息的`RequestHandledEvent`子类。|
你还可以创建和发布自己的自定义事件。下面的示例展示了一个扩展 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...
}
```
Kotlin
```
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
```
要发布自定义`ApplicationEvent`,请在`ApplicationEventPublisher`上调用`publishEvent()`方法。通常,这是通过创建一个实现`ApplicationEventPublisherAware`的类并将其注册为 Spring Bean 来完成的。下面的示例展示了这样一个类:
爪哇
```
public class EmailService implements ApplicationEventPublisherAware {
private List
之间进行简单的通信。然而,对于更复杂的 Enterprise
集成需求,单独维护的[Spring Integration](https://projects.spring.io/spring-integration/)项目提供了
构建轻量级、[以模式为导向](https://www.enterpriseintegrationpatterns.com)事件驱动的
架构的完整支持,这些架构建立在著名的 Spring 编程模型上。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### 基于注释的事件监听器
可以使用`@EventListener`注释在托管 Bean 的任何方法上注册事件侦听器。`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...
}
}
```
Kotlin
```
class BlockedListNotifier {
lateinit var notificationAddress: String
@EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
```
方法签名再次声明它监听的事件类型,但是,这一次,使用灵活的名称,并且不实现特定的监听器接口。只要实际的事件类型在其实现层次结构中解析你的泛型参数,就可以通过泛型来缩小事件类型的范围。
如果你的方法应该监听多个事件,或者如果你想在完全不使用参数的情况下定义它,那么也可以在注释本身上指定事件类型。下面的示例展示了如何做到这一点:
爪哇
```
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
```
Kotlin
```
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
// ...
}
```
还可以通过使用注释的`condition`属性添加额外的运行时过滤,该注释定义了一个[`SpEL`表达式](# 表达式),它应该与实际调用特定事件的方法相匹配。
下面的示例显示了只有当事件的`content`属性等于`my-event`时,才可以重写我们的通知符:
爪哇
```
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
```
Kotlin
```
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlockedListEvent(blEvent: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
```
每个`SpEL`表达式都针对一个专用上下文进行计算。下表列出了对上下文可用的项,以便你可以将它们用于条件事件处理:
| Name | Location |说明| Example |
|---------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| Event | root object |实际`ApplicationEvent`。| `#root.event` or `event` |
|Arguments array| root object |用于调用方法的参数(作为对象数组)。| `#root.args` or `args`; `args[0]` to access the first argument, etc. |
|*Argument name*|evaluation context|任何方法参数的名称。如果由于某种原因,名称不可用
(例如,因为编译的字节代码中没有调试信息),则单独的
参数也可以使用`#a<#arg>`语法,其中`<#arg>`表示
参数索引(从 0 开始)。|`#blEvent` or `#a0` (you can also use `#p0` or `#p<#arg>` parameter notation as an alias)|
请注意,`#root.event`允许你访问底层事件,即使你的方法签名实际上引用了已发布的任意对象。
如果需要发布一个事件作为处理另一个事件的结果,则可以更改方法签名以返回应该发布的事件,如下例所示:
爪哇
```
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
```
Kotlin
```
@EventListener
fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
```
| |[异步侦听器](#context-functionality-events-async)不支持此功能。|
|---|-----------------------------------------------------------------------------------------------|
`handleBlockedListEvent()`方法为它处理的每个`BlockedListEvent`发布一个新的`ListUpdateEvent`。如果需要发布多个事件,可以返回`Collection`或一个事件数组。
##### 异步侦听器
如果你希望一个特定的侦听器异步地处理事件,那么你可以重用[regular`@Async`support](integration.html#schooling-annotation-support-async)。下面的示例展示了如何做到这一点:
爪哇
```
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
```
Kotlin
```
@EventListener
@Async
fun processBlockedListEvent(event: BlockedListEvent) {
// BlockedListEvent is processed in a separate thread
}
```
在使用异步事件时要注意以下限制:
* 如果异步事件侦听器抛出`Exception`,则不会将其传播给调用方。详见[`AsyncUncaughtExceptionHandler`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/ AOP/interceptor/asyncuncaughtexceptionhandler.html)。
* 异步事件侦听器方法不能通过返回值来发布后续事件。如果需要发布另一个事件作为处理的结果,则注入一个[`ApplicationEventPublisher`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/context/applicationeventpublisher.html)来手动发布事件。
##### 命令听众
如果需要在调用另一个侦听器之前调用一个侦听器,则可以将`@Order`注释添加到方法声明中,如下例所示:
爪哇
```
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
```
Kotlin
```
@EventListener
@Order(42)
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
```
##### 一般事件
你还可以使用泛型来进一步定义事件的结构。考虑使用`EntityCreatedEvent
事件发送的任意对象。|
|---|--------------------------------------------------------------------------------------------------|
#### 1.15.3.对低级资源的方便访问
为了优化应用程序上下文的使用和理解,你应该熟悉 Spring 的`Resource`抽象,如[资源](#resources)中所述。
一个应用程序上下文是`ResourceLoader`,它可以用来加载`Resource`对象。a`Resource`本质上是 JDK`java.net.URL`类的一个功能更丰富的版本。实际上,在`Resource`的实现方式中,在适当的情况下包装`java.net.URL`的实例。a`Resource`可以以透明的方式从几乎任何位置获得低级资源,包括从 Classpath、文件系统位置、标准 URL 可描述的任何地方以及其他一些变体。如果资源位置字符串是一个没有任何特殊前缀的简单路径,那么这些资源的来源是特定的,并且适合实际的应用程序上下文类型。
可以将 Bean 部署到应用程序上下文中以实现特殊的回调接口`ResourceLoaderAware`,以便在初始化时自动回调,并将应用程序上下文本身作为`ResourceLoader`传入。还可以公开类型`Resource`的属性,用于访问静态资源。它们像其他任何属性一样被注入其中。你可以将这些`Resource`属性指定为简单的`String`路径,并依赖于在部署 Bean 时将这些文本字符串自动转换为实际的`Resource`对象。
提供给`ApplicationContext`构造函数的位置路径实际上是资源字符串,并且以简单的形式根据特定的上下文实现适当地处理。例如,`ClassPathXmlApplicationContext`将简单的位置路径视为 Classpath 位置。你还可以使用带有特殊前缀的位置路径(资源字符串)来强制从 Classpath 或 URL 加载定义,而不管实际的上下文类型如何。
#### 1.15.4.应用程序启动跟踪
`ApplicationContext`管理 Spring 应用程序的生命周期,并提供围绕组件的丰富的编程模型。因此,复杂的应用程序可以具有同样复杂的组件图和启动阶段。
使用特定的指标跟踪应用程序的启动步骤可以帮助了解在启动阶段花费的时间,但也可以将其用作更好地理解整个上下文生命周期的一种方式。
`AbstractApplicationContext`(及其子类)用`ApplicationStartup`进行检测,它收集关于各种启动阶段的`StartupStep`数据:
* 应用程序上下文生命周期(基本包扫描、配置类管理)
* bean 生命周期(实例化、智能初始化、后处理)
* 应用程序事件处理
下面是`AnnotationConfigApplicationContext`中的一个插装示例:
爪哇
```
// create a startup step and start recording
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
```
Kotlin
```
// create a startup step and start recording
val scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages))
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages)
// end the current step
scanPackages.end()
```
应用程序上下文已经通过多个步骤进行了检测。一旦记录了这些启动步骤,就可以用特定的工具收集、显示和分析这些步骤。有关现有启动步骤的完整列表,你可以查看[专用附录部分](#application-startup-steps)。
默认的`ApplicationStartup`实现是一个无操作的变体,以减少开销。这意味着默认情况下,在应用程序启动期间不会收集任何指标。 Spring Framework 附带了一种用于跟踪使用 爪哇 飞行记录器的启动步骤的实现:`FlightRecorderApplicationStartup`。要使用这个变体,你必须在创建它之后立即将它的实例配置为`ApplicationContext`。
如果开发人员提供自己的`AbstractApplicationContext`子类,或者如果他们希望收集更精确的数据,那么他们也可以使用`ApplicationStartup`基础架构。
| |`ApplicationStartup`仅用于应用程序启动期间和
核心容器;这绝不是对 爪哇 探查器或
等度量类库的替代。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
要开始收集自定义`StartupStep`,组件可以直接从应用程序上下文获得`ApplicationStartup`实例,使其组件实现`ApplicationStartupAware`,或者在任何注入点上请求`ApplicationStartup`类型。
| |开发人员在创建自定义启动步骤时不应使用`"spring.*"`名称空间。
此名称空间是为内部 Spring 使用而保留的,并且可能会发生更改。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.15.5.用于 Web 应用程序的方便的应用程序上下文实例化
你可以通过使用(例如)`ContextLoader`来声明性地创建`ApplicationContext`实例。当然,你也可以通过使用`ApplicationContext`实现之一以编程方式创建`ApplicationContext`实例。
你可以使用`ContextLoaderListener`注册`ApplicationContext`,如下例所示:
```
,甚至不向同一应用程序的其他模块公开组件。与基于
RAR 的`ApplicationContext`的交互通常通过它与
其他模块共享的 JMS 目的地进行。基于 RAR 的`ApplicationContext`也可以,例如,调度一些作业
或对文件系统中的新文件做出反应(或类似)。如果它需要允许来自外部的同步
访问,则它可以(例如)导出 RMI 端点,这可以由同一台机器上的其他应用程序模块使用
。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 1.16.the`BeanFactory`
`BeanFactory`API 为 Spring 的 IOC 功能提供了底层基础。它的特定契约主要用于与 Spring 和相关的第三方框架的其他部分的集成,并且它的`DefaultListableBeanFactory`实现是更高级别`GenericApplicationContext`容器中的一个关键委托。
`BeanFactory`和相关接口(如`BeanFactoryAware`、`InitializingBean`、`DisposableBean`)是其他框架组件的重要集成点。通过不需要任何注释,甚至不需要反射,它们允许容器与其组件之间进行非常有效的交互。应用程序级 bean 可能使用相同的回调接口,但通常更喜欢声明性依赖注入,或者通过注释,或者通过编程配置。
请注意,核心`BeanFactory`API 级别及其`DefaultListableBeanFactory`实现不对要使用的配置格式或任何组件注释进行假设。所有这些风格都是通过扩展(例如`XmlBeanDefinitionReader`和`AutowiredAnnotationBeanPostProcessor`)来实现的,并在共享的`BeanDefinition`对象上作为核心元数据表示进行操作。这就是 Spring 的容器如此灵活和可扩展的本质。
#### 1.16.1.`BeanFactory`还是`ApplicationContext`?
本节解释`BeanFactory`和`ApplicationContext`容器级别之间的差异以及对引导的影响。
除非你有充分的理由不这样做,否则你应该使用`ApplicationContext`,将`GenericApplicationContext`及其子类`AnnotationConfigApplicationContext`作为自定义引导的公共实现。这些是 Spring 核心容器的主要入口点,用于所有常见目的:加载配置文件,触发 Classpath 扫描,以编程方式注册 Bean 定义和注释的类,以及(截至 5.0)注册功能 Bean 定义。
因为`ApplicationContext`包含了`BeanFactory`的所有功能,所以一般建议使用普通的`BeanFactory`,除非需要对 Bean 处理进行完全控制。在`ApplicationContext`(例如`GenericApplicationContext`实现)中,可以根据约定(即通过 Bean name 或 Bean type——特别是后处理器)检测几种 bean,而普通的`DefaultListableBeanFactory`对任何特殊的 bean 都是不可知的。
对于许多扩展容器特性,例如注释处理和 AOP 代理,[`BeanPostProcessor`扩展点]是必不可少的。如果只使用普通的`DefaultListableBeanFactory`,则默认情况下不会检测到并激活这样的后处理器。这种情况可能会令人困惑,因为你的 Bean 配置实际上没有任何问题。相反,在这种情况下,容器需要通过额外的设置进行完全引导。
下表列出了`BeanFactory`和`ApplicationContext`接口和实现提供的功能。
|特征|`BeanFactory`|`ApplicationContext`|
|------------------------------------------------------------|-------------|--------------------|
|Bean 实例化/布线| Yes | Yes |
|集成的生命周期管理| No | Yes |
|自动`BeanPostProcessor`注册| No | Yes |
|自动`BeanFactoryPostProcessor`注册| No | Yes |
|方便`MessageSource`访问(用于国际化)| No | Yes |
|内置`ApplicationEvent`发布机制| No | Yes |
要显式地用`DefaultListableBeanFactory`注册 Bean 后处理器,需要以编程方式调用`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
```
Kotlin
```
val factory = DefaultListableBeanFactory()
// populate the factory with bean definitions
// now register any needed BeanPostProcessor instances
factory.addBeanPostProcessor(AutowiredAnnotationBeanPostProcessor())
factory.addBeanPostProcessor(MyBeanPostProcessor())
// now start using the factory
```
要将`BeanFactoryPostProcessor`应用于普通的`DefaultListableBeanFactory`,需要调用其`postProcessBeanFactory`方法,如下例所示:
爪哇
```
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));
// bring in some property values from a Properties file
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
// now actually do the replacement
cfg.postProcessBeanFactory(factory);
```
Kotlin
```
val factory = DefaultListableBeanFactory()
val reader = XmlBeanDefinitionReader(factory)
reader.loadBeanDefinitions(FileSystemResource("beans.xml"))
// bring in some property values from a Properties file
val cfg = PropertySourcesPlaceholderConfigurer()
cfg.setLocation(FileSystemResource("jdbc.properties"))
// now actually do the replacement
cfg.postProcessBeanFactory(factory)
```
在这两种情况下,显式的注册步骤都是不方便的,这就是为什么在 Spring 支持的应用程序中,各种`ApplicationContext`变体比普通的`DefaultListableBeanFactory`更受欢迎,特别是当在典型的 Enterprise 设置中依赖`BeanFactoryPostProcessor`和`BeanPostProcessor`实例用于扩展容器功能时。
| |一个`AnnotationConfigApplicationContext`已经注册了所有常见的注解后处理程序
,并且可以通过配置注解在
覆盖下引入额外的处理器,例如`@EnableTransactionManagement`。
在 Spring 的基于注解的配置模型的抽象级,
后处理程序的概念变成了仅仅是内部容器的详细信息。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
## 2. Resources
本章介绍了 Spring 如何处理资源,以及如何使用 Spring 中的资源。它包括以下主题:
* [导言](#resources-introduction)
* [the`Resource`接口](#resources-resource)
* [内置`Resource`实现](#resources-implementations)
* [the`ResourceLoader`接口](#resources-ResourceLoader)
* [the`ResourcePatternResolver`接口](#resources-ResourcePatternResolver)
* [the`ResourceLoaderAware`接口](#resources-ResourceLoaderAware)
* [作为依赖关系的资源](#resources-as-dependencies)
* [应用程序上下文和资源路径](#resources-app-ctx)
### 2.1.导言
令人遗憾的是,爪哇 的标准`java.net.URL`类和各种 URL 前缀的标准处理程序对于所有对低级资源的访问都不够充分。例如,没有标准化的`URL`实现,其可用于访问需要从 Classpath 或相对于`ServletContext`获得的资源。虽然可以为专门的`URL`前缀注册新的处理程序(类似于现有的用于`http:`等前缀的处理程序),但这通常是相当复杂的,而且`URL`接口仍然缺乏一些理想的功能,例如检查所指向的资源是否存在的方法。
### 2.2.`Resource`接口
Spring 的`Resource`接口位于`org.springframework.core.io.`包中,这意味着它是一个更有能力的接口,用于抽象对低级资源的访问。下面的清单提供了`Resource`接口的概述。有关更多详细信息,请参见[`Resource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/io/resource.html)爪哇doc。
```
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
boolean isFile();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
ReadableByteChannel readableChannel() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
```
正如`Resource`接口的定义所示,它扩展了`InputStreamSource`接口。下面的清单显示了`InputStreamSource`接口的定义:
```
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
```
来自`Resource`接口的一些最重要的方法是:
* `getInputStream()`:定位并打开资源,返回一个`InputStream`用于从资源中读取。预计每个调用都会返回一个新的`InputStream`。这是呼叫者的责任,以关闭该流。
* `exists()`:返回一个`boolean`,指示此资源是否实际以物理形式存在。
* `isOpen()`:返回一个`boolean`,指示此资源是否表示具有开放流的句柄。如果`true`,则`InputStream`不能多次读取,必须只读一次,然后关闭,以避免资源泄漏。对于所有常见的资源实现,返回`false`,但`InputStreamResource`除外。
* `getDescription()`:返回此资源的描述,用于在使用该资源时进行错误输出。这通常是完全限定的文件名或资源的实际 URL。
其他方法允许你获得代表资源的实际`URL`或`File`对象(如果底层实现是兼容的并且支持该功能)。
`Resource`接口的一些实现还实现了扩展的[`WritableResource`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/io/writableresource.html)接口,用于支持对其进行写入的资源。
Spring 本身广泛地使用`Resource`抽象,在需要资源时作为许多方法签名中的参数类型。在一些 Spring API 中的其他方法(例如各种`ApplicationContext`实现的构造函数)采用`String`,该方法以未修饰或简单的形式用于创建适合该上下文实现的`Resource`,或者通过`String`路径上的特殊前缀,让调用者指定必须创建和使用特定的`Resource`实现。
虽然`Resource`接口在 Spring 和 Spring 中被大量使用,但实际上,在你自己的代码中将其本身用作一个通用实用程序类来访问资源是非常方便的,即使你的代码不知道或不关心 Spring 的任何其他部分。虽然这将你的代码耦合到 Spring,但它实际上只将它耦合到这一小组实用程序类,它可以作为`URL`的更强大的替代,并且可以被认为与你将用于此目的的任何其他库等同。
| |`Resource`抽象不会取代功能。它将它封装在
可能的位置。例如,`UrlResource`包装一个 URL,并使用包装好的`URL`来完成其
工作。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 2.3.内置`Resource`实现
Spring 包括几个内置`Resource`实现方式:
* [`UrlResource`]
* [`ClassPathResource`](#resources-implementations-classpathresource)
* [`FileSystemResource`]
* [`PathResource`]
* [`ServletContextResource`](#resources-implementations-servletContextResource)
* [`InputStreamResource`]
* [`ByteArrayResource`]
有关 Spring 中可用的`Resource`实现的完整列表,请参阅[`Resource`]中的“所有已知实现类”部分(https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/core/core/io/resource.html)爪哇doc。
#### 2.3.1.`UrlResource`
`UrlResource`封装了`java.net.URL`,并可用于访问通常通过 URL 可访问的任何对象,例如文件、HTTPS 目标、FTP 目标和其他对象。所有的 URL 都有一个标准化的`String`表示,使得适当的标准化前缀被用来从另一个 URL 类型指示一个 URL 类型。这包括用于访问文件系统路径的`file:`,用于通过 HTTPS 协议访问资源的`https:`,用于通过 FTP 访问资源的`ftp:`,以及其他。
`UrlResource`是由 爪哇 代码通过显式使用`UrlResource`构造函数创建的,但是当你调用一个 API 方法时,它通常是隐式创建的,该 API 方法接受一个旨在表示路径的`String`参数。对于后一种情况,爪哇Beans`PropertyEditor`最终决定创建哪种类型的`Resource`。如果路径字符串包含一个众所周知的前缀(对于属性编辑器来说,就是)(例如`classpath:`),那么它将为该前缀创建一个适当的专门的`Resource`。但是,如果它不能识别前缀,它将假设字符串是一个标准的 URL 字符串,并创建一个`UrlResource`。
#### 2.3.2.`ClassPathResource`
这个类表示应该从 Classpath 获得的资源。它使用线程上下文类装入器、给定类装入器或给定类来装载资源。
这个`Resource`实现支持解析为`java.io.File`,如果类路径资源驻留在文件系统中,而不是驻留在 Classpath 资源中且未(通过 Servlet 引擎或任何环境)扩展到文件系统的 Classpath 资源。为了解决这个问题,各种`Resource`实现总是支持解析为`java.net.URL`。
`ClassPathResource`是由 爪哇 代码通过显式使用`ClassPathResource`构造函数创建的,但是当你调用一个 API 方法时,它通常是隐式创建的,该 API 方法接受一个旨在表示路径的`String`参数。对于后一种情况,爪哇Beans`PropertyEditor`识别字符串路径上的特殊前缀`classpath:`,并在这种情况下创建`ClassPathResource`。
#### 2.3.3.`FileSystemResource`
这是`Resource`句柄的`java.io.File`实现。它还支持`java.nio.file.Path`句柄,应用 Spring 的标准基于字符串的路径转换,但通过`java.nio.file.Files`API 执行所有操作。对于纯粹的基于`java.nio.path.Path`的支持,使用`PathResource`代替。`FileSystemResource`支持解析为`File`和`URL`。
#### 2.3.4.`PathResource`
这是用于`java.nio.file.Path`句柄的`Resource`实现,通过`Path`API 执行所有操作和转换。它支持`File`和`URL`的解析,并实现了扩展的`WritableResource`接口。`PathResource`实际上是一种纯粹的基于`java.nio.path.Path`的替代方法,与`FileSystemResource`具有不同的`createRelative`行为。
#### 2.3.5.`ServletContextResource`
这是`Resource`资源的`ServletContext`实现,用于解释相关 Web 应用程序根目录中的相对路径。
它始终支持流访问和 URL 访问,但仅当 Web 应用程序归档被扩展并且资源在文件系统上时才允许`java.io.File`访问。它是否被扩展并在文件系统上或直接从 jar 或类似于数据库的其他地方(这是可以想象的)访问,实际上取决于 Servlet 容器。
#### 2.3.6.`InputStreamResource`
`InputStreamResource`是给定`InputStream`的`Resource`实现。只有在不适用特定的`Resource`实现的情况下,才应该使用它。特别是,在可能的情况下,优选`ByteArrayResource`或任何基于文件的`Resource`实现方式。
与其他`Resource`实现不同,这是对已经打开的资源的描述符。因此,它从`isOpen()`返回`true`。如果你需要将资源描述符保存在某个地方,或者如果你需要多次读取流,请不要使用它。
#### 2.3.7.`ByteArrayResource`
这是给定字节数组的`Resource`实现。它为给定字节数组创建一个`ByteArrayInputStream`。
对于从任何给定的字节数组加载内容,而不必使用一次性`InputStreamResource`,它是有用的。
### 2.4.`ResourceLoader`接口
`ResourceLoader`接口旨在通过可以返回(即加载)`Resource`实例的对象来实现。下面的清单显示了`ResourceLoader`接口定义:
```
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
```
所有应用程序上下文都实现`ResourceLoader`接口。因此,可以使用所有应用程序上下文来获得`Resource`实例。
当你在特定的应用程序上下文中调用`getResource()`,而指定的位置路径没有特定的前缀时,你将返回一个适合该特定应用程序上下文的`Resource`类型。例如,假设对`ClassPathXmlApplicationContext`实例运行了以下代码片段:
爪哇
```
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
```
Kotlin
```
val template = ctx.getResource("some/resource/path/myTemplate.txt")
```
对于`ClassPathXmlApplicationContext`,该代码返回`ClassPathResource`。如果对`FileSystemXmlApplicationContext`实例运行相同的方法,它将返回`FileSystemResource`。对于`WebApplicationContext`,它将返回`ServletContextResource`。它将类似地为每个上下文返回适当的对象。
因此,你可以以适合特定应用程序上下文的方式加载资源。
另一方面,你也可以通过指定特殊的`classpath:`前缀来强制使用`ClassPathResource`,而不考虑应用程序的上下文类型,如下例所示:
爪哇
```
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
```
Kotlin
```
val template = ctx.getResource("classpath:some/resource/path/myTemplate.txt")
```
类似地,你可以通过指定任何标准的`java.net.URL`前缀来强制使用`UrlResource`。以下示例使用`file`和`https`前缀:
爪哇
```
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
```
Kotlin
```
val template = ctx.getResource("file:///some/resource/path/myTemplate.txt")
```
爪哇
```
Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");
```
Kotlin
```
val template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt")
```
下表总结了将`String`对象转换为`Resource`对象的策略:
| Prefix | Example |解释|
|----------|--------------------------------|----------------------------------------------------------------------------------------------------------------------|
|classpath:|`classpath:com/myapp/config.xml`|从 Classpath 加载。|
| file: | `file:///data/config.xml` |从文件系统加载为`URL`。另请参见[`FileSystemResource`caveats]。|
| https: | `https://myserver/logo.png` |加载为`URL`。|
| (none) | `/data/config.xml` |取决于底层`ApplicationContext`。|
### 2.5.`ResourcePatternResolver`接口
`ResourcePatternResolver`接口是`ResourceLoader`接口的扩展,它定义了将位置模式(例如, Ant 样式的路径模式)解析为`Resource`对象的策略。
```
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
```
如上所述,该接口还为来自类路径的所有匹配资源定义了一个特殊的`classpath*:`资源前缀。请注意,在这种情况下,资源位置应该是一个没有占位符的路径——例如,`classpath*:/config/beans.xml`。 jar 类路径中的文件或不同的目录可以包含具有相同路径和相同名称的多个文件。有关使用`classpath*:`资源前缀的通配符支持的更多详细信息,请参见[应用程序上下文构造函数资源路径中的通配符](#resources-app-ctx-wildcards-in-resource-paths)及其子节。
可以检查传入的`ResourceLoader`(例如,通过[`ResourceLoaderAware`](#resources-ResourceLoaderAware)语义提供的一个)是否也实现了这个扩展接口。
`PathMatchingResourcePatternResolver`是一个独立的实现,可以在`ApplicationContext`之外使用,并且`ResourceArrayPropertyEditor`也用于填充`Resource[]` Bean 属性。`PathMatchingResourcePatternResolver`能够将指定的资源定位路径解析为一个或多个匹配的`Resource`对象。源路径可以是具有到目标`Resource`的一对一映射的简单路径,或者可替换地包含特殊的`classpath*:`前缀和/或内部 Ant 式正则表达式(使用 Spring 的`org.springframework.util.AntPathMatcher`实用工具进行匹配)。后者实际上都是通配符。
| |在任何标准`ApplicationContext`中,默认的`ResourceLoader`实际上是
的一个实例`PathMatchingResourcePatternResolver`,它实现了`ResourcePatternResolver`接口。对于`ApplicationContext`实例本身也是如此,它也
实现了`ResourcePatternResolver`接口并将其委托给默认的`PathMatchingResourcePatternResolver`。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 2.6.`ResourceLoaderAware`接口
`ResourceLoaderAware`接口是一种特殊的回调接口,用于标识期望提供`ResourceLoader`引用的组件。下面的清单显示了`ResourceLoaderAware`接口的定义:
```
public interface ResourceLoaderAware {
void setResourceLoader(ResourceLoader resourceLoader);
}
```
当一个类实现`ResourceLoaderAware`并被部署到应用程序上下文中(作为 Spring 管理的 Bean)时,应用程序上下文将其识别为`ResourceLoaderAware`。然后,应用程序上下文调用`setResourceLoader(ResourceLoader)`,将自身作为参数提供(请记住, Spring 中的所有应用程序上下文都实现`ResourceLoader`接口)。
由于`ApplicationContext`是`ResourceLoader`, Bean 还可以实现`ApplicationContextAware`接口,并直接使用提供的应用程序上下文来加载资源。然而,一般来说,如果这就是你所需要的,那么最好使用专门的`ResourceLoader`接口。代码将仅耦合到资源加载接口(可被视为实用程序接口),而不是整个 Spring `ApplicationContext`接口。
在应用程序组件中,还可以依赖`ResourceLoader`的自动布线作为实现`ResourceLoaderAware`接口的替代方案。*传统的*`constructor`和`byType`自动布线模式(如[自动布线合作者](#beans-factory-autowire)中所述)能够分别为构造函数参数或 setter 方法参数提供`ResourceLoader`。要获得更大的灵活性(包括自动连接字段和多个参数方法的能力),请考虑使用基于注释的自动连接功能。在这种情况下,`ResourceLoader`将自动连接到一个字段、构造函数参数或方法参数中,只要所讨论的字段、构造函数或方法带有`ResourceLoader`注释,这些参数就会期望`ResourceLoader`类型。有关更多信息,请参见[使用`@Autowired`]。
| |要为包含通配符
的资源路径加载一个或多个`Resource`对象,或者使用特殊的`classpath*:`资源前缀,请考虑将[`ResourcePatternResolver`](#resources-ResourcePatternResolver)的实例自动连接到你的
应用程序组件中,而不是`ResourceLoader`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 2.7.作为依赖关系的资源
如果 Bean 本身将通过某种动态过程来确定和提供资源路径,则 Bean 使用`ResourceLoader`或`ResourcePatternResolver`接口来加载资源可能是有意义的。例如,考虑加载某种类型的模板,其中所需的特定资源取决于用户的角色。如果资源是静态的,那么完全消除`ResourceLoader`接口(或`ResourcePatternResolver`接口)的使用是有意义的,让 Bean 公开它需要的`Resource`属性,并期望将它们注入其中。
然后注入这些属性的原因是,所有应用程序上下文都注册并使用一个特殊的 爪哇Beans`PropertyEditor`,它可以将`String`路径转换为`Resource`对象。例如,下面的`MyBean`类具有类型`template`的`Resource`属性。
爪哇
```
package example;
public class MyBean {
private Resource template;
public setTemplate(Resource template) {
this.template = template;
}
// ...
}
```
Kotlin
```
class MyBean(var template: Resource)
```
在 XML 配置文件中,`template`属性可以为该资源配置一个简单的字符串,如下例所示:
```
检查`classpath*`是否有效的简单测试是使用`ClassLoader`从
在 Classpath 上的 jar 内加载一个文件:`getClass().getClassLoader().getResources("
文件尝试此测试——例如,在 Classpath 上具有相同名称和相同路径但位于不同 JAR 中的
文件。如果返回了
不合适的结果,请检查应用程序服务器文档中的设置
,这些设置可能会影响`ClassLoader`行为。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
你还可以在位置路径的其余部分(例如,`classpath*:META-INF/*-beans.xml`)中将`classpath*:`前缀与`PathMatcher`模式相结合。在这种情况下,解析策略相当简单:在最后一个非通配符路径段上使用`ClassLoader.getResources()`调用,以获取类装入器层次结构中的所有匹配资源,然后关闭每个资源,前面描述的`PathMatcher`分辨率策略也用于通配符的子路径。
##### 与通配符有关的其他注释
注意,`classpath*:`,当与 Ant 样式的模式相结合时,在模式开始之前仅与至少一个根目录可靠地工作,除非实际的目标文件驻留在文件系统中。这意味着,像`classpath*:*.xml`这样的模式可能不会从 jar 文件的根目录检索文件,而只会从扩展目录的根目录检索文件。
Spring 检索 Classpath 条目的能力源于 JDK 的`ClassLoader.getResources()`方法,该方法仅返回空字符串的文件系统位置(指示要搜索的潜在根)。 Spring 还在 jar 文件中评估`URLClassLoader`运行时配置和`java.class.path`清单,但这不能保证导致可移植行为。
| |Classpath 包的扫描需要在 Classpath 中存在相应的目录
条目。当使用 Ant 构建 JAR 时,不要激活 jar 任务的`files-only`开关。另外,在某些环境中, Classpath 目录可能不会基于安全性
策略而被公开——例如,在 JDK1.7.0\_45
上的独立应用程序以及更高的环境中(这要求在你的清单中设置“可信库”)。参见[https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources](https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources))。
在 JDK9 的模块路径(拼图)上, Spring 的 Classpath 扫描通常可以按预期工作。
将资源放入专用目录在这里也是非常值得推荐的,
在搜索 jar 文件根级别时避免了前面提到的可移植性问题。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
Ant-样式模式具有资源,如果要搜索的根包在多个 Classpath 位置可用,则不能保证找到匹配的资源。考虑以下资源位置示例:
```
com/mycompany/package1/service-context.xml
```
现在考虑一个 Ant 样式的路径,可能有人会使用它来尝试查找该文件:
```
classpath:com/mycompany/**/service-context.xml
```
这样的资源可以仅存在于 Classpath 中的一个位置,但是当使用诸如前面的示例的路径来尝试解析它时,解析器工作于由`getResource("com/mycompany");`返回的(第一个)URL。如果此基本包节点存在于多个`ClassLoader`位置中,则所需的资源可能不存在于第一个位置中。因此,在这种情况下,你应该更喜欢使用具有相同 Ant 样式模式的`classpath*:`,该模式搜索包含`com.mycompany`基本包的所有 Classpath 位置:`classpath*:com/mycompany/**/service-context.xml`。
#### 2.8.3.`FileSystemResource`注意事项
不附加到`FileSystemResource`的`FileSystemApplicationContext`(即当`FileSystemApplicationContext`不是实际的`ResourceLoader`时)按预期处理绝对路径和相对路径。相对路径是相对于当前工作目录的,而绝对路径是相对于文件系统的根的。
但是,由于向后兼容性(历史原因)的原因,当`FileSystemApplicationContext`是`ResourceLoader`时,这种情况会发生变化。`FileSystemApplicationContext`强制所有附加的`FileSystemResource`实例将所有位置路径视为相对的,无论它们是否以前导斜杠开始。在实践中,这意味着以下示例是等效的:
Java
```
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
```
Kotlin
```
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
```
Java
```
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
```
Kotlin
```
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
```
下面的例子也是等价的(尽管它们不同是有意义的,因为一种情况是相对的,另一种情况是绝对的):
Java
```
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
```
Kotlin
```
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
```
Java
```
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
```
Kotlin
```
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
```
在实践中,如果需要真正的绝对文件系统路径,则应避免使用带有`FileSystemResource`或`FileSystemXmlApplicationContext`的绝对路径,并使用`UrlResource`URL 前缀强制使用`file:`。下面的例子说明了如何做到这一点:
Java
```
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
```
Kotlin
```
// actual context type doesn't matter, the Resource will always be UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
```
Java
```
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");
```
Kotlin
```
// force this FileSystemXmlApplicationContext to load its definition via a UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
```
## 3. 验证、数据绑定和类型转换 #
考虑将验证作为业务逻辑有其优点和缺点, Spring 提供了一种验证(和数据绑定)设计,该设计不排除其中的任何一种。具体地说,验证不应该绑定到 Web 层,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题, Spring 提供了`Validator`契约,该契约在应用程序的每一层中都是基本的且非常可用的。
数据绑定对于让用户输入与应用程序的域模型(或用于处理用户输入的任何对象)动态绑定非常有用。 Spring 提供了恰当地命名为`DataBinder`的方法来做到这一点。`Validator`和`DataBinder`组成了`validation`包,该包主要用于但不限于 Web 层。
`BeanWrapper`是 Spring 框架中的一个基本概念,并在许多地方使用。但是,你可能不需要直接使用`BeanWrapper`。但是,由于这是参考文档,我们认为可能需要进行一些解释。我们将在本章中解释`BeanWrapper`,因为如果你打算使用它,那么在尝试将数据绑定到对象时,你很可能会使用它。
Spring 的`DataBinder`和较低级别的`BeanWrapper`都使用`PropertyEditorSupport`实现来解析和格式化属性值。`PropertyEditor`和`PropertyEditorSupport`类型是 JavaBeans 规范的一部分,也在本章中进行了说明。 Spring 3 引入了一个`core.convert`包,该包提供了一般的类型转换功能,以及用于格式化 UI 字段值的更高级别的“格式”包。你可以使用这些包作为`PropertyEditorSupport`实现的更简单的替代方案。本章还对这些问题进行了讨论。
Spring 通过设置基础设施和 Spring 自己的`Validator`合同的适配器支持 Java Bean 验证。应用程序可以在全局范围内一次启用 Bean 验证,如[Java Bean Validation](#validation-beanvalidation)中所述,并专门用于所有验证需求。在 Web 层中,应用程序可以进一步注册控制器-Local Spring 实例的每,如[配置](#validation-binder)中所述的那样,这对于插入自定义验证逻辑是有用的。
### 3.1.通过使用 Spring 的验证器接口进行验证
Spring 具有`Validator`接口,你可以使用该接口来验证对象。`Validator`接口通过使用`Errors`对象来工作,这样在验证时,验证器可以向`Errors`对象报告验证失败。
考虑以下一个小数据对象的示例:
Java
```
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
```
Kotlin
```
class Person(val name: String, val age: Int)
```
下一个示例通过实现`org.springframework.validation.Validator`接口的以下两种方法,为`Person`类提供了验证行为:
* `supports(Class)`:这个`Validator`可以验证所提供的`Class`的实例吗?
* `validate(Object, org.springframework.validation.Errors)`:验证给定的对象,在验证错误的情况下,用给定的`Errors`对象注册那些对象。
实现`Validator`相当简单,尤其是当你知道 Spring 框架还提供了`ValidationUtils`Helper 类时。下面的示例为`Person`实例实现`Validator`:
Java
```
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");
}
}
}
```
Kotlin
```
class PersonValidator : Validator {
/**
* This Validator validates only Person instances
*/
override fun supports(clazz: Class<*>): Boolean {
return Person::class.java == clazz
}
override fun validate(obj: Any, e: Errors) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
val p = obj as Person
if (p.age < 0) {
e.rejectValue("age", "negativevalue")
} else if (p.age > 110) {
e.rejectValue("age", "too.darn.old")
}
}
}
```
在`static`类上的`rejectIfEmpty(..)`方法用于拒绝`name`属性,如果它是`null`或空字符串。看看[`ValidationUtils`](https://DOCS. Spring.io/ Spring-framework/DOCS/5.3.16/javadoc-api/org/springframework/validation/validationutils.html)Javadoc,看看除了前面展示的示例之外,它还提供了什么功能。
虽然可以实现单个`Validator`类来验证富对象中的每个嵌套对象,但最好是将每个嵌套对象类的验证逻辑封装在其自己的`Validator`实现中。“rich”对象的一个简单示例是`Customer`,它由两个`String`属性(第一个和第二个名称)和一个复杂的`Address`对象组成。`Address`对象可以独立于`Customer`对象使用,因此已经实现了一个不同的`AddressValidator`对象。如果你希望你的`CustomerValidator`重用`AddressValidator`类中包含的逻辑而不使用复制和粘贴,则可以在你的`CustomerValidator`中使用依赖注入或实例化`AddressValidator`,如下例所示:
Java
```
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();
}
}
}
```
Kotlin
```
class CustomerValidator(private val addressValidator: Validator) : Validator {
init {
if (addressValidator == null) {
throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
}
if (!addressValidator.supports(Address::class.java)) {
throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
}
}
/*
* This Validator validates Customer instances, and any subclasses of Customer too
*/
override fun supports(clazz: Class<>): Boolean {
return Customer::class.java.isAssignableFrom(clazz)
}
override fun validate(target: Any, errors: Errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
val customer = target as Customer
try {
errors.pushNestedPath("address")
ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
} finally {
errors.popNestedPath()
}
}
}
```
将验证错误报告给传递给验证器的`Errors`对象。在 Spring Web MVC 的情况下,你可以使用`
(例如)`getAccount().setName()`或`getAccount().getName()`方法。|
| `account[2]` |指示索引属性`account`的*第三次*元素。索引属性
可以是类型`array`,`list`,或其他自然有序的集合。|
|`account[COMPANYNAME]`|指示由`account``Map`属性的`COMPANYNAME`键索引的映射项的值。|
(如果你不打算直接使用`BeanWrapper`,那么下一节对你来说并不是至关重要的。如果只使用`DataBinder`和`BeanFactory`及其默认实现,则应跳过[关于`PropertyEditors`的部分](#beans-beans-conversion)。
以下两个示例类使用`BeanWrapper`来获取和设置属性:
Java
```
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;
}
}
```
Kotlin
```
class Company {
var name: String? = null
var managingDirector: Employee? = null
}
```
Java
```
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;
}
}
```
Kotlin
```
class Employee {
var name: String? = null
var salary: Float? = null
}
```
以下代码片段展示了如何检索和操作实例化`Company`s 和`Employee`s 的一些属性的示例:
Java
```
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");
```
Kotlin
```
val company = BeanWrapperImpl(Company())
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.")
// ... can also be done like this:
val value = PropertyValue("name", "Some Company Inc.")
company.setPropertyValue(value)
// ok, let's create the director and tie it to the company:
val jim = BeanWrapperImpl(Employee())
jim.setPropertyValue("name", "Jim Stravinsky")
company.setPropertyValue("managingDirector", jim.wrappedInstance)
// retrieving the salary of the managingDirector through the company
val salary = company.getPropertyValue("managingDirector.salary") as Float?
```
#### 3.3.2.内置`PropertyEditor`实现
Spring 使用`PropertyEditor`的概念来实现`Object`和`String`之间的转换。以不同于对象本身的方式表示属性可能很方便。例如,`Date`可以以人类可读的方式表示(如`String`:`'2007-14-09'`),而我们仍然可以将人类可读形式转换回原始日期(或者,更好的是,将在人类可读形式中输入的任何日期转换回`Date`对象)。这种行为可以通过注册`java.beans.PropertyEditor`类型的自定义编辑器来实现。在`BeanWrapper`上注册自定义编辑器,或者在特定的 IoC 容器中注册自定义编辑器(如前一章中提到的),将为它提供如何将属性转换为所需类型的知识。有关`PropertyEditor`的更多信息,请参见[来自 Oracle 的`java.beans`包的 Javadoc](https://DOCS.oracle.com/javase/8/DOCS/api/java/beans/package-summary.html)。
Spring 中使用属性编辑的几个示例:
* 在 bean 上设置属性是通过使用`PropertyEditor`实现来完成的。当使用`String`作为在 XML 文件中声明的某个 Bean 属性的值时, Spring(如果相应属性的 setter 具有`Class`参数)使用`ClassEditor`来尝试将参数解析为`Class`对象。
* 在 Spring 的 MVC 框架中解析 HTTP 请求参数是通过使用各种`PropertyEditor`实现完成的,你可以在`CommandController`的所有子类中手动绑定这些实现。
Spring 具有许多内置的`PropertyEditor`实现,以使生活变得容易。它们都位于`org.springframework.beans.propertyeditors`包中。默认情况下,大多数(但不是全部,如下表所示)是由`BeanWrapperImpl`注册的。在属性编辑器以某种方式可配置的情况下,你仍然可以注册自己的变体以覆盖默认的变体。下表描述了 Spring 提供的各种`PropertyEditor`实现方式:
| Class |解释|
|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|`ByteArrayPropertyEditor`|字节数组的编辑器。将字符串转换为其对应的字节
表示形式。默认情况下由`BeanWrapperImpl`注册。|
| `ClassEditor` |解析将类表示为实际类的字符串,反之亦然。当未找到
类时,将抛出`IllegalArgumentException`。默认情况下,由`BeanWrapperImpl`注册。|
| `CustomBooleanEditor` |`Boolean`属性的可自定义属性编辑器。默认情况下,由`BeanWrapperImpl`注册,但可以通过将其自定义实例注册为
自定义编辑器来重写。|
|`CustomCollectionEditor` |集合的属性编辑器,将任何源`Collection`转换为给定的目标`Collection`类型。|
| `CustomDateEditor` |用于`java.util.Date`的可自定义属性编辑器,支持自定义`DateFormat`。不
默认注册。必须根据需要以适当的格式进行用户注册。|
| `CustomNumberEditor` |任何`Number`子类的可定制属性编辑器,例如`Integer`,`Long`,`Float`,或`Double`。默认情况下,由`BeanWrapperImpl`注册,但可以由
将其自定义实例注册为自定义编辑器来覆盖。|
| `FileEditor` |将字符串解析为`java.io.File`对象。默认情况下,由`BeanWrapperImpl`注册。|
| `InputStreamEditor` |一种单向属性编辑器,它可以获取一个字符串并产生(通过
中间值`ResourceEditor`和`Resource`)一个`InputStream`,这样`InputStream`属性就可以直接设置为字符串。请注意,默认用法不会为你关闭
的`InputStream`。默认情况下,由`BeanWrapperImpl`注册。|
| `LocaleEditor` |可以将字符串解析为`Locale`对象,反之亦然(字符串格式为`[language]_[country]_[variant]`,与`toString()`的`Locale`方法相同)。也接受空格作为分隔符,作为下划线的替代。
默认情况下,由`BeanWrapperImpl`注册。|
| `PatternEditor` |可以将字符串解析为`java.util.regex.Pattern`对象,反之亦然。|
| `PropertiesEditor` |可以将字符串(格式为`java.util.Properties`类的 Javadoc 中定义的格式)转换为`Properties`对象。默认情况下,由`BeanWrapperImpl`注册
。|
| `StringTrimmerEditor` |编辑字符串的属性编辑器。可选地允许将空字符串
转换为`null`值。默认情况下未注册——必须是用户注册的。|
| `URLEditor` |可以将 URL 的字符串表示解析为实际的`URL`对象。
默认情况下,由`BeanWrapperImpl`注册。|
Spring 使用`java.beans.PropertyEditorManager`设置可能需要的属性编辑器的搜索路径。搜索路径还包括`sun.bean.editors`,其中包括`PropertyEditor`类型的实现,例如`Font`、`Color`,以及大多数原始类型。还请注意,标准 JavaBeans 基础设施会自动发现`PropertyEditor`类(无需显式地注册它们),如果它们与它们处理的类在同一个包中,并且与该类具有相同的名称,并附加`Editor`。例如,可以有以下的类和包结构,这足以让`SomethingEditor`类被识别并用作`PropertyEditor`的`Something`类型属性。
```
com
chank
pop
Something
SomethingEditor // the PropertyEditor for the Something class
```
注意,这里也可以使用标准的`BeanInfo`JavaBeans 机制(在一定程度上描述了[here](https://docs.oracle.com/javase/tutorial/javabeans/advanced/customization.html))。下面的示例使用`BeanInfo`机制显式地用关联类的属性注册一个或多个`PropertyEditor`实例:
```
com
chank
pop
Something
SomethingBeanInfo // the BeanInfo for the Something class
```
下面引用的`SomethingBeanInfo`类的 Java 源代码将`CustomNumberEditor`与`age`类的`Something`属性关联起来:
Java
```
public class SomethingBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
@Override
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
}
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
```
Kotlin
```
class SomethingBeanInfo : SimpleBeanInfo() {
override fun getPropertyDescriptors(): Array {
T convert(S source);
}
```
要创建自己的转换器,请实现`Converter`接口,并将`S`参数化为要转换的类型,将`T`参数化为要转换的类型。如果需要将`S`的集合或数组转换为`T`的阵列或集合,那么也可以透明地应用这样的转换器,前提是已经注册了一个委托数组或集合转换器(默认情况下`DefaultConversionService`是这样做的)。
对于每个对`convert(S)`的调用,源参数保证不为空。如果转换失败,你的`Converter`可能抛出任何未经检查的异常。具体地说,它应该抛出`IllegalArgumentException`来报告无效的源值。注意确保你的`Converter`实现是线程安全的。
为了方便起见,在`core.convert.support`包中提供了几种转换器实现方式。这些包括从字符串到数字的转换器和其他常见类型的转换器。下面的清单显示了`StringToInteger`类,这是一个典型的`Converter`实现:
```
package org.springframework.core.convert.support;
final class StringToInteger implements Converter {
getConverter(Class
。赞成`Converter`或`ConverterFactory`对于基本类型
转换需要。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### 使用`ConditionalGenericConverter`
有时,只有当特定条件为真时,才希望运行`Converter`。例如,只有在目标字段上存在特定的注释时,你才可能希望运行`Converter`,或者,只有在目标类上定义了特定的方法(例如`static valueOf`方法)时,才希望运行`Converter`。`ConditionalGenericConverter`是`GenericConverter`和`ConditionalConverter`接口的联合,允许你定义这样的自定义匹配条件:
```
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}
```
`ConditionalGenericConverter`的一个很好的例子是`IdToEntityConverter`,它在持久实体标识符和实体引用之间转换。这样的`IdToEntityConverter`可能只有在目标实体类型声明了静态查找方法(例如,`findAccount(Long)`)时才匹配。你可以在`matches(TypeDescriptor, TypeDescriptor)`的实现中执行这样的查找方法检查。
#### 3.4.4.`ConversionService`api
`ConversionService`定义了用于在运行时执行类型转换逻辑的统一 API。转换器通常在以下 facade 接口后面运行:
```
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class> sourceType, Class> targetType);
系统。|
|---|------------------------------------------------------------------------------------------------------------|
要用 Spring 注册一个默认的`ConversionService`,请添加以下 Bean 定义,并使用`id`的`conversionService`:
```