秒懂Java代理与动态代理模式

2023-05-16

版权申明】非商业目的可自由转载
博文地址:https://blog.csdn.net/shusheng0007/article/details/80864854
出自:shusheng007

设计模式汇总篇,一定要收藏:

永不磨灭的设计模式(有这一篇真够了,拒绝标题党)

文章目录

  • 概述
    • 类型
    • 难度
  • 概念
    • 什么是代理模式
  • 解决什么问题
  • 什么是静态代理
  • 什么是动态代理
      • Jdk动态代理实现
      • JDK动态代理实现的原理
      • cgLib的动态代理实现
      • cgLib的动态代理原理
    • 动态代理在AOP中的应用
  • 总结

概述

什么是代理模式?解决什么问题(即为什么需要)?什么是静态代理?什么是动态代理模式?二者什么关系?具体如何实现?什么原理?如何改进?这即为我们学习一项新知识的正确打开方式,我们接下来会以此展开,让你秒懂。

类型

结构型(structural)

难度

3颗星

概念

什么是代理模式

定义:为其他对象提供一种代理以控制对这个对象的访问

定义总是抽象而晦涩难懂的,让我们回到生活中来吧。

实例:王二狗公司(天津在线回声科技发展有限公司)老板突然在发工资的前一天带着小姨子跑路了,可怜二狗一身房贷,被迫提起劳动仲裁,劳动局就会为其指派一位代理律师全权负责二狗的仲裁事宜。那这里面就是使用了代理模式,因为在劳动仲裁这个活动中,代理律师会全权代理王二狗。

解决什么问题

下面是一些使用场景,不过太抽象,暂时可以不要在意,随着你的不断进步你终究会明白的。

  • 远程代理 :为位于两个不同地址空间对象的访问提供了一种实现机制,可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高系统的整体运行效率。
  • 虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
  • 缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间。
  • 保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
  • 智能引用:要为一个对象的访问(引用)提供一些额外的操作时可以使用

什么是静态代理

静态代理是指预先确定了代理与被代理者的关系,例如王二狗的代理律师方文镜是在开庭前就确定的了。那映射到编程领域的话,就是指代理类与被代理类的依赖关系在编译期间就确定了。下面就是王二狗劳动仲裁的代码实现:

首先定义一个代表诉讼的接口

public interface ILawSuit {
    void submit(String proof);//提起诉讼
    void defend();//法庭辩护
}

王二狗诉讼类型,实现ILawSuit接口

public class SecondDogWang implements ILawSuit {
    @Override
    public void submit(String proof) {
        System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
    }

    @Override
    public void defend() {
        System.out.println(String.format("铁证如山,%s还钱","马旭"));
    }
}

代理律师诉讼类,实现ILawSuit接口

public class ProxyLawyer implements ILawSuit {

    ILawSuit plaintiff;//持有要代理的那个对象
    public ProxyLawyer(ILawSuit plaintiff) {
        this.plaintiff=plaintiff;
    }

    @Override
    public void submit(String proof) {
        plaintiff.submit(proof);
    }

    @Override
    public void defend() {
        plaintiff.defend();
    }
}

产生代理对象的静态代理工厂类

public class ProxyFactory {
    public static ILawSuit getProxy(){
        return new ProxyLawyer(new SecondDogWang());
    }
}

这样就基本构建了静态代理关系了,然后在客户端就可以使用代理对象来进行操作了。

    public static void main(String[] args) {
        ProxyFactory.getProxy().submit("工资流水在此");
        ProxyFactory.getProxy().defend();
    }

输出结果如下:

老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还钱

可以看到,代理律师全权代理了王二狗的本次诉讼活动。那使用这种代理模式有什么好处呢,我们为什么不直接让王二狗直接完成本次诉讼呢?现实中的情况比较复杂,但是我可以简单列出几条:这样代理律师就可以在提起诉讼等操作之前做一些校验工作,或者记录工作。例如二狗提供的资料,律师可以选择的移交给法庭而不是全部等等操作,就是说可以对代理的对做一些控制。例如二狗不能出席法庭,代理律师可以代为出席。。。

什么是动态代理

动态代理本质上仍然是代理,情况与上面介绍的完全一样,只是代理与被代理人的关系是动态确定的,例如王二狗的同事牛翠花开庭前没有确定她的代理律师,而是在开庭当天当庭选择了一个律师,映射到编程领域为这个关系是在运行时确定的。

