设计模式-代理模式(Proxy)

1. 概念

  • 代理模式(Proxy Pattern)是程序设计中的一种结构型设计模式。它为一个对象提供一个代理对象,并由代理对象控制对该对象的访问。

2. 原理结构图

在这里插入图片描述

  • 抽象角色(Subject):这是一个接口或抽象类,它声明了代理对象和目标对象共同的接口,这样在任何使用目标对象的地方都可以使用代理对象。代理对象和目标对象对外具有一致的方法。
  • 真实角色(Real Subject):也叫被代理角色,它实现了抽象角色,定义了真实对象在关注的行为中执行的操作。
  • 代理角色(Proxy):也叫委托类,它同样实现了抽象角色,并持有真实角色的实例,可以在调用真实角色前后添加一些操作,也可以不调用真实角色而直接返回结果。

3. 代码示例

3.1 示例1(静态代理)
  • 这个案例涉及到一个银行系统,其中有一个 Account 接口,一个实现该接口的 SavingsAccount 类,以及一个代理类 AccountProxy 来代表 SavingsAccount 对象,并添加额外的日志记录功能。
// 定义 Account 接口
interface Account {  
    void deposit(double amount);  
    double withdraw(double amount);  
    double getBalance();  
}

// 实现 Account 接口的 SavingsAccount 类
class SavingsAccount implements Account {  
    private double balance;  
  
    public SavingsAccount(double initialBalance) {  
        this.balance = initialBalance;  
    }  
  
    @Override  
    public void deposit(double amount) {  
        balance += amount;  
    }  
  
    @Override  
    public double withdraw(double amount) {  
        if (amount <= balance) {  
            balance -= amount;  
            return amount;  
        } else {  
            System.out.println("Insufficient balance!");  
            return 0;  
        }  
    }  
  
    @Override  
    public double getBalance() {  
        return balance;  
    }  
}

// 接着,我们创建静态代理类 AccountProxy,它实现 Account 接口并持有一个Account类型的引用(这里是SavingsAccount对象)
class AccountProxy implements Account {  
    private Account account;  
  
    public AccountProxy(Account account) {  
        this.account = account;  
    }  
  
    @Override  
    public void deposit(double amount) {  
        log("Depositing " + amount);  
        account.deposit(amount);  
        log("Deposited successfully.");  
    }  
  
    @Override  
    public double withdraw(double amount) {  
        log("Withdrawing " + amount);  
        double withdrawnAmount = account.withdraw(amount);  
        if (withdrawnAmount > 0) {  
            log("Withdrawn successfully.");  
        }  
        return withdrawnAmount;  
    }  
  
    @Override  
    public double getBalance() {  
        return account.getBalance();  
    }  
  
    private void log(String message) {  
        System.out.println("[LOG] " + message);  
    }  
}

// 在客户端代码中使用 AccountProxy 来代理 SavingsAccount 对象
public class BankClient {
    public static void main(String[] args) {
        // 创建SavingsAccount对象
        Account savingsAccount = new SavingsAccount(1000.0);

        // 使用AccountProxy代理SavingsAccount对象
        Account proxyAccount = new AccountProxy(savingsAccount);

        // 存款
        proxyAccount.deposit(500.0);

        // 取款
        double withdrawn = proxyAccount.withdraw(300.0);
        System.out.println("withdrawn: " + withdrawn);

        // 查询余额
        double balance = proxyAccount.getBalance();

        System.out.println("Current balance: " + balance);
    }
}
  • 输出
[LOG] Depositing 500.0
[LOG] Deposited successfully.
[LOG] Withdrawing 300.0
[LOG] Withdrawn successfully.
withdrawn: 300.0
Current balance: 1200.0
  • 在这个例子中, AccountProxy 类代理了 SavingsAccount 对象,并在每次调用方法时添加了日志记录功能。这种静态代理模式使得可以在不修改原始 SavingsAccount 类的情况下,为其添加额外的功能。但是,静态代理模式的一个缺点是,对于每个需要代理的类,都需要手动创建一个代理类,这可能导致代码冗余和难以维护。在更复杂的场景中,可能会考虑使用动态代理或AOP(面向切面编程)等更高级的技术来减少代理类的编写工作。

3.2 示例2(动态代理)
  • JDK动态代理模式在复杂场景下的一个典型应用是AOP(面向切面编程)。在这个场景下,我们可能会想要在不修改业务逻辑代码的情况下,为方法调用添加额外的行为,如日志记录、事务管理、安全检查等。下面是一个使用JDK动态代理模式的案例,模拟一个具有日志记录和性能监控功能的业务处理系统。
