谈谈Spring的动态代理

Spring框架中的动态代理是实现AOP(面向切面编程)的核心技术之一,它允许在运行时动态创建代理对象,从而实现对目标对象的增强。

一、动态代理概述

动态代理是一种在运行时动态创建代理对象的技术,无需为每个被代理类编写具体的代理类代码。Spring主要使用两种动态代理方式:

  1. ​JDK动态代理​​:基于接口的代理
  2. ​CGLIB代理​​:基于类继承的代理

1.1现实开发中的痛点场景

场景1:日志记录需求

假设你有50个Service类,现在需要为每个方法添加执行日志:

  • 传统做法:需要修改50个类,添加重复的日志代码
  • 使用动态代理:只需编写1个日志切面,统一处理所有方法

场景2:权限控制

系统有100个API接口需要添加权限检查:

  • 传统做法:在每个方法开始处添加if(!hasPermission()) return;
  • 动态代理方案:统一权限拦截器,配置需要拦截的方法

1.2动态代理的核心价值

1. 开闭原则的最佳实践

  • ​不修改​​原有代码(对扩展开放)
  • ​不影响​​现有功能(对修改封闭)
  • 符合SOLID设计原则

2. 解决"交叉关注点"问题

横切关注点(Cross-cutting concerns)的处理:

[传统写法]
Controller -> Service1(业务+日志+事务+安全)
Controller -> Service2(业务+日志+事务+安全)
...

[代理方案]
Controller -> Proxy -> (日志+事务+安全) -> 纯净的Service

3. 配置优于编码

可以通过配置决定哪些类/方法需要增强:

// 只需配置切点表达式,不需要修改业务代码
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) {
    // 统一的日志处理
}

 1.3传统硬编码方式 vs 动态代理

维度传统方式动态代理方案
代码侵入性需要修改业务类不修改业务类
维护成本每个类单独维护集中维护
一致性容易出现不一致实现统一处理保证一致
功能调整需要修改所有相关类只需修改代理逻辑
新功能添加需要修改大量现有代码添加新切面即可
代码量随业务类增长线性增加固定代码量+配置

二、JDK动态代理

工作原理

JDK动态代理通过java.lang.reflect.Proxy类和InvocationHandler接口实现,要求被代理的类必须实现至少一个接口。

实现步骤

  1. 创建InvocationHandler实现类
  2. 使用Proxy.newProxyInstance()方法创建代理对象

示例代码

public interface UserService {
    void save();
}

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

public class JdkProxyHandler implements InvocationHandler {
    private Object target;
    
    public JdkProxyHandler(Object target) {
        this.target = target;
    }
    
    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;
    }
    
    public static Object createProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new JdkProxyHandler(target)
        );
    }
}

// 使用
UserService proxy = (UserService) JdkProxyHandler.createProxy(new UserServiceImpl());
proxy.save();

特点

  • 只能代理接口方法
  • 性能较好
  • 无需第三方库支持

三、CGLIB动态代理

工作原理

CGLIB(Code Generation Library)通过继承目标类并在子类中重写方法来实现代理,不要求目标类实现接口。

实现步骤

  1. 创建MethodInterceptor实现类
  2. 使用Enhancer类创建代理对象

示例代码

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

public class CglibProxyInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置处理");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("后置处理");
        return result;
    }
    
    public static Object createProxy(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(new CglibProxyInterceptor());
        return enhancer.create();
    }
}

// 使用
UserService proxy = (UserService) CglibProxyInterceptor.createProxy(UserService.class);
proxy.save();

特点

  • 可以代理普通类
  • 通过继承方式实现,不能代理final类和方法
  • 性能略低于JDK动态代理
  • 需要引入CGLIB库

四、Spring中的代理选择机制

