设计模式结构型——代理模式

目录

代理模式的用途

代理模式的实现

静态代理

JDK动态代理

CGLIB动态代理

代理模式的特点

与其他模式比较


        代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过创建一个代理对象来间接访问原始对象。代理模式的核心思想是将对目标对象的访问进行控制,并在访问前后执行一些额外操作,以增强或限制原始对象的功能。

        在代理模式中,代理对象和原始对象都实现相同的接口,使得客户端无需知道实际的对象,只需要通过代理去访问目标对象。代理对象可以拦截对目标对象的访问,然后决定是否允许、何时以及如何访问目标对象。

        总之,代理模式通过引入代理对象,实现了对目标对象的间接访问和控制,提供了更加灵活和安全的对象访问方式。

代理模式的用途

代理模式的常见用途包括:

远程代理:通过代理对象在不同的地址空间中访问远程对象,隐藏了网络通信细节。
虚拟代理:延迟创建开销较大的对象,直到真正需要使用时才进行创建,以提升性能。
安全代理:控制对敏感对象的访问,限制非授权用户的操作权限。
智能代理:在访问对象前后执行额外的逻辑,如缓存结果、记录日志、实现懒加载等。

代理模式的实现

代理模式的角色

抽象主题角色(Subject):定义了真实主题(Real Subject)和代理(Proxy)之间的共同接口,代理类和被代理类都要实现该接口,这样代理对象可以替代真实主题进行操作。

真实主题角色(Real Subject):定义了代理所代表的真实对象,是最终执行业务逻辑的对象,供代理角色调用。

代理角色(Proxy):实现了抽象主题接口,并维护一个指向真实主题对象的引用,是真实角色的代理, 需要持有真实角色的引用。代理对象可以在执行真实主题操作前后进行一些额外处理。

        通过代理模式,客户端可以通过与代理对象进行交互来完成任务,代理对象在必要时会调用真实对象来执行具体的业务逻辑。这种方式可以增加额外的功能,同时也可以隐藏真实对象的细节,实现了客户端与真实对象之间的解耦。

代理模式的类图

代理模式的分类

代理模式分为:静态代理,JDK动态代理,CGLIB动态代理三类。

静态代理、JDK动态代理和CGLIB动态代理都是常见的代理模式实现方式,它们在实现上有一些区别:

静态代理:

  1. 静态代理需要手动编写代理类,代理类和真实类实现相同的接口或继承相同的父类。
  2. 在编译期间,代理类的代码就已经确定,无法在运行时动态修改代理行为。
  3. 需要为每个真实类编写一个对应的代理类,导致代码冗余。

JDK动态代理:

  1. JDK动态代理利用Java反射机制生成代理类,无需手动编写代理类。
  2. 基于接口的代理,代理对象必须实现一个或多个接口。
  3. 代理类是在运行时动态生成的,可以通过InvocationHandler接口在代理类的方法前后插入额外逻辑。
  4. JDK动态代理只能代理实现了接口的类,对于没有实现接口的类不能进行代理。

CGLIB动态代理:

  1. CGLIB是针对类进行代理的方式,通过继承来实现代理。
  2. CGLIB动态代理不需要目标类实现接口,因此可以代理没有实现接口的类。
  3. 代理类是通过字节码技术在运行时动态生成的,生成的代理类是目标类的子类。
  4. CGLIB代理相比JDK动态代理的效率略低。

        综上所述,静态代理在编译期间就确定代理类,需要为每个真实类编写代理类;JDK动态代理通过反射在运行时动态生成代理类,代理对象必须实现接口;而CGLIB动态代理是针对类进行代理,不需要实现接口,通过继承在运行时生成代理类。根据具体需求和场景,选择适合的代理方式。

下面将依次展现三种代理模式的实现

静态代理

其代码实现如下

抽象主题角色代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 抽象主题角色(Subject) 代理人
 * @date 2023/07/23 10:57:42
 */
public interface Agency {
    void renting();
}

真实主题角色代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 真实主题角色(Real Subject) Evan
 * @date 2023/07/23 10:59:29
 */
public class Evan implements Agency{

    @Override
    public void renting() {
        System.out.println("Evan 有一百套房子要出租 ");
    }
}

代理角色代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 代理角色(Proxy) 房屋代理人
 * @date 2023/07/23 11:00:31
 */
public class ProxyAgency implements Agency {

    private Agency agency;

    public ProxyAgency(Agency agency){
        this.agency = agency;
    }