interface BusinessService {
    void executeBusinessLogic();
}

class BusinessServiceImpl implements BusinessService {
    @Override
    public void executeBusinessLogic() {
        System.out.println("Executing business logic...");
        // 模拟业务逻辑执行时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Business logic executed.");
    }
}

class LoggingDynamicHandler {
    private Object target;

    public LoggingDynamicHandler(Object target) {
        this.target = target;
    }

    public Object getNewProxyInstance() {
        // 使用Proxy类的静态方法newProxyInstance来动态创建代理对象
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 方法调用前记录日志
                    System.out.println("[LOG] " + method.getName() + " is called.");
                    long startTime = System.currentTimeMillis();

                    // 调用目标对象的方法
                    Object result = method.invoke(target, args);

                    // 方法调用后记录日志和性能信息
                    long endTime = System.currentTimeMillis();
                    long elapsedTime = endTime - startTime;
                    System.out.println("[LOG] " + method.getName() + " completed in " + elapsedTime + " ms.");

                    return result;
                }
            }
        );
    }
}

public class ProxyPatternDemo {
    public static void main(String[] args) {
        // 创建目标对象(需要被代理的对象)
        BusinessService businessService = new BusinessServiceImpl();

        // 创建LoggingDynamicHandler对象,并传入目标对象
        LoggingDynamicHandler handler = new LoggingDynamicHandler(businessService);

        // 使用LoggingDynamicHandler类的getNewProxyInstance方法获取动态代理对象
        BusinessService proxyBusinessService = (BusinessService) handler.getNewProxyInstance();

        // 通过代理对象调用方法
        proxyBusinessService.executeBusinessLogic();
    }
}
  • 将看到如下输出:
[LOG] executeBusinessLogic is called.
Executing business logic...
Business logic executed.
[LOG] executeBusinessLogic completed in 1013 ms.
  • 在这个例子中,当调用proxyBusinessService.executeBusinessLogic()时,实际上会调用InvocationHandler的invoke方法,并在其中记录日志和计算方法执行的时间。通过这种方式,我们可以在不修改BusinessServiceImpl代码的情况下,为其添加日志记录和性能监控功能。
  • JDK动态代理模式的优点在于它简单易用,只需要目标对象实现接口,就可以为其创建代理对象。然而,它也有局限性,即只能为接口创建代理对象,不能为类创建代理对象。如果需要为类创建代理对象,可以考虑使用CGLIB等第三方库。

3.3 示例3(Cglib代理)

添加依赖

<dependency>  
    <groupId>cglib</groupId>  
    <artifactId>cglib</artifactId>  
    <version>3.3.0</version>  
</dependency>
  • 下面是一个CGLIB代理模式的示例,假设有一个 UserService 类,该类没有实现任何接口,想要使用CGLIB来创建其代理对象,并在代理对象中添加一些额外的逻辑(例如日志记录)。
class UserService {
    public void addUser(String username, String password) {
        System.out.println("添加用户: " + username + " 密码: " + password);
    }
}


class CglibProxyInterceptor implements MethodInterceptor {

    private final Object target;

    public CglibProxyInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在方法执行前添加逻辑
        beforeMethod(method);

        // 调用目标方法
        Object result = proxy.invokeSuper(obj, args);

        // 在方法执行后添加逻辑
        afterMethod(method);

        return result;
    }

    private void beforeMethod(Method method) {
        System.out.println("开始执行方法: " + method.getName());
        // 这里可以添加更多逻辑,比如日志记录、权限检查等
    }

    private void afterMethod(Method method) {
        System.out.println("结束执行方法: " + method.getName());
        // 这里可以添加更多逻辑,比如异常处理、资源清理等
    }
}


class CglibProxyFactory {

    public static <T> T createProxy(Class<T> clazz, Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new CglibProxyInterceptor(target));
        return (T) enhancer.create();
    }
}

public class ProxyCglibDemo {
    public static void main(String[] args) {
        // 创建一个目标对象
        UserService userService = new UserService();

        // 使用CGLIB代理工厂创建代理对象
        UserService proxyUserService = CglibProxyFactory.createProxy(UserService.class, userService);

        // 调用代理对象的方法
        proxyUserService.addUser("Alice", "password123");
    }
}
  • 将看到如下输出:
