java基础-注解和反射
注解的定义
Java 注解(Annotation)是 Java 5 引入的一种元数据机制,用于为代码提供额外的信息,这些信息可以被编译器、开发工具或运行时环境读取和处理。注解本身不直接影响代码逻辑,但能通过其他工具增强代码的功能。
注解的本质
注解通过 @interface 关键字定义,编译后会生成 extends java.lang.annotation.Annotation 的接口。
元注解(如 @Retention、@Target)决定了注解的行为:
@Retention:控制注解的生命周期(源码、编译后的类文件、运行时)。
@Target:限制注解可以标记的目标(类、方法、字段等)。
示例
@LogExecutionTime 注解
@Retention(RetentionPolicy.RUNTIME) // 注解保留到运行时
@Target(ElementType.METHOD) // 只能标记方法
public @interface LogExecutionTime {String value() default "默认描述";
}
作用:标记需要记录执行时间的方法。
处理方式:需要通过反射在运行时读取注解,并动态增强方法逻辑(例如使用 AOP 框架)。
@Override 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) // 注解仅在源码阶段保留
public @interface Override {}
作用:告诉编译器检查方法是否重写了父类方法。
处理方式:由编译器直接处理,编译后注解会被丢弃,不会保留到类文件中。
注解的处理方式
注解的“功能”由处理工具实现
注解本身没有逻辑:注解只是标记,其功能依赖外部工具解析并实现。
不同生命周期对应不同处理阶段:
SOURCE 级别(如 @Override):
由编译器直接处理,编译后丢弃。编译器会检查方法是否符合重写规则(方法名、参数、返回值是否与父类一致)。
RUNTIME 级别(如 @LogExecutionTime):
需通过反射在运行时读取注解,结合动态代理或 AOP 框架实现功能(例如记录方法耗时)。
@Override 的实现原理(SOURECE)
编译器内置规则:@Override 的检查逻辑直接内置于 Java 编译器(如 javac),不是通过反射或外部工具。
编译时检查:当编译器发现 @Override 注解时,会检查该方法是否真的重写了父类或接口的方法。若不符合,直接报错。
为什么用 SOURCE 保留策略:
因为检查在编译时完成,不需要保留到类文件或运行时。
如下
手动实现编译时注解流程:
定义注解 → 编写注解处理器(extends AbstractProcessor) → 注册处理器 → 编译时触发处理
这里看到@Transaction在class文件中还存在,意思是编译完了还存在
@Transactional 的事务管理由 Spring AOP 自动实现。
手动实现如下:
@LogExecutionTime 的实现原理(RUNTIME)
运行时反射读取:通过反射 API(如 Method.getAnnotation())获取注解。
动态增强代码:结合动态代理或 AOP 框架(如 AspectJ、Spring AOP)在方法执行前后插入记录时间的逻辑。
示例代码:
public class LogAspect {public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();LogExecutionTime annotation = method.getAnnotation(LogExecutionTime.class);if (annotation != null) {long start = System.currentTimeMillis();Object result = joinPoint.proceed();long end = System.currentTimeMillis();System.out.println(annotation.value() + ",耗时:" + (end - start) + "ms");return result;}return joinPoint.proceed();}
}
总结
1.注解是元数据:定义时不包含逻辑,功能由外部工具实现。
2.元注解控制行为:@Retention 和 @Target 是注解的“元数据”。
3.处理方式分阶段:
编译时:由编译器或注解处理器处理(如 @Override、Lombok)。
运行时:通过反射和框架处理(如 Spring 的 @Autowired)。
内置注解的特殊性:如 @Override 由编译器直接支持,无需开发者编写处理代码。
反射
反射(Reflection) 是 Java 提供的一种在运行时(Runtime)动态获取和操作类、方法、字段、注解等信息的机制。通过反射,可以在程序运行期间直接操作类的结构(如创建对象、调用方法、修改字段值),而无需在编译时确定具体的类或方法名称。简单来说,反射是让代码能够“自我感知”并动态修改自身行为的能力。
例如上面运行时候注解就是靠反射来实现
反射的核心功能
动态获取类的信息
通过类的全限定名(如 java.lang.String)加载类,并获取其方法、字段、构造器、注解等元信息。
Class<?> clazz = Class.forName("com.example.User");
获取类对象的方式
方法 | 特点 | 适用场景 |
---|---|---|
User.class | 静态获取,无需实例化 | 已知类名,直接引用 |
user.getClass() | 动态获取,依赖对象实例 | 已有对象实例时 |
Class.forName() | 动态加载,触发类初始化 | 类名在运行时确定(如配置文件) |
ClassLoader.loadClass | 动态加载,不触发初始化(默认行为) | 自定义类加载逻辑 |
动态创建对象
通过类的无参或有参构造器实例化对象。
Object obj = clazz.newInstance(); // 无参构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("张三", 25); // 有参构造器
动态调用方法
通过方法名称和参数类型调用方法(包括私有方法)。
Method method = clazz.getMethod("setName", String.class);
method.invoke(obj, "李四"); // 调用 setName("李四")
动态访问或修改字段
访问或修改对象的字段值(包括私有字段)。
Field field = clazz.getDeclaredField("name");
field.setAccessible(true); // 解除私有字段的访问限制
field.set(obj, "王五"); // 修改字段值
动态读取注解信息
获取类、方法、字段上的注解,并根据注解内容执行逻辑。
if (clazz.isAnnotationPresent(MyAnnotation.class)) {MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);System.out.println(annotation.value());
}
反射的使用场景
- 框架开发
依赖注入:如 Spring 的 @Autowired、@Resource。
ORM 映射:如 Hibernate 通过反射将数据库字段映射到实体类。
API 路由:如 Spring MVC 根据 @RequestMapping 动态调用 Controller 方法。
- 动态代理与 AOP
动态生成代理对象:如 JDK 动态代理基于接口,CGLIB 基于类。
AOP 切面编程:通过反射拦截方法调用,插入日志、事务等逻辑。
- 通用工具类
JSON 序列化/反序列化:如 Jackson 通过反射解析对象字段。
单元测试工具:如 JUnit 通过反射调用 @Test 标记的方法。
-
插件化架构
动态加载类:如通过 ClassLoader 加载外部 JAR 中的插件。 -
注解驱动开发
运行时注解处理:如结合 @Transactional 实现事务管理。
示例(动态获取类型)
protected T getData(MessageDTO dto) {try {ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();//子类获取继承的父类的泛型@SuppressWarnings("unchecked")Class<T> clazz = (Class<T>) parameterizedType.getActualTypeArguments()[0];//获取泛型具体类型if(clazz.isInstance(String.class)) {return (T) dto.getDataJson();}return JSON.parseObject(dto.getDataJson(), clazz); //转换相应类型}catch (Exception e) {log.error("异步执行流程 转换数据异常 数据:{}", dto, e);throw new RuntimeException("数据转换异常", e);}}
反射的作用范围
反射对实例对象的修改
可修改的范围
实例字段:
通过反射可读取或修改实例的字段值(包括私有字段),例如:
Field field = obj.getClass().getDeclaredField("fieldName");
field.setAccessible(true);
field.set(obj, newValue); // 直接修改实例的字段值
实例方法:
可调用实例的私有方法,例如:
Method method = obj.getClass().getDeclaredMethod("methodName");
method.setAccessible(true);
method.invoke(obj, args); // 调用实例的私有方法
生命周期限制
修改仅对当前实例有效,其他实例不受影响。
若实例被垃圾回收,修改也随之消失。
反射对类对象的修改
类加载前的修改
修改类的结构(如新增字段、方法):需在类加载前通过字节码增强工具(如 ASM、Byte Buddy)修改类的字节码,再通过自定义 ClassLoader 加载修改后的类。
示例场景:在 Spring Boot 中,通过 @Configuration 动态注册 Bean 时,底层依赖类加载前的结构修改。
类加载后的限制
无法修改类的结构:标准反射 API 无法在类加载后新增/删除字段或方法。
可修改的类属性:
静态字段:通过反射可直接修改静态字段的值(包括 private static 字段),例如:
Field staticField = MyClass.class.getDeclaredField("staticField");
staticField.setAccessible(true);
staticField.set(null, newValue); // 修改静态字段
静态代码块:无法修改已执行的静态代码块逻辑,但可通过反射调用静态方法间接影响行为。
总结
反射是 Java 在运行时动态获取类的元信息(如方法、字段、注解)并操作对象的能力,突破编译时类型限制,常用于框架开发(如 Spring 依赖注入、Hibernate ORM 映射)和动态代理等场景