    @Override
    public void renting() {
        System.out.println("向房客出租房屋");
        this.agency.renting();
        System.out.println("完成售后服务");
    }
}

测试代码

package com.common.demo.pattern.proxy;

/**
 * @author Evan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 客户类
 * @date 2023/07/23 11:03:21
 */
public class Client {

    public static void main(String[] args) {
        Agency evan = new Evan();
        Agency agency = new ProxyAgency(evan);
        agency.renting();
    }
}

测试截图

JDK动态代理

编写一个jdk代理实例的基本步骤如下:

  1. 编写业务接口
    因为jdk代理是基于接口的,因此,只能将业务方法定义成接口,但它可以一次生成多个接口的代理对象
  2. 编写调用处理器
    即编写一个java.lang.reflect.InvocationHandler接口的实现类,代理对象的业务逻辑就写成该接口的invoke方法中
  3. 生成代理对象
    有了业务接口和调用处理器后,将二者作为参数,通过Proxy.newProxyInstance方法便可以生成这个(或这些)接口的代理对象。比如上述示例代码中的businessProxy对象,它拥有greeting()这个方法,调用该方法时,实际执行的就是invoke方法。

其代码实现如下

抽象主题角色代码

package com.common.demo.pattern.proxyJdk;

/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 抽象主题角色(Subject) 代理人
 * @date 2023/07/23 10:57:42
 */
public interface JdkAgency {
    void renting();
}

真实主题角色代码

package com.common.demo.pattern.proxyJdk;

/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 真实主题角色(Real Subject) JdkEvan
 * @date 2023/07/23 10:59:29
 */
public class JdkEvan implements JdkAgency {

    @Override
    public void renting() {
        System.out.println("JdkEvan 有一百套房子要出租 ");
    }
}

代理角色代码

package com.common.demo.pattern.proxyJdk;

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

/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 代理角色(Proxy) 房屋代理人
 * @date 2023/07/23 11:00:31
 */
public class JdkProxyAgency implements InvocationHandler {

    //真实对象
    private Object target;

    /**
     * 建立代理对象和真实对象的代理关系方法,并返回代理对象
     *
     * @param target 真实对象
     * @return 代理对象
     */
    public Object bing(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    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;
    }
}

测试代码

package com.common.demo.pattern.proxyJdk;


/**
 * @author JdkEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 客户类
 * @date 2023/07/23 11:03:21
 */
public class JdkClient {

    public static void main(String[] args) {

        JdkProxyAgency jdkProxyAgency = new JdkProxyAgency();
        //绑定关系,因为挂在JdkAgency接口下,所以声明代理对象 jdkProxyAgency
        JdkAgency proxy = (JdkAgency) jdkProxyAgency.bing(new JdkEvan());
        //注意 此时JdkEvan对象已经是一个代理对象,它会进入代理的逻辑方法invoke
        proxy.renting();
    }
}

测试截图

CGLIB动态代理

编写一个cglib代理实例的基本步骤如下:

  1. 添加依赖:首先,需要在项目中添加 CGLIB 的相关依赖。可以通过 Maven 或其他构建工具将 CGLIB 加入到项目中。

  2. 创建目标类:定义一个目标类(被代理类),它不需要实现任何接口。

  3. 创建拦截器类:编写一个拦截器类,实现 MethodInterceptor 接口,并重写 intercept 方法。该方法在代理对象的方法调用前后执行额外的逻辑。

  4. 创建代理对象:使用 CGLIB 的 Enhancer 类来生成代理对象。设置目标类为父类,设置拦截器为回调方法。

  5. 调用代理对象:通过代理对象调用目标类的方法,这时会先调用拦截器中的逻辑,再调用目标类的方法。

抽象主题角色代码(可不需要)

真实主题角色代码

package com.common.demo.pattern.proxyCglib;

/**
 * @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 真实主题角色(Real Subject) CglibEvan
 * @date 2023/07/23 10:59:29
 */
public class CglibEvan{
    public void renting() {
        System.out.println("CglibEvan 有一百套房子要出租 ");
    }
}

代理角色代码

package com.common.demo.pattern.proxyCglib;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 代理角色(Proxy) 房屋代理人
 * @date 2023/07/23 11:00:31
 */
public class CglibProxyAgency implements MethodInterceptor {
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("带领房客看房...签租房协议");
        // 动态的回调父类当中的方法
        methodProxy.invokeSuper(o, objects);
        System.out.println("售后服务");
        return o;
    }
}

