在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()); } }
|
这段代码通过反射获取了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) { Method[] methods = MyAnnotation.class.getClassLoader() .loadClass(("com.pdai.java.annotation.TestMyAnnotation")) .getMethods();
for (Method method : methods) { if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation anno = method.getAnnotation(MyAnnotation.class); System.out.println(anno.title()); } } }
|
源码分析注解的底层实现
从 MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class); 开始分析。
1 2 3 4 5 6
| @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
|
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); if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) { return newAnnotationData; } } }
|
构建注解数据集 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
| private AnnotationData createAnnotationData(int classRedefinedCount) { 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(); if (AnnotationType.getInstance(annotationClass).isInherited()) { if (annotations == null) { annotations = new LinkedHashMap<>((Math.max( declaredAnnotations.size(), Math.min(12, declaredAnnotations.size() + superAnnotations.size()) ) * 4 + 2) / 3 ); } annotations.put(annotationClass, e.getValue()); } } } if (annotations == null) { annotations = declaredAnnotations; } else { 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
| native byte[] getRawAnnotations();
|
获取当前类对应的常量池 getConstantPool
getConstantPool 方法是一个本地方法,其作用是获取当前类对应的常量池对象。由于需要直接访问 JVM 内部的数据结构,所以采用本地方法实现。调用该方法后,能得到一个 ConstantPool 对象,进而访问常量池中的常量信息
1 2
| 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
| 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
|
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) { throw new TypeNotPresentException(var7, var19); } skipAnnotation(var0, false); return null; } catch (TypeNotPresentException var20) { if (var3) { 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
| 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
| 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 接口是所有程序元素(Class、Method和Constructor)的父接口。
Spring加与不加注解的区别
- 配置方式:
- 不加注解:可以通过XML配置文件来定义bean,这种方式更加直观,但需要编写XML文件。
- 加注解:通过使用
@Configuration和@Bean注解来定义bean,这种方式更加灵活,代码更加简洁,且易于维护和修改。
- 管理方式:
- 不加注解:需要手动编写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**:用于声明一个定时任务。