Java注解的底层源码剖析与技术认识

简介: Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。


概述

Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。

注解的底层实现涉及JDK动态代理和反射机制,通过代理对象调用注解的方法会最终调用AnnotationInvocationHandlerinvoke方法,该方法会从memberValues这个Map中查询出对应的值。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Java注解的底层源码,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。

功能点

1. 编写文档

通过代码里标识的元数据生成文档,这是注解最早也是最常见的用途之一。例如,JavaDoc工具可以根据代码中的注解生成HTML格式的API文档。

2. 代码分析

通过代码里标识的元数据对代码进行分析。例如,在编译时检查某些方法是否正确地重写了父类的方法(如@Override注解)。

3. 编译检查

通过代码里标识的元数据让编译器实现基本的编译检查。例如,使用@Deprecated注解标记某个类或方法已经过时,编译器会在编译时发出警告。

4. 运行时处理

注解还可以在运行时通过反射进行处理。例如,Spring框架通过注解实现依赖注入和配置。

背景

在Java 5之前,开发者通常使用XML文件来配置应用程序,指定一些元数据信息。然而,XML配置文件容易出错,而且阅读起来相对繁琐。通过引入注解,开发人员可以将元数据直接嵌入到源代码中,提高了代码的可读性和维护性。

业务点

1. 依赖注入

Spring框架通过注解实现依赖注入,大大简化了配置工作。例如,使用@Autowired注解可以自动装配依赖对象。

2. 配置简化

在Spring等框架中,注解可以用于替代XML配置文件,实现配置简化。例如,使用@Component@Service@Repository等注解标记类,Spring会自动扫描这些类并进行管理。

3. 框架扩展

开发者可以通过自定义注解来扩展框架的功能。例如,在MyBatis框架中,通过自定义注解可以定义SQL语句和映射规则。

底层原理

1. 注解的本质

注解本质是一个继承了java.lang.annotation.Annotation接口的特殊接口。通过@interface关键字定义注解时,编译器会自动生成一个实现了该接口的代理类。这个代理类是由JDK动态代理生成的,它实现了InvocationHandler接口,并重写了invoke方法。

2. 注解的保留策略

@Retention元注解定义了注解的保留策略,即注解在何时有效。RetentionPolicy是一个枚举类型,包含以下三个值:

  • SOURCE:注解仅在源代码中存在,编译器使用后就会丢弃它。
  • CLASS:注解在编译后仍然存在于类文件中,但在运行时虚拟机不保留注解。
  • RUNTIME:注解在运行时仍然可用,运行时虚拟机会保留注解,可以通过反射读取。

3. 注解的目标类型

@Target元注解定义了注解可以应用于哪些Java元素。ElementType是一个枚举类型,包含以下值:

  • TYPE:可以应用于类、接口(包括注解类型)或枚举声明。
  • FIELD:可以应用于字段声明(包括枚举常量)。
  • METHOD:可以应用于方法声明。
  • PARAMETER:可以应用于参数声明。
  • CONSTRUCTOR:可以应用于构造器声明。
  • LOCAL_VARIABLE:可以应用于局部变量声明。
  • ANNOTATION_TYPE:可以应用于注解类型声明。
  • PACKAGE:可以应用于包声明。

4. 注解的继承性

@Inherited元注解表示注解类型被自动继承。如果一个类使用了@Inherited修饰的注解,那么它的子类将自动继承该注解。

5. 注解的文档化

@Documented元注解表示注解类型的信息将被包含在JavaDoc中。

6. 注解的重复性

@Repeatable元注解用于表示某个注解类型可以在同一个元素上多次使用。在Java 8之前,同一个元素上不能多次使用同一个注解,@Repeatable注解解决了这个问题。

7. 注解的读取

在运行时通过反射读取注解信息,需要使用java.lang.reflect包中的相关类。例如,通过Class.getAnnotation(Class<T> annotationClass)方法可以获取指定类型的注解实例。

示例与优缺点

示例1:使用@Override注解

java复制代码
class Parent {
public void test() {
    }
}
class Child extends Parent {
@Override
public void test() {
    }
}

优点

  • 提高代码的可读性,让开发者清楚地知道这个方法是故意覆盖的。
  • 在编译时检测是否正确地覆盖了父类的方法。

缺点

  • 无明显缺点,但需要注意@Override注解只能用于方法上。

示例2:使用@Deprecated注解

java复制代码
@Deprecated
class DeprecatedClass {
// do something
}
class MyClass {
@Deprecated
public void deprecatedMethod() {
// do something
    }
}

优点

  • 提示开发者某个方法或类不再建议使用,鼓励使用新的替代方案。
  • 用于保持向后兼容性,但是表明不鼓励使用。

缺点

  • 仅提供警告信息,不会阻止开发者使用过时的方法或类。

示例3:使用@SuppressWarnings注解

