代理模式核心概念

news2025/6/4 12:22:20

代理模式核心概念

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


一、静态代理 (Static Proxy)

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

核心特点

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

实现步骤

// 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 动态代理(基于接口)
// 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 动态代理(基于继承)
// 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:代理无接口的类
    // Spring配置强制使用CGLIB
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    
  2. 事务管理

    @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)
    // 代理执行流程
    proxy.method() {
       beforeAdvice();     // 前置增强
       target.method();    // 原始方法
       afterAdvice();      // 后置增强
    }
    
  2. 实现方式对比

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

二、Spring AOP 的代理实现

1. JDK 动态代理(基于接口)
  • 触发条件:目标类实现了至少一个接口
  • 实现原理
    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 动态代理(基于继承)
  • 触发条件:目标类未实现接口
  • 实现原理
    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 代理工作流程

典型场景:日志记录切面
@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
    # 显式配置使用 CGLIB
    spring.aop.proxy-target-class=true
    
  • 传统 Spring:按目标类是否实现接口自动选择
2. 代理限制与解决方案
问题解决方案
自调用失效(this调用)使用 AopContext.currentProxy()
final 方法无法增强避免对 final 方法使用 AOP
构造方法不拦截改用初始化回调(@PostConstruct
私有方法不拦截Spring AOP 只拦截 public 方法
3. 性能优化建议
  • 减少切点匹配复杂度:精确限定切点范围
    // 优化前(低效)
    @Pointcut("execution(* com.example..*.*(..))")
    
    // 优化后(高效)
    @Pointcut("execution(public * com.example.service.*Service.*(..))")
    
  • 避免在切面中做重型操作:如数据库访问
  • 合理使用缓存:对重复计算的结果进行缓存

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

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

手动实现 AOP 效果(伪代码)
// 原始类
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 限制

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2395981.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贪心算法实战篇2

文章目录 前言序列问题摆动序列单调递增的数字 贪心解决股票问题买卖股票的最佳时机II 两个维度权衡问题分发糖果根据身高重建队列 前言 今天继续带大家进行贪心算法的实战篇2&#xff0c;本章注意来解答一些运用贪心算法的中等的问题&#xff0c;大家好好体会&#xff0c;怎么…

Java 大视界 -- Java 大数据机器学习模型在元宇宙虚拟场景智能交互中的关键技术(239)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

高速串行接口

1.网口设计方案 上图中给出了两种网口设计方案&#xff0c;最上面是传统设计方式&#xff0c;下面是利用GT作为PHY层的设计&#xff0c;然后FPGA中设计协议层和MAC层。 2.SRIO SRIO的本地操作和远程操作 3.其他高速接口 srio rapid io aurora8b10b aurora64b66b pcie s…

学习STC51单片机23(芯片为STC89C52RCRC)

每日一言 成功的路上从不拥挤&#xff0c;因为坚持的人不多&#xff0c;你要做那个例外。 通过单片机发指令给ESP8266进行通信 通信原理(也是接线原理) 代码如下 代码解释一下&#xff0c;因为我们的指令是字符数组&#xff08;c语言没有字符串的概念&#xff09;&#xff0c;…

一个完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (一)

整体链路 [应用服务器] --> [Filebeat] --> [Logstash] --> [Elasticsearch] --> [Kibana] 组件职责 Kibana&#xff1a; 可视化和分析日志数据Elasticsearch&#xff1a; 存储和索引日志数据Logstash&#xff1a; 解析、转换和丰富日志数据Filebeat&#xff1a…

网络系统中安全漏洞扫描为何重要?扫描啥?咋扫描?

在网络系统中&#xff0c;安全漏洞扫描占据着极其重要的位置&#xff0c;这一环节有助于我们发现并消除潜在的安全隐患&#xff0c;进而提高网络安全防护的等级。下面&#xff0c;我将对此进行详尽的说明。 基本概念 漏洞扫描技术可以揭示并评估网站存在的安全风险&#xff0…

Socket 编程 TCP

目录 1. TCP socket API 详解 1.1 socket 1.2 bind 1.3 listen 1.4 accept 1.5 read&&write 1.6 connect 1.7 recv 1.8 send 1.9 popen 1.10 fgets 2. EchoServer 3. 多线程远程命令执行 4. 引入线程池版本翻译 5. 验证TCP - windows作为client访问Linu…

基于TMC5160堵转检测技术的夹紧力控制系统设计与实现

点击下面图片带您领略全新的嵌入式学习路线 &#x1f525;爆款热榜 90万阅读 1.6万收藏 一、技术背景与系统原理 在工业自动化领域&#xff0c;夹紧力控制是精密装配、机床夹具等场景的核心需求。传统方案多采用压力传感器伺服电机的闭环控制方式&#xff0c;但存在系统复杂…

XCTF-web-fileclude

解析如下 <?php include("flag.php"); // 包含敏感文件&#xff08;通常包含CTF挑战的flag&#xff09; highlight_file(__FILE__); // 高亮显示当前PHP文件源代码&#xff08;方便查看代码逻辑&#xff09;if(isset($_GET["file1"]…

OpenShift AI - 启用过时版本的 Notebook 镜像

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.18 OpenShift AI 2.19 的环境中验证 文章目录 查看可用 Notebook 镜像控制台查看命令行查看 Notebook 镜像、Image Stream 和 Image Registry Repository 对应关系启用老版本的 Notebook 镜…

Redis 缓存穿透、缓存击穿、缓存雪崩详解与解决方案

在分布式系统中&#xff0c;Redis 凭借高性能和高并发处理能力&#xff0c;成为常用的缓存组件。然而&#xff0c;在实际应用中&#xff0c;缓存穿透、缓存击穿、缓存雪崩这三大问题会严重影响系统的性能与稳定性。本文将详细解析这三个问题的成因&#xff0c;并提供对应的解决…

DQN和DDQN(进阶版)

来源&#xff1a; *《第五章 深度强化学习 Q网络》.ppt --周炜星、谢文杰 一、前言 Q表格、Q网络与策略函数 Q表格是有限的离散的&#xff0c;而神经网络可以是无限的。 对于动作有限的智能体来说&#xff0c;使用Q网络获得当下状态的对于每个动作的 状态-动作值 。那么 a…

【组件】翻牌器效果

目录 效果组件代码背景素材 效果 组件代码 <template><divclass"card-flop":style"{height: typeof height number ? ${height}px : height,--box-width: typeof boxWidth number ? ${boxWidth}px : boxWidth,--box-height: typeof boxHeight nu…

CentOS 7 环境中部署 LNMP(Linux + Nginx + MySQL 5.7 + PHP)

在 CentOS 7 环境中部署 LNMP&#xff08;Linux Nginx MySQL 5.7 PHP&#xff09; 环境的详细步骤如下。此方案确保各组件版本兼容&#xff0c;并提供完整的配置验证流程。 1. 更新系统 sudo yum update -y 2. 安装 MySQL 5.7 2.1 添加 MySQL 官方 YUM 仓库 由于MySQL并不…

NX811NX816美光颗粒固态NX840NX845

NX811NX816美光颗粒固态NX840NX845 美光NX系列固态硬盘颗粒深度解析&#xff1a;技术、性能与市场全景透视 一、技术架构与核心特性解析 1. NX811/NX816&#xff1a;入门级市场的平衡之选 技术定位&#xff1a;基于176层TLC&#xff08;Triple-Level Cell&#xff09;3D NAN…

捋捋wireshark

本猿搬砖时会用到wireshark分析pcap包&#xff0c;但频率不高&#xff0c;记过一些笔记&#xff0c;今天捋捋&#xff0c;希望能给初学者节省一点时间。 wireshark是个网络封包分析软件&#xff08;network packet analyzer&#xff09;&#xff0c;可以用来抓流量包&#xff…

c++学习之---模版

目录 一、函数模板&#xff1a; 1、基本定义格式&#xff1a; 2、模版函数的优先匹配原则&#xff1a; 二、类模板&#xff1a; 1、基本定义格式&#xff1a; 2、类模版的优先匹配原则&#xff08;有坑哦&#xff09;&#xff1a; 3、缺省值的设置&#xff1a; 4、ty…

第十六章 EMQX黑名单与连接抖动检测

系列文章目录 第一章 总体概述 第二章 在实体机上安装ubuntu 第三章 Windows远程连接ubuntu 第四章 使用Docker安装和运行EMQX 第五章 Docker卸载EMQX 第六章 EMQX客户端MQTTX Desktop的安装与使用 第七章 EMQX客户端MQTTX CLI的安装与使用 第八章 Wireshark工具的安装与使用 …

新编辑器编写指南--给自己的备忘

欢迎使用Markdown编辑器 你好&#xff01; 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章&#xff0c;了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持&#x…

鸿蒙网络数据传输案例实战

一、案例效果截图 二、案例运用到的知识点 核心知识点 网络连接管理&#xff1a;connection模块HTTP数据请求&#xff1a;http模块RPC数据请求&#xff1a;rcp模块文件管理能力&#xff1a;fileIo模块、fileUri模块 其他知识点 ArkTS 语言基础V2版状态管理&#xff1a;Comp…