注解处理与动态代理
运行时读取注解、Proxy 动态代理实现 AOP
注解处理与动态代理
反射能"读"注解,动态代理能"拦截"方法调用。两者结合是 Spring AOP 的底层原理——在不修改原代码的情况下,给方法加上日志、事务、权限控制。
学完本章你将: 运行时读取注解、用 Proxy 创建动态代理。
运行时读取注解
定义注解只是第一步。要让它生效,需要在运行时通过反射读取并执行相应的逻辑。
java
// 1. 定义注解:保留到运行时
@Target(ElementType.METHOD) // 只能用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留(关键!)
@interface LogExecution {
String value() default "";
}
// 2. 使用注解
class UserService {
@LogExecution("用户注册")
public void register(String name) {
System.out.println("注册: " + name);
}
}
// 3. 运行时读取注解并执行逻辑
Method method = UserService.class.getMethod("register", String.class);
if (method.isAnnotationPresent(LogExecution.class)) {
LogExecution ann = method.getAnnotation(LogExecution.class);
System.out.println("执行前日志: " + ann.value()); // "用户注册"
}
💡 这就是 JUnit 的工作原理:
@Test注解的 Retention 是 RUNTIME,JUnit 扫描所有方法,找到带@Test的就执行,带@BeforeEach的就在每个测试前执行。
动态代理 —— 拦截方法调用
想象一个场景:你有一个 UserService,现在要给它的每个方法加上日志——但不想改原代码。动态代理可以在运行时生成一个"替身"对象,所有方法调用先经过你的拦截器,再转给原对象。
java
// 原对象
interface Greeting {
String sayHello(String name);
}
class GreetingImpl implements Greeting {
public String sayHello(String name) { return "Hello, " + name + "!"; }
}
// 创建动态代理
Greeting target = new GreetingImpl();
Greeting proxy = (Greeting) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
new Class<?>[] { Greeting.class }, // 要实现哪些接口
(obj, method, args) -> { // InvocationHandler:每次调用都经过这里
System.out.println("【调用前】" + method.getName());
Object result = method.invoke(target, args); // 调原对象
System.out.println("【调用后】返回:" + result);
return result; // 可以修改返回值!
}
);
// 调用 proxy 和调用 target 一样——但中间多了拦截逻辑
System.out.println(proxy.sayHello("Alice"));
// 输出:
// 【调用前】sayHello
// 【调用后】返回:Hello, Alice!
// Hello, Alice!
发生了什么? proxy 是一个运行时动态生成的类($Proxy0),它实现了 Greeting 接口。调用 proxy.sayHello() → 进入你的 InvocationHandler → 你决定要不要调原方法、调之前/之后做什么。
实际应用
| 场景 | 怎么用动态代理 |
|---|---|
| AOP 日志 | 拦截所有 Service 方法,自动打印入参和返回值 |
| 事务管理 | 方法前开启事务,方法后提交,异常回滚 |
| 权限检查 | 拦截方法,检查当前用户是否有权限 |
| MyBatis Mapper | 你只写接口,MyBatis 用动态代理生成实现类 |
| RPC 远程调用 | 调用本地接口,代理把请求发到远程服务器 |