类路径扫描和托管组件
本章中的大多数示例都使用 XML 来指定配置元数据,这些元数据在 Spring 容器中生成每个 BeanDefinition。上一节(基于注解的容器配置)演示了如何通过源代码级注解提供大部分配置元数据。然而,即使在这些示例中,“基础” bean 定义仍明确定义在 XML 文件中,而注解仅用于驱动依赖注入。
本节介绍了一种通过扫描类路径隐式检测候选组件的选项。候选组件是与筛选条件匹配并向容器注册了相应 bean 定义的类。这消除了使用 XML 进行 bean 注册的需要。相反,您可以使用注解(例如 @Component)、AspectJ 类型表达式或您自己的自定义筛选条件来选择哪些类向容器注册 bean 定义。
|
您可以使用 Java 而不是 XML 文件来定义 bean。请查看 |
@Component 和进一步的 Stereotype 注解
@Repository 注解是任何满足仓库角色或
Spring 提供了进一步的构造型注解:@Component、@Service 和 @Controller。@Component 是任何 Spring 管理组件的通用构造型。@Repository、@Service 和 @Controller 是 @Component 的专用版本,用于更具体的用例(分别在持久层、服务层和表现层)。因此,您可以使用 @Component 注解您的组件类,但通过使用 @Repository、@Service 或 @Controller 注解它们,您的类更适合工具处理或与方面关联。例如,这些构造型注解是切入点的理想目标。@Repository、@Service 和 @Controller 在 Spring Framework 的未来版本中也可能带有额外的语义。因此,如果您在服务层选择使用 @Component 或 @Service,那么 @Service 显然是更好的选择。同样,如前所述,@Repository 已被支持作为持久层中自动异常翻译的标记。
使用元注解和组合注解
Spring 提供的许多注解都可以在您自己的代码中用作元注解。元注解是可以应用于另一个注解的注解。例如,前面提到的 @Service 注解是用 @Component 元注解的,如下例所示
-
Java
-
Kotlin
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component (1)
public @interface Service {
// ...
}
| 1 | @Component 元注解使得 @Service 被视为与 @Component 相同。 |
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component (1)
annotation class Service {
// ...
}
| 1 | @Component 元注解使得 @Service 被视为与 @Component 相同。 |
您还可以组合元注解以创建“组合注解”。例如,Spring MVC 中的 @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 页面。
自动检测类并注册 Bean 定义
Spring 可以自动检测构造型类并向 ApplicationContext 注册相应的 BeanDefinition 实例。例如,以下两个类符合此类自动检测的条件
-
Java
-
Kotlin
@Service
public class SimpleMovieLister {
private final 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 {
// ...
}
为简洁起见,前面的示例可以使用注解的隐式 value 属性代替:@ComponentScan("org.example") |
以下示例使用 XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
使用 <context:component-scan> 隐式启用了 <context:annotation-config> 的功能。在使用 <context:component-scan> 时,通常不需要包含 <context:annotation-config> 元素。 |
|
扫描类路径包需要类路径中存在相应的目录条目。使用 Ant 构建 JAR 时,请确保您未激活 JAR 任务的“仅文件”开关。此外,在某些环境中,类路径目录可能由于安全策略而未暴露——例如,JDK 1.7.0_45 及更高版本上的独立应用程序(这需要在清单中设置“Trusted-Library”——请参阅 stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。 在模块路径(Java 模块系统)上,Spring 的类路径扫描通常按预期工作。但是,请确保您的组件类已在 |
此外,当您使用 <context:component-scan> 元素时,AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 都被隐式包含。这意味着这两个组件都被自动检测并连接在一起——所有这些都不需要 XML 中提供任何 bean 配置元数据。
您可以通过包含 annotation-config 属性并将其值设置为 false 来禁用 AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 的注册。 |
属性占位符和 Ant 风格模式
@ComponentScan 中的 basePackages 和 value 属性支持 ${…} 属性占位符,这些占位符将针对 Environment 解析,以及 Ant 风格的包模式,例如 "org.example.**"。
此外,可以指定多个包或模式,无论是单独指定还是在单个字符串中指定——例如,{"org.example.config", "org.example.service.**"} 或 "org.example.config, org.example.service.**"。
以下示例为 @ComponentScan 中的隐式 value 属性指定 app.scan.packages 属性占位符。
-
Java
-
Kotlin
@Configuration
@ComponentScan("${app.scan.packages}") (1)
public class AppConfig {
// ...
}
| 1 | app.scan.packages 属性占位符将针对 Environment 进行解析 |
@Configuration
@ComponentScan(["\${app.scan.packages}"]) (1)
class AppConfig {
// ...
}
| 1 | app.scan.packages 属性占位符将针对 Environment 进行解析 |
以下列表表示一个定义 app.scan.packages 属性的属性文件。在前面的示例中,假设此属性文件已注册到 Environment——例如,通过 @PropertySource 或类似机制。
app.scan.packages=org.example.config, org.example.service.**
使用过滤器自定义扫描
默认情况下,只有用 @Component、@Repository、@Service、@Controller、@Configuration 或本身用 @Component 注解的自定义注解标记的类才被检测为候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为 @ComponentScan 注解的 includeFilters 或 excludeFilters 属性(或作为 XML 配置中 <context:component-scan> 元素的 <context:include-filter /> 或 <context:exclude-filter /> 子元素)。每个过滤器元素都需要 type 和 expression 属性。下表描述了筛选选项
| 过滤器类型 | 示例表达式 | 描述 |
|---|---|---|
annotation (默认) |
|
在目标组件的类型级别 |
assignable |
|
目标组件可赋值给的类(或接口)(继承或实现)。 |
aspectj |
|
一个 AspectJ 类型表达式,将由目标组件匹配。 |
regex |
|
一个正则表达式,将由目标组件的类名匹配。 |
custom |
|
|
以下示例显示了 @ComponentScan 配置,该配置排除了所有 @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>
您还可以通过在注解上设置 useDefaultFilters=false 或提供 use-default-filters="false" 作为 <component-scan/> 元素的属性来禁用默认过滤器。这有效地禁用了对使用 @Component、@Repository、@Service、@Controller、@RestController 或 @Configuration 注解或元注解的类的自动检测。 |
命名自动检测组件
当组件作为扫描过程的一部分被自动检测时,它的 bean 名称由该扫描器已知的 BeanNameGenerator 策略生成。
默认情况下,使用 AnnotationBeanNameGenerator。对于 Spring 构造型注解,如果您通过注解的 value 属性提供名称,该名称将用作相应 bean 定义中的名称。此约定也适用于使用 @jakarta.inject.Named 注解而不是 Spring 构造型注解时。
自 Spring Framework 6.1 起,用于指定 bean 名称的注解属性名称不再必须是 value。自定义构造型注解可以声明具有不同名称(例如 name)的属性,并使用 @AliasFor(annotation = Component.class, attribute = "value") 注解该属性。有关具体示例,请参阅 ControllerAdvice#name() 的源代码声明。
|
自 Spring Framework 6.1 起,对基于约定的构造型名称的支持已弃用,并将在框架的未来版本中移除。因此,自定义构造型注解必须使用 |
如果无法从此类注解或其他任何检测到的组件(例如通过自定义过滤器发现的组件)派生出显式 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 定义所示。
如果您遇到由于多个自动检测到的组件具有相同的非限定类名(即,具有相同名称但位于不同包中的类)而导致的命名冲突,您可能需要配置一个 BeanNameGenerator,它将默认使用完全限定类名作为生成的 bean 名称。位于 org.springframework.context.annotation 包中的 FullyQualifiedAnnotationBeanNameGenerator 可用于此类目的。 |
-
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>
作为一般规则,当其他组件可能显式引用它时,考虑使用注解指定名称。另一方面,当容器负责连接时,自动生成的名称就足够了。
为自动检测组件提供作用域
与一般的 Spring 管理组件一样,自动检测组件的默认和最常见作用域是 singleton。但是,有时您需要通过 @Scope 注解指定不同的作用域。您可以在注解中提供作用域名称,如下例所示
-
Java
-
Kotlin
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
// ...
}
@Scope 注解仅在具体的 bean 类(对于带注解的组件)或工厂方法(对于 @Bean 方法)上进行内省。与 XML bean 定义相反,没有 bean 定义继承的概念,并且类级别的继承层次结构与元数据目的无关。 |
有关 Spring 上下文中特定于 Web 的作用域(例如“request”或“session”)的详细信息,请参阅请求、会话、应用程序和 WebSocket 作用域。与那些作用域的预构建注解一样,您也可以通过使用 Spring 的元注解方法来组成您自己的作用域注解:例如,一个自定义注解用 @Scope("prototype") 元注解,可能还声明一个自定义作用域代理模式。
要提供自定义作用域解析策略,而不是依赖于基于注解的方法,您可以实现 ScopeMetadataResolver 接口。请务必包含一个默认的无参数构造函数。然后,您可以在配置扫描器时提供完全限定的类名,如下面的注解和 bean 定义示例所示 |
-
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>
在使用某些非单例作用域时,可能需要为作用域对象生成代理。原因在作为依赖项的作用域 Bean 中进行了描述。为此,组件扫描元素上提供了一个 scoped-proxy 属性。可能的值有三个:no、interfaces 和 targetClass。例如,以下配置会生成标准的 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>
使用注解提供限定符元数据
@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 提供其限定符元数据的变体,因为该元数据是按实例而不是按类提供的。 |
在组件中定义 Bean 元数据
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 Language 元素通过 #{ <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 处理或其他限制。
|
您可以将 对静态
在给定组件或配置类的基类上以及在组件或配置类实现的接口中声明的 Java 默认方法上,也发现了 最后,单个类可以包含同一 bean 的多个 |