09-反射、注解与动态代理
对应原始资料:
数据库阶段_day05/01_课件/讲义/JavaSE:反射、注解、动态代理.md、day03/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 详解
第三个参数 InvocationHandler 的 invoke(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 框架最核心的工作原理。
练习建议
- 用反射读取一个对象的所有字段名和值(通用 toString 工具)。
- 自定义
@MyTest,扫描类中带该注解的方法并执行。 - 用 JDK 动态代理给任意接口加方法执行耗时日志。
- 实现一个迷你 IoC:扫描
@Component,把对象放进 Map,再从 Map 取出。