理解Spring注解编程模型是深入探讨Spring Boot注解驱动及自动装配的重要前提。
Spring Annotation Programming Model

Spring Annotation API

Spring使用AnnotationMetadata接口定义对某个类的注解的抽象访问。它有两种实现,一种是基于Java反射API的StandardAnnotationMetadata;一种是使用ASM的SimpleAnnotationMetadata。基于Java反射API的实现有一个前提,被反射的Class必须被ClassLoader加载,因此ASM的实现适用于不应该加载Class又需要获取Class内注解信息的情况。ASM的实现方式在性能上要优于Java反射API的实现方式。

罗列一下之后会用到的一些方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* ClassMetadata是Class元数据的一些抽象,基本等同于Class类的API;AnnotatedTypeMetadata抽象了
* class(AnnotationMetadata实例)或者method(MethodMetadata实例)对注解的访问。
*
*/
public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {

/**
* 此方法定义在AnnotatedTypeMetadata接口中!此处为了方便写在这里。
* 获取直接标注在被标注元素class、method)上的注解的详细信息。
*/
MergedAnnotations getAnnotations();

/**
* 获取存在于被标注类的所有注解类型的完全限定类名
*/
default Set<String> getAnnotationTypes() {
...
}

/**
* 获取传入的已标注在被标注类上的注解的元注解的完全限定类名。
*/
default Set<String> getMetaAnnotationTypes(String annotationName) {
...
}

/**
* 这个方法是一个创建AnnotationMetadata实例的工厂方法。入参就是被标注类,使用Java反射API实现。
*/
static AnnotationMetadata introspect(Class<?> type) {
return StandardAnnotationMetadata.from(type);
}
}

在Java反射编程模型中,注解之间无法继承,也不能实现接口,不过Java语言默认将所有注解实现Annotation接口,被标注的对象用API AnnotatedElement表达。通过**AnnotatedElement#getAnnotation(Class)**方法返回指定类型的注解对象,获取注解属性需要显式地调用对应的属性方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@TransactionalService(name = "test") // name 属性内容
public class TransactionalServiceAnnotationReflectionBootstrap {

public static void main(String[] args) {
// Class 实现了 AnnotatedElement 接口
AnnotatedElement annotatedElement = TransactionalServiceAnnotationReflectionBootstrap.class;
// 从 AnnotatedElement 获取 TransactionalService
TransactionalService transactionalService = annotatedElement.getAnnotation(TransactionalService.class);
// 显示地调用属性方法 TransactionalService#name() 获取属性
// String nameAttribute = transactionalService.name();
// System.out.println("@TransactionalService.name() = " + nameAttribute);

// 输出 @TransactionalService 属性
// printAnnotationAttribute(transactionalService);
// 获取 transactionalService 的所有的元注解
Set<Annotation> metaAnnotations = getAllMetaAnnotations(transactionalService);
// 输出结果
metaAnnotations.forEach(TransactionalServiceAnnotationReflectionBootstrap::printAnnotationAttribute);
}

private static Set<Annotation> getAllMetaAnnotations(Annotation annotation) {

Annotation[] metaAnnotations = annotation.annotationType().getAnnotations();

if (ObjectUtils.isEmpty(metaAnnotations)) { // 没有找到,返回空集合
return Collections.emptySet();
}
// 获取所有非 Java 标准元注解结合
Set<Annotation> metaAnnotationsSet = Stream.of(metaAnnotations)
// 排除 Java 标准注解,如 @Target,@Documented 等,它们因相互依赖,将导致递归不断
// 通过 java.lang.annotation 包名排除
.filter(metaAnnotation -> !Target.class.getPackage().equals(metaAnnotation.annotationType().getPackage()))
.collect(Collectors.toSet());

// 递归查找元注解的元注解集合
Set<Annotation> metaMetaAnnotationsSet = metaAnnotationsSet.stream()
.map(TransactionalServiceAnnotationReflectionBootstrap::getAllMetaAnnotations)
.collect(HashSet::new, Set::addAll, Set::addAll);

// 添加递归结果
metaMetaAnnotationsSet.add(annotation);
return metaMetaAnnotationsSet;
}


private static void printAnnotationAttribute(Annotation annotation) {
Class<?> annotationType = annotation.annotationType();
// 完全 Java 反射实现(ReflectionUtils 为 Spring 反射工具类)
ReflectionUtils.doWithMethods(annotationType,
method -> System.out.printf("@%s.%s() = %s\n", annotationType.getSimpleName(),
method.getName(), ReflectionUtils.invokeMethod(method, annotation)) // 执行 Method 反射调用
// , method -> method.getParameterCount() == 0); // 选择无参数方法
, method -> !method.getDeclaringClass().equals(Annotation.class));// 选择非 Annotation 方法
}
}

名词解释

元注解(Meta-Annotations)

