Java 注解详解(含底层原理)_java注解底层实现原理
今天打算系统梳理一下 Java 注解的知识 —— 写这篇文章的初衷,一是帮自己把零散的理解串联成体系,真正内化这部分内容;二也是希望能给同样在学习注解的朋友提供一份清晰的参考。注解这东西看似简单,深究起来其实藏着不少门道,从基础语法到实际应用,值得好好掰扯掰扯。
开篇词:博主在学习注解的过程中,有特别多的疑问。比如:注解到底是什么?用反射获取注解获取的是什么?为了解决心中的疑虑,今天就来好好探究一下《Java注解》!!
目录
一、什么是注解?
二、标记注解的注解——元注解
@Target
@Retention
@Documented
@Inherited
@Repeatable
三、自定义注解
定义注解,使用 @interface 关键字,基本结构:
注解属性的类型限制:
\"value\" 属性的特殊性:
四、反射 API 获取注解信息
4.1 可访问注解信息的核心类
4.2 反射 API 中获取注解的核心方法
4.2.1 Class 类的注解方法
4.2.2 Field 类的注解方法
4.2.3 Method 类的注解方法
4.2.4 Constructor 类的注解方法
4.2.4 Parameter 类的注解方法
4.2.5 AnnotatedType 类的注解方法
五、代码示例
六、底层原理
编译时处理注解的过程:
运行时处理注解的过程:
七、总结
一、什么是注解?
定义注解:注解是Java语言的元数据(在Java层面,所有代码都可视为数据,而注解就是为代码添加特定数据的机制),用于修饰代码元素(类、方法、字段等)。它本身不直接影响程序运行,需要通过工具(编译器、运行时环境、反射API)解析处理,实现编译检查、代码生成、运行时配置等功能。
注解的价值:简化传统的XML配置方式、减少模板代码、提高代码可读性、实现逻辑与配置解耦
与注释的区别:
- 替代传统XML配置方式
- 减少重复代码
- 提升代码可读性
- 实现逻辑与配置分离
注解的本质:注解的本质实际上是一个继承自 java.lang.annotation.Annotation 的接口
注解的处理时机:
- 编译期间:编译器根据注解处理器(一个继承了 javax.annotation.processing.AbstractProcessor 抽象类的类,该类由 javac 编译器进行读取),可以在编译期间对注解进行处理(代码生成)
- 运行期间:通过反射机制获取注解(此时 JVM 用动态代理为该注解生成了一个的代理类),可以在运行是获取注解的信息
二、标记注解的注解——元注解
元注解本质上是给 Java 工具链(编译器、JVM)“看” 的规则说明,用于告诉这些工具:“这个注解应该被如何处理?它能修饰什么?能保留到什么时候”。
Java 定义了 5 个标准的元注解类型且都位于 java.lang.annotation 包下:
@Target
用于指定注解可以应用的Java元素类型,例如类、方法、字段,属性的值由枚举类 java.lang.annotation.ElementType 提供:
@Retention
用于指定注解的保留策略,即注解在哪个阶段保留,属性的值由枚举类 java.lang.annotation.RetentionPolicy 提供:
@Documented
用于标注其他注解,使其在生成 Javadoc 文档时被包含在内。
@Inherited
用于标注其他注解,被标记的注解具有继承性。
当被标记的注解用在一个类上,那么该类的子类可以继承这个被标记的注解。
注意:@Inherited 仅对类级别的注解有效,对方法、字段、参数等其他程序元素的注解无继承效果。
示例:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited // 仅对类上的注解有效,方法和字段上的该注解不会被继承public @interface MyAnnotation { ... }
@Repeatable
用于标记一个注解可以在同一个程序元素上重复使用。
使用时需要指定一个 “容器注解”(该容器注解的属性是当前注解的数组)。
示例:
// 容器注解@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotations { MyAnnotation[] value();}// 重复注解(使用@Repeatable标记)@Repeatable(MyAnnotations.class)@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation { String value();}// 使用示例@MyAnnotation(\"a\")@MyAnnotation(\"b\")public class MyClass {}
三、自定义注解
定义注解,使用 @interface 关键字,基本结构:
import java.lang.annotation.*;// 元注解:指定注解的适用范围(类、方法、字段等)@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})// 元注解:指定注解的生命周期(SOURCE/CLASS/RUNTIME)@Retention(RetentionPolicy.RUNTIME)// 元注解:是否被javadoc文档提取@Documented// 元注解:是否允许子类继承该注解(仅对类注解有效)@Inheritedpublic @interface MyAnnotation { // 注解属性(类似接口方法,但可指定默认值) String value(); // 必选属性(无默认值) int count() default 1; // 可选属性(有默认值) String[] tags() default {}; // 数组类型}
注解属性的类型限制:
- 基本数据类型
- String、Class、枚举
- 其他注解
- 以上类型的数组
- 如果使用其他类型,编译时会直接报错
\"value\" 属性的特殊性:
当注解有且仅有`value`一个属性时,使用时可省略属性名
// 定义仅含value属性的注解public @interface MyAnnot { String value();}// 使用时可简化@MyAnnot(\"test\") // 等价于 @MyAnnot(value = \"test\")public class Demo {}
当注解有多个属性,但仅需指定`value`时,仍可省略属性名
public @interface MyAnnot { String value(); int count() default 1; // 有默认值}// 仅指定value,可省略属性名@MyAnnot(\"test\") // 等价于 @MyAnnot(value = \"test\", count = 1)public class Demo {}
若需指定多个属性,`value`不能省略属性名
public @interface MyAnnot { String value(); int count() default 1;}// 错误写法:多属性时不能省略value=// @MyAnnot(\"test\", count = 2) // 正确写法:必须显式指定value=@MyAnnot(value = \"test\", count = 2)public class Demo {}
四、反射 API 获取注解信息
4.1 可访问注解信息的核心类
java.lang.Classjava.lang.reflect.Fieldjava.lang.reflect.Methodjava.lang.reflect.Constructorjava.lang.reflect.Parameterjava.lang.reflect.AnnotatedType表示被注解的类型(如泛型类型、数组类型等)。
4.2 反射 API 中获取注解的核心方法
4.2.1 Class 类的注解方法
getAnnotation(Class annotationClass)@Inherited元注解标记,则包括从父类继承的注解),不存在则返回null。getAnnotations()getDeclaredAnnotation(Class annotationClass)getDeclaredAnnotations()isAnnotationPresent(Class annotationClass)4.2.2 Field 类的注解方法
getAnnotation(Class annotationClass)getAnnotations()getDeclaredAnnotations()getAnnotations()一致)。isAnnotationPresent(Class annotationClass)4.2.3 Method 类的注解方法
getAnnotation(Class annotationClass)getAnnotations()getDeclaredAnnotations()getAnnotations()(方法注解不可继承)。isAnnotationPresent(Class annotationClass)getParameterAnnotations()getAnnotationsByType(Class annotationClass)4.2.4 Constructor 类的注解方法
getAnnotation(Class annotationClass)getAnnotations()getDeclaredAnnotations()getAnnotations()(构造函数注解不可继承)。getParameterAnnotations()4.2.4 Parameter 类的注解方法
getAnnotation(Class annotationClass)getAnnotations()getDeclaredAnnotations()getAnnotations()(参数注解不可继承)。isAnnotationPresent(Class annotationClass)4.2.5 AnnotatedType 类的注解方法
getAnnotation(Class annotationClass)getAnnotations()getDeclaredAnnotations()getAnnotationsByType(Class annotationClass)Type getType()AnnotatedType 所表示的原始类型(如 List 对应的 Type 对象)。五、代码示例
自定义日志注解,用于标记需要记录日志的方法
import java.lang.annotation.*;/** * 自定义日志注解 * 用于标记需要记录日志的方法 */@Target(ElementType.METHOD) // 仅用于方法@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取@Documented // 生成文档时包含该注解public @interface Log { // 操作描述(value属性,可简化使用) String value() default \"\"; // 是否记录参数 boolean recordParams() default true; // 是否记录返回值 boolean recordResult() default false;}
运行时注解解析器,通过动态代理实现日志记录功能
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;/** * 运行时注解解析器 * 通过动态代理实现日志记录功能 */public class LogRuntimeParser implements InvocationHandler { // 目标对象 private final Object target; public LogRuntimeParser(Object target) { this.target = target; } // 创建代理对象 public static Object createProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LogRuntimeParser(target) ); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 检查方法是否有@Log注解 if (method.isAnnotationPresent(Log.class)) { Log log = method.getAnnotation(Log.class); // 记录开始时间 long startTime = System.currentTimeMillis(); // 打印日志信息 System.out.println(\"\\n===== 日志开始 =====\"); System.out.println(\"操作描述: \" + log.value()); System.out.println(\"方法名称: \" + method.getName()); // 如果需要记录参数 if (log.recordParams() && args != null) { System.out.println(\"参数列表: \" + Arrays.toString(args)); } // 执行目标方法 Object result = method.invoke(target, args); // 如果需要记录返回值 if (log.recordResult()) { System.out.println(\"返回结果: \" + result); } // 记录执行时间 System.out.println(\"执行时间: \" + (System.currentTimeMillis() - startTime) + \"ms\"); System.out.println(\"===== 日志结束 =====\"); return result; } // 没有@Log注解的方法,直接执行 return method.invoke(target, args); }}
用户服务接口,用于动态代理
/** * 用户服务接口,用于动态代理 */public interface UserServiceInterface { boolean login(String username, String password); String register(String username, String email); void updateProfile(String username, String newEmail);}
业务服务类,使用自定义的@Log注解
/** * 业务服务类,使用自定义的@Log注解 */public class UserService { // 使用注解,仅指定value(可简化写法) @Log(\"用户登录\") public boolean login(String username, String password) { System.out.println(\"执行登录逻辑...\"); return \"admin\".equals(username) && \"123456\".equals(password); } // 完整指定注解的所有属性 @Log(value = \"用户注册\", recordParams = true, recordResult = true) public String register(String username, String email) { System.out.println(\"执行注册逻辑...\"); return \"注册成功,用户ID: \" + System.currentTimeMillis(); } // 不使用注解的方法(不会被日志记录) public void updateProfile(String username, String newEmail) { System.out.println(\"执行更新资料逻辑...\"); }}
测试主类
/** * 测试主类 */public class Main { public static void main(String[] args) { // 创建目标对象 UserService userService = new UserService(); // 创建代理对象(用于运行时解析注解) UserServiceInterface proxy = (UserServiceInterface) LogRuntimeParser.createProxy(userService); // 调用被@Log注解的方法 proxy.login(\"admin\", \"123456\"); proxy.register(\"testUser\", \"test@example.com\"); // 调用未被@Log注解的方法 proxy.updateProfile(\"admin\", \"new@example.com\"); }}
六、底层原理
编译时处理注解的过程:
编译时处理注解实际上是通过一个实现了 javax.annotation.processing.AbstractProcessor 抽象类的类来进行处理的,具体的操作可以参考网址:
Java注解处理器实战 | Desperado
运行时处理注解的过程:
先来看一段代码,一段自定义的注解源代码,注解当中包含一个 value() 属性。代码如下:
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author NanJi * @version 1.0 * @annotationName InitMethod * @desc 自定义的初始化方法注解 * @date 2025/8/2: 11:20 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface InitMethod { String value() default \"\";}
经过Java编译器编译之后的代码,再反编译回来是什么样子呢?代码如下:
// 编译后的 InitMethod.classpublic interface InitMethod extends java.lang.annotation.Annotation { public abstract String value(); // 对应注解的value属性 // 编译器自动添加:返回注解类型 Class annotationType();}
可以看到,定义的 InitMethod 注解实际上是一个继承了 java.lang.annotation.Annotation 接口的接口。接下来我们来看一下运行时访问注解发生了什么?
目录结构如下:

同样以 InitMethod 为例:
import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author NanJi * @version 1.0 * @annotationName InitMethod * @desc 自定义的初始化方法注解 * @date 2025/8/2: 11:20 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface InitMethod { String value() default \"\";}
在定义一个 InitDemo 类,用来测试 InitMethod 注解,代码如下:
/** * @author NanJi * @version 1.0 * @className InitDemo * @desc 测试初始化方法注解 * @date 2025/8/2 : 11:23 */public class InitDemo { @InitMethod(\"init...\") public void init() { System.out.println(\"正在初始化...\"); }}
接着定义一个测试类 Main,代码如下:
import java.lang.reflect.*;/** * @author NanJi * @version 1.0 * @className Main * @desc 测试类 * @date 2025/8/2: 11:30 */public class Main { public static void main(String[] args) throws Exception { // 写入代理类文件 System.setProperty(\"jdk.proxy.ProxyGenerator.saveGeneratedFiles\", \"true\"); Class cls = InitDemo.class; // 获取 InitDemo 类的所有方法 Method[] methods = cls.getMethods(); // 遍历所有方法 for (Method method : methods) { // 判断方法是否有 InitMethod 注解 boolean isInitMethod = method.isAnnotationPresent(InitMethod.class); // 如果有 InitMethod 注解,则获取当前的注解代理类 if (isInitMethod) { // 获取 InitMethod 注解的代理类 InitMethod initMethod = method.getAnnotation(InitMethod.class); // 获取代理类的类实例 System.out.println(initMethod.getClass()); // 获取注解的值,实际上是通过代理类调用了注解的 value() 方法 String value = initMethod.value(); // 打印注解的 value() 方法的值 System.out.println(\"InitMethod value: \" + value); // 调用 InitDemo 类的 init() 方法 method.invoke(cls.getConstructor().newInstance()); } } }}
现在我们来运行一下这段程序,看看使用 InitMethod 这个注解发生了什么

可以看到控制台打印了三条语句:
第一条 class jdk.proxy2.$Proxy1 实际上就是生成的代理类。
第二条 InitMethod value: init... 实际上是通过代理类调用了 InitMethod 注解类的 value() 方法。
第三条 正在初始化... 实际上是通过反射调用了 InitDemo 类的 init() 方法。
那生成的代理类在哪里呢?类中的内容又有什么呢?接着往下看:
在运行测试类的 main 方法时,方法中的第一行代码:
// 写入代理类文件System.setProperty(\"jdk.proxy.ProxyGenerator.saveGeneratedFiles\", \"true\");
这行代码的作用就是允许代理类的class文件写入你的磁盘当中,默认是写入到你的项目模块中,会生成如下目录:

接着我们打开 jdk 目录下最后面的 proxy2 目录下的 $Proxy1 类,通过idea打开后查看类中的结构:
package jdk.proxy2;import com.ktjiaoyu.annotation.demo.init.InitMethod;import java.lang.invoke.MethodHandles;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy1 extends Proxy implements InitMethod { private static final Method m0; private static final Method m1; private static final Method m2; private static final Method m3; private static final Method m4; public $Proxy1(InvocationHandler var1) { super(var1); } public final int hashCode() { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final boolean equals(Object var1) { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String value() { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final Class annotationType() { try { return (Class)super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m0 = Class.forName(\"java.lang.Object\").getMethod(\"hashCode\"); m1 = Class.forName(\"java.lang.Object\").getMethod(\"equals\", Class.forName(\"java.lang.Object\")); m2 = Class.forName(\"java.lang.Object\").getMethod(\"toString\"); m3 = Class.forName(\"com.ktjiaoyu.annotation.demo.init.InitMethod\").getMethod(\"value\"); m4 = Class.forName(\"com.ktjiaoyu.annotation.demo.init.InitMethod\").getMethod(\"annotationType\"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(((Throwable)var2).getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(((Throwable)var3).getMessage()); } } private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException { if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) { return MethodHandles.lookup(); } else { throw new IllegalAccessException(var0.toString()); } }}
这个类是通过反射 API 获取注解时由JVM实现的,在这个类的静态代码块中为成员变量 m3 附了值,这个值实际上就是 InitMethod 注解中的 value() 方法。
static { try { m0 = Class.forName(\"java.lang.Object\").getMethod(\"hashCode\"); m1 = Class.forName(\"java.lang.Object\").getMethod(\"equals\", Class.forName(\"java.lang.Object\")); m2 = Class.forName(\"java.lang.Object\").getMethod(\"toString\"); m3 = Class.forName(\"com.ktjiaoyu.annotation.demo.init.InitMethod\").getMethod(\"value\"); m4 = Class.forName(\"com.ktjiaoyu.annotation.demo.init.InitMethod\").getMethod(\"annotationType\"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(((Throwable)var2).getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(((Throwable)var3).getMessage()); } }
在我们调用 value() 方法时,实际上是调用的代理类中的 value() 方法,代码如下:
public final String value() { try { return (String)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
这个 value() 方法就是获取注解 value 属性的值。到这里就清楚了,为什么可以通过反射去拿到注解的值。
七、总结
- 注解的作用是用来描述和标记Java代码当中的元素(类、方法、字段等)
- 元注解是用来标记注解的注解,作用是给 Java 工具链(编译器、JVM、注解处理器)去识别的规则,Java 工具链根据对应的规则进行对应的处理
- 注解的本质实际上是一个实现了 java.lang.annotation.Annotation 接口的接口
- 注解的处理时机发生在编译期,由一个实现了 javax.annotation.processing.AbstractProcessor 抽象类的类来进行操作
- 注解的处理时机发生在运行期,通过Java动态代理,去实现注解当中定义的方法,从而获取对应的值
- 通过 System.setProperty(\"jdk.proxy.ProxyGenerator.saveGeneratedFiles\", \"true\") 方法,设置JVM的系统属性,告诉JVM将生成的动态代理类保存到文件系统中,方便开发者查看和调试。
欧了,到这里我应该解释的差不多啦,我是南极,大胆做自己,活出精彩的人生👊👊👊


