代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
提醒:动态代理中涉及到以前的一些知识:
看不懂类加载器的可以去看:Java自学(十四、Java反射)里相应的讲解
看不懂匿名内部类的可以去看:Java自学(十、Java的Lambda表达式)的相关的讲解
对java反射中的的invoke()方法不了解的可以去看:Java自学(十四、Java反射)的相关内容

Java代理模式

  • 2 Java 代理模式
    • 2.1 静态代理
    • 小结
    • 2.2 动态代理
      • 2.2.1 JDK原生的动态代理
      • 2.2.2 cglib动态代理
    • 小结

2 Java 代理模式

代理模式就是有一个张三,别人都没有办法找到他,只有他的秘书可以找到他。那其他人想和张三交互,只能通过他的秘书来进行转达交互。这个秘书就是代理者,他代理张三。

再看看另一个例子:卖房子

卖房子的步骤:

1.找买家

2.谈价钱

3.签合同

4.和房产局签订一些乱七八糟转让协议

一般卖家只在签合同的时候可能出面一下,其他的1,2,4都由中介去做。那你问这样有什么用呢?

首先,一个中介可以代理多个卖房子的卖家,其次,我们可以在不修改卖家的代码的情况下,给他实现房子加价、打广告等等夹带私货的功能。

而Java的代理模式又分为静态代理和动态代理

2.1 静态代理

静态代理中存在着以下的角色:

  • 抽象角色:一般使用接口或者抽象类实现(一般是真实角色和代理角色抽象出来的共同部分,比如卖房子的人和中介都有公共的方法卖房子)
  • 真实角色:被代理的角色(表示一个具体的人,比如卖房子的张三)
  • 代理角色:代理真实角色的中介,一般在代理真实角色后,会做一些附属的操作
  • 客户:使用代理角色来进行一些操作(买房子的)

代码实现:

//接口(抽象角色)
public interface Singer{// 歌星要会唱歌void sing();
}

实体类男歌手

//具体角色,男歌手
public class MaleSinger implements Singer{private String name;public MaleSinger(String name) {this.name = name;}@Overridepublic void sing() {System.out.println(this.name+"男歌手在唱歌");}
}

歌手的经纪人

//代理角色
public class Agent implements Singer{private MaleSinger singer; //代理角色要有一个被代理角色public Agent(MaleSinger singer) {this.singer = singer;}@Overridepublic void sing() {System.out.println("协商出场费,做会场工作");//一定是被代理角色歌手去唱歌singer.sing();System.out.println("会场收拾,结算费用");}
}

客户

//客户
public class Client {public static void main(String[] args) {MaleSinger singer=new MaleSinger("周杰伦");Agent agent=new Agent(singer);agent.sing();//通过代理来运行唱歌}
}

可以看到抽象角色就包含了具体角色和代理角色公共的方法sing()。然后通过歌手的经纪人在歌手唱歌的前后可以任意增加自己想要增加的代码。从而达到不修改歌手类方法的同时给唱歌增加新功能的目的。

说白了。代理就是在不修改原来的代码的情况下,给源代码增强功能。

小结

静态代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用(经纪人保护周杰伦,外界不能直接接触周杰伦)
  • 代理对象可以扩展目标对象的功能(本来只能唱歌,现在又多了协商出场费,做会场工作等等功能)
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性

其主要缺点是:

