在Spring框架中,注解是一种非常重要的特性,它极大地简化了配置和开发过程。‌Spring注解是Spring框架中用于简化配置和开发的核心机制,主要分为组件管理、依赖注入、配置类、AOP、Web开发等类别‌。

参考原文:

Java注解底层实现原理 - 源码分析

Spring注解的底层实现逻辑

实践案例

Java注解的底层实现逻辑主要包括定义注解、使用注解和通过反射获取注解

定义注解

注解是通过@interface关键字定义的,例如:

1
2
3
4
5
6
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default ""; // 默认值
int[] numbers() default {};
}

这里,@Target@Retention是元注解,分别指定了注解的应用目标和保留策略。ElementType.TYPE表示该注解可以应用于类、接口或枚举上,RetentionPolicy.RUNTIME表示注解在运行时可以通过反射获取‌

使用注解

使用注解时,只需在声明处添加相应的注解即可。例如:

1
2
3
4
5
6
7
@MyAnnotation(value="hello")
public class TestClass {
public static void main(String[] args) {
MyAnnotation annotation = TestClass.class.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // 输出: hello
}
}

这段代码通过反射获取了TestClass类上的MyAnnotation注解,并打印了其value属性的值‌。

反射获取注解

通过反射,可以在运行时获取类、方法、参数等上的注解信息。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Class<?> clazz = TestClass.class;
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value());

public static void main(String[] args) {
// 获取所有methods
Method[] methods = MyAnnotation.class.getClassLoader()
.loadClass(("com.pdai.java.annotation.TestMyAnnotation"))
.getMethods();

// 遍历
for (Method method : methods) {
if (method.isAnnotationPresent(MyAnnotation.class)) { // 方法上是否有MyAnnotation注解
MyAnnotation anno = method.getAnnotation(MyAnnotation.class); // 获取MyAnnotation对象信息
System.out.println(anno.title()); // 访问注解的属性
}
}
}

源码分析注解的底层实现

MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class); 开始分析。

1
2
3
4
5
6
// 所属代码:【java.lang.Class#getAnnotation】
@SuppressWarnings("unchecked")
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
  • annotationData().annotations.get(annotationClass);:调用 annotationData() 方法获取当前类的注解数据,然后从 annotations 映射中获取指定类型的注解实例
  • (A):将获取到的注解实例强制转换为泛型类型 A

获取注解数据集 annotationData

使用 Atomic.casAnnotationData 方法(基于 CAS 乐观锁机制)尝试将新的 AnnotationData 对象更新到当前类中。若更新成功,返回新的 AnnotationData 对象;若失败,循环会继续重试,直到成功为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 所属代码:【java.lang.Class#annotationData】
/* 获取当前类的注解数据对象。如果缓存的注解数据已过期或不存在,则创建新的注解数据对象并尝试更新到缓存中。
* 该方法使用循环重试和 CAS 操作来保证数据的一致性和线程安全。
* @return 当前类的注解数据对象 */
private AnnotationData annotationData() {
while (true) { // 进入无限循环,实现重试机制,直到成功获取有效的注解数据
AnnotationData annotationData = this.annotationData; // 获取当前缓存的注解数据对象
int classRedefinedCount = this.classRedefinedCount; // 获取当前类的重定义计数,该值在类被重新定义时会递增
if (annotationData != null &&
annotationData.redefinedCount == classRedefinedCount) { // 检查缓存的注解数据是否存在且未过期
return annotationData; // 若注解数据有效,直接返回缓存的对象
}

// 若缓存的注解数据为空或已过期,乐观地创建一个新的注解数据对象
AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
// 尝试使用 CAS(Compare-And-Swap)操作将新的注解数据对象更新到缓存中
if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
return newAnnotationData; // 若 CAS 操作成功,说明新的注解数据已成功更新到缓存,返回该对象
}
// 若 CAS 操作失败,说明在创建新对象期间,其他线程已经更新了注解数据,进入下一次循环重试
}
}