开始执行方法: addUser
添加用户: Alice 密码: password123
结束执行方法: addUser
  • 在上面的示例中,UserService 类没有实现任何接口,但可以使用 CglibProxyFactory 为其创建代理对象。当调用 proxyUserService.addUser() 方法时,实际上会触发 CglibProxyInterceptor 中的 intercept 方法,可以在这个方法中添加自定义的逻辑。
  • 这个示例展示了如何为任意类创建CGLIB代理对象,并添加通用代理逻辑。在实际应用中,可以根据需要扩展CglibProxyInterceptor类,添加更多的自定义逻辑。

4. 优缺点

  • 主要作用
    • 提供对目标对象的间接访问,以实现控制、增强或简化原有对象的功能。
  • 优点
    • 降低耦合度:代理模式通过引入一个中间层,即代理对象,来实现对目标对象的访问控制,这样可以减少系统各个部分之间的直接依赖,从而提高系统的扩展性和维护性。
    • 保护目标对象:代理可以对目标对象进行封装,防止外部对目标对象的直接访问,从而起到保护的作用。
    • 功能增强:代理可以在不改变目标对象的基础上,为目标对象添加新的功能,比如日志记录、权限检查等。
  • 缺点
    • 性能损耗:由于每次调用目标对象的方法都需要先经过代理对象,因此可能会带来一定的性能损耗。
    • 复杂性增加:引入代理模式会增加代码的复杂性,需要额外编写代理对象的代码,并考虑代理对象与目标对象之间的交互。
    • 透明度问题:对于不熟悉代理模式的开发者来说,可能会觉得代码难以理解和维护,因为代理对象的存在增加了代码的间接性。

5. 应用场景

5.1 主要包括以下几个方面
  1. 远程代理:用于为远程对象提供本地代表。隐藏网络连接和通讯细节,让调用者就像访问本地对象一样访问远程服务。
  2. 安全代理:用于控制对敏感对象的访问。可以在代理中加入访问控制逻辑,以确保只有满足特定条件的请求才能访问目标对象。
  3. 智能引用代理:用于控制对象的引用计数,实现对象的缓存和再利用。常用于缓存框架和对象池中。
  4. 日志代理:用于在不改变原有代码的情况下,为目标对象的方法增加日志记录功能。可以记录方法调用的时间、参数、返回值等信息。
  5. 事务代理:用于控制复杂对象的所有事务操作,确保所有操作要么全部成功,要么全部失败。

5.2 实际应用
  1. 在客户端-服务器架构中,远程代理可以代表一个远程对象,处理网络通信细节,使得客户端可以像与本地对象交互一样与远程对象交互。
  2. 在访问控制系统中,安全代理用于控制对敏感资源的访问,例如确保用户在访问文件系统或数据库前已经通过身份验证和授权检查。
  3. 在业务层和服务层之间添加一个代理层,用于记录方法调用的详细信息,如参数、返回值和执行时间,以便于性能监控和故障排查。
  4. 在企业应用中,事务代理可以包装业务操作,确保所有操作在一个事务上下文中完成,要么全部成功提交,要么在出现错误时回滚。

6. JDK中的使用

  • AOP(面向切面编程)
    • Spring AOP 是 Spring 框架中的一个核心功能,它允许开发者定义跨越多个点的横切关注点,如安全、事务、缓存等。AOP 在运行时通过动态代理将这些横切关注点织入到目标对象的方法调用前后,从而实现对这些方法的增强。
    • Spring AOP 支持两种类型的动态代理:基于接口的 JDK 动态代理和基于继承的 CGLib 动态代理。JDK 动态代理要求目标类实现一个或多个接口,而 CGLib 动态代理则不要求目标类有接口,它通过生成目标类的子类来实现代理。
  • MyBatis
    • 拦截器(Interceptor)机制:MyBatis允许开发者自定义拦截器来拦截对Mapper接口方法的调用。这种机制也是基于代理模式实现的,拦截器可以在方法调用前后添加自定义的逻辑,比如修改参数、处理结果或者进行日志记录等。
    • Mapper接口的动态代理:在MyBatis中,当我们定义了一个Mapper接口和一个对应的XML配置文件后,我们并没有实现这个接口,但仍然可以调用它的方法来执行数据库操作。这是因为MyBatis使用动态代理来实例化这些Mapper接口。当应用程序尝试获取一个Mapper接口的实例时,MyBatis实际上返回的是一个动态生成的代理对象。这个代理对象内部持有一个指向Mapper接口的引用,并将接口方法的调用转发到这个内部引用上,进而执行相应的SQL语句。

