此版本仍在开发中,尚未稳定。如需最新的稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

类路径扫描和管理组件

本章中的大多数示例使用 XML 来指定配置元数据,从而在 Spring 容器中生成每个 BeanDefinition。上一节(基于注解的容器配置)展示了如何通过源码级注解提供大量配置元数据。然而,在这些示例中,“基础” Bean 定义仍显式地定义在 XML 文件中,而注解仅用于驱动依赖注入。spring-doc.cadn.net.cn

本节介绍了一种通过扫描类路径来隐式检测候选组件的选项。候选组件是指符合过滤条件并在容器中注册了相应 Bean 定义的类。这样就无需使用 XML 来进行 Bean 注册。您可以使用注解(例如,@Component)、AspectJ 类型表达式或自定义的过滤条件来选择哪些类需要在容器中注册 Bean 定义。spring-doc.cadn.net.cn

您可以使用Java而不是XML文件来定义bean。查看 @Configuration, @Bean, @Import, 和 @DependsOn 注解,了解如何 使用这些功能的示例。spring-doc.cadn.net.cn

@Component 以及其他构型注解

@Repository 注解是用于标记任何充当存储库(也称为数据访问对象或DAO)角色或构造型的类。此标记的用途之一是自动转换异常,如异常转换中所述。spring-doc.cadn.net.cn

Spring 提供了更多的构造型注解:@Component@Service@Controller@Component 是任何由 Spring 管理的组件的通用构造型。 @Repository@Service@Controller 分别是 @Component 在特定用例中的特殊化形式 (分别用于持久化层、服务层和表现层)。因此,你可以使用 @Component 来注解你的组件类, 但若改用 @Repository@Service@Controller 进行注解,则你的类将更适于被工具处理或与切面关联。 例如,这些构造型注解非常适合作为切点的目标。@Repository@Service@Controller 在未来的 Spring 框架版本中 也可能带有额外的语义。因此,如果你在服务层中需要选择使用 @Component@Service,那么 @Service 显然是更好的选择。 同样,如前所述,@Repository 已经被支持作为持久化层中自动异常转换的标记。spring-doc.cadn.net.cn

使用元注解和组合注解

Spring提供的许多注解都可以在您自己的代码中作为元注解使用。元注解是指可以应用于另一个注解的注解。例如,前面提到的@Service注解被@Component注解作为元注解,如下例所示:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

此外,组合注解可以选择性地重新声明来自元注解的属性,以允许自定义。这在您只想公开元注解的部分属性时尤其有用。例如,Spring 的 @SessionScope 注解将作用域名称硬编码为 session,但仍允许对 proxyMode 进行自定义。下面的列表显示了 SessionScope 注解的定义:spring-doc.cadn.net.cn

@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,如下所示:spring-doc.cadn.net.cn

@Service
@SessionScope
public class SessionScopedService {
	// ...
}
@Service
@SessionScope
class SessionScopedService {
	// ...
}

您还可以覆盖 proxyMode 的值,如下例所示:spring-doc.cadn.net.cn

@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
	// ...
}
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
	// ...
}

如需更多详细信息,请参阅 Spring 注解编程模型 wiki 页面。spring-doc.cadn.net.cn

自动检测类并注册Bean定义

Spring 可以自动检测带有 stereotype 的类,并将相应的 BeanDefinition 实例注册到 ApplicationContext 中。例如,以下两个类适用于这种自动检测:spring-doc.cadn.net.cn

@Service
public class SimpleMovieLister {

	private MovieFinder movieFinder;

	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}
}
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
@Repository
public class JpaMovieFinder implements MovieFinder {
	// implementation elided for clarity
}
@Repository
class JpaMovieFinder : MovieFinder {
	// implementation elided for clarity
}

要自动检测这些类并注册相应的bean,你需要将 @ComponentScan 添加到你的 @Configuration 类中,其中 basePackages 属性是这两个类的公共父包。 (或者,您可以指定一个用逗号、分号或空格分隔的列表,其中包括每个类的父包。)spring-doc.cadn.net.cn

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
	// ...
}
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
	// ...
}
为了简洁起见,前面的示例可以使用注解的 value 属性(即 @ComponentScan("org.example"))。

以下是对 XML 的另一种使用方式:spring-doc.cadn.net.cn

<?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)。spring-doc.cadn.net.cn

在模块路径(Java 模块系统)上,Spring 的类路径扫描通常能按预期工作。但是,请确保您的组件类在 module-info 描述符中已导出。如果您期望 Spring 调用类中的非公共成员,请确保这些类是“打开的”(即,在 module-info 描述符中使用 opens 声明而不是 exports 声明)。spring-doc.cadn.net.cn