那既然动态代理没有为我们增强代理方面的任何功能,那我们为什么还要用动态代理呢,静态代理不是挺好的吗?凡是动态确定的东西大概都具有灵活性,强扩展的优势。上面的例子中如果牛翠花也使用静态代理的话,那么就需要再添加两个类。一个是牛翠花诉讼类,一个是牛翠花的代理律师类,还的在代理静态工厂中添加一个方法。而如果使用动态代理的话,就只需要生成一个诉讼类就可以了,全程只需要一个代理律师类,因为我们可以动态的将很多人的案子交给这个律师来处理。

Jdk动态代理实现

在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler接口、另一个则是 Proxy类,这个类和接口是实现我们动态代理所必须用到的。

InvocationHandler接口是给动态代理类实现的,负责处理被代理对象的操作的,而Proxy是用来创建动态代理类实例对象的,因为只有得到了这个对象我们才能调用那些需要代理的方法。

接下来我们看下实例,牛翠花动态指定代理律师是如何实现的。
1.构建一个牛翠花诉讼类

public class CuiHuaNiu implements ILawSuit {
    @Override
    public void submit(String proof) {
        System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
    }
    @Override
    public void defend() {
        System.out.println(String.format("铁证如山,%s还牛翠花血汗钱","马旭"));
    }
}

2.构建一个动态代理类

public class DynProxyLawyer implements InvocationHandler {
    private Object target;//被代理的对象
    public DynProxyLawyer(Object obj){
        this.target=obj;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	    System.out.println("案件进展:"+method.getName());
        Object result=method.invoke(target,args);
        return result;
    }
}

3.修改静态工厂方法

public class ProxyFactory {
	...

    public static Object getDynProxy(Object target) {
        InvocationHandler handler = new DynProxyLawyer(target);
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
    }
}

4.客户端使用

    public static void main(String[] args) {
        ILawSuit proxy= (ILawSuit) ProxyFactory.getDynProxy(new CuiHuaNiu());
        proxy.submit("工资流水在此");
        proxy.defend();
    }

输出结果为:

案件进展:submit
老板欠薪跑路,证据如下:工资流水在此
案件进展:defend
铁证如山,马旭还牛翠花血汗钱

JDK动态代理实现的原理

首先Jdk的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范。然后通过Proxy类产生的代理对象调用被代理对象的操作,而这个操作又被分发给InvocationHandler接口的 invoke方法具体执行

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

此方法的参数含义如下
proxy:代表动态代理对象
method:代表正在执行的方法
args:代表当前执行方法传入的实参
返回值:表示当前执行方法的返回值

例如上面牛翠花案例中,我们使用Proxy类的newProxyInstance()方法生成的代理对象proxy去调用了proxy.submit("工资流水在此");操作,那么系统就会将此方法分发给invoke().其中proxy对象的类是系统帮我们动态生产的,其实现了我们的业务接口ILawSuit

cgLib的动态代理实现

由于JDK只能针对实现了接口的类做动态代理,而不能对没有实现接口的类做动态代理,所以cgLib横空出世!CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。

1 引入cgLib 库
cglib-nodep-3.2.6.jar:使用nodep包不需要关联asm的jar包,jar包内部包含asm的类.

2 定义业务类,被代理的类没有实现任何接口

public class Frank {
   public void submit(String proof) {
       System.out.println(String.format("老板欠薪跑路,证据如下:%s",proof));
   }
   public void defend() {
       System.out.println(String.format("铁证如山,%s还Frank血汗钱","马旭"));
   }
}

3 定义拦截器,在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口。

public class cgLibDynProxyLawyer implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
        if (method.getName().equals("submit"))
            System.out.println("案件提交成功,证据如下:"+ Arrays.asList(params));
        Object result = methodProxy.invokeSuper(o, params);
        return result;
    }
}

4定义动态代理工厂,生成动态代理

public class ProxyFactory {
    public static Object getGcLibDynProxy(Object target){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new cgLibDynProxyLawyer());
        Object targetProxy= enhancer.create();
        return targetProxy;
    }
}

5客户端调用

  public static void main(String[] args) {
        Frank cProxy= (Frank) ProxyFactory.getGcLibDynProxy(new Frank());
        cProxy.submit("工资流水在此");
        cProxy.defend();
    }

输出结果:

案件提交成功,证据如下:[工资流水在此]
老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还Frank血汗钱

可见,通过cgLib对没有实现任何接口的类做了动态代理,达到了和前面一样的效果。这里只是简单的讲解了一些cgLib的使用方式,有兴趣的可以进一步了解其比较高级的功能,例如回调过滤器(CallbackFilter)等。

cgLib的动态代理原理

CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

CGLIB缺点:对于final方法,无法进行代理。

