动态代理的使用场景与适用时机

动态代理(尤其是 Spring 动态代理)的核心价值是在不修改目标类源码的前提下,对目标方法进行增强 (如前置校验、后置处理、异常捕获、日志记录等),本质是面向切面编程(AOP) 的落地实现。下面结合实际开发场景,详细说明「什么时候需要用动态代理」以及「核心使用场景」。

一、核心使用时机(什么时候需要创建动态代理)

简单来说,只要满足以下任一条件,就适合用动态代理:

  1. 需要对方法进行 "无侵入式增强":不想修改目标类的源码(如第三方库的类、公司核心基础类),但要在方法执行前后添加额外逻辑;
  2. 需要统一处理多个类的通用逻辑:多个类 / 方法有重复的通用操作(如日志、事务、权限校验),希望抽离成公共逻辑,避免代码冗余;
  3. 需要动态控制方法执行:比如根据条件决定是否执行目标方法、修改方法入参 / 返回值、捕获方法异常并兜底;
  4. 需要解耦业务逻辑与非业务逻辑:将日志、事务、监控等 "横切逻辑" 与核心业务逻辑分离,符合「单一职责原则」。

反例:如果只是简单调用目标方法,没有任何额外增强逻辑,或只需要对单个方法做一次性修改,直接修改源码即可,无需动态代理(过度设计)。

二、动态代理的核心使用场景(附 Spring 实战示例)

以下是动态代理最常见的落地场景,全部基于 Spring 动态代理实现(也是 Spring 框架自身的核心应用场景):

场景 1:日志记录 / 接口监控

核心需求 :记录接口的调用时间、入参、返回值、耗时,方便排查问题和性能分析,且不侵入业务代码。实现方式:通过动态代理在方法执行前后添加日志逻辑。

示例(Spring 动态代理实现日志监控)
复制代码
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

// 1. 前置日志增强(记录入参和开始时间)
class LogBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("[监控] 方法 " + method.getName() + " 开始执行,入参:" + (args == null ? "无" : args[0]));
    }
}

// 2. 后置日志增强(记录返回值和耗时)
class LogAfterAdvice implements AfterReturningAdvice {
    private long startTime;

    // 初始化开始时间(可结合 Around Advice 更优雅)
    public void setStartTime(long startTime) {
        this.startTime = startTime;
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        long cost = System.currentTimeMillis() - startTime;
        System.out.println("[监控] 方法 " + method.getName() + " 执行完成,返回值:" + returnValue + ",耗时:" + cost + "ms");
    }
}

// 3. 业务目标类
interface OrderService {
    String createOrder(String orderNo);
}

class OrderServiceImpl implements OrderService {
    @Override
    public String createOrder(String orderNo) {
        // 核心业务逻辑
        try { Thread.sleep(100); } catch (InterruptedException e) {} // 模拟耗时
        return "订单创建成功:" + orderNo;
    }
}

// 4. 测试代码
public class LogProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建 Spring 代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        
        // 添加前置/后置增强
        LogBeforeAdvice beforeAdvice = new LogBeforeAdvice();
        LogAfterAdvice afterAdvice = new LogAfterAdvice();
        proxyFactory.addAdvice(beforeAdvice);
        proxyFactory.addAdvice((MethodInterceptor) invocation -> {
            afterAdvice.setStartTime(System.currentTimeMillis());
            return invocation.proceed(); // 执行目标方法
        });
        proxyFactory.addAdvice(afterAdvice);
        
        // 创建代理对象并调用
        OrderService proxy = (OrderService) proxyFactory.getProxy();
        proxy.createOrder("ORDER_666");
    }
}

执行结果

复制代码
[监控] 方法 createOrder 开始执行,入参:ORDER_666
[监控] 方法 createOrder 执行完成,返回值:订单创建成功:ORDER_666,耗时:101ms

场景 2:事务管理(Spring 声明式事务核心)

