十三、java反射+注解+动态代理

3 阅读5分钟

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. 反射执行流程(底层理解)

  1. 加载类 → 生成 Class 对象
  2. 获取成员(构造/方法/字段)
  3. 设置可访问(暴力破解)
  4. 动态调用

6. 反射的优缺点

优点

  • 极度灵活,动态性极强
  • 框架基石
  • 可突破封装

缺点

  • 性能比正常调用差很多(方法查找 + 权限检查 + JIT 无法优化)
  • 破坏封装,不安全
  • 代码可读性差
  • 过度使用会让逻辑混乱

7. 企业高频坑

  1. 暴力破解 private 字段导致业务逻辑被破坏
  2. 性能问题:循环中大量反射调用
  3. Class.forName 找不到类 → ClassNotFoundException
  4. 类型转换异常
  5. 空指针异常
  6. 权限异常 IllegalAccessException
  7. 方法签名写错 → 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 动态代理(必须掌握)

核心:

  • 目标类必须实现接口
  • 代理类实现相同接口

步骤

  1. 写接口
  2. 写目标实现类
  3. 写 InvocationHandler
  4. 通过 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 底层)

流程极简还原:

  1. 扫描类 → 反射读取 @Service @Aspect
  2. 判断是否需要增强
  3. 生成 JDK/CGLIB 代理对象
  4. 调用方法 → 进入代理的 invoke
  5. 执行前置通知 → 目标方法 → 后置通知

这就是 AOP


七、企业级高频巨坑(超级重要)

反射坑

  1. 循环大量反射 → 性能暴跌
  2. setAccessible(true) 破坏封装,引发隐藏 BUG
  3. 反射方法签名写错 → NoSuchMethod
  4. 基本类型自动装箱拆箱导致参数不匹配
  5. 泛型擦除导致反射获取泛型困难

注解坑

  1. @Retention 没写 RUNTIME → 反射读不到
  2. 注解属性命名错误,框架不识别
  3. 重复注解、覆盖注解导致配置失效
  4. 注解属性默认值引发逻辑错误

动态代理坑

  1. JDK 代理必须有接口,否则报错
  2. 目标类是 final → CGLIB 失败
  3. 代理对象强转失败 ClassCastException
  4. 事务失效:内部方法调用不经过代理(this.method())
  5. 方法内抛异常被代理吞掉,线上难以排查

八、高频面试题(100% 考)

  1. 什么是反射?Class 对象如何获取?
  2. 反射能访问私有成员吗?如何实现?
  3. 反射优缺点?
  4. 什么是注解?元注解有哪些?
  5. @Retention 三种级别区别?
  6. 如何通过反射读取注解?
  7. JDK 动态代理原理?
  8. JDK 代理 vs CGLIB 区别?
  9. 动态代理能做什么?AOP 原理?
  10. 为什么 Spring AOP 会导致事务失效?