动态代理在AOP中的应用

什么是AOP? 维基百科上如是说:

定义:In computing, aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
AOP是一种编程范式,其目标是通过隔离切面耦合来增加程序的模块化。

首先声明,AOP是OOP的补充,其地位及其重要性远不及OOP,总体来说OOP面向名词领域而AOP面向动词领域,例如对一个人的设计肯定是使用OOP,例如这个人有手,脚,眼睛瞪属性。而对这个人上厕所这个动作就会涉及到AOP,例如上厕所前的先确定一下拿没拿手纸等。要理解AOP就首先要理解什么是切面耦合(cross-cutting concerns)。例如有这样一个需求,要求为一个程序中所有方法名称以test开头的方法打印一句log,这个行为就是一个典型的cross-cutting场景。首先这个打印log和业务毫无关系,然后其处于分散在整个程序当中的各个模块,如果按照我们原始的方法开发,一旦后期需求变动将是及其繁琐的。所以我们就需要将这个切面耦合封装隔离,不要将其混入业务代码当中。

例如在王二狗的案子中,我们希望在案子起诉后打印一句成功的log,如果不使用代理的话,我们是需要将log写在相应的业务逻辑里面的,例如王二狗诉讼类SecondDogWang里面的submit()方法中。使用了动态代理后,我们只需要在InvocationHandler 里面的invoke()方法中写就可以了,不会侵入业务代码当中,在以后的维护过程中对业务毫无影响,这是我们希望看到的。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getName().equals("submit"))
           System.out.println("案件提交成功,证据如下:"+ Arrays.asList(args));
    Object result=method.invoke(target,args);
    return result;
}

输出结果为:

案件提交成功,证据如下:[工资流水在此]
老板欠薪跑路,证据如下:工资流水在此
铁证如山,马旭还牛翠花血汗钱

所以AOP主要可以用于:日志记录,性能统计,安全控制,事务处理,异常处理等场景下。

总结

静态代理比动态代理更符合OOP原则,在日常开发中使用也较多。动态代理在开发框架时使用较多,例如大名鼎鼎的Spring

最后希望王二狗和牛翠花他们可以顺利拿回自己的血汗钱。

GitHub源码地址:design-patterns

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

