代理模式

关于代理模式,查阅比较专业的资料是这么定义的:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

上述概念看起来比较模糊,举些日常的例子,比方说火车票是个目标对象,咱们要去买,那么我们不一定非得去火车站才能买到,其实在很多代理点也能买到。再比如猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。

静态代理模式

代码演示: 下面咱们通过买火车票的案例演示一下代理模式,具体代码如下: 抽象接口:

/*** @Author: 老猫* @Description: 票*/
public interface Ticket {void getTicket();
}
复制代码

火车站实现抽象接口具备买票功能

/*** @Author: 老猫* @Description: 火车站*/
public class RailwayStation implements Ticket {@Overridepublic void getTicket() {System.out.println("买了张火车票");}
}
复制代码

火车站代理类实现抽象接口具备买票功能

/*** @Author: 老猫* @Description: 代理类* @Date: 2021/12/22 5:35 下午*/
public class RailwayAgencyProxy implements Ticket{private RailwayStation railwayStation;public RailwayAgencyProxy(RailwayStation railwayStation) {this.railwayStation = railwayStation;}@Overridepublic void getTicket() {railwayStation.getTicket();}
}
复制代码

上述其实就是静态代理模式。

优点:

  1. 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
  2. 职责非常清晰,一目了然。

缺点:

  1. 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  2. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
  3. 代码层面来看,如果接口发生改变,代理类也会发生变更。

动态代理

有了上面的基础,咱们正式聊聊动态代理。 上面的例子其实我们不难发现,每个代理类只能够实现一个接口服务。那么如果当我们的软件工程中出现多个适用代理模式的业务类型时那么咱们就会创建多个代理类,那么我们如何去解决这个问题呢?其实我们的动态代理就应运而生了。

很显然动态代理类的字节码在程序运行时由Java反射机制动态生成,无需我们手工编写它的源代码。 那咱们基于上述的案例来先看看JDK动态代理类

JDK动态代理

直接看一下JDK动态代理的使用,如下代码块

public class JDKDynamicProxy implements InvocationHandler {//被代理的对象private Object object;public JDKDynamicProxy(Object object) {this.object = object;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = method.invoke(object,args);return result;}//生成代理类public Object createProxyObj(){return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);}
}
复制代码

静态代理,动态代理调用如下:

public class TestProxy {public static void main(String[] args) {//静态代理测试RailwayStation railwayStation = new RailwayStation();railwayStation.getTicket();RailwayAgencyProxy railwayAgencyProxy = new RailwayAgencyProxy(railwayStation);railwayAgencyProxy.getTicket();//动态代理测试Ticket ticket = new RailwayStation();JDKDynamicProxy jdkDynamicProxy = new JDKDynamicProxy(ticket);Ticket proxyBuyTicket = (Ticket) jdkDynamicProxy.createProxyObj();proxyBuyTicket.getTicket();}
}
复制代码

观察上面动态代理以及静态代理测试,动态代理的优势就显而易见了。如果我们再演示另外猪八戒和高翠兰代理场景的时候,是不是就不用再去创建GaocuiLanProxy了,我们只需要通过JDKDynamicProxy的方式去创建代理类即可。

注意Proxy.newProxyInstance()方法接受三个参数:

  1. ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的。
  2. Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
  3. InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法

通过上面的例子以及上述参数,我们不难发现JDK动态代理有这样一个特性: JDK动态代理是面向接口的代理模式,如果要用JDK代理的话,首先得有个接口,例如上面例子中的Ticket接口

cglib动态代理

咱们再来看一下cglib动态代理。先了解一下cglib是什么。关于cglib,其实其官方解释是比较少的,但是其本身是十分强大的,这也是很多人所诟病的。CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

接下来我们看一下用法,由于cglib并不是jdk自带的,所以如果是maven项目的话,咱们首先需要的是引入cglib相关的pom依赖,如下:

<dependency>    <groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
复制代码

由于cglib代理的对象是类,这个是和JDK动态代理不一样的地方,这个地方标红加重点。 这样的话,咱们同样的代理类的话则应该如下定义,

public class Ticket {public void getTicket(){   System.out.println("买了张火车票");  }   final public void refundTicket(){  System.out.println("退了张火车票"); }}
复制代码

显然,上面的类定义了两个方法,一个是买火车票另外的话退火车票。

public class CglibDynamicProxy implements MethodInterceptor {    /*** @param o cglib生成的代理对象* @param method 被代理对象的方法* @param objects 传入方法的参数* @param methodProxy 代理的方法* @return* @throws Throwable*/   public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("execute pre");Object obj = methodProxy.invokeSuper(o,objects);System.out.println("execute after");return obj;}
}
复制代码

