本章中的大多数示例都使用 XML 来指定生成的配置元数据 每个都在 Spring 容器中。上一节 (Annotation-based Container Configuration) 演示了如何提供大量配置 通过源级注释的元数据。然而,即使在这些例子中,“基地” Bean 定义在 XML 文件中显式定义,而注解仅驱动 依赖注入。本节介绍用于隐式检测 候选组件,通过扫描类路径。候选组件是 与筛选条件匹配,并注册相应的 Bean 定义 容器。这样就不需要使用 XML 来执行 Bean 注册了。相反,你 可以使用注释(例如,)、AspectJ 类型表达式或您自己的 自定义筛选条件,用于选择哪些类注册了 Bean 定义 容器。BeanDefinition@Component

您可以使用 Java 而不是使用 XML 文件来定义 Bean。查看 、 、 和 注释,了解如何执行以下操作的示例 使用这些功能。@Configuration@Bean@Import@DependsOn

@Component和进一步的刻板印象注释

注释是满足存储库(也称为数据访问对象或 DAO)角色或构造型的任何类的标记。用途 此标记是异常的自动转换,如异常转换中所述。@Repository

Spring 提供了进一步的构造型注解:、 和 。 是任何 Spring 管理组件的通用构造型。、 和 是 for 的专用化 更具体的用例(在持久性、服务和表示中 层)。因此,您可以使用 来批注组件类,但是,通过用 、 或 来批注它们,您的类更适合于通过工具进行处理或关联 与方面。例如,这些刻板印象注释是 切入点。、 、 和 可能 在 Spring Framework 的未来版本中携带其他语义。因此,如果你是 在使用或用于服务层之间进行选择是 显然是更好的选择。同样,如前所述,已经 支持作为持久层中自动异常转换的标记。@Component@Service@Controller@Component@Repository@Service@Controller@Component@Component@Repository@Service@Controller@Repository@Service@Controller@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 的注解将作用域名称硬编码为 但仍然允许 自定义 .以下列表显示了批注的定义:@SessionScopesessionproxyModeSessionScope

  • 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
)

然后,您可以在不声明的情况下使用,如下所示:@SessionScopeproxyMode

  • 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 Annotation Programming Model wiki 页面。

自动检测类并注册 Bean 定义

Spring 可以自动检测定型类,并向 .例如,以下两个类 符合此类自动检测条件:BeanDefinitionApplicationContext

  • 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@ConfigurationbasePackages

  • 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:annotation-config><context:component-scan>

类路径包的扫描需要存在相应的目录 类路径中的条目。当您使用 Ant 构建 JAR 时,请确保您没有这样做 激活 JAR 任务的 files-only 开关。此外,类路径目录可能不是 根据某些环境中的安全策略公开,例如,独立应用 JDK 1.7.0_45 及更高版本(需要在清单中设置“Trusted-Library”,请参阅 stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。

在 JDK 9 的模块路径 (Jigsaw) 上,Spring 的类路径扫描通常按预期工作。 但是,请确保在描述符中导出组件类。如果你希望 Spring 调用你的类的非公共成员,请将 确保它们是“打开的”(也就是说,它们在描述符中使用声明而不是声明)。module-infoopensexportsmodule-info

此外,当您使用 component-scan 元素。这意味着这两个组件是自动检测的,并且 连线在一起 — 所有这些都没有在 XML 中提供任何 Bean 配置元数据。AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor

您可以通过包含属性来禁用 和 的注册 值为 。AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessorannotation-configfalse

使用过滤器自定义扫描

默认情况下,用 、 、 、 、 或本身用 注释的自定义注释的类是 唯一检测到的候选组件。但是,您可以修改和扩展此行为 通过应用自定义过滤器。将它们添加为 或属性 注释(或 AS 或元素的子元素 XML 配置)。每个筛选器元素都需要 and 属性。 下表描述了筛选选项:@Component@Repository@Service@Controller@Configuration@ComponentincludeFiltersexcludeFilters@ComponentScan<context:include-filter /><context:exclude-filter /><context:component-scan>typeexpression

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

注释(默认)

org.example.SomeAnnotation

在目标组件的类型级别存在元存在的注释。

可分配的

org.example.SomeClass

目标组件可分配给的类(或接口)(扩展或实现)。

Aspectj

org.example..*Service+

要与目标组件匹配的 AspectJ 类型表达式。

正则表达式

org\.example\.Default.*

要与目标组件的类名匹配的正则表达式表达式。

习惯

org.example.MyTypeFilter

接口的自定义实现。org.springframework.core.type.TypeFilter

以下示例显示了忽略所有批注的配置 并改用“存根”存储库:@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=falseuse-default-filters="false"<component-scan/>@Component@Repository@Service@Controller@RestController@Configuration

在组件中定义 Bean 元数据

Spring 组件还可以向容器提供 Bean 定义元数据。你可以做 这与用于在带注释的类中定义 Bean 元数据的注释相同。以下示例演示如何执行此操作:@Bean@Configuration

  • 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 组件,其方法中包含特定于应用程序的代码。但是,它还贡献了一个具有工厂的 Bean 定义 方法是指方法。注释标识 工厂方法和其他 Bean 定义属性,例如通过 注释。可以指定的其他方法级批注包括 、 和自定义限定符批注。doWork()publicInstance()@Bean@Qualifier@Scope@Lazy

除了其在组件初始化中的作用外,还可以将注释放置在标有 或 的注入点上。在这种情况下, 它导致注入延迟解析代理。但是,这种代理方法 相当有限。用于复杂的懒惰交互,尤其是组合交互 对于可选的依赖项,我们建议改为使用。@Lazy@Autowired@InjectObjectProvider<MyTargetBean>

如前所述,支持自动连线字段和方法,并附加了 支持方法的自动接线。以下示例演示如何执行此操作:@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)
}

