Java注解底层竟然是个Map?

案例介绍

案例一:普通注解用法
下面的代码定义了一个注解 @Test,然后在 AnnotationTest 中获取到这个注解,然后打印出它 value() 方法的值。

/* by yours.tools - online tools website : yours.tools/zh/calcthickness.html */
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface Test {  
    String value() default "";  
}

@Test("test")  
public class AnnotationTest {  
    public static void main(String[] args) {  
        Test test = AnnotationTest.class.getAnnotation(Test.class);  
        System.out.println(test.value());  
    }  
}

执行上面的代码,结果输出如下:
image.png

案例二:组合注解用法
下面的代码中尝试从某个类上获取它的注解,然后再从这个注解中获取它上面的注解。组合注解在 Spring 中很常见,比如常用的 @RestController,它实际上就是组合了 @Controller@ResponseBody 这两个注解。

/* by yours.tools - online tools website : yours.tools/zh/calcthickness.html */
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface Test {  
    String value() default "";  
}

@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
@Test("composite")  
public @interface TestComposite {  
}

@TestComposite
public class AnnotationTest {  
    public static void main(String[] args) {  
        TestComposite testComposite = AnnotationTest.class.getAnnotation(TestComposite.class);  
        boolean existInProxyObj = testComposite.getClass().isAnnotationPresent(Test.class);  
        System.out.println("是否在动态代理对象的Class对象上存在:" + existInProxyObj);  
        boolean existInOriginal = testComposite.annotationType().isAnnotationPresent(Test.class);  
        System.out.println("是否在原始的Class对象上存在:" + existInOriginal);  
    }  
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(annotation = Controller.class)
    String value() default "";
}

执行上面的代码,结果输出如下:
image.png
可以看到执行结果在 testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解,而是在 testComposite.annotationType() 返回的 Class 对象上才存在 @Test 这个注解。这个问题的原理是什么呢?

先说下结论:

  • 使用 @interface 定义了一个注解之后编译得到的 class 文件会默认继承 @Annotation 这个接口;
  • 在代码中获取注解时实际上是获取到的是 JDK 为它创建的一个动态代理对象。这个动态代理对象实现了注解定义的方法,当代码中调用注解的方法获取值时,实际上是调用的这个动态代理对象的方法获取到的值;
  • 接口上修饰的注解无法在其实现类上获取到;

源码分析

先来看下 Annotation 接口,它里面定义了 annotationType() 方法,也就是上面组合注解调用的方法,所有注解在编译之后生成的 class 文件都会自动继承该接口。通过查看 @Test 注解编译之后的注解对应的 class 文件,可以看到它继承了 Annotation 接口。代码和执行结果如下:

public interface Annotation {
    boolean equals(Object obj);
    
    int hashCode();
    
    String toString();

    Class<? extends Annotation> annotationType();
}

image.png

当调用 Class 类中 getAnnotation() 方法获取注解对象时,实际上是从 AnnotationDataannotations 属性中获取的,这个属性实际上就是一个 Map 对象,它的 key 是一个注解的 Class 对象,value 是 Annotation 类的一个对象实例,即注解的动态代理对象。代码如下:

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {  
    Objects.requireNonNull(annotationClass);  
    return (A) annotationData().annotations.get(annotationClass);  
}

private static class AnnotationData {  
    // 这里是一个Map结构,
    final Map<Class<? extends Annotation>, Annotation> annotations;  
    final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;  
  
    // Value of classRedefinedCount when we created this AnnotationData instance  
    final int redefinedCount;  
  
    AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,  
                   Map<Class<? extends Annotation>, Annotation> declaredAnnotations,  
                   int redefinedCount) {  
        this.annotations = annotations;  
        this.declaredAnnotations = declaredAnnotations;  
        this.redefinedCount = redefinedCount;  
    }  
}

AnnotationData 对象的创建则是在 annotationData() 方法中完成的,在这个方法里面实际上调用了 createAnnotationData() 方法来创建 AnnotationData 对象,它首先调用了 AnnotationParserparseAnnotations() 方法生成 Map 对象,然后基于它创建了 AnnotationData 对象,实现对注解的动态代理对象的生成就是在 AnnotationParser 中实现的。

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;  
        }  
    }  
}

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) { // lazy construction  
                    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);  
    }  
    // 创建AnnotationData对象
    return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);  
}