调用测试入口方法调用

public class TestCglibProxy {public static void main(String[] args) {// 代理类class文件存入本地磁盘方便我们反编译查看源码System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/kdaddy/project/log");// 通过CGLIB动态代理获取代理对象的过程Enhancer enhancer = new Enhancer();// 设置enhancer对象的父类enhancer.setSuperclass(Ticket.class);// 设置enhancer的回调对象enhancer.setCallback(new CglibDynamicProxy());// 创建代理对象Ticket ticket = (Ticket) enhancer.create();// 通过代理对象调用目标方法ticket.getTicket();// 通过代理尝试调用final对象调用目标方法ticket.refundTicket();}
}
复制代码

运行之后我们得到的结果如下:

execute pre
买了张火车票
execute after
取消了张火车票
复制代码

根据日志的打印情况,我们很容易发现相关打印“取消了张火车票”并没有被代理,所以我们由此可以得到一个结论cglib动态代理无法代理被final修饰的方法。

上述源码中,我们提及将代理类class写到了相关的磁盘中,打开对应的目录,我们会发现下面三个文件

TicketEnhancerByCGLIBEnhancerByCGLIB4e79a04a类为cglib生成的代理类,该类即成了Ticket。 咱们来慢慢看看相关的源码:

public class Ticket$$EnhancerByCGLIB$$4e79a04a extends Ticket implements Factory {private boolean CGLIB$BOUND;public static Object CGLIB$FACTORY_DATA;private static final ThreadLocal CGLIB$THREAD_CALLBACKS;private static final Callback[] CGLIB$STATIC_CALLBACKS;//拦截器private MethodInterceptor CGLIB$CALLBACK_0; private static Object CGLIB$CALLBACK_FILTER;//被代理方法private static final Method CGLIB$getTicket$0$Method; //代理方法private static final MethodProxy CGLIB$getTicket$0$Proxy;private static final Object[] CGLIB$emptyArgs;private static final Method CGLIB$equals$1$Method;private static final MethodProxy CGLIB$equals$1$Proxy;private static final Method CGLIB$toString$2$Method;private static final MethodProxy CGLIB$toString$2$Proxy;private static final Method CGLIB$hashCode$3$Method;private static final MethodProxy CGLIB$hashCode$3$Proxy;private static final Method CGLIB$clone$4$Method;private static final MethodProxy CGLIB$clone$4$Proxy;static void CGLIB$STATICHOOK1() {CGLIB$THREAD_CALLBACKS = new ThreadLocal();CGLIB$emptyArgs = new Object[0];Class var0 = Class.forName("kdaddy.com.cglibDynamic.Ticket$$EnhancerByCGLIB$$4e79a04a");Class var1;CGLIB$getTicket$0$Method = ReflectUtils.findMethods(new String[]{"getTicket", "()V"}, (var1 = Class.forName("kdaddy.com.cglibDynamic.Ticket")).getDeclaredMethods())[0];CGLIB$getTicket$0$Proxy = MethodProxy.create(var1, var0, "()V", "getTicket", "CGLIB$getTicket$0");Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());CGLIB$equals$1$Method = var10000[0];CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");CGLIB$toString$2$Method = var10000[1];CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");CGLIB$hashCode$3$Method = var10000[2];CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");CGLIB$clone$4$Method = var10000[3];CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");}
}
复制代码

我们通过代理类的源码可以看到,代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应,当然被final修饰的方法除外,上述源码中我们也确实没有看到之前的refundTicket方法。接下来往下看。

咱们看其中一个方法的调用。

//代理方法(methodProxy.invokeSuper会调用)
final void CGLIB$getTicket$0() {super.getTicket();}//被代理方法(methodProxy.invoke会调用,这就是为什么在拦截器中调用methodProxy.invoke会死循环,一直在调用拦截器)public final void getTicket() {MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;if (var10000 == null) {CGLIB$BIND_CALLBACKS(this);var10000 = this.CGLIB$CALLBACK_0;}if (var10000 != null) {//调用拦截器var10000.intercept(this, CGLIB$getTicket$0$Method, CGLIB$emptyArgs, CGLIB$getTicket$0$Proxy);} else {super.getTicket();}}
复制代码

通过上述,我们看下getTicket整个的调用链路: 调用getTicket()方法->调用拦截器->methodProxy.invokeSuper->CGLIBgetTicketgetTicket0->被代理的getTicket方法。

接下来,咱们再来看一下比较核心的MethodProxy,咱们直接看下核心:methodProxy.invokeSuper,具体源码如下:

