代理模式核心概念

代理模式核心概念

代理模式是一种结构型设计模式,通过创建一个代理对象来控制对原始对象的访问。主要分为两类:


一、静态代理 (Static Proxy)

定义 :在编译期确定代理关系的模式,代理类和目标类都需要实现相同的接口。

核心特点

  1. 手动编码:需要为每个目标类编写对应的代理类
  2. 编译时绑定:代理关系在编译期确定
  3. 强类型:代理类直接实现目标接口
  4. 无反射:直接调用目标方法,性能较高

实现步骤

java 复制代码
// 1. 定义接口
interface Database {
    void query(String sql);
}

// 2. 真实目标类
class MySQL implements Database {
    public void query(String sql) {
        System.out.println("执行MySQL查询: " + sql);
    }
}

// 3. 静态代理类
class DatabaseProxy implements Database {
    private Database target;
    
    public DatabaseProxy(Database target) {
        this.target = target;
    }
    
    public void query(String sql) {
        // 前置增强
        System.out.println("[日志] 开始执行查询: " + sql);
        
        // 调用真实对象
        target.query(sql);
        
        // 后置增强
        System.out.println("[日志] 查询完成");
    }
}

// 4. 使用代理
public class Main {
    public static void main(String[] args) {
        Database realDB = new MySQL();
        Database proxy = new DatabaseProxy(realDB);
        proxy.query("SELECT * FROM users");
    }
}

输出

复制代码
[日志] 开始执行查询: SELECT * FROM users
执行MySQL查询: SELECT * FROM users
[日志] 查询完成

优点

  • 代码直观,易于理解
  • 编译期检查,类型安全
  • 执行效率高(无反射开销)

缺点

  • 每个目标类都需要创建代理类
  • 接口变更时代码需要同步修改
  • 无法动态扩展功能

适用场景

  • 代理少量固定类
  • 需要严格类型检查的场景
  • 性能敏感的场景

二、动态代理 (Dynamic Proxy)

定义 :在运行时动态创建代理对象的模式,无需提前编写代理类。

核心特点

  1. 运行时生成:代理类在程序运行时动态创建
  2. 基于接口:JDK动态代理要求目标类必须实现接口
  3. 反射机制:通过反射调用目标方法
  4. 灵活扩展:一个代理类可代理多个目标类
1. JDK 动态代理(基于接口)
java 复制代码
// 1. 定义接口(同上)
interface Database { ... }

// 2. 真实目标类(同上)
class MySQL implements Database { ... }

// 3. 实现InvocationHandler
class LoggingHandler implements InvocationHandler {
    private Object target;
    
    public LoggingHandler(Object target) {
        this.target = target;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[日志] 开始执行: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("[日志] 执行完成");
        return result;
    }
}

// 4. 使用代理
public class Main {
    public static void main(String[] args) {
        Database realDB = new MySQL();
        
        Database proxy = (Database) Proxy.newProxyInstance(
            Database.class.getClassLoader(),
            new Class[]{Database.class},
            new LoggingHandler(realDB)
        );
        
        proxy.query("SELECT * FROM orders");
    }
}
2. CGLIB 动态代理(基于继承)
java 复制代码
// 1. 目标类(无需接口)
class PaymentService {
    public void pay(double amount) {
        System.out.println("支付金额: " + amount);
    }
}

// 2. 方法拦截器
class PaymentInterceptor 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;
    }
}

// 3. 使用代理
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PaymentService.class);
        enhancer.setCallback(new PaymentInterceptor());
        
        PaymentService proxy = (PaymentService) enhancer.create();
        proxy.pay(199.99);
    }
}

输出

复制代码
[安全校验] 开始支付
支付金额: 199.99
[通知] 支付成功

优点

  • 无需编写代理类
  • 支持代理多个目标类
  • 功能扩展灵活
  • 适应接口变化

缺点

  • JDK代理要求目标类必须实现接口
  • CGLIB不能代理final类/方法
  • 反射调用有性能开销
  • 调试相对复杂