AnnotationParser 中最终会调用到它的 parseAnnotation2() 方法,在该方法中从二进制流中解析到注解的类型以及它的定义成员和值的映射关系。然后在 annotationForMap()方法中创建动态代理对象,这个动态代理的方法的调用都会调用到 AnnotationInvocationHandler 中。代码如下:

private static Annotation parseAnnotation2(ByteBuffer buf,  
    ConstantPool constPool,  
    Class<?> container,  
    boolean exceptionOnMissingAnnotationClass,  
    Class<? extends Annotation>[] selectAnnotationClasses) {  
    int typeIndex = buf.getShort() & 0xFFFF;  
    Class<? extends Annotation> annotationClass = null;  
    String sig = "[unknown]";  
    try {  
        try {  
            sig = constPool.getUTF8At(typeIndex);  
            // 这里解析到注解的实际类型
            annotationClass = (Class<? extends Annotation>)parseSig(sig, container);  
        } catch (IllegalArgumentException ex) {  
            // support obsolete early jsr175 format class files  
            annotationClass = (Class<? extends Annotation>)constPool.getClassAt(typeIndex);  
        }  
    } catch (NoClassDefFoundError e) {  
        // 省略代码
    }    

    // 这里解析到成员和值的映射关系
    Map<String, Object> memberValues =  
        new LinkedHashMap<String, Object>(type.memberDefaults());  

    // 省略代码


    return annotationForMap(annotationClass, memberValues);  
}

public static Annotation annotationForMap(
    final Class<? extends Annotation> type, final Map<String, Object> memberValues) {  
    return AccessController.doPrivileged(new PrivilegedAction<Annotation>() {  
        public Annotation run() {  
            return (Annotation) Proxy.newProxyInstance(  
                type.getClassLoader(), new Class<?>[] { type },  
                new AnnotationInvocationHandler(type, memberValues));  
        }});  
}

AnnotationInvocationHandler 实现了 InvocationHandler 接口,而 InvocationHandler 则是实现动态代理的老朋友了。在 AnnotationInvocationHandlerinvoke() 方法中根据当前调用的方法获取名称,然后根据名称从 Map 中获取中对应的值,然后返回,这就是在代码中调用注解的方法能够拿到对应的设置的值的原理,底层的实现就是一个 Map。

class AnnotationInvocationHandler implements InvocationHandler, Serializable {  
    private static final long serialVersionUID = 6182022883658399397L;  
    // 这里的type就是对应的注解类型
    private final Class<? extends Annotation> type;  
    private final Map<String, Object> memberValues;  
  
    public Object invoke(Object proxy, Method method, Object[] args) { 
        // 这里获取到对应方法的名称 
        String member = method.getName();  
        Class<?>[] paramTypes = method.getParameterTypes();  
  
        // Handle Object and Annotation methods  
        if (member.equals("equals") && paramTypes.length == 1 &&  
            paramTypes[0] == Object.class)  
            return equalsImpl(args[0]);  
        if (paramTypes.length != 0)  
            throw new AssertionError("Too many parameters for an annotation method");  
  
        switch(member) {  
        case "toString":  
            return toStringImpl();  
        case "hashCode":  
            return hashCodeImpl();  
        case "annotationType":  
            return type;  
        }  
  
        // 根据方法的名称从Map中映射获取到对应的值返回
        Object result = memberValues.get(member);  
  
        if (result == null)  
            throw new IncompleteAnnotationException(type, member);  
  
        if (result instanceof ExceptionProxy)  
            throw ((ExceptionProxy) result).generateException();  
  
        if (result.getClass().isArray() && Array.getLength(result) != 0)  
            result = cloneArray(result);  
  
        return result;  
    }
}

在 Java 程序启动的时候设置参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 可以让 JDK 把给注解生成的动态代理类的 Class 文件保存到磁盘上。下面是 JDK 给 @TestComposite 注解生成的动态代理类,可以看到它内部有个 InvocationHandler 类型的实例变量,当调用 annotationType() 方法时,实际上代理到了 InvocationHandlerinvoke() 方法中。代码如下:

public final class $Proxy1 extends Proxy implements TestComposite {  
    private static Method m1;  
    private static Method m2;  
    private static Method m3;  
    private static Method m0;  
  
    public $Proxy1(InvocationHandler var1) throws  {  
        super(var1);  
    }  
  