A meta-annotation is an annotation that is declared on another annotation. An annotation is therefore meta-annotated if it is annotated with another annotation. For example, any annotation that is declared to be documented is meta-annotated with @Documented from the java.lang.annotation package.

所谓元注解是指一个能声明在其他注解上的注解。在Spring的使用场景中,@Component就是标准的元注解。

Spring模式注解(Stereotype Annotations)

A stereotype annotation is an annotation that is used to declare the role that a component plays within the application.

@Component is a generic stereotype for any Spring-managed component. Any component annotated with @Component is a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with @Component is also a candidate for component scanning.

Core Spring provides several stereotype annotations out of the box, including but not limited to: @Component, @Service, @Repository, @Controller, @RestController, and @Configuration. @Repository, @Service

由于Java语言规范规定,Annotation不允许继承,没有类派生子类的能力。因此Spring采用元标注的方式实现注解之间的“派生”。

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}

Spring @Component注解派生性原理

Spring的组件扫描是由ClassPathBeanDefinitionScanner和AnnotationTypeFilter相互配合实现的。ClassPathBeanDefinitionScanner默认在指定根包路径(basePackage)下,将查找所有标注@Component及“派生”注解的BeanDefinition集合,并且默认的过滤规则由AnnotationTypeFilter以及@Component的元信息(ClassMetaData和AnnotationMetaData)共同决定。

默认的includeFilters:

1
this.includeFilters.add(new AnnotationTypeFilter(Component.class));

AnnotationTypeFilter会匹配给定的注解类型(@Component)以及以其作为元注解的注解类型,默认的是@Repository、@Service、@Controller。官方文档只写了这4种,但是Spring Framework3.0以后引入的@Configuration也是一样的。

但是AnnotationTypeFilter只能匹配直接标注@Component作为元注解的注解类型,无法匹配多层级的注解类型(例如:@SpringBootConfiguration)

ClassPathBeanDefinitionScanner#doScan方法使用MetadataReader接口的具体实现SimpleMetadataReader构造SimpleAnnotationMetadataReadingVisitor

Spring Framework从4.x开始,通过AnnotationAttributessReadingVisitor采用递归的方式查找元注解,从而支持多层次派生。

Spring 组合注解(Composed Annotations)

A composed annotation is an annotation that is meta-annotated with one or more annotations with the intent of combining the behavior associated with those meta-annotations into a single custom annotation. For example, an annotation named @TransactionalService that is meta-annotated with Spring’s @Transactional and @Service annotations is a composed annotation that combines the semantics of @Transactional and @Service. @TransactionalService is technically also a custom stereotype annotation.

Spring组合注解和模式注解的元信息抽象是一样的,均由AnnotationMetadata API抽象表达,具体某个注解的“元注解”信息则通过getMetaAnnotationTypes(String)方法查询。

Spring注解属性别名和覆盖(Attribute Aliases and Overrides)

Spring注解元信息抽象AnnotationMetadata提供了getMetaAnnotationTypes(String)方法获取所有元注解类型集合,再结合getAnnotationAttributes(String)方法返回注解所有关联的属性信息,以Map结构储存。AnnotationMetadata有两种实现:基于ASM的AnnotationMetadataReadingVisitor和Java反射API的StandardAnnotationMetadata。

Spring提供两种实现的可能是考虑到不同的使用场景,凡是基于Java反射API的实现必然需要一个大前提,即被反射的Class必须被ClassLoader加载。当Spring应用指定的Java package扫描Spring模式注解时,应用并不需要、更不应该把package下所有的Class悉数加载,所以需要用到ASM的方式直接读取Class文件信息。此外ASM实现方式在性能上要优于反射的实现方式。

AnnotationAttributes是Spring对注解属性的抽象,它直接拓展了LinkedHashMap,既使用Key-Value的数据结构,又确保了其顺序与属性方法声明一致。同时由于它采用Map结构,若不同层次的元注解之间出现同名属性,则必然出现重叠的情况。

An attribute override is an annotation attribute that overrides (or shadows) an annotation attribute in a meta-annotation. Attribute overrides can be categorized as follows.

  1. Implicit Overrides: given attribute A in annotation @One and attribute A in annotation @Two, if @One is meta-annotated with @Two, then attribute A in annotation @One is an implicit override for attribute A in annotation @Two based solely on a naming convention (i.e., both attributes are named A).
  2. Explicit Overrides: if attribute A is declared as an alias for attribute B in a meta-annotation via @AliasFor, then A is an explicit override for B.
  3. Transitive Explicit Overrides: if attribute A in annotation @One is an explicit override for attribute B in annotation @Two and B is an explicit override for attribute C in annotation @Three, then A is a transitive explicit override for C following the law of transitivity.

覆盖采取就近原则,例如@Service的value属性会覆盖@Component的value属性。或者使用@AliasFor进行覆盖。