Spring AOP根据以下规则自动选择代理方式:

  1. 如果目标对象实现了接口,默认使用JDK动态代理
  2. 如果目标对象没有实现接口,使用CGLIB代理
  3. 可以通过配置强制使用CGLIB代理:
    <aop:config proxy-target-class="true">
        <!-- other beans defined here... -->
    </aop:config>
    或使用注解:
    @EnableAspectJAutoProxy(proxyTargetClass = true)

五、动态代理的应用场景

  1. ​AOP实现​​:事务管理、日志记录、性能监控等
  2. ​远程方法调用(RPC)​​:如RMI、Hessian等
  3. 延迟加载​​:Hibernate的延迟加载实现
  4. ​​权限控制​​:方法调用前的权限检查
  5. ​​缓存​:方法结果缓存

六、性能比较

  1. ​创建速度​​:JDK动态代理比CGLIB快
  2. ​执行速度​​:JDK动态代理1.6及以后版本与CGLIB相当,甚至更快
  3. ​内存占用​​:CGLIB生成的类会占用更多永久代内存(Java 8之前)

七、选择策略

  1. 如果目标类实现了接口,优先使用JDK动态代理
  2. 如果需要代理没有接口的类,使用CGLIB
  3. 对于final类或方法,无法使用CGLIB代理
  4. 考虑使用Spring AOP的自动代理机制,而非手动创建代理

八、AOP、动态代理与反射的关系 

1.回顾一下三者的基本概念

1.1. AOP (面向切面编程)

一种编程范式,将横切关注点(如日志、事务、安全等)与核心业务逻辑分离,通过"切面"模块化这些横切功能。

1.2. 动态代理

在运行时动态创建代理对象的技术,分为:

  • ​JDK动态代理​​:基于接口
  • ​CGLIB代理​​:基于继承

1.3. 反射(Reflection)

Java提供的在运行时检查/修改类、方法、字段等元信息的能力,可以动态调用方法和构造对象。

2.三者关系图解

[反射] ← 提供基础能力支持
    ↓
[动态代理] ← 利用反射实现代理逻辑
    ↓
[AOP] ← 基于动态代理实现切面编程

3.具体关系分析

3.1. 动态代理依赖反射实现

  • ​JDK动态代理​​:

    • Proxy.newProxyInstance()内部使用反射创建代理类
    • InvocationHandler.invoke()通过反射调用目标方法(Method.invoke())
  • ​CGLIB代理​​:

    • 使用MethodProxy(基于反射优化)调用父类方法
    • 通过反射生成子类字节码

3.2. AOP基于动态代理实现

Spring AOP的实现机制:

  • 代理创建阶段:

    • 使用动态代理技术创建代理对象
    • 选择JDK或CGLIB取决于目标对象特性
  • 方法调用阶段:

    • 代理对象拦截方法调用
    • 通过反射机制执行连接点(JoinPoint)
    • 按顺序应用通知(Advice)

3.3. 反射在AOP中的关键作用

  • ​定位切点​​:通过反射分析类的方法元数据
  • ​方法调用​​:反射调用目标方法和通知方法
  • ​注解处理​​:解析@Aspect、@Pointcut等注解

4.工作流程示例(Spring AOP)

  1. ​初始化阶段​​:

    • 通过反射扫描@Aspect组件
    • 解析切点表达式,匹配目标方法
  2. ​代理创建​​:

    // 伪代码展示核心逻辑
    if(目标实现了接口) {
        使用JDK动态代理创建代理对象;
    } else {
        使用CGLIB生成子类代理;
    }
  3. ​方法拦截​​:

    // InvocationHandler或MethodInterceptor中
    public Object invoke(...) {
        // 反射获取方法元信息
        Method method = target.getClass().getMethod(...);
        
        // 执行前置通知(@Before)
        for(Advice advice : beforeAdvices) {
            advice.getMethod().invoke(advice.getAspect()); // 反射调用
        }
        
        // 反射调用目标方法
        Object result = method.invoke(target, args);
        
        // 执行后置通知(@After)
        // ...
        return result;
    }

 九、InvocationHandler 详解

 InvocationHandler 是 Java 动态代理机制中的核心接口,用于定义代理对象的方法调用行为。它是 JDK 动态代理的关键组成部分,允许开发者在运行时动态处理代理对象的方法调用。