    public final boolean equals(Object var1) throws  {  
        try {  
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});  
        } catch (RuntimeException | Error var3) {  
            throw var3;  
        } catch (Throwable var4) {  
            throw new UndeclaredThrowableException(var4);  
        }  
    }  
  
    public final String toString() throws  {  
        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 Class annotationType() throws  {  
        try {  
            return (Class)super.h.invoke(this, m3, (Object[])null);  
        } catch (RuntimeException | Error var2) {  
            throw var2;  
        } catch (Throwable var3) {  
            throw new UndeclaredThrowableException(var3);  
        }  
    }  
  
    public final int hashCode() throws  {  
        try {  
            return (Integer)super.h.invoke(this, m0, (Object[])null);  
        } catch (RuntimeException | Error var2) {  
            throw var2;  
        } catch (Throwable var3) {  
            throw new UndeclaredThrowableException(var3);  
        }  
    }  
  
    static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));  
            m2 = Class.forName("java.lang.Object").getMethod("toString");  
            m3 = Class.forName("TestComposite").getMethod("annotationType");  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");  
        } catch (NoSuchMethodException var2) {  
            throw new NoSuchMethodError(((Throwable)var2).getMessage());  
        } catch (ClassNotFoundException var3) {  
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());  
        }  
    }  
}

问题解答

基于以上分析来回答一下组合注解那里提出的问题:为什么 testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解,而是在 testComposite.annotationType() 返回的 Class 对象上才存在 @Test 这个注解呢?

testComposite.getClass() 方法返回的 Class 对象实际上就是上面的 $Proxy1.class,它实现了 TestComposite 接口,而 @Test 注解实际上是标记在 @TestComposite 注解上的(本质上是一个接口),根据 Java 的规则,接口上面的注解在其实现类上是获取不到的,因此testComposite.getClass() 返回的 Class 对象上不存在 @Test 这个注解;

testComposite.annotationType() 返回的 Class 对象实际上就是 TestComposite.class 这个 Class 对象,而 @Test 注解实际上是标记在 @TestComposite 注解上的(本质上是一个接口),所以它上面是存在 @Test 这个注解的。