秒懂Java代理与动态代理模式 的相关文章

  • 将 WAR 部署到 Tomcat(Spring Boot + Angular)

    我正在尝试使用以下命令部署 Spring Boot 应用程序WAR包装至Tomcat 10 应用程序已成功部署 但是 当我尝试访问端点时 它会导致404 未找到 战争文件 应用程序 war http localhost 8080 appli
  • 使用 TLS PSK 加密时如何正确检测流结束?

    我已经准备好了一个简单的 TLS PSK 客户端测试用例 https github com afarber jetty newbie tree master TlsPskClient2 src main java de afarber tl
  • IBM Websphere MQ - 用于 Tomcat 部署的 EJB 和 MDB 迁移

    我已经为此苦苦挣扎了很长一段时间 我有一个 IBM Websphere MQ 它使用 EJB 和 MDB 以下是配置ejb mdb的地方
  • java 拖放

    我尝试熟悉java中的拖放 但我发现的所有教程都是 让我生气 我想要的只是从 JList 包含在名为 UserPanel 的自制 JPanel 中 拖动 PublicUserLabel 并将其放入从 JTabbedPanel 继承的自制类中
  • 如何杀死 Java Future?

    我正在开发的服务使用 Future 来并行运行多个任务 每个任务最多可能需要一分钟才能完成 然而 外部库似乎有问题 因为在某些情况下 2 的时间 它不会返回 在这些情况下 我想给出 2 分钟的等待时间 如果还没有返回 我想杀死 future
  • 传递自定义类型查询参数

    如何接受自定义类型查询参数 public String detail QueryParam request final MYRequest request 上面的行在启动服务器时出现错误 jersey server model ModelV
  • 使用 https 的 Web 服务身份验证给出错误

    我编写了一个简单的 Web 服务 并使用摘要和 HTTPS 身份验证来保护它 我已经使用 Java 中的 keytool 生成了我的证书 当我通过创建 war 文件在 Tomcat 中部署 Web 服务时 axis 的欢迎页面正确显示 但是
  • 如何在正则表达式中编写可选单词?

    我想编写一个识别以下模式的 java 正则表达式 abc def the ghi and abc def ghi 我试过这个 abc def the ghi 但是 它没有识别第二种模式 我哪里出错了 abc def the ghi 删除多余
  • java:为什么主线程等待子线程完成

    我有一个简单的java程序 主线程 main 创建并启动另一个线程t class T extends Thread Override public void run while true System out println Inside
  • 我的 Kafka 流应用程序刚刚退出,代码为 0,什么也不做

    为了尝试 Kafka 流 我这样做了 public static void main String args final StreamsBuilder builder new StreamsBuilder final Properties
  • java绕中心旋转矩形

    我想围绕其中心点旋转一个矩形 它应该保留在应该绘制的位置并在该空间中旋转 这是我的代码 AffineTransform transform new AffineTransform transform rotate Math toRadian
  • 始终将双精度舍入

    我怎么总是能把一个double to an int 并且永远不要将其四舍五入 我知道Math round double 但我希望它始终向上舍入 所以如果是的话3 2 四舍五入为 4 您可以使用Math ceil method 请参阅Java
  • BigDecimal汇总统计

    我有一个 BigDecimal 列表 List
  • React Native v0.71.8 React-native-vector-icons 你看不到的图标

    我在用react native版本v0 71 8 我安装了react native vector icons库 但图标未显示 似乎链接在最新版本的 React Native 中不再起作用 所以我按照说明进行操作 但它不再编译 出现以下错误
  • Spring Security 角色层次结构不适用于 Thymeleaf sec:authorize

    我正在使用 Spring Security 3 2 5 RELEASE 和 ThymeLeaf 2 1 4 RELEASE 我已经在安全上下文中定义了角色层次结构 在我的视图层中我正在使用sec authorize属性来定义菜单项 我希望看
  • 在 Eclipse RCP 应用程序中禁用插件贡献

    我经常遇到这个问题 但尚未找到解决方案 每当我编写一个新的基于 Eclipse RCP 的应用程序并包含来自 Eclipse 平台的插件时 我都会 继承 其中一些插件的 UI 贡献 大多数贡献 菜单项 键盘快捷键 属性页 都很有用 但有时我
  • Spring MVC:通用 DAO 和服务类

    我正在 Spring MVC 中编写网页 我使用 Generic DAO 编写了所有 DAO 现在我想重写我的服务类 我该如何写 通用服务 我的 DAO 如下 DAO package net example com dao import j
  • ebean 映射到 BYTEA 的数据类型是什么?

    我有一个游戏 2 0 2 需要在数据库中存储一些文件的应用程序 我们使用 Ebean 作为 ORM 我相信我的数据库中需要一个 BYTEA 列来存储该文件 但我不确定在我的模型中使用什么数据类型 我应该使用某种Blob 或者只是一个byte
  • junit4 使用特定测试方法创建测试套件

    在 junit4 中 我想执行来自不同类的特定测试方法 即想要使用来自不同类的特定测试方法创建一个测试套件 假设我有两门课 public class Test Login Test public void test Login 001 Sy
  • MyBatis 枚举的使用

    我知道以前有人问过这个问题 但我无法根据迄今为止找到的信息实施解决方案 所以也许有人可以向我解释一下 我有一个表 状态 它有两列 id 和 name id是PK 我不想使用 POJO Status 而是使用枚举 我创建了这样一个枚举 如下所