该示例将方法参数自动连接到另一个名为 的 Bean 上的属性值。Spring Expression Language 元素 通过表示法定义属性的值。对于注释,表达式解析器被预配置为在以下情况下查找 Bean 名称 解析表达式文本。StringcountryageprivateInstance#{ <expression> }@Value

从 Spring Framework 4.3 开始,您还可以将 Factory 方法参数类型(或其更具体的子类: )声明为 访问触发当前 Bean 创建的请求注入点。 请注意,这仅适用于 Bean 实例的实际创建,而不适用于 注入现有实例。因此,此功能对以下方面最有意义 原型范围的 Bean。对于其他范围,工厂方法只看到 触发在给定范围内创建新 Bean 实例的注入点 (例如,触发创建懒惰单例 Bean 的依赖关系)。 在这种情况下,可以将提供的注入点元数据与语义关怀一起使用。 以下示例演示如何使用:InjectionPointDependencyDescriptorInjectionPoint

  • 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 组件中的方法的处理方式与它们的处理方式不同 春季课程中的对应物。不同之处在于,类不会使用 CGLIB 进行增强以拦截方法和字段的调用。 CGLIB 代理是调用方法或方法中的字段的方法 在类中创建对协作对象的 Bean 元数据引用。 这些方法不是用普通的 Java 语义调用的,而是通过 容器,以提供 Spring 通常的生命周期管理和代理 bean,即使通过对方法的编程调用来引用其他 bean。 相比之下,在普通类中的方法中调用方法或字段具有标准的 Java 语义,没有特殊的 CGLIB 处理或其他 适用的约束条件。@Bean@Configuration@Component@Bean@Configuration@Bean@Bean@Component

您可以将方法声明为 ,允许在没有 将其包含配置类创建为实例。这使得特别 在定义后处理器 Bean(例如,类型为 OR)时,因为此类 Bean 在容器中早期初始化 生命周期,并应避免在此时触发配置的其他部分。@BeanstaticBeanFactoryPostProcessorBeanPostProcessor

由于技术原因,容器永远不会截获对静态方法的调用,即使在类内(如本节前面所述)也不会被拦截 限制:CGLIB 子类只能覆盖非静态方法。因此, 对另一个方法的直接调用具有标准的 Java 语义,因此 在直接从工厂方法本身返回的独立实例中。@Bean@Configuration@Bean

方法的 Java 语言可见性不会对 Spring 容器中生成的 bean 定义。您可以自由声明您的 工厂方法,因为您认为适合非类,也适用于静态 方法在任何地方。但是,课堂上的常规方法需要 可重写 — 也就是说,它们不得声明为 或 。@Bean@Configuration@Bean@Configurationprivatefinal

@Bean方法也可以在给定组件的基类上发现,或者 configuration 类,以及在接口中声明的 Java 8 默认方法 由组件或配置类实现。这允许很多 灵活地组合复杂的配置安排,甚至具有多个 从Spring 4.2开始,可以通过Java 8默认方法进行继承。