1.基本概念

1.1. 定义

InvocationHandler 是 java.lang.reflect 包中的一个接口,只有一个方法:

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

1.2. 作用

当通过代理对象调用方法时,调用会被转发到 InvocationHandler 的 invoke 方法,由它决定如何处理这次调用。

2.核心方法解析

invoke 方法参数说明

参数类型说明
proxyObject生成的代理对象本身(通常不直接使用,避免递归调用
methodMethod被调用的方法对象,包含方法的所有元信息
argsObject[]方法调用时传入的参数数组,如果没有参数则为 null

返回值

  • 返回被代理方法的执行结果
  • 如果方法返回类型是 void,则返回 null

3.工作原理

  1. 代理对象接收到方法调用
  2. 调用被转发到关联的 InvocationHandler
  3. invoke 方法被执行
  4. 开发者可以在 invoke 方法中:
    • 添加前置逻辑
    • 决定是否调用原始方法
    • 修改参数或返回值
    • 添加后置逻辑
  5. 返回最终结果给调用者

4.使用示例

基础实现示例

public class MyInvocationHandler implements InvocationHandler {
    private Object target; // 被代理的真实对象
    
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置处理
        System.out.println("Before method: " + method.getName());
        
        // 调用真实对象的方法
        Object result = method.invoke(target, args);
        
        // 后置处理
        System.out.println("After method: " + method.getName());
        
        return result;
    }
}

创建代理对象

// 真实对象
RealSubject real = new RealSubject();

// 创建InvocationHandler
InvocationHandler handler = new MyInvocationHandler(real);

// 创建代理对象
Subject proxy = (Subject) Proxy.newProxyInstance(
    RealSubject.class.getClassLoader(),
    RealSubject.class.getInterfaces(),
    handler
);

// 使用代理对象
proxy.doSomething();

5.高级应用场景

1. 权限控制

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.isAnnotationPresent(RequiresAuth.class)) {
        checkAuthentication();
    }
    return method.invoke(target, args);
}

2. 性能监控

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = method.invoke(target, args);
    long duration = System.currentTimeMillis() - start;
    logMethodPerformance(method.getName(), duration);
    return result;
}

3. 缓存机制

private Map<String, Object> cache = new ConcurrentHashMap<>();

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String cacheKey = generateCacheKey(method, args);
    
    if (cache.containsKey(cacheKey)) {
        return cache.get(cacheKey);
    }
    
    Object result = method.invoke(target, args);
    cache.put(cacheKey, result);
    return result;
}

6.注意事项

  1. ​递归调用问题​​:

    • 避免在 invoke 方法中通过 proxy 参数再次调用方法,会导致无限递归
    • 正确做法是调用 target 对象的方法
  2. ​equals/hashCode/toString​​:

    • 代理对象默认继承自 Object 的这些方法
    • 如果需要特殊处理,需要在 invoke 方法中显式判断
  3. ​性能考虑​​:

    • 反射调用 (method.invoke()) 比直接调用慢
    • 对性能敏感的场景应考虑缓存 Method 对象
  4. ​异常处理​​:

    • 需要妥善处理被代理方法抛出的异常
    • 可以选择捕获、转换或直接抛出

7.与Spring AOP的关系

Spring AOP 的 JDK 动态代理实现中,核心类 JdkDynamicAopProxy 实现了 InvocationHandler 接口:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    // 实现了invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Spring的代理逻辑...
    }
}

 

十、通过比喻对 InvocationHandler 示例解析

比喻:快递代收点

想象你网购了一件商品:

  • ​真实对象​​ = 你本人(真正要收货的人)
  • ​代理对象​​ = 小区快递代收点
  • ​InvocationHandler​​ = 代收点的处理规则