适用场景

  • AOP实现(如Spring)
  • 远程方法调用(RPC)
  • 事务管理
  • 权限控制
  • 日志记录

三、关键对比

特性 静态代理 动态代理
创建时机 编译期 运行时
实现方式 手动编码代理类 自动生成字节码
接口要求 需要实现相同接口 JDK代理需要接口/CGLIB不需要
性能 高(直接调用) 中(反射调用)
扩展性 差(每类需单独代理) 强(通用代理处理器)
代码复杂度 高(重复代码多) 低(集中处理)
维护成本 高(接口变更需修改) 低(自动适应)
代理类数量 与目标类数量相同 运行时动态生成

四、Spring框架中的应用

  1. AOP实现

    • JDK动态代理:代理接口实现类
    • CGLIB:代理无接口的类
    java 复制代码
    // Spring配置强制使用CGLIB
    @EnableAspectJAutoProxy(proxyTargetClass = true)
  2. 事务管理

    java 复制代码
    @Transactional // 基于动态代理实现
    public void transfer(Account from, Account to, double amount) {
        // ...
    }
  3. 解决循环依赖

    • 通过三级缓存存储代理对象
    • 提前暴露代理对象解决依赖
  4. 声明式服务

    • @Cacheable 缓存代理
    • @Async 异步方法代理
    • @Retryable 重试代理

最佳实践 :在Spring Boot 3.x中,默认优先使用CGLIB代理(通过设置spring.aop.proxy-target-class=true),因为它能代理任何类而不仅限于接口实现类。

AOP 中的代理机制详解

在 AOP(面向切面编程)中,代理是实现横切关注点(如日志、事务、安全等)的核心技术。Spring AOP 主要使用动态代理实现切面功能,下面详细解析其在 AOP 中的应用:


一、代理在 AOP 中的作用

  1. 解耦核心业务与横切逻辑

    • 代理对象包裹原始对象(Target)
    • 在方法执行前后插入增强逻辑(Advice)
    java 复制代码
    // 代理执行流程
    proxy.method() {
       beforeAdvice();     // 前置增强
       target.method();    // 原始方法
       afterAdvice();      // 后置增强
    }
  2. 实现方式对比

    代理类型 实现机制 在 AOP 中的应用场景
    静态代理 手动编写代理类 简单场景,不常用
    动态代理 运行时生成字节码 Spring AOP 默认实现

二、Spring AOP 的代理实现

1. JDK 动态代理(基于接口)
  • 触发条件:目标类实现了至少一个接口

  • 实现原理

    java 复制代码
    public class JdkDynamicProxy {
        public static Object createProxy(Object target) {
            return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), // 关键:获取所有接口
                (proxy, method, args) -> {
                    System.out.println("[前置增强]");
                    Object result = method.invoke(target, args);
                    System.out.println("[后置增强]");
                    return result;
                }
            );
        }
    }
2. CGLIB 动态代理(基于继承)
  • 触发条件:目标类未实现接口

  • 实现原理

    java 复制代码
    public class CglibProxy {
        public static Object createProxy(Class<?> targetClass) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(targetClass); // 关键:设置父类
            enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
                System.out.println("[事务开始]");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("[事务提交]");
                return result;
            });
            return enhancer.create();
        }
    }

三、Spring AOP 代理工作流程

典型场景:日志记录切面
java 复制代码
@Aspect
@Component
public class LoggingAspect {
    
    // 切点定义
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    // 环绕通知(最强大的通知类型)
    @Around("serviceMethods()")
    public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        
        // 前置增强
        System.out.println("[LOG] 进入方法: " + methodName);
        