随机推荐

  • Mac实用的远程ssh连接工具( Royal TSX安装及使用)

    Mac实用的远程ssh连接工具 Royal TSX安装及使用 1 下载地址 https www royalapps com ts mac download 2 如何连接远程服务器 2 1 首先下载插件Terminal 2 2 然后创建新的D
  • 尝试VC控制外部程序

    这两天尝试VC控制外部程序呢 xff0c 慢慢完善 在参考了网络学习以后 xff0c 简单做了以下工作 xff1a 期间用了spy 43 43 器件 void CVCControlDlg OnStartreader 启动朗读女 TODO A
  • Windows Sever 2012 R2设置组策略对“不显示最后的登录名”选项已启用

    Windows Sever 2012 R2设置组策略对 不显示最后的登录名 选项已启用 作者 xff1a 我道梦 关注我的CSDN博客 xff0c 更多笔记知识还在更新 xff01 设置组策略启用 不显示最后的登录名 后 xff0c 系统将
  • Ubuntu22.04.1 & WIN11 双系统+双硬盘 grub启动项中无WIN11开机引导

    本机UEFI 43 GPT安装的双系统 xff0c 两块固态硬盘 xff0c 两个系统各自使用自己的硬盘分区 xff0c xff08 选择的全盘安装在新硬盘 xff0c 没有自定义分区 xff0c 所以安装的时候也没有提示与当前window
  • tightvnc,tightvnc软件介绍,详细介绍

    tightvnc一款用于windows操作系统的应用软件 xff0c 是一款远程控制软件 出门在外忘了带档案怎么办 xff1f FTP server 上头忘了开帐号怎么办 xff1f 这些麻烦的问题其实都可以靠 VNC 解决 tightvn
  • OpenCore-EFI-配置模版(持续更新)

    前言 随着OpenCore日臻完善 xff0c 将在以后会更多的用于黑苹果的安装 同时 xff0c 在各位大佬的大力支持与推广 xff0c 各种入门 xff0c 进阶教程的推出 xff0c OpenCore已经从神界降临到人间 逐渐为普通黑
  • OpenCore(OC)引导开机声音与图形界面设置

    关键字 xff1a OC xff1b OpenCore xff1b 引导 xff1b 开机声音 xff1b 图形界面 下面的设置基于OpenCore0 5 8 04 10编译版与1 22 0 0版OpenCore Configurator
  • The BMJ研究:现有的新冠病毒诊断AI模型,几乎毫无用处

    图片出处 xff1a unsplash 本文作者 xff1a 朱演瑞 新型冠状病毒对全球健康造成了严重的威胁 xff0c 为了减轻医疗保健系统的负担 xff0c 也给患者提供最佳的护理 xff0c 高效的诊断和疾病预后信息问题亟待解决 理论
  • 06-Docker-Centos 7.2 (Vmware最小化安装)之一篇搞定hyperledger/fabric的e2e_cli测试运行所遇到的ERROR总结

    bug产生原因分析如下 xff1a 1 系统过于单纯或复杂 xff08 即最小化安装与全部安装以及自行安装了很多软件 xff09 xff0c 很多命令和工具无法使用和执行或冲突 2 自己操作失误 xff0c 敲错代码 xff08 关键词和语
  • 秒懂Java之方法引用(method reference)详解

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 相关文章 xff1a 秒懂Java之深入理解Lambda表达式 文章目录 概述使用条件使用场景如何使用方法引用的类型调用类的静态方法调用传入的实例参数的方法调
  • 产品设计中关于思考力那些事

    这周的面试 xff0c 对我自己来说 xff0c 更像是一种迭代反思 从做什么怎么做 xff0c 到为什么做 xff0c 的一种强制思考 一方面是入行时间短 xff0c 另一方面是公司产品业务主导 xff0c 相对不需要产品去思考 xff0
  • 永不磨灭的设计模式(有这一篇真够了,拒绝标题党)

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述定义分类创建型 xff08 creational xff09 结构型 xff08 structural xff09 行为型 xff08 beha
  • shusheng007编程手记

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述工具篇IntelliJ IDEA在Idea中下载源码时 xff0c 报无法下载源码 Postman使用Postman发送Post请求服务端报得到
  • SpringBoot如何整合RabbitMQ

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述rabbitmq简介SpringBoot整合安装rabbitmq初级用法高级用法配置交换器与队列发送消息消费消息测试 总结 概述 好久没有写博客
  • 秒懂SpringBoot之@Async如何自定义线程池

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述异步初探线程池ThreadPoolExecutorThreadPoolTaskExecutor 验证线程池配置拒绝策略为AbortPolicy拒
  • 秒懂SpringBoot之参数验证全解析(@Validated与@Valid)

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 文章目录 概述实例SpringBoot 验证概述引入依赖使用相关注解标记使用 64 Valid标记统一处理异常 高级用法复杂对象参数验证基本类型参数验证Ser
  • 如何添加本地JAR文件到Maven项目中

    版权申明 非商业目的可自由转载 博文地址 xff1a 出自 xff1a shusheng007 相关文章 xff1a 秒懂Java序列化与反序列化 秒懂 Java注解类型 xff08 64 Annotation xff09 秒懂Java多线
  • 秒懂Java泛型

    版权申明 非商业目的可自由转载 博文地址 xff1a https blog csdn net ShuSheng0007 article details 80720406 出自 xff1a shusheng007 文章目录 概述什么是泛型为什
  • 实际项目中如何使用Git做分支管理

    版权申明 非商业目的注明出处可自由转载 出自 xff1a shusheng007 相关文章 Git日常开发常用命令汇总 文章目录 前言概述Git的基本使用方法使用Git管理项目的方式主分支支持分支总结图 总结 前言 记得刚工作的时候根本不知
  • 秒懂Java代理与动态代理模式

    版权申明 非商业目的可自由转载 博文地址 xff1a https blog csdn net shusheng0007 article details 80864854 出自 xff1a shusheng007 设计模式汇总篇 xff0c