java复制代码
@SuppressWarnings("unchecked")
public void addItems(String item) {
List items = new ArrayList();
    items.add(item);
}

优点

  • 在某些情况下,开发者可能知道一些代码是安全的,可以通过使用该注解来消除相关的警告。
  • 提高代码的可读性,指明为何要忽略某些警告。

缺点

  • 滥用@SuppressWarnings注解可能会隐藏潜在的代码问题。

示例4:自定义注解

java复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "default value";
}
public class AnnotationTest {
@MyAnnotation("test value")
public void testMethod() {
        System.out.println("This is a test method.");
    }
public static void main(String[] args) {
try {
Method method = AnnotationTest.class.getMethod("testMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                System.out.println("Annotation value: " + annotation.value());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

优点

  • 提供了一种灵活的方式来为代码添加元数据。
  • 可以与反射机制结合使用,在运行时处理注解信息。

缺点

  • 自定义注解需要额外的工作来定义和处理。
  • 过度使用自定义注解可能会导致代码变得难以理解和维护。

底层源码剖析

1. 注解的定义

当我们定义一个注解时,如:

java复制代码
public @interface MyAnnotation {
    String value() default "default value";
}

编译器会自动生成一个实现了java.lang.annotation.Annotation接口的代理类。这个代理类通常是一个动态生成的类,其名称类似于$ProxyN(N是一个数字)。

2. 注解的代理类

代理类实现了InvocationHandler接口,并重写了invoke方法。当通过反射调用注解的方法时,实际上会调用这个invoke方法。invoke方法会从memberValues这个Map中查询出对应的值。

3. memberValues的来源

memberValues是一个Map,它存储了注解的属性名和对应的值。这个Map是在注解实例化时由编译器填充的。注解的属性值通常来源于Java常量池,这样可以确保注解的值在编译时就已确定。

4. AnnotationInvocationHandler

AnnotationInvocationHandler是JDK提供的一个内部类,它实现了InvocationHandler接口。当通过反射调用注解的方法时,实际上会调用AnnotationInvocationHandlerinvoke方法。invoke方法会从memberValues中查询出对应的值并返回。

总结

Java注解是一种强大的元数据机制,它提供了在代码中添加额外信息的方式。通过注解,开发者可以实现更灵活的编程和更好的代码管理。注解的底层实现涉及JDK动态代理和反射机制,通过代理对象调用注解的方法会最终调用AnnotationInvocationHandlerinvoke方法。了解注解的底层原理有助于开发者更好地使用和理解注解。

在实际开发中,注解被广泛应用于各种场景,如依赖注入、配置简化、框架扩展等。通过自定义注解,开发者可以根据应用程序的需求创建自己的元数据标记。然而,过度使用自定义注解可能会导致代码变得难以理解和维护,因此需要谨慎使用。

希望本文能够帮助读者对Java注解有一个全新的认识,并能够在实际开发中灵活运用注解来提高代码的可读性、可维护性和灵活性。

相关文章
|
2月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
75 11
|
15天前
|
JavaScript Java 测试技术
基于Java+SpringBoot+Vue实现的车辆充电桩系统设计与实现(系统源码+文档+部署讲解等)
面向大学生毕业选题、开题、任务书、程序设计开发、论文辅导提供一站式服务。主要服务:程序设计开发、代码修改、成品部署、支持定制、论文辅导,助力毕设!
39 6
|
2月前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
1025 1
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
1月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
2月前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
144 13
|
安全 Java
Java并发编程笔记之CopyOnWriteArrayList源码分析
并发包中并发List只有CopyOnWriteArrayList这一个,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行修改操作和元素迭代操作都是在底层创建一个拷贝数组(快照)上进行的,也就是写时拷贝策略。
19565 0
|
Java 安全
Java并发编程笔记之读写锁 ReentrantReadWriteLock 源码分析
我们知道在解决线程安全问题上使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而实际情况下会有写少读多的场景,显然 ReentrantLock 满足不了需求,所以 ReentrantReadWriteLock 应运而生,ReentrantReadWriteLock 采用读写分离,多个线程可以同时获取读锁。
3150 0
|
Java
Java并发编程笔记之FutureTask源码分析
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
4307 0
|
Java 调度 API
Java并发编程笔记之Timer源码分析
timer在JDK里面,是很早的一个API了。具有延时的,并具有周期性的任务,在newScheduledThreadPool出来之前我们一般会用Timer和TimerTask来做,但是Timer存在一些缺陷,为什么这么说呢?   Timer只创建唯一的线程来执行所有Timer任务。
3023 0
|
Java
Java并发编程笔记之Semaphore信号量源码分析
JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那么,Semaphore 的内部实现是怎样的呢?   Semaphore 信号量也是Java 中一个同步容器,与CountDownLatch 和 CyclicBarrier 不同之处在于它内部的计数器是递增的。
4291 0