测试代码

package com.common.demo.pattern.proxyCglib;

import net.sf.cglib.proxy.Enhancer;

/**
 * @author CglibEvan Walker 昂焱数据: https://www.ayshuju.com
 * @version 1.0
 * @desc 客户类
 * @date 2023/07/23 11:03:21
 */
public class CglibClient {

    public static void main(String[] args) {

        //生成空的字节码对象
        Enhancer enhancer = new Enhancer();
        // 设置这个字节码对象的父类(目标对象)
        enhancer.setSuperclass(CglibEvan.class);
        //设置被增强的方法
        enhancer.setCallback(new CglibProxyAgency());
        //得到代理对象
        CglibEvan factory = (CglibEvan) enhancer.create();
        //执行被代理的方法
        factory.renting();
    }
}

测试截图

代理模式的特点

优点:

  1. 隐藏对象的具体实现:代理模式通过代理对象与客户端进行交互,客户端无需知道实际的对象是如何实现的,从而将对象的具体实现细节隐藏起来。
  2. 控制对对象的访问:代理对象可以控制对目标对象的访问,并在访问前后执行一些额外操作。例如,在访问一个敏感对象时,代理可以验证用户的权限;在访问一个远程对象时,代理可以处理网络通信等。
  3. 扩展原始对象的功能:代理模式可以通过在代理对象中添加一些额外的功能,来扩展原始对象的功能。这样可以避免直接修改原始对象的代码,符合开闭原则。
  4. 分离客户端和目标对象:代理模式使得客户端只需要与代理对象进行交互,无需直接与目标对象交互。这样可以降低系统的耦合度,提高系统的灵活性和可维护性。
  5. 懒加载:在使用虚拟代理时,可以延迟创建开销较大的对象,直到真正需要使用时才进行创建。这样可以提升系统的性能,并减少资源的占用。
  6. 透明性:代理模式可以实现透明的访问,即客户端无需感知自己正在使用的是代理对象还是目标对象,可以将代理视为目标对象的透明扩展。

缺点:

  1. 引入代理对象会增加系统的复杂性,涉及到多个类之间的交互,增加了开发和维护的成本。
  2. 由于代理模式需要额外的对象来进行封装和管理,可能会导致系统的运行效率降低。
  3. 代理模式在某些情况下可能会造成请求的处理速度变慢,特别是远程代理的情况下会涉及到网络通信的延迟。
  4. 如果代理对象的实现不当,可能会导致系统出现更多的问题,如资源泄漏、并发访问的问题等。

使用场景: 

  1. 远程代理(Remote Proxy):在客户端和远程对象之间建立代理,使得客户端能够通过代理访问远程对象,隐藏了网络通信的细节。

  2. 虚拟代理(Virtual Proxy):当创建一个对象的成本很高时,可以先使用代理对象来替代真实对象,延迟真实对象的创建。例如,图片加载时可以使用虚拟代理,在需要显示图片时再真正加载。

  3. 安全代理(Protection Proxy):控制对真实对象的访问权限。代理对象可以检查调用者是否具有执行特定操作的权限,从而保护真实对象的安全性。

  4. 缓存代理(Caching Proxy):为开销较大的计算结果提供缓存,当再次请求相同的数据时,直接返回缓存结果,避免重复计算。

  5. 日志记录代理(Logging Proxy):在调用真实对象的方法前后添加日志记录功能,用于记录方法的调用信息、参数等,方便调试和跟踪。

  6. 延迟加载代理(Lazy Loading Proxy):延迟加载即只在真正需要时才创建真实对象,代理对象会在第一次访问时进行初始化。这种方式可以提高系统启动速度和内存占用。

  7. AOP 切面编程(Aspect-Oriented Programming):代理模式常被应用于 AOP 中,通过代理将横切逻辑(如事务管理、日志记录)与业务逻辑分离。