最后,单个类可以包含多个相同的方法 bean,作为多种工厂方法的安排,根据可用情况使用 运行时的依赖项。这与选择“最贪婪”的算法相同 其他配置方案中的构造函数或工厂方法:具有 在施工时选择最多数量的可满足依赖关系, 类似于容器如何在多个构造函数之间进行选择。@Bean@Autowired

命名自动检测的组件

当组件在扫描过程中被自动检测时,其 Bean 名称为 由该扫描仪已知的策略生成。BeanNameGenerator

默认情况下,使用 。对于 Spring 构造型注解, 如果通过注释的属性提供名称,则该名称将用作 相应 Bean 定义中的名称。此约定也适用于以下情况: 使用以下 JSR-250 和 JSR-330 注释代替 Spring 构造型 注释: , , , 和 .AnnotationBeanNameGeneratorvalue@jakarta.annotation.ManagedBean@javax.annotation.ManagedBean@jakarta.inject.Named@javax.inject.Named

从 Spring Framework 6.1 开始,用于指定 annotation 属性的名称 Bean 名称不再需要 。自定义构造型注释可以 声明具有不同名称的属性(如 )并注释该属性 跟。查看源代码 声明为一个具体的例子。valuename@AliasFor(annotation = Component.class, attribute = "value")ControllerAdvice#name()

从 Spring Framework 6.1 开始,不推荐使用基于约定的构造型名称 并将在框架的未来版本中删除。因此,自定义刻板印象 批注必须用于声明属性的显式别名 在。有关具体示例,请参阅 和 的源代码声明。@AliasForvalue@ComponentRepository#value()ControllerAdvice#name()

如果显式 Bean 名称无法从此类注释或任何其他注释派生 检测到的组件(例如由自定义过滤器发现的组件),默认 Bean 名称 生成器返回未大写的非限定类名。例如,如果 检测到以下组件类,名称为 和 。myMovieListermovieFinderImpl

  • 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 接口,并确保包含默认的 no-arg 构造函数。然后,提供完整的 配置扫描程序时限定的类名,如以下示例注释所示 和 Bean 定义显示。

如果由于多个自动检测的组件具有 相同的非限定类名(即,具有相同名称但驻留在 不同的软件包),您可能需要配置一个默认为 生成的 Bean 名称的完全限定类名。从 Spring Framework 5.2.3 开始,位于包中可以用于此类目的。BeanNameGeneratorFullyQualifiedAnnotationBeanNameGeneratororg.springframework.context.annotation
  • 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注释仅在 Concrete Bean 类上进行内省(对于 Annotated components) 或工厂方法(用于方法)。与 XML Bean 相比 定义,没有 bean 定义、继承和继承的概念 类级别的层次结构与元数据无关。@Bean

有关 Spring 上下文中特定于 Web 的范围(例如“request”或“session”)的详细信息, 请参阅请求、会话、应用程序和 WebSocket 作用域。与这些作用域的预构建注释一样, 您还可以使用 Spring 的元注释来编写自己的范围注释 方法:例如,一个自定义注解的元注释,带有 可能还声明自定义作用域代理模式。@Scope("prototype")

为范围解析提供自定义策略,而不是依赖于 基于注解的方法,可以实现 ScopeMetadataResolver 接口。请确保包含默认的 no-arg 构造函数。然后,您可以提供 配置扫描程序时的完全限定类名,如下图所示 注解和 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>

使用某些非单例作用域时,可能需要为 作用域对象。推理在 Scoped Bean as Dependencies 中进行了描述。 为此,在组件扫描上提供了作用域代理属性 元素。三个可能的值是:、 和 。例如 以下配置会导致标准 JDK 动态代理:nointerfacestargetClass

  • 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>

提供带有注释的限定符元数据

注释在使用限定符微调基于注释的自动连线中进行了讨论。 该部分中的示例演示了注释和 自定义限定符注释,用于在解析 AutoWire 时提供细粒度控制 候选人。由于这些示例基于 XML Bean 定义,因此限定符 通过使用 XML 中候选 Bean 定义的 or 子元素提供元数据。当依赖类路径扫描时 自动检测组件,您可以提供类型级别的限定符元数据 候选类的注释。以下三个示例证明了这一点 技术:@Qualifier@Qualifierqualifiermetabean

  • 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 的相同类型,以提供其限定符元数据的变体,因为 元数据是按实例提供的,而不是按类提供的。