当快递员(调用者)送货时:

  1. 快递员先到代收点(代理对象)
  2. 代收点按照预定规则(InvocationHandler)处理:
    • 记录快递信息(前置处理)
    • 决定是否通知你取件(是否调用真实方法)
    • 可能帮你验货(修改参数/结果)
    • 记录取件时间(后置处理)

分步骤代码演示

第一步:定义真实服务接口

// 银行服务接口
public interface BankService {
    void deposit(double amount);  // 存款
    void withdraw(double amount); // 取款
    double checkBalance();        // 查余额
}

第二步:实现真实服务

// 真实银行服务实现
public class RealBankService implements BankService {
    private double balance = 0;
    
    @Override
    public void deposit(double amount) {
        balance += amount;
        System.out.println("存入: " + amount);
    }
    
    @Override
    public void withdraw(double amount) {
        if(balance >= amount) {
            balance -= amount;
            System.out.println("取出: " + amount);
        } else {
            System.out.println("余额不足");
        }
    }
    
    @Override
    public double checkBalance() {
        return balance;
    }
}

第三步:创建调用处理器

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class BankServiceHandler implements InvocationHandler {
    private final Object realService; // 持有真实对象
    
    public BankServiceHandler(Object realService) {
        this.realService = realService;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("╔════════════════════════════╗");
        System.out.println("  代理拦截到方法调用: " + method.getName());
        
        // 前置处理:权限检查
        if(method.getName().equals("withdraw")) {
            System.out.println("  → 执行取款前的安全检查");
        }
        
        // 调用真实对象的方法
        System.out.println("  ↓ 转发给真实服务处理...");
        Object result = method.invoke(realService, args);
        
        // 后置处理:记录日志
        if(method.getName().equals("deposit")) {
            System.out.println("  ← 存款操作已完成,更新账户流水");
        }
        
        // 特殊处理查余额
        if(method.getName().equals("checkBalance")) {
            System.out.println("  当前余额查询结果: " + result);
        }
        
        System.out.println("╚════════════════════════════╝");
        return result;
    }
}

第四步:创建并使用代理对象

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        // 1. 创建真实服务对象
        BankService realBank = new RealBankService();
        
        // 2. 创建调用处理器,传入真实对象
        BankServiceHandler handler = new BankServiceHandler(realBank);
        
        // 3. 创建代理对象
        BankService proxyBank = (BankService) Proxy.newProxyInstance(
            BankService.class.getClassLoader(),
            new Class[]{BankService.class},
            handler
        );
        
        // 4. 使用代理对象(所有调用都会被handler处理)
        System.out.println("\n=== 测试存款 ===");
        proxyBank.deposit(1000);
        
        System.out.println("\n=== 测试取款 ===");
        proxyBank.withdraw(500);
        
        System.out.println("\n=== 测试查询 ===");
        double balance = proxyBank.checkBalance();
        System.out.println("最终余额: " + balance);
    }
}

第五步:控制台输出结果

=== 测试存款 ===
╔════════════════════════════╗
  代理拦截到方法调用: deposit
  ↓ 转发给真实服务处理...
存入: 1000.0
  ← 存款操作已完成,更新账户流水
╚════════════════════════════╝

=== 测试取款 ===
╔════════════════════════════╗
  代理拦截到方法调用: withdraw
  → 执行取款前的安全检查
  ↓ 转发给真实服务处理...
取出: 500.0
╚════════════════════════════╝

=== 测试查询 ===
╔════════════════════════════╗
  代理拦截到方法调用: checkBalance
  ↓ 转发给真实服务处理...
  当前余额查询结果: 500.0
╚════════════════════════════╝
最终余额: 500.0
调用者 → 代理对象.method() → InvocationHandler.invoke() → 真实对象.method()
        ↑                                     ↓
        └────── 拦截并增强处理 ───────────────┘

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值