7. 注意事项

  • 明确代理目的:代理应明确增强、控制或保护目标对象的目的。
  • 确保代理逻辑正确:代理类中的代理逻辑应准确无误,避免引入新的问题。
  • 保持目标对象不变:代理模式不应修改目标对象的代码,仅通过代理对象进行交互。
  • 考虑性能影响:代理可能会引入一定的性能开销,需权衡利弊。
  • 注意线程安全:若代理对象在多线程环境下使用,需确保线程安全。
  • 妥善处理异常:代理过程中可能出现的异常应得到妥善处理。
  • 选择合适的代理类型:根据具体需求选择静态代理、动态代理或虚拟代理等合适的代理类型。

8. 外观模式 VS 代理模式

模式目的模式架构主要角色应用场景
外观模式简化接口,减少系统的复杂性外观角色和子系统角色复杂的系统且包含多个子系统,简化系统的接口
代理模式控制对被代理类的访问代理角色和目标角色访问对象前进行一些预处理操作的场景

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

觉得自己有讨好型人格,怎么办?

生活中&#xff0c;许多人可能有过这样的困扰&#xff1a; 不敢拒绝别人提出的要求&#xff0c;过于草率地作出承诺&#xff0c;等到发现自己无力兑现承诺&#xff0c;又不敢去面对现实、向别人道出真相&#xff0c;只好编造理由和借口来逃避承诺。 跟别人意见不一时&#xff0…

解决jenkins运行sh报process apparently never started in XXX