构建注解数据集 createAnnotationData

  • 调用 AnnotationParser.parseAnnotations 方法,解析当前类的原始注解数据,将结果存储在 declaredAnnotations 映射中,键为注解类,值为注解实例
  • 处理父类继承注解:父类存在,获取父类的注解数据中的注解映射 superAnnotations。遍历 superAnnotations,对于每个注解,检查其是否使用了@Inherited元注解(通过 AnnotationType.getInstance(annotationClass).isInherited() 判断)。若使用了 @Inherited 元注解,且 annotations 为 null,则进行懒初始化,创建一个 LinkedHashMap 来存储继承的注解。将继承的注解添加到 annotations 映射中
  • 合并注解:若 annotations 仍为 null,说明没有继承的注解,直接将 annotations 指向 declaredAnnotations。若存在继承的注解,将 declaredAnnotations 中的注解添加到 annotations 中,当前类声明的注解会覆盖继承的注解
  • 总结:该方法的核心逻辑是解析当前类的声明注解,若父类存在,获取父类中可继承的注解,将两者合并后创建 AnnotationData 对象。这样可以保证 AnnotationData 对象包含当前类完整的注解信息
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
// 所属代码:【java.lang.Class#createAnnotationData】
private AnnotationData createAnnotationData(int classRedefinedCount) {
// ------------------------ 处理父类继承注解 ----------------------------------
// 解析当前类的原始注解数据,将结果存储在 declaredAnnotations 映射中
Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);

Class<?> superClass = getSuperclass(); // 父类获取
Map<Class<? extends Annotation>, Annotation> annotations = null; // 初始化
if (superClass != null) { // 若父类存在
Map<Class<? extends Annotation>, Annotation> superAnnotations =
superClass.annotationData().annotations; // 获取父类的注解数据中的注解映射
// 遍历
for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
Class<? extends Annotation> annotationClass = e.getKey();
// 判断每个注解是否使用了元注解@Inherited
if (AnnotationType.getInstance(annotationClass).isInherited()) {
if (annotations == null) { // lazy construction (懒初始化)
// 创建一个 LinkedHashMap 来存储继承的注解
annotations = new LinkedHashMap<>((Math.max(
declaredAnnotations.size(),
Math.min(12, declaredAnnotations.size() + superAnnotations.size())
) * 4 + 2) / 3
);
}
// 将继承的注解添加到 annotations 映射中
annotations.put(annotationClass, e.getValue());
}
}
}
// ------------------------ 处理父类继承注解 --------------------------------
// --------------------------- 合并注解 ----------------------------------
if (annotations == null) { // annotations 仍为 null,说明没有继承的注解
// no inherited annotations -> share the Map with declaredAnnotations
annotations = declaredAnnotations;
// --------------------------- 合并注解 ----------------------------------
} else { // 存在继承的注解 添加后,当前类声明的注解会覆盖继承的注解
// at least one inherited annotation -> declared may override inherited
annotations.putAll(declaredAnnotations);
}
return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);
}

获取当前类的原始注解数据 getRawAnnotations

  • native 关键字:在 Java 里,native 关键字用于声明本地方法。本地方法并非用 Java 实现,而是借助其他编程语言(像 C、C++)实现。Java 虚拟机(JVM)会负责加载并调用这些本地方法。通常,本地方法用于和底层系统交互,或实现对性能要求极高的操作。
  • byte[] 返回类型:该方法返回一个字节数组 byte[]。这意味着方法会返回当前类原始注解数据的字节表示形式。原始注解数据是注解在字节码层面的存储形式,可能包含注解类型、注解属性值等信息。
  • 元注解:作用于自定义注解类型的注解类,在JDK 1.5中提供了4个标准的元注解:@Target,@Retention,@Documented,@Inherited,在JDK 1.8中提供了两个元注解 @Repeatable@Native
1
2
// 所属代码:【java.lang.Class#getRawAnnotations】
native byte[] getRawAnnotations();

