Classpath Scanning and Managed Components
本章中的大多数示例都使用 XML 来指定在 Spring 容器中产生每个 BeanDefinition
的配置元数据。前面的部分 (Annotation-based Container Configuration) 演示了如何通过源级注释提供许多配置元数据。但是,即使在那些示例中,“基础”Bean 定义仍然在 XML 文件中显式定义,而注释仅驱动依赖注入。本部分描述了一种通过扫描类路径来隐式检测候选组件的选项。候选组件是与筛选标准匹配并在容器中注册了相应 Bean 定义的类。这消除了使用 XML 来执行 Bean 注册的需要。相反,你可以使用注释(例如,@Component
)、AspectJ 类型表达式或你自己的自定义筛选标准来选择向容器注册 Bean 定义的类。
可以使用 Java 而不是 XML 文件来定义 bean。请查看 |
@Component
and Further Stereotype Annotations
"@49" 注解是任何实现存储库 (也称为数据访问对象或 DAO) 的角色或 "@50" 的类的标记。这个标记的用法之一是自动转换异常,如 "@51" 中所述。
Spring 提供了更多陈规定型注释:@Component
、@Service
和 @Controller
。@Component
是任何 Spring 管理组件的通用陈规定型。@Repository
、@Service
和 @Controller
是 @Component
在更具体的用例(分别在持久化、服务和表示层)中的专业化。因此,可以使用 @Component
为组件类加上注释,但通过使用 @Repository
、@Service
或 @Controller
为它们加上注释,这些类更适合由工具处理或与切面关联。例如,这些陈规定型注释是切入点的理想目标。@Repository
、@Service
和 @Controller
在 Spring Framework 的未来版本中也可能包含其他语义。因此,如果你在为服务层选择使用 @Component
或 @Service
之间进行选择,@Service
明显是更好的选择。与此类似,如前所述,@Repository
已被支持为持久层中自动异常翻译的标记。
Using Meta-annotations and Composed Annotations
Spring 提供的许多注解都可以作为你自己的代码中的元注解来使用。元注解是可以应用于另一个注解的注解。例如,"@52" 注解在 "@54" 中被提及,它被 "@53" 元注解,如下面的示例所示:
- Java
-
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component (1) public @interface Service { // ... }
1 | @Component 使得 @Service 在 @Component 中被以相同的方式处理。
|
2 | @Component 使得 @Service 在 @Component 中被以相同的方式处理。 |
还可以合并元注释以创建“复合注释
”。例如,Spring MVC 中的 @RestController
注释由 @Controller
和 @ResponseBody
组成。
此外,复合注释可以有选择地重新声明元注释中的属性以进行自定义。在你只想公开元注释属性的子集时,这可能特别有用。例如,Spring 的 @SessionScope
注释将范围名称硬编码为 session
,但仍然允许自定义 proxyMode
。以下清单显示了 SessionScope
注释的定义:
-
Java
-
Kotlin
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
@get:AliasFor(annotation = Scope::class)
val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)
然后,可以如下所示使用 @SessionScope
而不声明 proxyMode
:
-
Java
-
Kotlin
@Service
@SessionScope
public class SessionScopedService {
// ...
}
@Service
@SessionScope
class SessionScopedService {
// ...
}
也可以重写 proxyMode
的值,如下例所示:
-
Java
-
Kotlin
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
// ...
}
有关更多详细信息,请参阅 Spring 注释编程模型wiki 页面。
Automatically Detecting Classes and Registering Bean Definitions
Spring 可以自动检测陈规定型类,并将相应的 BeanDefinition
实例注册到 ApplicationContext
。例如,以下两个类符合此类自动检测:
-
Java
-
Kotlin
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
-
Java
-
Kotlin
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
@Repository
class JpaMovieFinder : MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的 bean,需要将 @ComponentScan
添加到 @Configuration
类中,其中 basePackages
属性是两个类的公共父包。(或者,可以指定一个逗号分隔的、分号分隔的或空格分隔的列表,其中包含每个类的父包。)
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig {
// ...
}
为简洁起见,前面的示例可以使用该注释的 |
以下替代项使用了 XML:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
使用 |
扫描类路径包需要类路径中存在相应的目录条目。使用 Ant 构建 JAR 时,请确保未激活 JAR 任务的仅文件开关。此外,在某些环境中,类路径目录可能无法根据安全策略公开,例如 JDK 1.7.0_45 及更高版本上的独立应用程序(这需要在清单中设置“受信任库”——请参阅 https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
在 JDK 9 的模块路径(拼图)上,Spring 的类路径扫描通常按预期工作。但是,请确保在 |
此外,当你使用 component-scan
元素时,AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
都隐含包含。这意味着这两个组件会被自动检测,并连接起来,而无需在 XML 中提供任何 bean 配置元数据。
你可以通过包括 |
Using Filters to Customize Scanning
默认情况下,只有用 @Component
、@Repository
、@Service
、@Controller
、@Configuration
或自定义注解(其中注解本身用 @Component
标记)标记的类才是唯一可以检测到的候选组件。但是,你可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为 @ComponentScan
注解的 includeFilters
或 excludeFilters
属性(或作为 <context:component-scan>
元素在 XML 配置中的 <context:include-filter />
或 <context:exclude-filter />
子元素)。每个过滤器元素都需要 type
和 expression
属性。下表描述了筛选选项:
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) |
|
要在目标组件中 present 或 meta-present 的类型级别的注释。 |
assignable |
|
目标组件可以分配给(扩展或实现)的类(或接口)。 |
aspectj |
|
目标组件要匹配的 AspectJ 类型表达式。 |
regex |
|
一个正则表达式,根据目标组件的类名进行匹配。 |
custom |
|
|
以下示例展示了忽略所有 @Repository
注解并改为使用 “stub
” 存储库的配置:
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"],
includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
excludeFilters = [Filter(Repository::class)])
class AppConfig {
// ...
}
以下清单展示了对应的 XML:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
你还可以通过在注释上设置 |
Defining Bean Metadata within Components
Spring 组件还可以将 bean 定义元数据添加到容器。你可以使用与用于在 @Configuration
标记类中定义 bean 元数据的 @Bean
注解相同。以下示例展示了如何执行此操作:
-
Java
-
Kotlin
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
@Component
class FactoryMethodComponent {
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
fun doWork() {
// Component method implementation omitted
}
}
前面的类是一个 Spring 组件,其 doWork()
方法中有特定于应用程序的代码。但是,它还提供了一个 bean 定义,其工厂方法引用 publicInstance()
方法。@Bean
注解标识工厂方法和其他 bean 定义属性,例如通过 @Qualifier
注解进行的限定符值。可以指定的其他方法级别注解包括 @Scope
、@Lazy
和自定义限定符注解。
除了组件初始化之外,你还可以将 |
如前所述,支持自动装配的字段和方法,并额外支持对 @Bean
方法的自动装配。以下示例展示了如何执行此操作:
-
Java
-
Kotlin
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
@Component
class FactoryMethodComponent {
companion object {
private var i: Int = 0
}
@Bean
@Qualifier("public")
fun publicInstance() = TestBean("publicInstance")
// use of a custom qualifier and autowiring of method parameters
@Bean
protected fun protectedInstance(
@Qualifier("public") spouse: TestBean,
@Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
this.spouse = spouse
this.country = country
}
@Bean
private fun privateInstance() = TestBean("privateInstance", i++)
@Bean
@RequestScope
fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}
示例将 String
方法参数 country
自动装配到名为 privateInstance
的另一个 bean 的 age
属性值。Spring 表达式语言元素通过符号 #{ <expression> }
定义属性值。对于 @Value
注解,预先配置一个表达式解析器,在解析表达式文本时查找 bean 名称。
从 Spring Framework 4.3 开始,你还可以声明一个类型为 InjectionPoint
的工厂方法参数(或其更具体的子类:DependencyDescriptor
)以访问触发当前 bean 创建的请求注入点。请注意,这仅适用于 bean 实例的实际创建,而不适用于现有实例的注入。因此,此功能最适用于原型作用域的 bean。对于其他作用域,工厂方法只会看到触发给定作用域中新建 bean 实例的注入点(例如,触发延迟单例 bean 创建的依赖项)。在这种情况下,你可以谨慎地使用提供的注入点元数据。以下示例展示了如何使用 InjectionPoint
:
-
Java
-
Kotlin
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
@Component
class FactoryMethodComponent {
@Bean
@Scope("prototype")
fun prototypeInstance(injectionPoint: InjectionPoint) =
TestBean("prototypeInstance for ${injectionPoint.member}")
}
常规 Spring 组件中的 @Bean
方法的处理方式与 Spring @Configuration
类中的同类方法不同。区别在于,@Component
类不会使用 CGLIB 增强,从而拦截方法和字段的调用。CGLIB 代理是 @Configuration
类中的 @Bean
方法中调用方法或字段创建对协作对象的 bean 元数据引用的方式。此类方法不会使用普通 Java 语义调用,而是通过容器提供 Spring bean 的通常生命周期管理和代理,即使通过对 @Bean
方法的编程调用引用其他 bean 也是如此。相比之下,在普通 @Component
类中的 @Bean
方法中调用方法或字段具有标准 Java 语义,不适用任何特殊 CGLIB 处理或其他约束。
你可以将 |
Naming Autodetected Components
当一个组件作为扫描过程的一部分被自动检测到时,其 bean 名称由该扫描器所知的 BeanNameGenerator
策略生成。
默认情况下,使用 "@55"。对于 Spring "@61",如果你通过注解的 "@56" 属性提供了一个名称,则该名称将用作相应 Bean 定义中的名称。当使用以下 JSR-250 和 JSR-330 注解代替 Spring 固定用法注解时,也会适用此约定:"@57"、"@58"、"@59" 和 "@60"。
从 Spring Framework 6.1 开始,不再需要用于指定 bean 名称的注解属性的名称为“value”。自定义 stereotype annotations 可以声明一个名称不同的属性(例如,“name”),并使用 @AliasFor(annotation = Component.class, attribute = "value")
注释该属性。请参阅 ControllerAdvice#name()
的源代码声明以获取具体示例。
从 Spring Framework 6.1 开始,对基于约定的 stereotype 名称的支持已弃用,并且将在框架的未来版本中移除。因此,自定义 stereotype annotations 必须使用 @AliasFor
为 @Component
中的 value
属性声明一个显式别名。请参阅 Repository#value()
和 ControllerAdvice#name()
的源代码声明以获取具体示例。
如果无法从此类注释或任何其他检测到的组件(例如自定义筛选器发现的组件)中获取明确的 bean 名称,则默认 Bean 名称生成器将返回非限定的类小写名称。例如,如果检测到了以下组件类,名称将为 myMovieLister
和 movieFinderImpl
。
-
Java
-
Kotlin
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
// ...
}
-
Java
-
Kotlin
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
如果你不想依赖默认的 bean 命名策略,你可以提供一个自定义 bean 命名策略。首先,实现 BeanNameGenerator
接口,并且务必包括一个默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名称,如下面的示例注释和 Bean 定义所示。
如果你因有多个自动检测的组件具有相同的非限定类名称(即,在不同包中驻留的名称相同的类)而遇到命名冲突,则可能需要配置一个 |
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
作为一个通用规则,只要有其他组件可能对它进行显式引用,就考虑用注解来指定该名称。另一方面,只要容器负责连接,自动生成的名称就足够了。
Providing a Scope for Autodetected Components
与 Spring 管理的组件一样,自动检测组件的默认范围和最常见范围是 singleton
。但是,有时需要不同的范围,可由 @Scope
注解指定。您可以按照以下示例所示提供注解中的范围名称:
-
Java
-
Kotlin
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
|
有关 Spring 上下文中特定于 Web 的范围(例如 "@62" 或 "@63")的详细信息,请参阅 "@65"。与用于这些范围的预置注解一样,你也可以使用 Spring 的元注解方法来组合你自己的范围注解:例如,用 "@64" 进行元注解的自定义注解,也有可能声明自定义作用域代理模式。
要提供一个自定义的范围解析策略,而不是依赖于基于注释的方法,您可以实现https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/ScopeMetadataResolver.html[ |
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
在使用某些非单例范围时,可能需要为范围对象生成代理。推理在 "@69" 中描述。为此,组件扫描元素中提供了 scoped-proxy 属性。三个可能的值是:"@66"、"@67" 和 "@68"。例如,以下配置将生成标准 JDK 动态代理:
-
Java
-
Kotlin
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
Providing Qualifier Metadata with Annotations
在 Fine-tuning Annotation-based Autowiring with Qualifiers 中讨论了 @Qualifier
注释。该部分中的示例演示了如何使用 @Qualifier
注释和自定义限定符注释在解析自动装配候选对象时提供细粒度控制。由于这些示例基于 XML Bean 定义,因此限定符元数据是使用 XML 中 bean
元素的 qualifier
或 meta
子元素在候选 Bean 定义上提供的。在依靠类路径扫描自动检测组件时,可以使用候选类上的类型级注释提供限定符元数据。以下三个示例演示了此技术:
-
Java
-
Kotlin
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
-
Java
-
Kotlin
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
// ...
}
-
Java
-
Kotlin
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
// ...
}
与大多数基于注释的替代方案一样,请记住注释元数据绑定到类定义本身,而使用 XML 允许具有相同类型的多个 Bean 在其品质元数据中提供变体,因为该元数据是按实例提供的,而不是按类提供的。 |