|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
类路径扫描和管理组件
本章的大多数示例使用XML来指定生成Spring容器中每个BeanDefinition的配置元数据。上一节(基于注解的容器配置)演示了如何通过源代码级别的注解提供大量配置元数据。然而,在这些示例中,“基础”的Bean定义仍然在XML文件中显式定义,而注解仅驱动依赖注入。本节描述了一种通过扫描类路径隐式检测候选组件的方法。候选组件是符合过滤条件并已在容器中注册对应Bean定义的类。这消除了使用XML进行Bean注册的需要。相反,您可以使用注解(例如@Component)、AspectJ类型表达式或自定义的过滤条件来选择哪些类具有已注册到容器中的Bean定义。
|
您可以使用Java而不是XML文件来定义bean。查看
|
@Component 以及其他构型注解
@Repository 注解用于标记任何履行仓库(也称为数据访问对象或 DAO)角色或样式的类。此标记的一个用途是如异常转换中所述的自动转换异常。
Spring 提供了其他类型注解: @Component,@Service,和
@Controller。 @Component 是用于任何 Spring 管理组件的通用类型注解。
@Repository,@Service 和 @Controller 是 @Component 的特化类型,用于更具体的使用场景(分别在持久化、服务和表示层中)。因此,你可以用 @Component 注解你的组件类,但是,通过用 @Repository、@Service 或 @Controller 注解它们,你的类更适合被工具处理或与方面相关联。例如,这些类型注解是切入点的理想目标。 @Repository、@Service 和 @Controller 在 Spring 框架的未来版本中也可能包含额外的语义。因此,如果你在选择在服务层使用 @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 可以自动检测带有 stereotype 的类,并将相应的
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 {
// ...
}
为了简洁起见,前面的示例可以使用注解的 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)。 在 JDK 9 的模块路径(Jigsaw)上,Spring 的类路径扫描通常按预期工作。
但是,请确保您的组件类在您的 |
此外,当你使用 component-scan 元素时,AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 都会被隐式包含。这意味着这两个组件会被自动检测并连接在一起 — 而无需在 XML 中提供任何 bean 配置元数据。
您可以通过包含值为 false 的 annotation-config 属性来禁用 AutowiredAnnotationBeanPostProcessor 和
CommonAnnotationBeanPostProcessor 的注册。 |
使用过滤器自定义扫描
默认情况下,使用 @Component、@Repository、@Service、@Controller、
@Configuration 或自定义注解标注的类是唯一被检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们作为 includeFilters 或 excludeFilters 属性添加到 @ComponentScan 注解中(或作为 <context:include-filter /> 或
<context:exclude-filter /> 子元素添加到 XML 配置中的 <context:component-scan> 元素中)。每个过滤器元素都需要 type 和 expression 属性。
下表描述了过滤选项:
| 过滤器类型 | 示例表达式 | 描述 |
|---|---|---|
注解(默认) |
|
一个注解,应在目标组件的类型级别上存在或元存在。 |
可分配的 |
|
一个类(或接口),目标组件可以分配给它(继承或实现)。 |
面向方面编程 |
|
一个 AspectJ 类型表达式,用于匹配目标组件。 |
正则表达式 |
|
用于匹配目标组件类名的正则表达式。 |
自定义 |
|
对 |
以下示例显示了忽略所有 @Repository 注解并改用“占位”仓库的配置:
-
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 或者在 <component-scan/> 元素上提供 use-default-filters="false" 作为属性来禁用默认过滤器。这会有效禁用对使用 @Component、@Repository、@Service、@Controller、@RestController 或 @Configuration 注解或元注解的类的自动检测。 |
在组件中定义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 和自定义限定符注解。
除了在组件初始化中的作用外,您还可以将 @Lazy 注解放在用 @Autowired 或 @Inject 标记的注入点上。在此上下文中,它会导致注入一个延迟解析代理。然而,这种代理方法有一定的局限性。对于复杂的延迟交互,特别是与可选依赖项结合使用时,我们建议使用 ObjectProvider<MyTargetBean>。 |
@Autowired 字段和方法得到支持,如前所述,还增加了对 @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框架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 代理是通过在 @Bean 方法中调用方法或字段来创建协作对象的 bean 元数据引用的方式。
这些方法不是通过正常的 Java 语义调用的,而是经过容器以提供 Spring beans 的常规生命周期管理和代理功能,即使通过对 @Bean 方法的编程调用来引用其他 beans 也是如此。
相比之下,在普通 @Component 类中的 @Bean 方法中调用方法或字段具有标准的 Java 语义,没有任何特殊的 CGLIB 处理或其他约束适用。
|
您可以将 对静态 Java语言中
最后,一个类可能包含多个 |
命名自动检测的组件
当组件在扫描过程中被自动检测到时,其bean名称由该扫描器所知的BeanNameGenerator策略生成。默认情况下,任何包含名称value的Spring构造型注解(@Component、@Repository、@Service和@Controller)都会将该名称提供给相应的bean定义。
如果这样的注解不包含名称 value 或者对于任何其他检测到的组件(例如通过自定义过滤器发现的组件),默认的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 名称的全限定类名。从 Spring Framework 5.2.3 开始,可以使用位于包 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上下文中“request”或“session”等特定于Web的作用域的详细信息,
请参阅 Request, Session, Application, and WebSocket Scopes。与这些作用域的预定义注解一样,
您也可以通过使用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作为依赖项。为此,component-scan元素上提供了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 中 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 提供其限定符元数据的差异,因为该元数据是按实例提供的,而不是按类提供的。 |
生成候选组件的索引
虽然类路径扫描非常快速,但通过在编译时创建候选列表,可以提高大型应用程序的启动性能。在此模式下,所有作为组件扫描目标的模块都必须使用此机制。
您现有的 @ComponentScan 或 <context:component-scan/> 指令必须保持不变,以请求上下文扫描特定包中的候选者。当 ApplicationContext 检测到这样的索引时,会自动使用它而不是扫描类路径。 |
要生成索引,请向每个包含组件扫描指令目标组件的模块添加额外的依赖项。以下示例展示了如何使用 Maven 进行操作:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>6.0.23</version>
<optional>true</optional>
</dependency>
</dependencies>
在 Gradle 4.5 及更早版本中,依赖项应声明在 compileOnly 配置中,如以下示例所示:
dependencies {
compileOnly "org.springframework:spring-context-indexer:6.0.23"
}
使用 Gradle 4.6 及更高版本时,依赖项应声明在 annotationProcessor 配置中,如下面的示例所示:
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:6.0.23"
}
spring-context-indexer 项目生成一个 META-INF/spring.components 文件,该文件包含在 jar 文件中。
在IDE中使用此模式时,必须将spring-context-indexer注册为注解处理器,以确保当候选组件更新时索引保持最新。 |
在类路径上找到 META-INF/spring.components 文件时,索引会自动启用。如果某些库(或使用场景)的索引部分可用,但无法为整个应用程序构建索引,可以通过将 spring.index.ignore 设置为 true,以回退到常规的类路径配置(就好像根本没有索引一样),既可以作为 JVM 系统属性,也可以通过
SpringProperties 机制来实现。 |