此外,当你使用 component-scan 元素时,AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 都会被隐式包含。这意味着这两个组件会被自动检测并连接在一起 — 而无需在 XML 中提供任何 bean 配置元数据。spring-doc.cadn.net.cn

您可以通过包含值为 falseannotation-config 属性来禁用 AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 的注册。

使用过滤器自定义扫描

默认情况下,使用 @Component@Repository@Service@Controller@Configuration 或自定义注解标注的类是唯一被检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们作为 includeFiltersexcludeFilters 属性添加到 @ComponentScan 注解中(或作为 <context:include-filter /><context:exclude-filter /> 子元素添加到 XML 配置中的 <context:component-scan> 元素中)。每个过滤器元素都需要 typeexpression 属性。 下表描述了过滤选项:spring-doc.cadn.net.cn

表 1. 过滤器类型
过滤器类型 示例表达式 描述

注解(默认)spring-doc.cadn.net.cn

org.example.SomeAnnotationspring-doc.cadn.net.cn

一个注解,应在目标组件的类型级别上存在或元存在。spring-doc.cadn.net.cn

可分配的spring-doc.cadn.net.cn

org.example.SomeClassspring-doc.cadn.net.cn

一个类(或接口),目标组件可以分配给它(继承或实现)。spring-doc.cadn.net.cn

面向方面编程spring-doc.cadn.net.cn

org.example..*Service+spring-doc.cadn.net.cn

一个 AspectJ 类型表达式,用于匹配目标组件。spring-doc.cadn.net.cn

正则表达式spring-doc.cadn.net.cn

org\.example\.Default.*spring-doc.cadn.net.cn

用于匹配目标组件类名的正则表达式。spring-doc.cadn.net.cn

自定义spring-doc.cadn.net.cn

org.example.MyTypeFilterspring-doc.cadn.net.cn

org.springframework.core.type.TypeFilter 接口的自定义实现。spring-doc.cadn.net.cn

以下示例显示了忽略所有 @Repository 注解并改用“占位”仓库的配置:spring-doc.cadn.net.cn

@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:spring-doc.cadn.net.cn

<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注解来实现此操作。下面的示例显示了如何做到这一点:spring-doc.cadn.net.cn

@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 和自定义限定符注解。spring-doc.cadn.net.cn

除了在组件初始化中的作用外,您还可以将 @Lazy 注解放在用 @Autowired@Inject 标记的注入点上。在此上下文中,它会导致注入一个延迟解析代理。然而,这种代理方法有一定的局限性。对于复杂的延迟交互,特别是与可选依赖项结合使用时,我们建议使用 ObjectProvider<MyTargetBean>spring-doc.cadn.net.cn

@Autowired 字段和方法得到支持,如前所述,还增加了对 @Bean 方法的自动连线支持。下面的示例展示了如何操作:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

从Spring框架4.3版本开始,您还可以声明一个类型为 InjectionPoint(或其更具体的子类:DependencyDescriptor)的工厂方法参数,以 访问触发当前bean创建的请求注入点。 请注意,这仅适用于bean实例的实际创建,而不是现有实例的注入。因此,此功能对于原型作用域的bean最有意义。对于其他作用域,工厂方法只会看到在给定作用域中触发新bean实例创建的注入点 (例如,触发延迟单例bean创建的依赖项)。 在这种情况下,您可以谨慎地使用提供的注入点元数据。 下面的示例展示了如何使用InjectionPointspring-doc.cadn.net.cn

@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 处理或其他约束适用。spring-doc.cadn.net.cn

您可以将 @Bean 方法声明为 static,从而无需将包含它们的配置类作为实例来调用。这在定义后处理器 bean(例如类型为 BeanFactoryPostProcessorBeanPostProcessor 的 bean)时特别有意义,因为这些 bean 在容器生命周期的早期就会被初始化,并应在那时避免触发配置的其他部分。spring-doc.cadn.net.cn

对静态@Bean方法的调用永远不会被容器拦截,即使在@Configuration类中也是如此(如本节前面所述),这是由于技术限制:CGLIB子类化只能覆盖非静态方法。结果是对另一个@Bean方法的直接调用具有标准的Java语义,导致工厂方法本身直接返回一个独立实例。spring-doc.cadn.net.cn

Java语言中@Bean方法的可见性对Spring容器中的最终bean定义没有直接影响。您可以根据需要在非@Configuration类中自由声明您的工厂方法,也可以在任何地方声明静态方法。然而,@Bean类中的常规@Configuration方法需要是可覆盖的——也就是说,它们不能被声明为privatefinalspring-doc.cadn.net.cn