获取当前类对应的常量池 getConstantPool

getConstantPool 方法是一个本地方法,其作用是获取当前类对应的常量池对象。由于需要直接访问 JVM 内部的数据结构,所以采用本地方法实现。调用该方法后,能得到一个 ConstantPool 对象,进而访问常量池中的常量信息

1
2
// 所属代码:【java.lang.Class#getConstantPool】
native ConstantPool getConstantPool();

解析当前类的原始注解数据 parseAnnotations

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
// 所属代码:【sun.reflect.annotation.AnnotationParser#parseAnnotations】
public static Map<Class<? extends Annotation>, Annotation> parseAnnotations(
byte[] var0, ConstantPool var1, Class<?> var2) {
if (var0 == null) {
return Collections.emptyMap();
} else {
try {
return parseAnnotations2(var0, var1, var2, (Class[])null);
} catch (BufferUnderflowException var4) {
throw new AnnotationFormatError("Unexpected end of annotations.");
} catch (IllegalArgumentException var5) {
throw new AnnotationFormatError(var5);
}
}
}

private static Map<Class<? extends Annotation>, Annotation> parseAnnotations2(
byte[] var0, ConstantPool var1, Class<?> var2, Class<? extends Annotation>[] var3) {
LinkedHashMap var4 = new LinkedHashMap();
ByteBuffer var5 = ByteBuffer.wrap(var0);
int var6 = var5.getShort() & '\uffff';

for(int var7 = 0; var7 < var6; ++var7) {
Annotation var8 = parseAnnotation2(var5, var1, var2, false, var3);
if (var8 != null) {
Class var9 = var8.annotationType();
if (AnnotationType.getInstance(var9).retention() == RetentionPolicy.RUNTIME && var4.put(var9, var8) != null) {
throw new AnnotationFormatError("Duplicate annotation for class: " + var9 + ": " + var8);
}
}
}

return var4;
}

具体会调用到下面的 parseAnnotation2 方法,该方法主要都是解析注解里面的信息,解析出来的值最终会给到我们去创建代理对象用。我们重点关注的是 annotationForMap 这个方法,该方法里面就是通过动态代理来创建注解实例

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 所属代码:【sun.reflect.annotation.AnnotationParser#parseAnnotation2】
/* 解析注解字节数据,生成注解实例。
* @param var0 包含注解数据的字节缓冲区
* @param var1 常量池对象,用于获取注解相关的常量信息
* @param var2 注解所属的类
* @param var3 若为 true,在遇到类型不存在异常时抛出异常;否则跳过该注解
* @param var4 要解析的注解类型数组,若为 null 则解析所有注解
* @return 解析得到的注解实例,若跳过解析则返回 null
*/
private static Annotation parseAnnotation2(
ByteBuffer var0, ConstantPool var1, Class<?> var2, boolean var3, Class<? extends Annotation>[] var4) {

int var5 = var0.getShort() & '\uffff'; // 从字节缓冲区读取注解类型的常量池索引
Object var6 = null; // 用于存储临时对象
String var7 = "[unknown]"; // 注解类型名称,初始为未知

// 尝试获取注解类型的类对象
try {
try {
var7 = var1.getUTF8At(var5); // 从常量池中获取注解类型的名称
var21 = parseSig(var7, var2); // 解析签名,获取注解类型的类对象
} catch (IllegalArgumentException var18) {
var21 = var1.getClassAt(var5); // 若解析签名失败,直接从常量池中获取类对象
}
} catch (NoClassDefFoundError var19) { // 若找不到类定义
if (var3) { // 若 var3 为 true,抛出类型不存在异常
throw new TypeNotPresentException(var7, var19);
}
skipAnnotation(var0, false); // 跳过当前注解的解析
return null;
} catch (TypeNotPresentException var20) { // 若类型不存在
if (var3) { // 若 var3 为 true,抛出类型不存在异常
throw var20;
}
skipAnnotation(var0, false); // 跳过当前注解的解析
return null;
}

// 检查是否只需要解析特定类型的注解
if (var4 != null && !contains(var4, var21)) { // 若当前注解类型不在需要解析的类型列表中,跳过解析
skipAnnotation(var0, false);
return null;
} else {
Object var8 = null; // 用于存储临时对象
try {
var23 = AnnotationType.getInstance(var21); // 获取注解类型的元数据
} catch (IllegalArgumentException var17) { // 若获取元数据失败,跳过当前注解的解析
skipAnnotation(var0, false);
return null;
}
Map var9 = var23.memberTypes(); // 获取注解类型的成员类型映射
LinkedHashMap var10 = new LinkedHashMap(var23.memberDefaults()); // 初始化注解成员值映射,使用默认值填充
int var11 = var0.getShort() & '\uffff'; // 从字节缓冲区读取注解成员的数量
for(int var12 = 0; var12 < var11; ++var12) { // 遍历所有注解成员
int var13 = var0.getShort() & '\uffff'; // 从字节缓冲区读取成员名称的常量池索引
String var14 = var1.getUTF8At(var13); // 从常量池中获取成员名称
Class var15 = (Class)var9.get(var14); // 获取成员的类型
if (var15 == null) { // 若成员类型不存在,跳过该成员值的解析
skipMemberValue(var0);
} else {
Object var16 = parseMemberValue(var15, var0, var1, var2); // 解析成员值
// 若成员值类型不匹配,设置异常代理的成员信息
if (var16 instanceof AnnotationTypeMismatchExceptionProxy) {
((AnnotationTypeMismatchExceptionProxy)var16).setMember((Method)var23.members().get(var14));
}
var10.put(var14, var16); // 将解析得到的成员值放入映射中
}
}
return annotationForMap(var21, var10); // 根据注解类型和成员值映射生成注解实例
}
}

