Z
ZHANK
编程在线教程Java 教程注解处理与动态代理
反射与注解

注解处理与动态代理

运行时读取注解、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 远程调用调用本地接口,代理把请求发到远程服务器