欢迎大家关注我的公众号【javadaydayup】
本系统旨在构建一套面向高等院校的综合性教务管理平台,涵盖学生、教师及教务处三个核心角色的业务需求。系统设计着重于实现教学流程的规范化与数据处理的自动化,以提升日常教学管理工作的效率与准确性。 在面向学生的功能模块中,系统提供了课程选修服务,学生可依据培养方案选择相应课程,并生成个人专属的课表。成绩查询功能支持学生查阅个人各科目成绩,同时系统可自动计算并展示该课程的全班最高分、平均分、最低分以及学生在班级内的成绩排名。 教师端功能主要围绕课程与成绩管理展开。教师可发起课程设置申请,提交包括课程编码、课程名称、学分学时、课程概述在内的新课程信息,亦可对已开设课程的信息进行更新或撤销。在课程管理方面,教师具备录入所授课程期末考试成绩的权限,并可导出选修该课程的学生名单。 教务处作为管理中枢,拥有课程审批与教学统筹两大核心职能。课程设置审批模块负责处理教师提交的课程申请,管理员可根据教学计划与资源情况进行审核批复。教学安排模块则负责全局管控,包括管理所有学生的选课最终结果、生成包含学号、姓名、课程及成绩的正式成绩单,并能基于选课与成绩数据,统计各门课程的实际选课人数、最高分、最低分、平均分以及成绩合格的学生数量。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
<think>我们正在讨论Java注解的实现原理及其在软件开发中的主要作用。根据引用[3],注解是一种标记或标签,用于修饰代码,但本身不影响代码逻辑。注解的使用依赖于开发者通过Java反射来读取和处理。注解可以用于编译器检查、生成代码、日志记录、权限控制等场景。引用[1]提到,注解虽然能提高开发效率和代码优雅性,但需要注意其可能带来的性能问题。 接下来,我将从以下几个方面展开: 1. 注解的基本概念和类型 2. 注解的实现原理(主要涉及Java反射和字节码操作) 3. 注解的主要用途和应用场景 4. 注解的性能考虑 为了更清晰地说明,我会使用代码示例和表格对比。</think>### Java注解工作机制详解:实现原理与核心应用 #### 一、注解的本质与类型 Java注解(Annotation)是一种**元数据标记机制**,本质是特殊的Java接口,其实现原理基于JVM的**元数据存储区**: ```java // 注解定义示例 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PerformanceMonitor { int threshold() default 100; // 注解参数 String unit() default "ms"; } ``` | 注解类型 | 保留策略 | 应用场景 | |----------------|------------------------|------------------------------| | **源码注解** | `RetentionPolicy.SOURCE` | 编译器检查(如`@Override`) | | **编译时注解** | `RetentionPolicy.CLASS` | APT生成代码(如Lombok) | | **运行时注解** | `RetentionPolicy.RUNTIME`| 反射读取(如Spring注解) | > 关键特性:注解本身不包含业务逻辑,其作用完全依赖**处理工具的实现**[^3] --- #### 二、注解底层实现原理 ##### 1. 编译期处理 - **编译器检查**:如`@Override`验证方法签名正确性 - **APT注解处理器**:在编译时扫描注解并生成代码 ```java // 注解处理器示例(生成日志代码) @AutoService(Processor.class) public class LogProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { // 扫描@Loggable注解并生成Logger类 } } ``` ##### 2. 运行时处理(核心机制) 基于**Java反射API**动态读取注解信息: ```java // 反射读取注解示例 Method method = obj.getClass().getMethod("processData"); if (method.isAnnotationPresent(PerformanceMonitor.class)) { PerformanceMonitor monitor = method.getAnnotation(PerformanceMonitor.class); System.out.println("性能阈值: " + monitor.threshold() + monitor.unit()); } ``` 内存结构解析: ``` JVM方法区 │ ├── 类元数据 │ ├── 方法表 │ │ └── methodA → [@Transaction, @Async] // 注解存储位置 │ └── 字段表 │ └── 注解存储区 (独立于代码的元数据区) ``` --- #### 三、核心应用场景 ##### 1. 框架配置驱动 ```java // Spring MVC路由配置 @RestController public class UserController { @GetMapping("/users/{id}") public User getUser(@PathVariable Long id) { ... } } ``` - **优势**:配置与代码解耦,提升可维护性 ##### 2. 自动化代码生成 | 工具 | 注解示例 | 生成内容 | |--------------|--------------------|--------------------| | **Lombok** | `@Getter` `@Setter` | Getter/Setter方法 | | **MapStruct**| `@Mapper` | 对象转换代码 | ##### 3. 运行时行为控制 ```java // 权限控制实现 @Secured(roles = {"ADMIN"}) public void deleteResource(Long id) { // 方法执行前自动进行权限校验 } ``` ##### 4. 数据验证 ```java public class User { @NotBlank(message = "用户名不能为空") @Size(min=3, max=20) private String username; @Email private String email; } ``` --- #### 四、性能优化建议 注解处理可能引入性能损耗: 1. **反射优化**: - 缓存`Class.getAnnotation()`结果 - 使用`AnnotationUtils.findAnnotation()`(Spring优化版) 2. **编译时替代方案**: - 注解处理器(APT)生成代码 - 字节码增强(ASM/Javassist) 3. **慎用场景**: ```mermaid graph LR A[高频调用方法] --> B{是否含运行时注解} B -->|是| C[评估反射开销] C --> D[考虑AOP替代] ``` > 最佳实践:在Spring等框架中,优先使用**编译时注解**(如`@Entity`),运行时注解用于动态配置[^1] --- #### 五、自定义注解开发步骤 1. 定义注解接口 2. 选择保留策略(`@Retention`) 3. 指定作用目标(`@Target`) 4. 实现处理逻辑: ```java // 自定义注解处理器 public class AuditProcessor { public static void process(Object obj) { for (Method method : obj.getClass().getMethods()) { if (method.isAnnotationPresent(AuditLog.class)) { // 织入审计日志逻辑 } } } } ``` --- ### 总结:注解的核心价值 | 维度 | 作用 | 实现机制 | |--------------|--------------------------------|------------------------------| | **声明式编程** | 简化配置(如Spring Boot) | 运行时反射 | | **代码生成** | 减少样板代码(如Lombok) | 注解处理器(APT) | | **契约约束** | 规范行为(如JPA实体映射) | 编译时检查+运行时验证 | | **AOP支撑** | 实现横切关注点(如事务管理) | 动态代理+反射 | > 💡 **设计哲学**:注解通过**约定优于配置**(Convention over Configuration)提升开发效率,但需警惕过度使用导致的**可调试性下降**问题[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值