注意事项:

  1. 接口设计要合理:在定义抽象主题(Subject)时,应该仔细考虑需要暴露给代理对象的方法,避免过于冗杂或不必要的接口方法。

  2. 选择适当的代理类型:代理模式有静态代理和动态代理两种实现方式。静态代理需要手动编写代理类,而动态代理则可以在运行时生成代理对象。根据具体需求选择合适的代理类型。

  3. 理解代理对象与真实对象的关系:代理对象作为真实对象的代表,应该能够处理与真实对象相关的事务,并在必要时将请求转发给真实对象。同时,代理对象还可以在调用前后执行额外的操作,如权限验证、性能监控等。

  4. 考虑代理对象的生命周期:代理对象的生命周期可能与真实对象不同。需要确保代理对象的创建、销毁等操作符合实际需求,避免产生过多的代理对象或无效的代理对象。

  5. 避免滥用代理模式:代理模式适用于在访问真实对象之前或之后添加额外逻辑的场景。但过度使用代理模式可能会导致代码复杂性增加,降低系统性能。

  6. 应用场景考虑:代理模式适用于很多场景,比如远程代理、安全代理、延迟加载等。在应用代理模式时,要明确自己的需求,选择合适的代理实现。

与其他模式比较

代理模式和装饰者模式的不同

        装饰器模式:强调的是增强自身,增强后你还是你,只不过能力更强了而已。

        代理模式:强调要让别人(代理类)帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)