个人记录 问题 process apparently never started in /var/jenkins_home/workspace/ks-springboot_mastertmp/durable-bbfe5f99(running Jenkins temporarily with -Dorg.jenkinsci.plugins.durabletask.BourneShellScript.LAUNCH_DIAGNOSTICStrue might make the problem cl…

Linux 中 CPU 利用率是如何算出来的?

在线上服务器观察线上服务运行状态的时候&#xff0c;绝大多数人都是喜欢先用 top 命令看看当前系统的整体 cpu 利用率。例如&#xff0c;随手拿来的一台机器&#xff0c;top 命令显示的利用率信息如下&#xff1a; 这个输出结果说简单也简单&#xff0c;说复杂也不是那么容易就…

【Java基础题型】矩阵的对角线求和

一、题目-矩阵 求一个33矩阵对角线元素之和。 输入格式 矩阵 输出格式 主对角线 副对角线 元素和 样例输入 1 2 3 1 1 1 3 2 1 样例输出 3 7 二、参考的知识 这里给大家送点英语单词&#xff0c;记得学习&#xff1a; p r i m a r y. adj.主要的&#xff1b;初…

论文详解:字节万卡集群训练大模型,算力利用率达55.2%

原论文链接&#xff1a;https://arxiv.org/abs/2402.15627 摘要 我们介绍了MegaScale的设计、实现和工程经验&#xff0c;这是一个用于训练大语言模型&#xff08;LLMs&#xff09;的生产系统&#xff0c;其规模超过10,000个GPU。在这个规模上训练LLMs带来了前所未有的训练效率…

Opentelemetry——Signals-Baggage

Baggage Contextual information that is passed between signals 信号之间传递的上下文信息 In OpenTelemetry, Baggage is contextual information that’s passed between spans. It’s a key-value store that resides alongside span context in a trace, making values…

逆向案例二十四——投某界登录接口逆向,扣代码

网址&#xff1a;aHR0cHM6Ly91c2VyLnBlZGFpbHkuY24vbG9naW4uYXNweA 抓包登录接口&#xff1a; 在登录界面登录&#xff0c;不然不会出现login的js文件&#xff0c;按关键词搜索&#xff0c;进入loginjs文件&#xff0c;在文件中搜索&#xff0c;找到疑似加密的位置&#xff0c…

【Java多线程】案例(4):定时器

目录 一、定时器是什么? 二、Java标准库中的定时器 三、自己实现定时器 四、标准库中更推荐使用的定时器 一、定时器是什么? 定时器是一种用于在指定时间间隔或特定时间点执行特定任务的工具或设备。在计算机科学中&#xff0c;定时器通常是软件或硬件组件&#xff0c;用…

《手机维修600G资料》云盘下载地址

无意中发现一个生财之道&#xff0c;哈哈哈&#xff0c;就是发现有人在一些视频平台&#xff0c;发手机维修之类的视频吸引客户。这样自己就不用开店也可以接生意了。问题剩下就一个了&#xff0c;把手机维修技术学好&#xff0c;一技在手&#xff0c;天上我有。 《手机维修600…

python基础——类型注解【变量,函数,Union】

&#x1f4dd;前言&#xff1a; 上一篇文章Python基础——面相对象的三大特征提到&#xff0c;python中的多态&#xff0c;python中&#xff0c;类型是动态的&#xff0c;这意味着我们不需要在声明变量时指定其类型。然而&#xff0c;这可能导致运行时错误&#xff0c;因为我们…

HTML5学习记录

简介 超文本标记语言&#xff08;HyperText Markup Language&#xff0c;简称HTML&#xff09;&#xff0c;是一种用于创建网页的标准标记语言。 编辑器 下载传送门https://code.visualstudio.com/ 下载编辑器插件 标题 标题通过 <h1> - <h6> 标签进行定义。 …

前端开发攻略---Vue实现防篡改水印的效果。删除元素无效!更改元素属性无效!支持图片、元素、视频等等。

1、演示 2、水印的目的 版权保护&#xff1a;水印可以在图片、文档或视频中嵌入作者、品牌或版权所有者的信息&#xff0c;以防止未经授权的复制、传播或使用。当其他人使用带有水印的内容时&#xff0c;可以追溯到原始作者或版权所有者&#xff0c;从而加强版权保护。 身份识…

2024mathorcup妈妈杯C题数学建模无水印高质量论文新鲜出炉

以下展示部分正文内容&#xff1a;完整内容见文末名片 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片…

搭建个人智能家居 4 -WS2812B-RGB灯

搭建个人智能家居 4 - WS2812B-RGB灯 前言说明ESPHomeHomeAssistant 前言 上一篇文章我们已经完成了第一个外设的添加&#xff08;一个LED灯&#xff09;&#xff0c;今天接着来“壮大”这个系统&#xff0c;添加第二个外设“RGB灯”。 环境搭建可以回顾前面的文章。前文回顾&…

Elasticsearch下载安装 以及Reindex(数据迁移)

部署Elasticsearch集群 这里介绍使用的是Elasticsearch 7.6.1的版本&#xff0c;配置两台服务器&#xff0c;一台部署主节点&#xff0c;一台部署两个从节点。 下载地址&#xff1a;https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.16.2-linux-x86_64…

版本控制工具Git的使用

1、Git的基本概念和使用 1、Git是什么? ● Git: 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。 ● GitHub: 全球最大的面向开源及私有软件项目的托管平台,免费注册并且可以免费托管开源代码。 ● GitLab:与GitHub类似&a…

2024认证杯数学建模C题思路模型代码

目录 2024认证杯数学建模C题思路模型代码&#xff1a;4.11开赛后第一时间更新&#xff0c;获取见文末名片 以下为2023年认证杯C题&#xff1a; 2024年认证杯数学建模C题思路模型代码见此 2024认证杯数学建模C题思路模型代码&#xff1a;4.11开赛后第一时间更新&#xff0c;获…

(云HIS)云医院管理系统源码 SaaS模式 B/S架构 基于云计算技术

一、云HIS系统框架简介 1、技术框架 &#xff08;1&#xff09;总体框架&#xff1a; SaaS应用&#xff0c;全浏览器访问 前后端分离&#xff0c;多服务协同 服务可拆分&#xff0c;功能易扩展 &#xff08;2&#xff09;技术细节&#xff1a; 前端&#xff1a;AngularNg…

【C++】深度解析--拷贝构造函数(从0开始,详解浅拷贝到深拷贝,小白一看就懂!!!)

目录 一、前言 二、拷贝构造函数 &#x1f34e;概念解析 &#x1f95d;特性解析 &#x1f4a6;为什么拷贝构造函数使用传值方式会引发无穷递归调用&#xff1f; &#x1f4a6;为什么拷贝构造函数的形参中要加入 const 修饰 &#x1f4a6;若未显式定义&#xff0c;编译器会生…

使用阿里云试用Elasticsearch学习:4. 聚合——2

近似聚合 如果所有的数据都在一台机器上&#xff0c;那么生活会容易许多。 CS201 课上教的经典算法就足够应付这些问题。如果所有的数据都在一台机器上&#xff0c;那么也就不需要像 Elasticsearch 这样的分布式软件了。不过一旦我们开始分布式存储数据&#xff0c;就需要小心…