核心需求 :保证一组数据库操作要么全部成功(提交事务),要么全部失败(回滚事务),且事务逻辑与业务逻辑解耦。实现方式:Spring 通过动态代理在业务方法执行前开启事务,执行后提交事务,异常时回滚事务。

简化示例(模拟 Spring 事务代理)
复制代码
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;

// 1. 事务管理增强器
class TransactionAdvice implements MethodBeforeAdvice, ThrowsAdvice {
    // 前置:开启事务
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("[事务] 开启数据库事务");
    }

    // 后置异常:回滚事务
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) {
        System.out.println("[事务] 捕获异常:" + ex.getMessage() + ",回滚事务");
    }

    // 后置成功:提交事务
    public void afterReturning(Method method, Object[] args, Object target, Object returnValue) {
        System.out.println("[事务] 提交数据库事务");
    }
}

// 2. 业务DAO类
class UserDAO {
    public void insertUser(String username) {
        System.out.println("执行插入用户:" + username);
        // 模拟异常(触发事务回滚)
        // throw new RuntimeException("插入失败:用户名重复");
    }
}

// 3. 测试代码
public class TransactionProxyDemo {
    public static void main(String[] args) {
        UserDAO target = new UserDAO();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.setProxyTargetClass(true); // CGLIB代理(无接口)
        proxyFactory.addAdvice(new TransactionAdvice());
        // 添加后置成功增强
        proxyFactory.addAdvice((AfterReturningAdvice) (returnValue, method, args, target1) -> {
            ((TransactionAdvice) proxyFactory.getAdvisors()[0].getAdvice()).afterReturning(method, args, target1, returnValue);
        });

        UserDAO proxy = (UserDAO) proxyFactory.getProxy();
        try {
            proxy.insertUser("张三");
        } catch (Exception e) {
            // 捕获异常,不影响程序执行
        }
    }
}

正常执行结果

复制代码
[事务] 开启数据库事务
执行插入用户:张三
[事务] 提交数据库事务

触发异常时结果

复制代码
[事务] 开启数据库事务
执行插入用户:张三
[事务] 捕获异常:插入失败:用户名重复,回滚事务

场景 3:权限校验

核心需求 :在执行敏感操作(如删除数据、修改配置)前,校验用户是否有对应的权限,无权限则拒绝执行。实现方式:通过动态代理在方法执行前校验权限,不满足则抛出异常。

示例
复制代码
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

// 1. 权限校验增强
class PermissionAdvice implements MethodBeforeAdvice {
    private String currentUserRole;

    public PermissionAdvice(String currentUserRole) {
        this.currentUserRole = currentUserRole;
    }

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        // 检查方法是否需要管理员权限
        if (method.getName().equals("deleteData")) {
            if (!"ADMIN".equals(currentUserRole)) {
                throw new SecurityException("权限不足:仅管理员可执行删除操作");
            }
        }
        System.out.println("[权限校验] " + currentUserRole + " 用户执行 " + method.getName() + " 操作,校验通过");
    }
}

// 2. 数据管理类
class DataManager {
    public void queryData() {
        System.out.println("执行数据查询");
    }

    public void deleteData() {
        System.out.println("执行数据删除");
    }
}

// 3. 测试代码
public class PermissionProxyDemo {
    public static void main(String[] args) {
        // 测试1:普通用户执行删除(无权限)
        System.out.println("===== 普通用户 =====");
        DataManager target = new DataManager();
        ProxyFactory proxyFactory1 = new ProxyFactory();
        proxyFactory1.setTarget(target);
        proxyFactory1.setProxyTargetClass(true);
        proxyFactory1.addAdvice(new PermissionAdvice("USER"));
        DataManager proxy1 = (DataManager) proxyFactory1.getProxy();
        proxy1.queryData(); // 普通操作允许
        try {
            proxy1.deleteData(); // 敏感操作拒绝
        } catch (SecurityException e) {
            System.out.println(e.getMessage());
        }

        // 测试2:管理员执行删除(有权限)
        System.out.println("\n===== 管理员 =====");
        ProxyFactory proxyFactory2 = new ProxyFactory();
        proxyFactory2.setTarget(target);
        proxyFactory2.setProxyTargetClass(true);
        proxyFactory2.addAdvice(new PermissionAdvice("ADMIN"));
        DataManager proxy2 = (DataManager) proxyFactory2.getProxy();
        proxy2.deleteData();
    }
}