代理模式和外观模式的不同

        代理模式:是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。

        外观模式:定义了一个高层接口,为多个子系统中的接口提供一个一致的界面,不对目标功能进行增强。

 更多消息资讯,请访问昂焱数据(https://www.ayshuju.com)

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

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

相关文章

预科C语言

1.day10 1、perror() 原型:void perror(const char *s); 根据errno呈现错误信息 perror("malloc error"); malloc error: Cannot allocate memory 2、多文件编译 .c ---预处理(.i -E)---汇编(.s -S&#xf…

Visual Studio Code Python 扩展中的包管理

排版:Alan Wang Python 凭借其简单的语法和强大的库,目前已成为最流行的编程语言之一,也是最适合那些刚接触编程的人们的语言。但是,随着项目复杂性和规模的增长,管理依赖项的复杂性也会增加。当新用户不断承接更成熟的…

探秘MySQL底层架构:设计与实现流程

前言 Mysql,作为一款优秀而广泛使用的数据库管理系统,对于众多Java工程师来说,几乎是日常开发中必不可少的一环。无论是存储海量数据,还是高效地检索和管理数据,Mysql都扮演着重要的角色。然而,除了使用My…

《golang设计模式》第一部分·创建型模式-01-单例模式(Singleton)

文章目录 1. 概述1.1 目的1.2 实现方式 2. 代码示例2.1 设计2.2 代码 1. 概述 1.1 目的 保证类只有一个实例有方法能让外部访问到该实例 1.2 实现方式 懒汉式 在第一次调用单例对象时创建该对象,这样可以避免不必要的资源浪费 饿汉式 在程序启动时就创建单例对象…

Spring中事务失效的8中场景

1. 数据库引擎不支持事务 这里以 MySQL为例,MyISAM引擎是不支持事务操作的,一般要支持事务都会使用InnoDB引擎,根据MySQL 的官方文档说明,从MySQL 5.5.5 开始的默认存储引擎是 InnoDB,之前默认的都是 MyISAM&#xff…

数据结构--线性表2-1

目录 一、线性结构的定义 二、线性表的表示 三、顺序表的实现(或操作) 1、修改: 2、插入: 四、顺序表的运算效率分析:时间效率分析: 一、线性结构的定义 若结构时非空有限集,则有且仅有一个…

【MySQL】库和表的操作

目录 一、库的操作 1.1创建数据库 1.2创建数据库案例 1.3字符集和校验规则 (1)查看系统默认字符集以及校验规则 (2)查看数据库支持的字符集 (3)查看数据库支持的字符集校验规则 (4&…

Layui下拉多选框

标题xmSelect插件&#xff1a; xmSelect文档 下载Layui第三方插件 下拉多选框效果&#xff1a; 实现方法(例子)&#xff1a; 将xmSelect插件的xm-select.js文件引入到layui中&#xff1a; <script src"public/js/xm-select/xm-select.js"></script> …

Ubuntu搭建Samba服务-学习记录

文章目录 Ubuntu安装Samba流程Samba配置文件Samba添加账户配置文件修改Samba服务控制设置开机自动启动通过systemctl 启动服务通过 rc.local 启动 Windows访问参考链接 当前文章仅用于记录&#xff0c;在 Ubuntu中安装使用Samba&#xff0c;在Windows访问 系统环境&#xff1a;…

数据库管理-第九十四期 19c OCM之路-第四堂(02)(20230725)

第九十四期 19c OCM之路-第四堂&#xff08;02&#xff09;&#xff08;20230725&#xff09; 第四堂继续&#xff01; 考点3&#xff1a;SQL statement tuning SQL语句调优 收集Schema统计信息 exec dbms_stats.gather_schems_stats(HR);开启制定表索引监控 create index…

Android性能优化之游戏的Theme背景图

近期&#xff0c;对游戏的内存优化&#xff0c;通过内存快照发现&#xff0c;某个Activity的theme背景图 占用3M 多。考虑着手对齐进行优化。 问题 查看游戏中的内存快照&#xff0c;发现有一个图片bitmap 占用3M 多&#xff0c;设置在Activity的背景中&#xff1a; 查看Phon…

scrcpy2.0+实时将手机画面显示在屏幕上并用鼠标模拟点击2023.7.26

想要用AI代打手游&#xff0c;除了模拟器登录&#xff0c;也可以直接使用第三方工具Scrcpy&#xff0c;来自github&#xff0c;它是一个开源的屏幕镜像工具&#xff0c;可以在电脑上显示Android设备的画面&#xff0c;并支持使用鼠标进行交互。 目录 1. 下载安装2. scrcpy的高级…

使用serverless实现从oss下载文件并压缩

公司之前开发一个网盘系统, 可以上传文件, 打包压缩下载文件, 但是在处理大文件的时候, 服务器遇到了性能问题, 主要是这个项目是单机部署.......(离谱), 然后带宽只有100M, 现在用户比之前多很多, 然后所有人的压缩下载请求都给到这一台服务器了, 比如多个人下载的时候带宽问…

python与深度学习(四):ANN和fashion_mnist二

目录 1. 说明2. fashion_mnist的ANN模型测试2.1 导入相关库2.2 加载数据和模型2.3 设置保存图片的路径2.4 加载图片2.5 图片预处理2.6 对图片进行预测2.7 显示图片 3. 完整代码和显示结果4. 多张图片进行测试的完整代码以及结果 1. 说明 本篇文章是对上篇文章训练的模型进行测…

CASAtomic原子操作详解

一、CAS&#xff08;Compare And Swap&#xff09; 1、CAS介绍 CAS原理&#xff1a;假设有三个值&#xff0c;E&#xff08;旧值&#xff09;、U&#xff08;需要更新的值&#xff09;、V&#xff08;内存中真实的值&#xff09;&#xff0c;具体参照下图&#xff1a; 作用&a…

2023第五届全国生物资源提取与应用创新论坛即将举办

01、会议背景 为进一步加强生物资源提取行业交流与合作&#xff0c;促进业“产学研用”融合&#xff0c;提升行业科技创新水平&#xff0c;增强行业国际竞争力&#xff0c;中国生物发酵产业协会、浙江科技学院、浙江工业职业技术学院、浙江省农业生物资源生化制造协同创新中心&…

GFLv2 论文学习

1. 解决了什么问题&#xff1f; 预测定位质量对于目标检测很重要&#xff0c;在 NMS 时它能提供准确的得分排序&#xff0c;提高模型的表现。现有方法都是通过分类或回归的卷积特征来预测定位质量得分。 2. 提出了什么方法&#xff1f; 受到 GFLv1 的 general distribution …

Mysql 主从复制、读写分离

目录 一、前言&#xff1a; 二、主从复制原理 2.1 MySQL的复制类型 2.2 MySQL主从复制的工作过程 2.2.1 MySQL主从复制延迟 2.3 MySQL 三种数据同步方式 2.3.1、异步复制&#xff08;Async Replication&#xff09; 2.3.2、同步复制&#xff08;Sync Replication&#…

【基于CentOS 7 的iscsi服务】

目录 一、概述 1.简述 2.作用 3. iscsi 4.相关名称 二、使用步骤 - 构建iscsi服务 1.使用targetcli工具进入到iscsi服务器端管理界面 2.实现步骤 2.1 服务器端 2.2 客户端 2.2.1 安装软件 2.2.2 在认证文件中生成iqn编号 2.2.3 开启客户端服务 2.2.4 查找可用的i…

微服务远程调用openFeign简单回顾(内附源码示例)

目录 一. OpenFeign简介 二. OpenFeign原理 演示使用 provider模块 消费者模块 配置全局feign日志 示例源代码: 一. OpenFeign简介 OpenFeign是SpringCloud服务调用中间件&#xff0c;可以帮助代理服务API接口。并且可以解析SpringMVC的RequestMapping注解下的接口&#x…