 public Object invokeSuper(Object obj, Object[] args) throws Throwable {try {this.init();MethodProxy.FastClassInfo fci = this.fastClassInfo;return fci.f2.invoke(fci.i2, obj, args);} catch (InvocationTargetException var4) {throw var4.getTargetException();}}//进一步看一下FastClassInfo
private static class FastClassInfo {FastClass f1;//被代理类FastClassFastClass f2;//代理类FastClassint i1; //被代理类的方法签名(index)int i2;//代理类的方法签名private FastClassInfo() {}}
复制代码

上面代码调用过程就是获取到代理类对应的FastClass,并执行了代理方法。还记得之前生成三个class文件吗?TicketEnhancerByCGLIBEnhancerByCGLIB4e79a04aFastClassByCGLIBFastClassByCGLIBf000183.class就是代理类的FastClass,TicketFastClassByCGLIBFastClassByCGLIBa79cabb2.class就是被代理类的FastClass。

关于FastClass Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。 这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。下面我们反编译一个FastClass看看:

public int getIndex(Signature var1) {String var10000 = var1.toString();switch(var10000.hashCode()) {case -80792013:if (var10000.equals("getTicket()V")) {return 0;}break;case 189620111:if (var10000.equals("cancelTicket()V")) {return 1;}break;case 1826985398:if (var10000.equals("equals(Ljava/lang/Object;)Z")) {return 2;}break;case 1913648695:if (var10000.equals("toString()Ljava/lang/String;")) {return 3;}break;case 1984935277:if (var10000.equals("hashCode()I")) {return 4;}}return -1;}...此处省略部分代码...public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {Ticket var10000 = (Ticket)var2;int var10001 = var1;try {switch(var10001) {case 0:var10000.getTicket();return null;case 1:var10000.cancelTicket();return null;case 2:return new Boolean(var10000.equals(var3[0]));case 3:return var10000.toString();case 4:return new Integer(var10000.hashCode());}} catch (Throwable var4) {throw new InvocationTargetException(var4);}throw new IllegalArgumentException("Cannot find matching method/constructor");}复制代码

FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放在了缓存中。

//MethodProxy invoke/invokeSuper都调用了init()
private void init() {if(this.fastClassInfo == null) {Object var1 = this.initLock;synchronized(this.initLock) {if(this.fastClassInfo == null) {MethodProxy.CreateInfo ci = this.createInfo;MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();fci.f1 = helper(ci, ci.c1);//如果缓存中就取出,没有就生成新的FastClass,此处感兴趣的小伙伴可以仔细看一下底层源码,老猫此处提及一下。fci.f2 = helper(ci, ci.c2);fci.i1 = fci.f1.getIndex(this.sig1);//获取方法的indexfci.i2 = fci.f2.getIndex(this.sig2);this.fastClassInfo = fci;this.createInfo = null;}}}}
复制代码

总结

如果屏幕前的你也像张小帅那样,该如何应对呢?其实大部分的答案都在上面了。总结一下 (1)JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。 (2)JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。 (3)JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

面试造火箭系列,栽在了cglib和jdk动态代理相关推荐

  1. 输出cglib以及jdk动态代理产生的class文件

    好奇心重的小伙伴有一种知其然,亦欲知其所以然的特性,我们在spring事务应用中会接触到aop技术,而aop背后隐藏的恰恰是以jdk以及cglib为基础的动态代理技术,博主不才,将自己的学习历程记录于 ...

  2. Cglib和jdk动态代理

    前言:动态代理解决了方法之间的紧耦合,IOC解决了类与类之间的紧耦合. Cglib和jdk动态代理的区别? 1.Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成 ...

  3. Cglib和jdk动态代理的区别

    Cglib和jdk动态代理的区别 动态代理解决了方法之间的紧耦合, IOC解决了类与类之间的紧耦合! Cglib和jdk动态代理的区别? 1.Jdk动态代理:利用拦截器(必须实现InvocationH ...

  4. 【手写系列】纯手写实现JDK动态代理

    前言 在Java领域,动态代理应用非常广泛,特别是流行的Spring/MyBatis等框架.JDK本身是有实现动态代理技术的,不过要求被代理的类必须实现接口,不过cglib对这一不足进行了有效补充.本 ...

  5. CGLib 和JDK 动态代理对比

    1.JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象. 2.JDK 和CGLib 都是在运行期生成字节码,JDK 是直接写Class 字节码,CGLib 使用ASM框架写Cla ...

  6. cglib和jdk动态代理以及 按指定顺序排列list

    2019独角兽企业重金招聘Python工程师标准>>> package com.proxy.cglib; import java.lang.annotation.Annotation ...

  7. cglib动态代理和JDK动态代理

    cglib动态代理和JDK动态代理 maven依赖 cglib动态代理需要jar包支持,CGLib采用了非常底层的字节码技术. <!-- https://mvnrepository.com/ar ...

  8. 静态代理,cglib动态代理,jdk动态代理区别以及流程详解

    1.静态代理 静态代理使用的是代理设计模式,不讲高大上的思想,我们直接实战 这是动物接口,其中有一个吃饭方法 这是其中的一只动物,实现了动物接口,覆盖了吃饭方法 现在我们思考,我想要给猫找一个代理,希 ...

  9. 前端面试instanceof_面试造火箭,看下这些大厂原题

    " 需求已改活已加,加班通宵看朝霞.终是上线已延期,bug还是改不完. 面试造火箭,工作拧螺丝,虽然我只想拧螺丝,可是我需要用造火箭的技术去寻找拧螺丝的工作,如何能在面试过程中让自己处于不败 ...

最新文章

  1. 设计模式-合成复用原则
  2. linux 文本编辑命令grep sed awk
  3. OpenResty 概要及原理科普
  4. insert 语句_替换某字段中的特定字符串——MySQL REPLACE 与INSERT 函数详解
  5. MySQL 深潜 - 一文详解 MySQL Data Dictionary
  6. JS高级程序设计笔记——事件(一)
  7. document.compatMode介绍
  8. python闭包技巧_案例详析:Python闭包与nonlocal关键字
  9. 7月11日安全沙龙演讲主题漏洞与网站挂马
  10. 贾俊平《统计学基于R》(第三版)第八章方差分析习题答案
  11. css里nav是什么,css中的nav什么意思.doc
  12. 以码为梦,心向远方,路在脚下|211应届计算机毕业生的迷茫
  13. 华大基因首席运营官张凌离职
  14. Ubuntu/Debain下安装微信、QQ等Windows应用(最简便方法)
  15. [转]有关FMS(FCS)设置
  16. Demo20211202
  17. 最新微信ipad协议 CODE获取 公众号授权等
  18. switch()语句中,default,break关键字作用,以及贯穿问题
  19. Luminati LPM在Linux下安装教程
  20. 一文教你分清持续集成,持续交付,持续部署

热门文章

  1. 卷积神经网络中feature map是什么
  2. 「普通人VS程序员」电脑还可以这样关机,神操作 建议阅读
  3. Day23(CopyFile,InputStreamReader,OutputStreamWruiter,Buffered,BufferedInputStream,BufferedOutputStr)
  4. 【自学Docker 】Docker ps命令
  5. 华为计算机apk,华为手机助手安卓版apk
  6. 短距离无线通讯-RFID
  7. pdf转jpg在线转换免费
  8. ecw2c认真有效地在线查找帮助!
  9. 萧伯纳:劳动和运动(转)
  10. 谴责那些没有良知的人