@Bean 方法也会在给定组件或配置类的基类上被发现,以及在组件或配置类实现的接口中声明的 Java 8 默认方法。这允许在组合复杂的配置安排时具有很大的灵活性,从 Spring 4.2 开始,甚至可以通过 Java 8 默认方法实现多重继承。spring-doc.cadn.net.cn

最后,一个类可能包含多个 @Bean 方法用于同一个 bean,这是根据运行时可用的依赖关系来安排使用多个工厂方法。这与在其他配置场景中选择“最贪婪”的构造函数或工厂方法的算法相同:在构造时会选择满足依赖项数量最多的变体,类似于容器在多个 @Autowired 构造函数之间进行选择的方式。spring-doc.cadn.net.cn

命名自动检测的组件

当一个组件在扫描过程中被自动检测到时,其bean名称将由该扫描器所知的BeanNameGenerator策略生成。spring-doc.cadn.net.cn

默认情况下,使用 AnnotationBeanNameGenerator。对于 Spring 构造型注解, 如果你通过注解的 value 属性提供了一个名称,则该名称将用作相应 Bean 定义中的名称。当使用以下 JSR-250 和 JSR-330 注解代替 Spring 构造型注解时,此约定同样适用:@jakarta.annotation.ManagedBean@javax.annotation.ManagedBean@jakarta.inject.Named@javax.inject.Namedspring-doc.cadn.net.cn

从 Spring Framework 6.1 开始,用于指定 bean 名称的注解属性名称不再必须为 value。自定义构造型注解可以声明一个不同名称的属性(例如 name),并将该属性用 @AliasFor(annotation = Component.class, attribute = "value") 进行注解。具体示例请参见 ControllerAdvice#name() 的源码声明。spring-doc.cadn.net.cn

从 Spring Framework 6.1 开始,基于约定的构造型名称支持已被弃用,并将在框架的未来版本中移除。因此,自定义构造型注解必须使用 @AliasFor 来为 @Component 中的 value 属性声明一个显式别名。具体示例请参见 Repository#value()ControllerAdvice#name() 的源码声明。spring-doc.cadn.net.cn

如果无法从此类注解或其他任何检测到的组件(例如通过自定义过滤器发现的组件)派生出明确的bean名称,则默认的bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,则名称将分别为 myMovieListermovieFinderImplspring-doc.cadn.net.cn

@Service("myMovieLister")
public class SimpleMovieLister {
	// ...
}
@Service("myMovieLister")
class SimpleMovieLister {
	// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
	// ...
}
@Repository
class MovieFinderImpl : MovieFinder {
	// ...
}

如果您不想依赖默认的Bean命名策略,可以提供一个自定义的Bean命名策略。首先,实现 BeanNameGenerator 接口,并确保包含一个默认的无参构造函数。然后,在配置扫描器时提供完全限定的类名,如下面的注解和Bean定义所示。spring-doc.cadn.net.cn

如果由于多个自动检测到的组件具有相同的非限定类名(即类名相同但位于不同包中)而导致命名冲突,则可能需要配置一个 BeanNameGenerator,使其默认使用完全限定类名作为生成的bean名称。FullyQualifiedAnnotationBeanNameGenerator 位于 org.springframework.context.annotation 包中,可用于此类目的。
@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-doc.cadn.net.cn

为自动检测的组件提供作用域

与Spring管理的组件一样,自动检测的组件的默认和最常见作用域是singleton。但是,有时你需要一个不同的作用域,可以通过@Scope注解来指定。你可以在注解中提供作用域的名称,如下例所示:spring-doc.cadn.net.cn

@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") 进行元注解的自定义注解, 可能还会声明一个自定义的作用域代理模式。spring-doc.cadn.net.cn

为了提供一个自定义的作用域解析策略,而不是依赖基于注解的方法,您可以实现 ScopeMetadataResolver 接口。请确保包含一个默认的无参构造函数。然后在配置扫描器时,可以提供完全限定的类名,如下所示的注解和Bean定义示例所示:
@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属性。三个可能的值是:nointerfacestargetClass。例如,以下配置会产生标准的JDK动态代理:spring-doc.cadn.net.cn

@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 中 qualifiermeta 元素作为 bean 元素的子元素来提供限定符元数据。当依赖类路径扫描来自动检测组件时,可以在候选类上使用类型级别的注解来提供限定符元数据。下面三个示例演示了这一技术:spring-doc.cadn.net.cn

@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
	// ...
}
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
	// ...
}
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
	// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
	// ...
}
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
	// ...
}
与大多数基于注解的替代方案一样,请注意,注解元数据是绑定到类定义本身的,而使用 XML 允许同一类型的多个 bean 提供其限定符元数据的差异,因为该元数据是按实例提供的,而不是按类提供的。