执行结果

复制代码
===== 普通用户 =====
[权限校验] USER 用户执行 queryData 操作,校验通过
执行数据查询
[权限校验] USER 用户执行 deleteData 操作,校验通过
权限不足:仅管理员可执行删除操作

===== 管理员 =====
[权限校验] ADMIN 用户执行 deleteData 操作,校验通过
执行数据删除

场景 4:缓存管理

核心需求 :对高频查询的方法结果进行缓存,避免重复计算 / 查询数据库,提升性能。实现方式:通过动态代理在方法执行前检查缓存,有缓存则直接返回,无缓存则执行方法并将结果存入缓存。

场景 5:远程调用(如 RPC 框架)

核心需求 :本地调用接口方法时,动态发起网络请求调用远程服务(如 Dubbo、Feign)。实现方式 :动态代理生成接口的代理类,代理类的 invoke 方法中封装网络请求逻辑,调用远程服务并返回结果。

场景 6:异常统一处理

核心需求 :对多个方法的异常进行统一捕获和处理(如转换异常类型、记录异常日志、返回兜底结果)。实现方式:通过动态代理捕获目标方法的异常,执行统一的异常处理逻辑。

三、Spring 框架中动态代理的典型应用

除了上述业务场景,Spring 自身也大量使用动态代理:

  1. Spring AOP :所有 @Aspect 切面的实现(如 @Before@After@Around);
  2. Spring 声明式事务@Transactional 注解的底层实现;
  3. Spring Security:权限校验、认证逻辑的增强;
  4. Spring Cache@Cacheable@CacheEvict 等缓存注解;
  5. Spring Cloud Feign:声明式 HTTP 客户端的远程调用;
  6. MyBatis:Mapper 接口的代理(虽然不是 Spring 代理,但原理一致)。

四、总结

核心要点回顾

  1. 使用时机:需要无侵入增强方法、统一处理通用逻辑、解耦横切逻辑与业务逻辑时,优先用动态代理;
  2. 核心场景:日志监控、事务管理、权限校验、缓存管理、远程调用、异常统一处理;
  3. Spring 落地 :Spring 动态代理是 AOP 的核心,ProxyFactory 是创建代理的核心工厂,可灵活选择 JDK/CGLIB 代理,通过 Advice/Interceptor 实现方法增强。

动态代理的核心价值是「开闭原则」------ 对扩展开放(新增增强逻辑),对修改关闭(不修改目标类源码),这也是它成为框架核心机制的根本原因。

相关推荐
Moe4881 小时前
Java 反射机制
java·后端·架构
椰猫子1 小时前
数据库MySQL
数据库
丶小鱼丶2 小时前
数据结构和算法之【链表】
java·数据结构·算法
Sun 32852 小时前
MyBatis-Plus 新版代码生成器的使用
java·spring boot·后端·spring·配置·mybatis-plus·代码生成器
一直都在5722 小时前
新Java基础(二十五):异常类
java·开发语言
礼拜天没时间.2 小时前
力扣热题100实战 | 第31期:下一个排列——数组规律的极致探索
java·算法·leetcode·字典序·原地算法·力扣热题100
哈__2 小时前
告别复杂 SQL 性能瓶颈!金仓智能下推技术的实战解析
数据库·sql
源远流长jerry2 小时前
dpdk19.08编译问题解决方案
数据库·postgresql·sqlserver
微学AI2 小时前
复杂查询中 JOIN 条件下推失败导致的性能瓶颈-金仓数据库
数据库