Skip to content

09-反射、注解与动态代理

对应原始资料:数据库阶段_day05/01_课件/讲义/JavaSE:反射、注解、动态代理.mdday03/06_预习/动态代理扩展.md

本章是 Spring 框架的底层基石,框架之所以「自动 new 对象、自动注入、自动事务」,全靠这三个东西。

一、反射(Reflection)

1. 什么是反射

运行期间动态获取类的信息(字段、方法、构造方法),并能创建对象、调用方法。

2. 获取 Class 对象的三种方式

java
// 1. 类名.class
Class<Student> c1 = Student.class;
// 2. 对象.getClass()
Class<?> c2 = new Student().getClass();
// 3. Class.forName("全限定名")  最常用
Class<?> c3 = Class.forName("com.itheima.Student");

三种方式获取的是同一个 Class 对象(同一类型在 JVM 中只有一份)。

3. 通过反射操作成员

java
Class<?> c = Student.class;

// 创建对象
Object obj = c.getConstructor().newInstance();   // 调无参构造(JDK 9+ 推荐)
Object obj2 = c.getDeclaredConstructor(String.class, int.class)
               .newInstance("张三", 18);          // 调有参构造

// 操作字段
Field f = c.getDeclaredField("name");
f.setAccessible(true);          // 突破 private
f.set(obj, "李四");
Object val = f.get(obj);

// 调用方法
Method m = c.getDeclaredMethod("study", String.class);
m.setAccessible(true);
m.invoke(obj, "数学");           // 相当于 obj.study("数学")

4. 反射的应用

  • 框架底层创建 Bean(Spring IoC)。
  • 注解解析。
  • 读取配置文件,动态加载类(解耦)。
  • 测试框架(JUnit)、ORM(MyBatis 把结果集列映射到字段)。

5. 反射的代价

反射比直接调用慢,性能敏感场景慎用(可缓存 Method/Field 对象)。

二、注解(Annotation)

1. 概念

@接口名 标记程序元素(类、方法、字段、参数等),是给编译器/JVM/框架看的元数据。

2. 内置注解

  • @Override:校验方法重写。
  • @Deprecated:标记已过时。
  • @SuppressWarnings:压制警告。
  • @FunctionalInterface:函数式接口校验。

3. 元注解(修饰注解的注解)

元注解作用
@Target注解能用在哪些地方(TYPE、METHOD、FIELD...)
@Retention保留策略(SOURCE、CLASS、RUNTIME
@Documented出现在 javadoc 中
@Inherited子类可继承

4. 自定义注解

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
    String value() default "";
    int timeout() default 0;
}

属性语法:类型 属性名() default 默认值; 使用:@MyTest(value = "a", timeout = 1000) 若只有一个属性名为 value,可省略 value=

5. 注解解析(结合反射)

java
Method[] ms = clazz.getDeclaredMethods();
for (Method m : ms) {
    if (m.isAnnotationPresent(MyTest.class)) {
        MyTest t = m.getAnnotation(MyTest.class);
        System.out.println(t.value() + " " + t.timeout());
        m.invoke(obj);   // 模拟 JUnit 执行带 @MyTest 的方法
    }
}

重点:注解本身没有功能,必须配合反射的解析代码才有用。框架做的工作就是"扫描 + 解析 + 执行"。

三、动态代理

1. 静态代理的局限

每个被代理类都要手写代理类,类爆炸。

2. JDK 动态代理(基于接口)

通过 Proxy.newProxyInstance 在运行时生成代理类,必须实现接口

java
interface UserService {
    void save();
}

class UserServiceImpl implements UserService {
    public void save() { System.out.println("保存用户"); }
}

UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    (proxyObj, method, args) -> {
        System.out.println("开启事务");
        Object result = method.invoke(target, args);   // 真正执行
        System.out.println("提交事务");
        return result;
    }
);
proxy.save();

3. InvocationHandler 详解

第三个参数 InvocationHandlerinvoke(proxy, method, args) 会在每次调用代理方法时被调用:

  • method 是被调用的方法。
  • args 是参数。
  • 返回值就是代理方法的返回值。

4. CGLIB 动态代理(基于子类)

  • 不要求实现接口,通过生成子类实现代理。
  • 不能代理 final 类/方法。
  • Spring AOP 中,目标类有接口默认用 JDK 动态代理,无接口用 CGLIB(SpringBoot 2.x 起默认 CGLIB)。

5. 动态代理的应用

  • Spring AOP:事务、日志、权限、缓存。
  • MyBatis Mapper:接口无需实现类,代理对象根据 SQL 注解/XML 执行。
  • RPC 远程调用:把本地调用转成网络请求。

四、综合示例:手写 IoC + AOP 思路

1. 定义 @MyComponent 注解(RUNTIME)。
2. 启动时扫描包,找出所有带 @MyComponent 的类。
3. 通过反射 newInstance 创建对象,存入 Map<String, Object>(IoC 容器)。
4. 字段上有 @MyAutowired 注解时,从容器里取出对应对象赋值(依赖注入)。
5. 方法上有 @MyTransactional 时,用 JDK 动态代理包装该 Bean,前后加事务逻辑(AOP)。

这 5 步就是 Spring 框架最核心的工作原理。

练习建议

  1. 用反射读取一个对象的所有字段名和值(通用 toString 工具)。
  2. 自定义 @MyTest,扫描类中带该注解的方法并执行。
  3. 用 JDK 动态代理给任意接口加方法执行耗时日志。
  4. 实现一个迷你 IoC:扫描 @Component,把对象放进 Map,再从 Map 取出。