通过java动态代理实例化注解代理对象

  • AccessController.doPrivileged:该方法用于在特权环境下执行特定操作。在 Java 安全模型里,有些操作需要特定权限才能执行,使用 doPrivileged 可确保代码在足够权限下运行
  • annotationForMap 方法利用 Java 的反射和代理机制,根据给定的注解类型和成员值映射,动态创建一个注解实例。借助 AccessController 确保操作在特权环境下执行,最终返回一个实现了指定注解接口的代理对象(此处需要注意,用的代理类 AnnotationInvocationHandler
1
2
3
4
5
6
7
8
9
10
11
12
// 所属代码:【sun.reflect.annotation.AnnotationParser#annotationForMap】
public static Annotation annotationForMap(
final Class<? extends Annotation> var0, final Map<String, Object> var1) {

return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(),
new Class[]{var0},
new AnnotationInvocationHandler(var0, var1));
}
});
}

实现 AnnotationInvocationHandler

  • AnnotationInvocationHandler 类中的 invoke 方法,该类实现了 InvocationHandler 接口。在 Java 动态代理机制里,InvocationHandler 接口的 invoke 方法是核心,当调用代理对象的方法时,实际上会调用 invoke 方法来处理
  • 处理特殊方法调用:
    • 若被调用方法是 toString,调用 toStringImpl 方法生成注解的字符串表示并返回
    • 若被调用方法是 hashCode,调用 hashCodeImpl 方法计算注解的哈希码并返回
    • 若被调用方法是 annotationType,返回注解的类型 this.type
  • 处理注解成员方法调用:
    • 若被调用方法是注解的成员方法,从 memberValues 映射中获取对应的值
    • 若值为 null,说明注解实例缺少该成员的值,抛出 IncompleteAnnotationException 异常
    • 若值是 ExceptionProxy 类型,调用 generateException 方法抛出异常
    • 若值是数组且不为空,调用 cloneArray 方法克隆数组,避免外部修改原始数组,最后返回该值
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
// 所属代码:【sun.reflect.annotation.AnnotationInvocationHandler】
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}

