Java 反射 + 注解 + 动态代理
一、反射(Reflection)—— 框架底层灵魂
1. 什么是反射
在运行时动态获取类的完整信息(构造、方法、字段、接口、父类、注解),并能动态调用方法、操作属性。
一句话: 运行时,解剖类,随便玩。
2. 反射的核心作用
- 框架底层必备(Spring IOC/AOP、MyBatis、Tomcat、Dubbo)
- 动态加载类、动态创建对象
- 突破封装,访问私有成员
- 通用工具类、ORM 映射、配置驱动
3. Class 对象——反射的入口
一个类在 JVM 中只有一个 Class 对象。
获取 Class 对象的 3 种方式
// 1. 类名.class
Class<User> clazz = User.class;
// 2. 对象.getClass()
User user = new User();
Class<? extends User> clazz = user.getClass();
// 3. Class.forName(全类名)【最常用、最动态】
Class<?> clazz = Class.forName("com.example.User");
4. 反射可以做什么(完整 API)
4.1 获取类信息
clazz.getName(); // 全类名
clazz.getSimpleName(); // 简名
clazz.getPackage(); // 包
clazz.getSuperclass(); // 父类
clazz.getInterfaces(); // 接口
clazz.getModifiers(); // 修饰符
Modifier.isPublic(...)
Modifier.isStatic(...)
4.2 构造方法——动态创建对象
// 获取无参构造
Constructor<User> constructor = clazz.getConstructor();
User user = constructor.newInstance();
// 获取有参构造
Constructor<User> con = clazz.getConstructor(String.class, int.class);
User user = con.newInstance("张三", 20);
// 获取私有构造
Constructor<User> privateCon = clazz.getDeclaredConstructor();
privateCon.setAccessible(true); // 暴力破解
User user = privateCon.newInstance();
4.3 字段 Field——动态操作属性
// 获取 public 字段
Field nameField = clazz.getField("name");
// 获取任意字段(包括 private)【常用】
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
// 赋值/取值
ageField.set(user, 20);
int age = (int) ageField.get(user);
4.4 方法 Method——动态执行
// 获取 public 方法
Method method = clazz.getMethod("setName", String.class);
// 获取任意方法(private)
Method method = clazz.getDeclaredMethod("privateMethod");
method.setAccessible(true);
// 执行方法
method.invoke(user); // 无参
method.invoke(user, "参数"); // 有参
4.5 获取注解(后面讲)
clazz.getAnnotation(MyAnno.class);
method.getAnnotation(MyAnno.class);
5. 反射执行流程(底层理解)
- 加载类 → 生成 Class 对象
- 获取成员(构造/方法/字段)
- 设置可访问(暴力破解)
- 动态调用
6. 反射的优缺点
优点
- 极度灵活,动态性极强
- 框架基石
- 可突破封装
缺点
- 性能比正常调用差很多(方法查找 + 权限检查 + JIT 无法优化)
- 破坏封装,不安全
- 代码可读性差
- 过度使用会让逻辑混乱
7. 企业高频坑
- 暴力破解 private 字段导致业务逻辑被破坏
- 性能问题:循环中大量反射调用
- Class.forName 找不到类 → ClassNotFoundException
- 类型转换异常
- 空指针异常
- 权限异常 IllegalAccessException
- 方法签名写错 → NoSuchMethodException
二、注解(Annotation)—— 元数据、配置、标记
1. 什么是注解
相当于给类/方法/字段贴标签,JVM / 框架可以读取标签做逻辑。
不是注释,是可被程序读取的配置信息。
2. 内置常用注解(必须会)
@Override // 重写
@Deprecated // 过时
@SuppressWarnings // 忽略警告
@FunctionalInterface // 函数式接口
3. 元注解(修饰注解的注解)
3.1 @Target
指定注解能用在哪里:
- TYPE:类/接口
- FIELD:字段
- METHOD:方法
- PARAMETER:参数
- CONSTRUCTOR:构造
- LOCAL_VARIABLE:局部变量
3.2 @Retention
生命周期(最重要)
- SOURCE:只在源码,编译丢弃
- CLASS:编译进 class,运行时不加载(默认)
- RUNTIME:运行时存在 → 可反射读取(框架必备)
3.3 @Documented
生成 javadoc 时包含
3.4 @Inherited
子类自动继承父类的注解
4. 自定义注解(语法固定)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
// 注解属性
String value() default "";
int age() default 0;
String[] names() default {};
}
使用:
@MyAnno(value = "test", age = 20)
public class User {}
5. 反射读取注解(框架核心)
Class<User> clazz = User.class;
MyAnno anno = clazz.getAnnotation(MyAnno.class);
String value = anno.value();
int age = anno.age();
方法/字段上同理:
Method method = clazz.getMethod("xxx");
MyAnno a = method.getAnnotation(MyAnno.class);
6. 注解本质
本质是接口,继承自 Annotation。
JVM 动态生成代理类实现该接口。
7. 企业真实用途
- Spring:
@Controller@Service@Autowired - MyBatis:
@Mapper@Select - Lombok:
@Data@Slf4j(编译期生效) - 全局异常:
@RestControllerAdvice - 权限校验、日志、缓存、事务 AOP
三、动态代理(AOP 底层核心)
1. 什么是代理
不改动目标类,对方法进行增强。
增强场景:
- 日志
- 事务
- 权限
- 缓存
- 性能监控
- 异常捕获
2. Java 两种动态代理
2.1 JDK 动态代理(基于接口)
2.2 CGLIB 动态代理(基于类继承)
四、JDK 动态代理(必须掌握)
核心:
- 目标类必须实现接口
- 代理类实现相同接口
步骤
- 写接口
- 写目标实现类
- 写 InvocationHandler
- 通过 Proxy.newProxyInstance 创建代理对象
示例代码
接口
public interface UserService {
void add();
}
目标类
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加用户");
}
}
增强处理器
public class MyHandler implements InvocationHandler {
private Object target;
public MyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强——日志");
Object result = method.invoke(target, args);
System.out.println("后置增强——事务提交");
return result;
}
}
获取代理
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MyHandler(target)
);
proxy.add(); // 走代理
执行结果
前置增强——日志
添加用户
后置增强——事务提交
五、CGLIB 动态代理(无需接口)
原理
- 继承目标类,重写方法
- 运行时生成子类
Spring AOP 规则
- 目标有接口 → JDK 代理
- 目标无接口 → CGLIB
企业注意
- CGLIB 不能代理 final 类、final 方法
- SpringBoot 2.0 后默认推荐 CGLIB
六、反射 + 注解 + 动态代理 综合作用(Spring AOP 底层)
流程极简还原:
- 扫描类 → 反射读取
@Service@Aspect - 判断是否需要增强
- 生成 JDK/CGLIB 代理对象
- 调用方法 → 进入代理的 invoke
- 执行前置通知 → 目标方法 → 后置通知
这就是 AOP。
七、企业级高频巨坑(超级重要)
反射坑
- 循环大量反射 → 性能暴跌
- setAccessible(true) 破坏封装,引发隐藏 BUG
- 反射方法签名写错 → NoSuchMethod
- 基本类型自动装箱拆箱导致参数不匹配
- 泛型擦除导致反射获取泛型困难
注解坑
- @Retention 没写 RUNTIME → 反射读不到
- 注解属性命名错误,框架不识别
- 重复注解、覆盖注解导致配置失效
- 注解属性默认值引发逻辑错误
动态代理坑
- JDK 代理必须有接口,否则报错
- 目标类是 final → CGLIB 失败
- 代理对象强转失败 ClassCastException
- 事务失效:内部方法调用不经过代理(this.method())
- 方法内抛异常被代理吞掉,线上难以排查
八、高频面试题(100% 考)
- 什么是反射?Class 对象如何获取?
- 反射能访问私有成员吗?如何实现?
- 反射优缺点?
- 什么是注解?元注解有哪些?
- @Retention 三种级别区别?
- 如何通过反射读取注解?
- JDK 动态代理原理?
- JDK 代理 vs CGLIB 区别?
- 动态代理能做什么?AOP 原理?
- 为什么 Spring AOP 会导致事务失效?