Spring 注解处理

最后更新: 2025-09-25 03:47:26 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

Spring 将注解用作声明式元数据,用于组件发现、配置、依赖注入、事务、Web 请求映射以及其他框架服务。Java 反射公开原始注解;Spring 则增加了用于组合注解、别名、搜索策略以及无需加载类即可读取元数据的一致模型。

Java 注解语义 #

注解声明本身是一个接口,其隐式超接口是 java.lang.annotation.Annotation:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Audited {
}

当类上直接存在@Audited时,运行时反射可以检查它。@Retention、@Target和@Documented等元注解用于描述注解声明,但不会导致任意的传递式查找。

SOURCE、CLASS和RUNTIME保留策略决定元数据仅保留在源代码中、保留在类文件中,还是可通过反射获得。@Target限制声明位置或类型使用位置。可重复注解通过@Repeatable指定容器;反射和Spring可以公开逻辑上的重复注解,也可以公开其物理容器。

@Inherited是一个范围有限的例外。当注解声明带有@Inherited时,某些Class查找方法可以在超类上找到继承的注解。该规则不适用于接口,也不涵盖方法或字段,并且不同于Spring的组合注解模型。

组合注解 #

Spring会解释放置在其他注解声明上的注解。例如,@Service使用@Component作为元注解:

java
@Component
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

因此,使用@Service注解的类会成为组件候选者,尽管普通反射不会报告该类直接存在@Component。应用程序可以使用相同机制创建领域专用注解:

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
@Transactional(readOnly = true)
public @interface ReadOnlyService {
}

这是一种编程模型,而不是Java语言规则。Spring代码必须执行遍历、循环检测、过滤和属性合成。

合并注解 #

MergedAnnotations是Spring用于直接存在、继承存在和元存在注解的核心查询模型。它的SearchStrategy控制要遍历哪些类型关系:

  • DIRECT仅检查元素上直接声明的注解。
  • INHERITED_ANNOTATIONS遵循Java的@Inherited规则。
  • SUPERCLASS搜索超类层次结构。
  • TYPE_HIERARCHY搜索超类和已实现的接口。

应使用与框架功能匹配的最窄策略。宽泛的层次结构搜索可能产生仅凭Java反射无法公开的元数据。

MergedAnnotation<T>表示一个已发现的注解,以及它的距离、聚合位置、来源和合成属性。合成过程会解析通过@AliasFor声明的Spring别名关系。因此,它可以在组合注解及其元注解之间呈现一致的属性视图。

别名声明必须保持一致。在同一个注解内,互为别名的属性必须具有兼容的返回类型和默认值。跨注解别名必须标识元注解层次结构中可达的属性。

元数据抽象 #

Spring将通用的带注解元素元数据与类级和方法级专用元数据分离:

AnnotAantniootnaMteetdaTdyapteaMetaMdeatthaodMetadata
  • AnnotatedTypeMetadata公开类型和方法共有的注解查询。
  • AnnotationMetadata增加类信息以及对带注解方法的访问。
  • MethodMetadata增加声明类、返回类型和方法属性。

基于反射的实现会检查已加载的类。类读取实现使用字节码元数据,使组件扫描和配置解析等基础设施能够在不初始化类的情况下检查类。避免加载类可以防止静态初始化副作用,并允许在所有可选依赖都可解析之前发现元数据。

对于已加载的类,请使用受支持的内省入口点:

java
AnnotationMetadata metadata =
    AnnotationMetadata.introspect(ExampleConfiguration.class);

boolean configuration =
    metadata.hasAnnotation(Configuration.class.getName());

框架内部则可以从MetadataReaderFactory获取MetadataReader,以读取.class资源。

配置类处理 #

ConfigurationClassParser在处理@Configuration、组件扫描、导入、属性源、条件和@Bean方法时消费类元数据。@Import可以贡献配置类、选择器或注册器。@Conditional实现可以在适用的解析阶段或Bean注册阶段否决配置。 整体流程如下:

  1. 发现候选Bean定义。
  2. 读取类元数据。
  3. 确定每个候选者是否为配置类。
  4. 评估条件,并遍历导入项和组件扫描。
  5. 发现Bean方法并注册生成的Bean定义。

注解解析描述定义;后续的Bean工厂阶段负责实例化和初始化Bean。保持这些阶段彼此分离,可以解释为什么发现注解不会立即执行被注解的行为。

API选择 #

当直接的Java注解语义已经足够时,使用Java反射。实现必须遵循Spring组合注解或别名的基础设施时,使用MergedAnnotations。在容器启动期间必须检查类级和方法级元数据时,使用AnnotationMetadata或MetadataReader,尤其是在加载类尚为时过早的情况下。

应用程序代码通常应使用更高层次的Spring功能,例如组件扫描、事务管理或MVC映射,而不是重新实现注解搜索规则。

评论

在 GitHub 上阅读或评论(发表评论需要登录)