public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}

其他相关问题

注解如何生效

编译期扫描处理,运行期反射处理。编译期扫描处理一般只有Java内置注解会用到,比如@Override修饰的方法,编译器会检查父类是否有相同的方法。大部分自定义的注解,都是在运行期通过反射拿到并处理。

运行期如何获取注解

运行时注解存放在class文件中的attributes属性表
反射获取注解的核心在:java.lang.reflect下的 AnnotatedElement接口,而AnnotatedElement 接口是所有程序元素(ClassMethodConstructor)的父接口。

Spring加与不加注解的区别

  1. 配置方式‌:
    • 不加注解‌:可以通过XML配置文件来定义bean,这种方式更加直观,但需要编写XML文件。
    • 加注解‌:通过使用@Configuration@Bean注解来定义bean,这种方式更加灵活,代码更加简洁,且易于维护和修改。
  2. 管理方式‌:
    • 不加注解‌:需要手动编写XML配置文件,并通过XML配置文件来管理bean的创建和依赖关系。
    • 加注解‌:通过注解来定义bean,Spring框架在启动时会扫描这些注解并自动创建和管理bean,减少了手动配置的工作量。

常用注解及解释

核心注解

  • ‌**@Component**‌:用于把当前类对象存入Spring容器中。@Controller、@Service、@Repository都可以称为@Component,它们分别用于控制层、业务层和数据访问层。
  • ‌**@Autowired**‌:自动按照类型注入Spring容器中的bean。它可以作用在变量、setter方法或构造函数上。
  • ‌**@Qualifier**‌:在按照类型注入的基础上,通过名称进行注入。通常与@Autowired一起使用,用于解决相同类型bean的注入冲突。
  • ‌**@Inject**‌:由JSR-330提供,用法与@Autowired相似,但它是Java标准的一部分,而@Autowired是Spring特有的。
  • ‌**@Resource**‌:由JSR-250提供,按照bean的id进行注入,可以独立使用。
  • ‌**@Primary**‌:当存在多个相同类型的bean时,标记首选的bean进行注入。

Java配置类相关注解

  • ‌**@Configuration**‌:声明当前类为配置类,相当于传统的XML配置文件。
  • ‌**@Bean**‌:注解在方法上,声明当前方法的返回值为一个bean,替代XML中的<bean>标签。
  • ‌**@ComponentScan**‌:用于指定Spring在创建容器时要扫描的包,以找到带有@Component、@Repository、@Service、@Controller等注解的类,并注册为bean。

切面(AOP)相关注解

  • ‌**@Aspect**‌:声明一个切面。
  • ‌**@After**‌:在目标方法执行之后执行。
  • ‌**@Before**‌:在目标方法执行之前执行。
  • ‌**@Around**‌:在目标方法执行之前和之后执行,可以围绕目标方法创建一个“拦截器”。
  • ‌**@PointCut**‌:声明一个切点,即指定哪些方法将被增强。

配置和环境相关注解

  • ‌**@Value**‌:用于注入基本类型和String类型的数据,支持使用Spring EL表达式。
  • ‌**@Profile**‌:指定组件在哪个环境的情况下才能被注册到容器中。
  • ‌**@Conditional**‌:通过实现Condition接口,并重写matches方法,从而决定该bean是否被实例化。

其他常用注解

  • ‌**@Lazy**‌:用于延迟初始化bean,即只有在第一次使用时才会创建和初始化。
  • ‌**@Scope‌:用于指定bean的作用范围,如单例(singleton)或多例(prototype)**。
  • ‌**@EnableAsync**‌:在配置类中通过此注解开启对异步任务的支持。
  • ‌**@Async**‌:在实际执行的bean方法使用该注解来声明其是一个异步任务。
  • ‌**@EnableScheduling**‌:在配置类上使用,开启计划任务的支持。
  • ‌**@Scheduled**‌:用于声明一个定时任务。