  • 代理模式会造成系统设计中类的数量增加(多了个代理类)
  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢(每次都要先找中介才能找到周杰伦)
  • 增加了系统的复杂度(一开始只是个独立的流浪歌手,然后有了经纪人后就十分复杂了)

2.2 动态代理

静态代理中,比如上述的例子,我们所写的经纪人只能服务malesinger,不能再服务其他的类型的歌手,这很不现实。因为经纪人肯定能去服务不止一种歌手,甚至可能连歌手都不是,去服务跳舞的了。如果静态代理中要实现这个结果,那我们要手动编写好多个agent类,十分繁琐而复杂。所以就出现了动态代理,动态代理可以自动生成代理人的代码。

2.2.1 JDK原生的动态代理

核心类:InvocationHandler类Proxy类
看不懂类加载器的可以去看:Java自学(十四、Java反射)里相应的讲解
看不懂匿名内部类的可以去看:Java自学(十、Java的Lambda表达式)的相关的讲解
对java反射中的的invoke()方法不了解的可以去看:Java自学(十四、Java反射)的相关内容
我们重新写一下Singer接口,给他多一个跳舞的方法

//歌手接口
public interface Singer2 {void sing();void dance();
}

当然对应的男歌手实现类也要改变

//男歌手实现类
public class MaleSinger2 implements Singer2 {private String name;public MaleSinger2(String name) {this.name = name;}@Overridepublic void sing() {System.out.println(this.name+"在唱歌");}@Overridepublic void dance() {System.out.println(this.name+"在跳舞");}
}

然后我们直接进入客户,测试。

import com.hj.Agent2;
import com.hj.MaleSinger2;
import com.hj.Singer2;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class Client2 {public static void main(String[] args) {System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//设置用于输出jdk动态代理产生的类//简单例子,把所有东西放到一段来解释System.out.println("实例1------------------------------------------------");MaleSinger2 maleSinger = new MaleSinger2("周杰伦");//新建代理实例//newProxyInstance(ClassLoader loader, 类加载器,不懂的可以去看https://blog.csdn.net/Doraemon_Nobita/article/details/115702012?spm=1001.2014.3001.5501//Class<?>[] interfaces, 实现的接口,注意是个数组//InvocationHandler h 处理函数)Singer2 agent = (Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),new Class[]{Singer2.class}, new InvocationHandler() {//匿名内部类的方式实现InvocationHandler接口,对这个看不懂的可以参考https://blog.csdn.net/Doraemon_Nobita/article/details/115506705?spm=1001.2014.3001.5501@Override// 这个invoke就是我们调用agent.sing()后调用的方法// invoke(Object proxy, 代理对象// Method method, method是方法,即我们要调用的方法(是唱歌还是跳舞,在调用的时候会是sing()还是dance())// Object[] args 参数列表,可能你需要传参)public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("协商出场费,做会场工作");//关于invoke的讲解,详情可以参考:https://blog.csdn.net/Doraemon_Nobita/article/details/115702012?spm=1001.2014.3001.5501 调用指定的方法的那部分。//invoke方法的参数,一个是Object类型,也就是调用该方法的对象,//第二个参数是一个可变参数类型,也就是给这个方法的传参,外层的这个已经给我们封装成args了,直接用就是了Object invoke = method.invoke(maleSinger,args);//通过反射获取到的method名我们再invoke激活一下,传入要调用该方法的对象。这里用maleSingerSystem.out.println("会场收拾,结算费用");return invoke;}});agent.sing();//可以调用到maleSinger的sing()agent.dance();//调用到maleSinger的dance()System.out.println("实例2------------------------------------------------");//这个简单例子不行啊,我还每次必须写死这里是maleSinger,以后想换别的还得改这里。动态代理岂是如此不便之物。//所以我们直接实现一下InvocationHandler接口,取名为Agent2MaleSinger2 JayZ=new MaleSinger2("周杰伦");MaleSinger2 JJ =new MaleSinger2("林俊杰");Singer2 agentJJ=(Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),new Class[]{Singer2.class}, new Agent2(JJ));Singer2 agentJayZ=(Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),new Class[]{Singer2.class}, new Agent2(JayZ));//可以看到现在代理人创建就十分方便了agentJJ.dance();agentJJ.sing();agentJayZ.sing();agentJayZ.dance();}
}

在第一个例子中,可以看到我们需要利用Proxy类的newProxyInstance()方法就可以生成一个代理对象。而newProxyInstance()的参数又有类加载器、实现的接口数组、以及InvocationHandler对象。在这里使用匿名内部类来实现InvocationHandler接口。实现该接口需要实现他的invoke方法,这个方法就是我们代理对象调用原方法的时候会使用到的方法。区别于反射中的invoke方法,它有三个参数分别是代理对象,调用的方法,方法的参数数组。这里代理对象我们不管,调用的方法则是通过反射获取到的我们使用该代理调用sing()方法或者dance()方法的方法名。通过反射中的invoke方法,可以运行这个指定的对象里方法名的方法。

而第二个例子中,为了实现可以代理任何类,我们实现InvocationHandler接口,并把取类名为Agent2。下面是Agent2的代码。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class Agent2 implements InvocationHandler {private Object object;//想代理谁都可以,随便啊public Agent2(Object object) {this.object = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("协商出场费,做会场工作");//一定是歌手去唱歌Object invoke = method.invoke(object,args);System.out.println("会场收拾,结算费用");return invoke;}
}

可以看出,这里和第一个例子的实现是差不多的,只不过我们使用Object类来代替了之前的写死的MaleSinger类,这样我们就可以代理任何的类型了,只要这个类型需要我们在前后加"协商出场费,做会场工作"、“会场收拾,结算费用”。那可以看到第二个例子中,林俊杰和周杰伦的代理人可以很方便地创建出来,哪怕后面再实现了一个FemaleSinger类,也可以直接生成他的代理人。

加了

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//设置用于输出jdk动态代理产生的类这句代码以后,我们就可以在项目中找到JDK自动生成的代理类代码:

打开可以看到就是自动生成的一段帮我们写代理的方法。

可以看到就是调用了h.invoke,这个h就是我们传参为InvocationHandler的对象,调用了我们自己写的invoke方法。

2.2.2 cglib动态代理

我们需要在maven配置文件中导入相应的包。在pom.xml文件里增加如下代码:

<dependencies><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
</dependencies>

使用方法和JDK的动态代理类似,只是我们不需要再实现接口了,定义一个普通类CglibMaleSinger.java

public class CglibMaleSinger {public CglibMaleSinger(String name) {this.name = name;}private String name;public CglibMaleSinger() {//注意这里一定要有无参构造器,不然之后会报错Superclass has no null constructors but no arguments were given}public void sing(){System.out.println(this.name+"要去唱歌了");}public void dance(){System.out.println(this.name+"要去跳舞了");}
}

然后直接在客户端测试:

import com.hj.CglibMaleSinger;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CglibClient {public static void main(String[] args) {System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./class");//用于输出生成的代理class文件,"./class"表示存储在class文件夹中CglibMaleSinger JayZ=new CglibMaleSinger("周杰伦");Enhancer enhancer = new Enhancer();//定义一个增强器enhancerenhancer.setSuperclass(CglibMaleSinger.class);//设置其超类,我们要代理哪个类就传哪个类//MethodInterceptor是拦截器,就是把我的方法拦截住然后再去增强enhancer.setCallback(new MethodInterceptor() {//设置方法拦截器// o 是指被增强的对象,指自己// method是拦截后的方法,把父类的方法拦截,增强后写在了子类里// objs 参数// methodProxy 父类的方法(拦截前的方法对象)@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("谈出场费");method.invoke(JayZ,objects);System.out.println("出场费谈完了");return null;}});CglibMaleSinger cglibMaleSinger = (CglibMaleSinger)enhancer.create();cglibMaleSinger.sing();cglibMaleSinger.dance();}
}

和JDK的动态代理使用方法基本一致,只是invoke方法变成了intercept方法而已。

加上System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"存储路径");语句后,在你自己设置的存储路径下会出现一个包含生成的class文件的文件夹。

点开hj文件夹下的.class文件

可以看到是继承了我们的CglibMaleSinger类,并且重写了我们的方法,重写内容中调用了intercept()方法。

小结

  • Java动态代理只能够对接口进行代理,不能对普通类进行代理(因为所有生成的代理类的父类为Proxy,java不支持多重继承)
  • CGLIB可以代理普通类
  • Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;而CGLIB使用ASM框架直接对字节码(.class)改了,所以运行的时候是要比Java原生的效率要高些。

设计模式之代理模式(静态代理、Java动态代理、Cglib动态代理)相关推荐

  1. 第六周 Java语法总结_设计原则_工厂模式_单例模式_代理模式(静态代理_动态代理)_递归_IO流_网络编程(UDP_TCP)_反射_数据库

    文章目录 20.设计原则 1.工厂模式 2.单例模式 1)饿汉式 2)懒汉式 3.Runtime类 4.代理模式 1)静态代理 2)动态代理 动态代理模板 21.递归 22.IO流 1.File 2. ...

  2. [设计模式] - 代理模式(静态代理与动态代理)

    文章目录 一.代理模式简介 1. 什么是代理模式 2. 简单举例 二.代理模式的设计思路 1. 代理模式的构成 1. 静态代理 2. 动态代理 (1)接口代理 (2)Cglib代理 三. 代理模式总结 ...

  3. 【Android 插件化】Hook 插件化框架 ( Hook 技术 | 代理模式 | 静态代理 | 动态代理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. 代理模式——静态代理,动态代理(JDK代理和CGLib代理)

    概述 由于某些原因需要给某对象提供一个代理以控制对该对象的访问. 这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介. Java中的代理按照代理类生成时机不同又分为 ...

  5. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  6. Java代理模式——静态代理动态代理

    proxy mode 1. 什么是代理 1.1 例子解释 1.2 作用 2. 静态代理 2.1 优缺点分析 2.2 以厂家卖u盘用代码说明 3. 动态代理 3.1 什么是动态代理 3.2 jdk实现原 ...

  7. Android代理模式(静态代理,动态代理,Retrofit代理模式分析)

    文章目录 代理模式 前言:AOP编程(面向切面编程) 一. 代理思想 1. 静态代理 2. 动态代理 3. 动态代理的实现 二. Retrofit代理模式分析 代理模式 前言:AOP编程(面向切面编程 ...

  8. 代理模式 静态代理、JDK动态代理、Cglib动态代理

    1 代理模式 使用代理模式时必须让代理类和被代理类实现相同的接口: 客户端通过代理类对象来调用被代理对象方法时,代理类对象会将所有方法的调用分派到被代理对象上进行反射执行: 在分派的过程中还可以添加前 ...

  9. 深入理解Java Proxy和CGLIB动态代理原理

    点击上方关注,每天进步一点点 动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译 ...

  10. Java Proxy和CGLIB动态代理原理

    动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...

最新文章

  1. 5G UE — UE 的位置信息
  2. 日期时间类,按特定格式显示日期时间
  3. java 常量池详解
  4. mysql libstdc .so.6_编译安装mysql报错 ./mysqld: /usr/lib64/libstdc++.so.6:
  5. 为什么要用Redis?
  6. 提高学生对计算机学科学习兴趣的研究的结题报告,《如何在体育教学中运用体育游戏》的研究结题报告...
  7. jdbc数据源连接oracle,请教JDBC怎么连接ORACLE数据库
  8. 苹果Mac 3D 模型展开工具:Unfolder
  9. 【深度解析】Google第二代深度学习引擎TensorFlow开源
  10. RTKLIB学习:数据下载和数据转换
  11. MP3标题重命名以及文本去掉模板尾巴
  12. Word2010撤销按钮失效,Ctrl+Z失效解决办法
  13. Java做图片上传、文件上传、 批量上传、 Base64图片上传 。附上源码
  14. C语言输出单个汉字字符
  15. 分区助手扩大C盘,亲测有效
  16. 【mysql】关闭mysql缓存的方法
  17. vue中微信分享总结
  18. 各类申报:限价申报与市价申报
  19. 销售清单怎么打印有什么软件
  20. 新零售复购分析,简单 3 步抓住回头客

热门文章

  1. 系统分析技术简单介绍
  2. JAVA毕设项目新型药物临床信息管理系统(java+VUE+Mybatis+Maven+Mysql)
  3. CRO无菌车间设计方案SICOLAB
  4. 我该怎么跟老板开口说辞职?
  5. ubuntu完美安装espeak支持中文和粤语 不再报错:Full dictionary is not installed for 'zh'
  6. LoRa芯片SX1278官方驱动移植
  7. nacos1.4.1启动报错acos is starting with cluster
  8. 历年来计算机基础知识,计算机基础知识题单选题历年高考真题归纳.docx
  9. Beautiful Soup爬虫
  10. JuiceFS:写流程源码解析+刷盘+数据一致性分析