        try {
            // 执行原始方法
            Object result = joinPoint.proceed();
            
            // 后置增强
            System.out.println("[LOG] 方法成功: " + methodName);
            return result;
            
        } catch (Exception e) {
            // 异常增强
            System.out.println("[LOG] 方法异常: " + methodName);
            throw e;
        }
    }
}
代理执行时序:
  1. 容器创建目标 Bean(如 UserService
  2. 检测到需要 AOP 增强
  3. 根据目标类选择代理方式:
    • 有接口 → JDK 代理
    • 无接口 → CGLIB 代理
  4. 生成代理对象并注入到依赖方
  5. 方法调用时执行增强链

四、关键特性解析

1. 代理选择策略
  • Spring Boot 2.x+ :默认优先使用 CGLIB

    properties 复制代码
    # 显式配置使用 CGLIB
    spring.aop.proxy-target-class=true
  • 传统 Spring:按目标类是否实现接口自动选择

2. 代理限制与解决方案
问题 解决方案
自调用失效(this调用) 使用 AopContext.currentProxy()
final 方法无法增强 避免对 final 方法使用 AOP
构造方法不拦截 改用初始化回调(@PostConstruct
私有方法不拦截 Spring AOP 只拦截 public 方法
3. 性能优化建议
  • 减少切点匹配复杂度 :精确限定切点范围

    java 复制代码
    // 优化前(低效)
    @Pointcut("execution(* com.example..*.*(..))")
    
    // 优化后(高效)
    @Pointcut("execution(public * com.example.service.*Service.*(..))")
  • 避免在切面中做重型操作:如数据库访问

  • 合理使用缓存:对重复计算的结果进行缓存


五、静态代理在 AOP 中的应用

虽然 Spring AOP 主要使用动态代理,但理解静态代理有助于掌握 AOP 本质:

手动实现 AOP 效果(伪代码)
java 复制代码
// 原始类
class UserService {
    public void saveUser(User user) {
        // 业务逻辑
    }
}

// 静态代理增强
class UserServiceProxy extends UserService {
    private UserService target;
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void saveUser(User user) {
        // 前置增强
        log.info("开始保存用户");
        
        // 调用原始方法
        target.saveUser(user);
        
        // 后置增强
        log.info("用户保存成功");
    }
}

注意:实际 Spring AOP 不采用此方式,因为需要为每个类创建代理,无法应对复杂系统。


六、最佳实践

  1. 优先使用接口:方便 JDK 代理,避免 CGLIB 限制

    java 复制代码
    // 推荐实现接口
    public class OrderService implements IOrderService {...}
  2. 最小化切面范围:精确控制增强目标

    java 复制代码
    // 精确到具体方法
    @Pointcut("execution(* com.example.service.OrderService.createOrder(..))")
  3. 谨慎使用 @Around :确保调用 proceed()

    java 复制代码
    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) {
       // 必须调用 pjp.proceed()
       return pjp.proceed(); 
    }
  4. 代理类型检测

    java 复制代码
    if (AopUtils.isJdkDynamicProxy(bean)) {
        // JDK 代理处理
    } else if (AopUtils.isCglibProxy(bean)) {
        // CGLIB 代理处理
    }

通过合理利用代理机制,Spring AOP 实现了业务逻辑与横切关注的完美解耦,是构建可维护、可扩展系统的关键技术。

相关推荐
pengles1 天前
Spring AI 代理模式(Agent Agentic Patterns)
人工智能·spring·代理模式
_abab2 天前
Nginx 基本概念深度解析:从服务器特性到代理模式应用
服务器·nginx·代理模式
蔡蓝2 天前
设计模式-代理模式
设计模式·代理模式
ErizJ3 天前
Golang | 代理模式
开发语言·golang·代理模式
季鸢4 天前
Java设计模式之代理模式详解
java·设计模式·代理模式
洛阳泰山4 天前
Spring AI 智能体代理模式(Agent Agentic Patterns)
人工智能·spring·代理模式·智能体·spring ai
雾喔4 天前
LCR 094. 分割回文串 II
代理模式
Logan Lie4 天前
在 Ubuntu 终端中配置网络代理:优化 npm、apt等的下载速度
linux·ubuntu·npm·代理模式
iceslime7 天前
算法分析与设计实验:找零钱问题的贪心算法与动态规划解决方案
代理模式