)
```
#### 1.9.9.使用`@PostConstruct`和`@PreDestroy`
`CommonAnnotationBeanPostProcessor`不仅可以识别`@Resource`注释,还可以识别 JSR-250 生命周期注释:`javax.annotation.PostConstruct`和 `javax.annotation.predestroy’。在 Spring 2.5 中引入的,对这些注释的支持为[初始化回调](#beans-factory-lifecycle-initializingbean)和[销毁回调](#beans-factory-lifecycle-disposablebean)中描述的生命周期回调机制提供了一种替代方案。如果在 Spring `ApplicationContext`中注册了 `CommonAnnotationBeanPostProcessor’,则携带这些注释之一的方法在生命周期的同一点被调用,作为相应的 Spring 生命周期接口方法或显式声明的回调方法。在下面的示例中,缓存在初始化时被预先填充,在销毁时被清除:
Java
```
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
```
Kotlin
```
class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
fun clearMovieCache() {
// clears the movie cache upon destruction...
}
}
```
有关结合各种生命周期机制的影响的详细信息,请参见[结合生命周期机制](#beans-factory-lifecycle-combined-effects)。
| |和`@Resource`一样,`@PostConstruct`和`@PreDestroy`注释类型也是从 JDK6 到 8 的标准 Java 库中
的一部分。然而,整个`javax.annotation`包在 JDK9 中与核心 Java 模块分离,并最终在
JDK11 中删除。如果需要,现在需要通过 Maven `javax.annotation-api`Central 获得
工件,只需像任何其他库一样添加到应用程序的 Classpath 中。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
### 1.10. Classpath 扫描和管理组件
本章中的大多数示例都使用 XML 来指定在 Spring 容器中产生每个`BeanDefinition`的配置元数据。上一节([基于注释的容器配置](#beans-annotation-config))演示了如何通过源级注释提供大量配置元数据。然而,即使在这些示例中,“基本” Bean 定义也是在 XML 文件中明确定义的,而注释仅驱动依赖注入。本节描述了用于通过扫描 Classpath 隐式地检测候选组件的选项。 Bean 候选组件是与筛选条件匹配的类,并且具有与容器注册的相应定义。这消除了使用 XML 来执行 Bean 注册的需要。相反,你可以使用注释(例如,`@Component`)、AspectJ 类型表达式或你自己的自定义筛选条件来选择哪些类具有在容器中注册的 Bean 定义。
| |从 Spring 3.0 开始, Spring JavaConfig 项目提供的许多特性都是
核心 Spring 框架的一部分。这允许你使用 Java 来定义 bean,而不是使用传统的 XML 文件
。看看`@Configuration`、`@Bean`、`@import’和`@DependsOn`注释,以了解如何使用这些新功能。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.1.`@Component`和进一步的原型注释
`@Repository`注释是满足存储库角色或原型(也称为数据访问对象或 DAO)的任何类的标记。此标记的用途之一是异常的自动转换,如[异常转换](data-access.html#orm-exception-translation)中所述。
Spring 提供了进一步的原型注释:`@Component`,`@Service`,和 `@controller`。`@Component`是任何 Spring-托管组件的通用原型。@repository`、`@Service`和`@Controller`是`@Component`的专门化,用于更具体的用例(分别在持久性、服务层和表示层中)。因此,你可以使用“@Component”对组件类进行注释,但是,通过使用`@Repository`、`@Service`或`@Controller`对它们进行注释,你的类更适合于通过工具进行处理或与方面进行关联。例如,这些原型注释为切入点提供了理想的目标。`@Repository`、`@Service`和`@Controller`还可以在 Spring 框架的未来版本中携带额外的语义。因此,如果你在使用`@Component`或`@Service`作为服务层时进行选择,`@Service`显然是更好的选择。类似地,如前所述,`@Repository`已经被支持作为持久性层中自动异常转换的标记。
#### 1.10.2.使用元注释和组合注释
Spring 提供的许多注释可以在你自己的代码中用作元注释。元注释是一种可以应用于另一种注释的注释。例如,所提到的`@Service`注释[earlier](#beans-stereotype-annotations)被元注释为`@Component`,如下例所示:
Java
```
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
```
|**1**|`@Component`导致`@Service`被以与`@Component`相同的方式处理。|
|-----|---------------------------------------------------------------------------------|
Kotlin
```
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
```
|**1**|`@Component`导致`@Service`被以与`@Component`相同的方式处理。|
|-----|---------------------------------------------------------------------------------|
你还可以合并元注释来创建“组合注释”。例如,来自 Spring MVC 的`@RestController`注释由`@Controller`和 `@responsebody’组成。
此外,合成注释可以选择从元注释中重新声明属性以允许定制。当你只想公开元注释属性的一个子集时,这可能特别有用。例如, Spring 的“@SessionScope”注释将作用域名称硬编码为`session`,但仍然允许自定义`proxyMode`。下面的清单显示了“SessionScope”注释的定义:
Java
```
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* 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 可以自动地检测到原型类并用`ApplicationContext`注册相应的 `BeanDefinition’实例。例如,以下两个类可以进行这种自动检测:
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,你需要在`@Configuration`类中添加 `@ComponentScan`,其中`basePackages`属性是这两个类的公共父包。(或者,你可以指定一个逗号或分号或空格分隔的列表,其中包括每个类的父包。
Java
```
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
```
| |为了简洁起见,前面的示例可以使用`value`注释(即`@ComponentScan("org.example")`)的`value`属性。|
|---|------------------------------------------------------------------------------------------------------------------------------------------|
以下替代方案使用 XML:
```
```
| |使用``隐式启用了 `` 的功能。在使用``时,通常不需要包含 `` 元素。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |Classpath 包的扫描需要在 Classpath 中存在相应的目录
条目。当你使用 Ant 构建 JAR 时,请确保没有
激活 jar 任务的仅文件开关。另外,在某些环境中,基于安全策略,目录可能不会
公开,例如,在
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`描述符中使用`module-info`声明,而不是 `exports’声明)。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
此外,当你使用 Component-Scan 元素时,`AutowiredAnnotationBeanPostProcessor`和 `CommonAnnotationBeanPostProcessor’都是隐式包含的。这意味着这两个组件是自动检测和连接在一起的——所有这些都没有 XML 提供的任何 Bean 配置元数据。
| |你可以禁用`AutowiredAnnotationBeanPostProcessor`和 `commonAnnotationBeanPostProcessor’的注册,方法是使用`annotation-config`属性
,其值为`false`。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.4.使用筛选器自定义扫描
默认情况下,使用`@Component`、`@Repository`、`@Service`、`@Controller`、`@configuration’或自身使用`@Component`进行注释的自定义注释的类是唯一检测到的候选组件。但是,你可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为`includeFilters`或`excludeFilters`注释的属性(或在 XML 配置中添加``或<``元素的子元素)。每个筛选器元素都需要`type`和`expression`属性。下表介绍了筛选选项:
| Filter Type | Example Expression |说明|
|--------------------|----------------------------|------------------------------------------------------------------------------------------|
|annotation (default)|`org.example.SomeAnnotation`|在目标组件的类型级别上,注释为*礼物*或*元存在*。|
| assignable | `org.example.SomeClass` |可将目标组件分配给(扩展或实现)的类(或接口)。|
| aspectj | `org.example..*Service+` |由目标组件匹配的 AspectJ 类型表达式。|
| regex | `org\.example\.Default.*` |由目标组件的类名匹配的正则表达式。|
| custom | `org.example.MyTypeFilter` |`org.springframework.core.type.TypeFilter`接口的自定义实现。|
下面的示例显示了忽略所有`@Repository`注释并使用“存根”存储库的配置:
Java
```
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
class AppConfig {
// ...
}
```
下面的清单显示了等效的 XML:
```
```
| |你还可以通过在
注释上设置`useDefaultFilters=false`或通过提供`use-default-filters="false"`作为 `` 元素的属性来禁用默认过滤器。这有效地禁用了对带有
注释或 meta 注释的类`@Component`、`@Repository`、`@Service`、`@Controller`、`@restcontroller` 或`@Configuration`的自动检测。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.5.在组件中定义 Bean 元数据
Spring 组件还可以向容器贡献 Bean 定义元数据。你可以使用与`@Bean`注释类中定义 Bean 元数据相同的`@Configuration`注释来完成此操作。下面的示例展示了如何做到这一点:
爪哇
```
@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`方法的自动连线。下面的示例展示了如何做到这一点:
爪哇
```
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
```
Kotlin
```
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
```
该示例将`String`方法参数`country`自动连接到另一个名为`privateInstance`的 Bean 上的`age`属性的值。 Spring 表达式语言元素通过记号`#{ }`来定义该属性的值。对于`@Value`注释,表达式解析程序预先配置为在解析表达式文本时查找 Bean 名称。
在 Spring Framework4.3 中,还可以声明类型为 `injectionPoint’的工厂方法参数(或其更具体的子类:`DependencyDescriptor`),以访问触发当前 Bean 创建的请求注入点。请注意,这仅适用于 Bean 实例的实际创建,而不适用于现有实例的注入。因此,对于原型范围的 bean 来说,这个特性是最有意义的。对于其他作用域,工厂方法只会看到在给定的作用域中触发创建新 Bean 实例的注入点(例如,触发创建惰性单例 Bean 的依赖项)。在这样的场景中,你可以使用提供的注入点元数据进行语义维护。下面的示例展示了如何使用`InjectionPoint`:
爪哇
```
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
```
Kotlin
```
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
```
普通 Spring 组件中的`@Bean`方法的处理方式与 Spring `@Configuration`类中的方法的处理方式不同。不同之处在于,`@Component`类不会用 CGlib 进行增强,以拦截方法和字段的调用。CGLIB 代理是在`@Configuration`中调用`@Bean`方法中的方法或字段创建 Bean 对协作对象的元数据引用的一种方法。这样的方法不是用普通的 爪哇 语义来调用的,而是通过容器来提供 Spring bean 的通常的生命周期管理和代理,即使是通过对`@Bean`方法的编程调用来引用其他 bean 时也是如此。相反,在普通的`@Component`类中调用`@Bean`方法中的方法或字段具有标准的 爪哇 语义,没有应用特殊的 CGlib 处理或其他约束。
| |你可以将`@Bean`方法声明为`static`,这样就可以调用它们,而不需要将其包含的配置类创建为实例。在定义后处理器 bean(例如,类型`BeanFactoryPostProcessor`或`BeanPostProcessor`)时,这使得
具有特殊意义,因为这样的 bean 在容器
生命周期的早期就被初始化了,并且应该避免在那个时间点触发配置的其他部分,
对静态`@Bean`方法的调用永远不会被容器拦截,甚至在 `@configuration’类中也不会被拦截,(如本节前面所述),由于技术
的限制:CGLIB 子类只能覆盖非静态方法。因此,
直接调用另一个`@Bean`方法具有标准的 爪哇 语义,结果
在一个独立的实例中被直接从工厂方法本身返回。
`@Bean`方法的 爪哇 语言可见性对
Spring 容器中的结果 Bean 定义没有立即的影响。你可以自由地声明你的
工厂方法,这在非 `@configuration’类中是合适的,在任何地方也适用于静态
方法。但是,`@Bean`类中的常规`@Configuration`方法需要
才能被重写——也就是说,它们不能被声明为`private`或`final`。
@ Bean ` 方法也可以在给定组件的基类上发现,或者
配置类,以及在接口
中声明的 8 个默认方法上由组件或配置类实现。这允许在组成复杂的配置安排时有很多
的灵活性,即使是多个
继承也可以通过 爪哇8 的默认方法实现,截至 Spring 4.2,
最后,对于相同的
Bean,单个类可以容纳多个`@Bean`方法,根据运行时可用的
依赖关系,作为使用的多个工厂方法的安排。这与在其他配置场景中选择“greediest”
构造函数或工厂方法的算法相同:具有
的变量在构造时选择最大数量的可满足依赖项,
类似于容器如何在多个`@Autowired`构造函数之间进行选择。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.6.命名自动检测到的组件
当组件作为扫描过程的一部分被自动检测时,其名称由该扫描仪已知的`BeanNameGenerator`策略生成。默认情况下,任何包含名称`value`的 Spring 原型注释(`@component`,`@Repository`,`@Service`,和 `@controller`)都将该名称提供给相应的 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)接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描仪时提供完全限定的类名,如下面的示例注释和 Bean 定义所示。
| |如果由于多个具有
相同的非限定类名称(即名称相同但位于
不同包中的类)的自动检测组件而导致命名冲突,则可能需要为生成的 Bean 名称配置一个`BeanNameGenerator`默认为
完全限定类名称的`BeanNameGenerator`类。截至 Spring 框架 5.2.3,位于包 `org.SpringFramework.Context.Annotation’中的 `FullyQualifiedAnnotationBeannameGenerator’可用于此目的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
爪哇
```
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
```
```
```
作为一条一般规则,当其他组件可能对其进行显式引用时,可以考虑使用注释来指定名称。另一方面,只要容器负责布线,自动生成的名称就足够了。
#### 1.10.7.为自动检测的组件提供一个范围
与一般的 Spring-管理组件一样,自动检测组件的默认和最常见的作用域是`singleton`。然而,有时你需要一个不同的作用域,该作用域可以由`@Scope`注释指定。你可以在注释中提供作用域的名称,如下例所示:
爪哇
```
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
```
Kotlin
```
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
```
| |`@Scope`注释仅在具体的 Bean 类(对于注释的
组件)或工厂方法(对于`@Bean`方法)上进行内省。与 XML Bean
定义相反,没有 Bean 定义继承的概念,并且在类级别上的继承
层次结构对于元数据目的是无关的。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
有关 Spring 上下文中的“请求”或“会话”等特定于 Web 的作用域的详细信息,请参见[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 {
// ...
}
```
```
```
当使用某些非单例作用域时,可能需要为作用域对象生成代理。推理在[作为依赖项的作用域 bean](#beans-factory-scopes-other-injection)中进行了描述。为此,在 Component-Scan 元素上提供了一个作用域-Proxy 属性。这三个可能的值是:`no`,`interfaces`,和`targetClass`。例如,以下配置将生成标准的 JDK 动态代理:
爪哇
```
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
// ...
}
```
```
```
#### 1.10.8.提供带有注释的限定符元数据
`@Qualifier`注释在[使用限定符对基于注释的自动连线进行微调](#beans-autowired-annotation-qualifiers)中讨论。该部分中的示例演示了在解析 AutoWire 候选项时使用`@Qualifier`注释和自定义限定符注释来提供细粒度的控制。因为这些示例是基于 XML Bean 定义的,所以通过在 XML 中使用`qualifier`或`meta`元素中的`bean`子元素,在候选 Bean 定义上提供了限定符元数据。当依赖 Classpath 扫描来自动检测组件时,你可以在候选类上为限定符元数据提供类型级别的注释。以下三个示例演示了这种技术:
爪哇
```
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
```
Kotlin
```
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
```
爪哇
```
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
```
Kotlin
```
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
```
爪哇
```
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
```
Kotlin
```
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
```
| |与大多数基于注释的替代方法一样,请记住注释元数据是
绑定到类定义本身的,而使用 XML 允许多个相同类型的 bean
在其限定符元数据中提供变体,因为
元数据是按实例而不是按类提供的。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 1.10.9.生成候选组件的索引
Classpath 虽然扫描非常快,但通过在编译时创建候选的静态列表,可以提高大型应用程序的启动性能。在这种模式下,所有组件扫描的目标模块都必须使用这种机制。
| |你现有的`@ComponentScan`或``指令必须保持
不变,以请求上下文扫描某些包中的候选项。当“ApplicationContext”检测到这样的索引时,它会自动使用它,而不是扫描
Classpath。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
要生成索引,请向每个包含组件的模块添加一个附加依赖项,这些组件是组件扫描指令的目标。下面的示例展示了如何使用 Maven 来实现这一点:
```
org.springframework
spring-context-indexer
5.3.16
true
```
对于 Gradle 4.5 或更早的版本,应该在`compileOnly`配置中声明依赖项,如下例所示:
```
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.3.16"
}
```
对于 Gradle 4.6 及更高版本,依赖关系应该在`annotationProcessor`配置中声明,如以下示例所示:
```
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:5.3.16"
}
```
`spring-context-indexer`工件生成一个`META-INF/spring.components`文件,该文件包含在 jar 文件中。
| |在 IDE 中使用此模式时,`spring-context-indexer`必须将
注册为注释处理器,以确保在更新
候选组件时索引是最新的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| |当在 Classpath 上找到`META-INF/spring.components`文件
时,将自动启用索引。如果对于某些库(或用例)
有部分可用的索引,但无法为整个应用程序构建索引,则可以通过将
设置为 `true’,返回到常规的 Classpath
安排(就像根本没有索引一样),可以作为 JVM 系统属性,也可以通过[“SpringProperties”](appendix.html#appendix-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:
javax=2583”/>”javav=2587“/>”>“>”gt="gt=2587|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 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 movieFinder;
@Inject
public void setMovieFinder(Provider movieFinder) {
this.movieFinder = movieFinder;
}
public void listMovies() {
this.movieFinder.get().findMovies(...);
// ...
}
}
```
Kotlin
```
import javax.inject.Inject
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
fun listMovies() {
movieFinder.findMovies(...)
// ...
}
}
```
如果你希望为应该注入的依赖项使用限定名称,那么你应该使用`@Named`注释,如下例所示:
爪哇
```
import javax.inject.Inject;
import javax.inject.Named;
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(@Named("main") MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
```
Kotlin
```
import javax.inject.Inject
import javax.inject.Named
class SimpleMovieLister {
private lateinit var movieFinder: MovieFinder
@Inject
fun setMovieFinder(@Named("main") movieFinder: MovieFinder) {
this.movieFinder = movieFinder
}
// ...
}
```
与`@Autowired`一样,`@Inject`也可以与`java.util.Optional`或 `@nullable’一起使用。这在这里甚至更适用,因为`@Inject`不具有`required`属性。以下两个示例展示了如何使用`@Inject`和 `@nullable’:
```
public class SimpleMovieLister {
@Inject
public void setMovieFinder(Optional movieFinder) {
// ...
}
}
```
爪哇
```
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
```
Kotlin
```
class SimpleMovieLister {
@Inject
var movieFinder: MovieFinder? = null
}
```
#### 1.11.2.`@Named`和`@ManagedBean`:`@Component`注释的标准等价物
而不是`@Component`,你可以使用`@javax.inject.Named`或`javax.annotation.ManagedBean`,如下例所示:
爪哇
```
import javax.inject.Inject;
import javax.inject.Named;
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
```
Kotlin
```
import javax.inject.Inject
import javax.inject.Named
@Named("movieListener") // @ManagedBean("movieListener") could be used as well
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
```
在不指定组件名称的情况下使用`@Component`是非常常见的。@named 可以以类似的方式使用,如下例所示:
爪哇
```
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Inject
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
```
Kotlin
```
import javax.inject.Inject
import javax.inject.Named
@Named
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
```
当你使用`@Named`或`@ManagedBean`时,你可以使用组件扫描,其方式与使用 Spring 注释时的方式完全相同,如下例所示:
爪哇
```
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
```
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
```
| |与`@Component`相反,JSR-330`@Named`和 JSR-250`@ManagedBean`注释是不可组合的。你应该使用 Spring 的原型模型来构建
自定义组件注释。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
#### 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 容器。它包括以下主题:
* [Basic Concepts: `@Bean` and `@Configuration`](#beans-java-basic-concepts)
* [Instantiating the Spring Container by Using `AnnotationConfigApplicationContext`](#beans-java-instantiating-container)
* [Using the `@Bean` Annotation](#beans-java-bean-annotation)
* [Using the `@Configuration` annotation](#beans-java-configuration-annotation)
* [编写基于 爪哇 的配置](#beans-java-composing-configuration-classes)
* [Bean Definition Profiles](#beans-definition-profiles)
* [“PropertySource”抽象](#beans-property-source-abstraction)
* [Using `@PropertySource`](#beans-using-propertysource)
* [语句中的占位符解析](#beans-placeholder-resolution-in-statements)
#### 1.12.1.基本概念:`@Bean`和`@Configuration`
Spring 新的 爪哇-Configuration 支持中的核心构件是“@configuration”-带注释的类和`@Bean`-带注释的方法。
`@Bean`注释用于指示方法实例化、配置和初始化一个新对象,该对象将由 Spring IOC 容器管理。对于那些熟悉 Spring 的``XML 配置的人来说,`@Bean`注释所起的作用与``元素相同。你可以使用`@Bean`-带注释的方法处理任何 Spring `@component`。然而,它们最常用于`@Configuration`bean。
用`@Configuration`注释一个类表明,它的主要目的是作为 Bean 定义的来源。此外,`@Configuration`类通过调用同一类中的其他`@Bean`方法来定义 Bean 之间的依赖关系。最简单的`@Configuration`类如下:
爪哇
```
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
```
Kotlin
```
@Configuration
class AppConfig {
@Bean
fun myService(): MyService {
return MyServiceImpl()
}
}
```
前面的`AppConfig`类等价于下面的 Spring ``XML:
```
```
完整的 @ 配置 VS“Lite”@ Bean 模式?
当`@Bean`方法在没有使用 `@Configuration’注释的类中声明时,它们被称为正在以“精简”模式进行处理。 Bean 在`@Component`中声明的方法,甚至在一个普通的旧类中声明的方法,都被认为是“lite”,具有不同的包含类的主要目的,而`@Bean`方法是那里的一种奖励。例如,服务组件可以在每个适用的组件类上通过一个额外的`@Bean`方法向容器公开管理视图。在这样的场景中,`@Bean`方法是一种通用的工厂方法机制。
与 full`@Configuration`不同,lite`@Bean`方法不能声明 Bean 之间的依赖关系。相反,它们对包含它们的组件的内部状态进行操作,并可选地对它们可能声明的参数进行操作。因此,这样的`@Bean`方法不应该调用其他 `@ Bean ` 方法。每个这样的方法实际上只是用于特定 Bean 引用的工厂方法,没有任何特殊的运行时语义。这里的积极的副作用是,在运行时不需要应用 CGLIB 子类,因此在类设计方面没有限制(即,包含的类可能是`final`等)。
在常见的场景中,`@Bean`方法要在`@Configuration`类中声明,以确保始终使用“完全”模式,并确保跨方法引用因此被重定向到容器的生命周期管理。这可以防止通过常规的 爪哇 调用意外调用相同的“@ Bean”方法,这有助于减少在“精简”模式下操作时很难追踪到的细微错误。
下面的部分将深入讨论`@Bean`和`@Configuration`注释。然而,首先,我们介绍了通过使用基于 爪哇 的配置来创建 Spring 容器的各种方法。
#### 1.12.2.使用`AnnotationConfigApplicationContext`### 实例化 Spring 容器
下面的部分记录了 Spring 的`AnnotationConfigApplicationContext`,在 Spring 3.0 中介绍了它。这种通用的`ApplicationContext`实现不仅能够接受 `@Configuration’类作为输入,而且还能够接受用 JSR-330 元数据注释的普通`@Component`类和类。
当`@Configuration`类被提供为输入时,`@Configuration`类本身被注册为 Bean 定义,并且类中所有声明的`@Bean`方法也被注册为 Bean 定义。
当`@Component`和 JSR-330 类被提供时,它们被注册为 Bean 定义,并且假定在必要的情况下,在那些类中使用诸如`@Autowired`或`@Inject`的 DI 元数据。
##### 简单的构造
就像 Spring XML 文件在实例化 `ClassPathXMLApplicationContext’时用作输入一样,在实例化`@Configuration`时,可以使用`AnnotationConfigApplicationContext`类作为输入。这允许完全无 XML 地使用 Spring 容器,如下例所示:
爪哇
```
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
```
Kotlin
```
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
val myService = ctx.getBean()
myService.doStuff()
}
```
如前所述,`AnnotationConfigApplicationContext`并不限于仅与`@Configuration`类一起工作。任何`@Component`或 JSR-330 注释类都可以作为构造函数的输入提供,如下例所示:
爪哇
```
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
```
Kotlin
```
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
val myService = ctx.getBean()
myService.doStuff()
}
```
前面的示例假设`MyServiceImpl`、`Dependency1`和`Dependency2`使用 Spring 依赖注入注释,例如`@Autowired`。
##### 通过使用`register(Class>…)`#### 以编程方式构建容器
你可以使用 no-arg 构造函数实例化`AnnotationConfigApplicationContext`,然后使用`register()`方法对其进行配置。当以编程方式构建`AnnotationConfigApplicationContext`时,这种方法特别有用。下面的示例展示了如何做到这一点:
爪哇
```
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}
```
Kotlin
```
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext()
ctx.register(AppConfig::class.java, OtherConfig::class.java)
ctx.register(AdditionalConfig::class.java)
ctx.refresh()
val myService = ctx.getBean()
myService.doStuff()
}
```
##### 使用`scan(String…)`启用组件扫描
要启用组件扫描,你可以对`@Configuration`类作如下注释:
爪哇
```
@Configuration
@ComponentScan(basePackages = "com.acme") (1)
public class AppConfig {
// ...
}
```
|**1**|此注释使组件扫描成为可能。|
|-----|-------------------------------------------|
Kotlin
```
@Configuration
@ComponentScan(basePackages = ["com.acme"]) (1)
class AppConfig {
// ...
}
```
|**1**|此注释使组件扫描成为可能。|
|-----|-------------------------------------------|
| |有经验的 Spring 用户可能熟悉来自
Spring 的`context:`名称空间的等效 XML 声明,如以下示例所示: