Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群

作者丨黎杜

来源丨非科班的科班(LDCldc123095)

前言

这一篇主要也是讲解动态代理的实现机制。

动态代理包括「jdk的动态代理」「cglib 的动态代理」,两者实现相同的功能,但是实现方式却是有明显的区别。

下面我们就通过代码的方式层层的深入这两种动态代理,了解他们的性能、底层的实现原理以及应用场景。

代理模式

在详细介绍动态代理之前,先来说说Java中的代理模式。代理模式分为两种:

  1. 「静态代理」:也就是23种设计模式中的代理模式,由程序员自己编写源代码并进行编译,在程序运行之前已经编译好了.class文件。

  2. 「动态代理」:包括jdk的动态代理和cglib的动态代理,运行时通过反射动态创建。

代理模式定义:我的个人理解就是给某一个对象提供一个代理对象,在代理对象中拥有被代理对象的引用,并在代理对象中调用被代理对象的方法之前和之后进行方法的增强。

我这里画了一张代理模式的类图,设计模式中的代理模式比较简单,代理类和委托类有公共的接口,最后由代理类去执行委托类的方法:

代理模式就好像生活中的中介,去帮你做事,而不用自己去做事。举个例子,比如你要买车,但是买车之前你要到处找车源,找到车源给钱了还要办理一堆手续。

(1)下面我们以买车这个案例进行代理模式的代码编写,首先要有一个公共的接口Person,Person接口里面定义公共的方法:

public interface Person{void buyCar();
}

(2)然后定义一个委托类,也就是我本人Myself,并实现Person接口,具体代码如下:

public class Myself implements Person {@Overridepublic void buyCar() {System.out.println("我要买车了");}
}

(3)最后就是创建代理类CarProxy,同样也是实现Person接口,具体实现代码如下:

public class CarProxy implements Person{private Myself  myself ;public CarProxy(final Myself  myself ) {this.myself = myself ;}@Overridepublic void buyCar() {System.out.println("买车前去找车源");myself .buyCar();System.out.println("买车后办理手续");}
}

这个代理的demo很简单,如上面的类图所示,代理类和委托类都实现公共的接口Person,在委托类中进行方法的具体业务逻辑的实现,而代理类中再次对这个方法进行增强。

「代理模式」的优点就是「能够对目标对象进行功能的扩展」,缺点是每一个业务类都要创建一个代理类,这样会「使我们系统内的类的规模变得很大,不利于维护」

于是就出现了动态代理,仔细思考静态代理的缺点,就是一个委托类就会对象一个代理类,那么是否可以将代理类做成一个通用的呢?

我们仔细来看一下下面的这个图:

我们把静态代理所有的执行过程都可以抽象成这张图的执行过程,Proxy角色无非是在「调用委托类处理业务的方法之前或者之后做一些额外的操作」

那么为了做一个通用性的处理,就把调用委托类的method的动作抽出来,看成一个通用性的处理类,于是就有了InvocationHandler角色,抽象成一个处理类。

这样在Proxy和委托类之间就多了一个InvocationHandler处理类的角色,这个角色主要是「将之前代理类调用委托类的方法的动作进行统一的调用,都由InvocationHandler来处理」

于是之前上面的类图就有了这样的改变,在Proxy和委托类之间加入了InvocationHandler,具体的实现图如下:

看完上面的图似乎有那么一点点的理解,下面我们就来详细的深入动态代理。

jdk动态代理

上面讲解到动态代理是在运行时环境动态加载class文件,并创建对应的class对象,那么动态代理和静态代理的执行时机是在哪里呢?

我这边又画了一张原理图,感觉我为画图操碎了心,每一个点都会画一个截图,是不是很暖。

这个是静态代理的运行原理图,静态代理在程序运行时就已经创建好了class文件,在程序启动后的某一个时机(用到class文件)就会加载class文件到内存中。

当在运行时期动态生成class文件并加载class文件的运行原理图如下:

在JVM运行期时遵循JVM字节码的结构和规范生成二进制文件,并加载到内存中生成对应的Class对象。这样,就完成了动态创建class文件和Class对象的功能了。

在jdk的动态代理中的「Proxy类和委托类要求实现相同的功能」,这里的相同是指「他们都可以调用统一的逻辑业务方法」。要实现这样的设计有以下三种方法:

  1. 「实现同一个接口」:接口里面定义公共的方法。

  2. 「继承」:Proxy继承委托类,这样Proxy就有了和委托类一样的功能,或者两者都继承同一个类,把公共实现业务逻辑的方法放在父类中,这样也能实现。

  3. 「两者内部都有同一个类的引用」:这个和继承有异曲同工之妙,都可以统一的调用统一的业务逻辑方法。

在jdk的动态代理中,是采用第一种方法进行实现,必须有公共的接口,下面我们还是通过静态代理的案例使用动态代理来实现。

(1)首先创建一个公共接口Person:

public interface Person{void buyCar();
}

(2)然后创建接口的实现类Myself:

public class Myself implements Person {@Overridepublic void buyCar() {System.out.println("我要买车了");}
}

(3)这一步就是比较关键的,要创建一个类并实现InvocationHandler

public class InvocationHandlerImpl implements InvocationHandler {private Person person;public InvocationHandlerImpl(Person  person){this.person=person;}@Overridepublic Object invoke(Object proxy, Method method,  Object[] args) throws Throwable {System.out.println("买车前开始找车源。。。。");method.invoke(person, args);System.out.println("买车后办理手续。。。。");return null;}
}

(4)最后一步就是进行测试:

public class Test {public static void main(String[] args) {Myself myself= new Myself();// 创建代理对象,这里有三个参数,第一个是类的ClassLoader,第二个是该类的接口集合,第三个就是InvocationHandlerObject o = Proxy.newProxyInstance(myself.getClass().getClassLoader(), myself.getClass().getInterfaces(), new InvocationHandlerImpl(myself));Person person= (Person) o;person.buyCar();}
}

整体来说jdk动态代理的应用过程还是比较简单的,重要的实现理解他的底层实现过程,它的重要实现步骤就是InvocationHandler中 的invoke方法处理。

invoke方法才是实现方法的调用者,根据上面的参数最后才会创建代理对象newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

那么在实现jdk动态代理的过程都做了哪些工作呢?具体有以下6个步骤:

  1. 获取委托类也就是Myself上的所有接口。

  2. 生成代理,生成的代理的名称也是有规律的,一般是在「com.sun.proxy.$ProxyXXX」

  3. 动态创建代理类的字节码信息,也就是class文件。

  4. 根据class文件创建Class对象。

  5. 创建自己的InvocationHandler并实现InvocationHandler重写invoke方法,实现对委托类方法的调用和增强。

  6. 最后是代理对象的创建,并调用方法,实现代理的功能。

我们可以通过反编译工具来看看生成的代理类的源码是怎么样的,我这里使用的反编译工具是jd-gui,推荐给大家。

 public final class MyselfProxy extends Proxy  implements Person  {private static Method m1;private static Method m3;private static Method m0;private static Method m2;public MyselfProxy(InvocationHandler paramInvocationHandler)  throws    {super(paramInvocationHandler);}public final boolean equals(Object paramObject)  throws    {try   { // InvocationHandler 实现equals的调用return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();}   catch (Error|RuntimeException localError)   {throw localError;}  catch (Throwable localThrowable)  {throw new UndeclaredThrowableException(localThrowable);}}public final void buyCar()  throws  {try   {// InvocationHandler实现buyCar的调用this.h.invoke(this, m3, null);return;}  catch (Error|RuntimeException localError)   {throw localError;}   catch (Throwable localThrowable)   {throw new UndeclaredThrowableException(localThrowable);}}public final int hashCode()   throws    {try   {// InvocationHandler实现hashCode方法的调用return ((Integer)this.h.invoke(this, m0, null)).intValue();}catch (Error|RuntimeException localError){throw localError;}catch (Throwable localThrowable){throw new UndeclaredThrowableException(localThrowable);}}public final String toString()   throws    {try   {// InvocationHandler实现toString的调用return (String)this.h.invoke(this, m2, null);}   catch (Error|RuntimeException localError)   {throw localError;}   catch (Throwable localThrowable)  {throw new UndeclaredThrowableException(localThrowable);}}static   {try   {  //在静态块中通过反射初始化函数m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m3 = Class.forName("com.ldc.org.Person").getMethod("buyCar", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);return;}  catch (NoSuchMethodException localNoSuchMethodException)    {throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}  catch (ClassNotFoundException localClassNotFoundException)    {throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}}
}

从上面反编译的源码中可以可以看出,在「静态块中直接通过反射的方式来生成Method对象」,对方法的调用则是通过InvocationHandler对象来进行调用。

仔细的总结可以看出上面反编译出来的代理类有以下特征:

  1. 继承 「java.lang.reflect.Proxy」类,并实现统一的接口Person。

  2. 所有的方法都是「final」修饰的。

  3. 都是通过「InvocationHandler」对象执行invoke方法的调用统一调用函数,invoke方法通过Method参数来区分是什么方法,进而相应的处理。

到这里我想大家应该对jdk的动态代理有一个清晰的认识了,包括他的底层实现的原理,下面我们就来详细的了解cglib动态代理的是实现方式。

cglib动态代理

在实现jdk的动态代理的实现会发现,「jdk动态代理必须实现一个接口」,并且代理类也「只能代理接口中实现的方法」,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。

基于这种情况cglib便出现了,他也可以在运行期扩展Java类和Java接口。

cglib底层是采用「字节码技术」,其原理是通过字节码技术生成一个子类,并在子类中拦截父类的方法的调用,织入业务逻辑。

因为原理是采用继承的方式,所以被代理的类不能被final修饰,在Spring Aop中底层的实现是以这两种动态代理作为基础进行实现。

当使用cglib动态代理一个类demo时,JVM又做了哪些工作呢?

  1. 「首先找到demo类中的所有非final的公共方法。」

  2. 「然后将这些方法转化为字节码。」

  3. 「通过这些字节码转化为Class对象。」

  4. 「最后由MethodInterceptor实现代理类中所有方法的调用。」

(1)那么我们通过代码也来实现cglib动态代理,还是创建Myself类,但是此时不需要实现接口:

public class Myself {@Overridepublic void buyCar() {System.out.println("I'm going to buy a house");}
}

(2)然后是创建MyMethodInterceptor类实现MethodInterceptor接口,这个和动态代理实现InvocationHandler方式一样,实现统一方法的调用。

public class MyMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args,  MethodProxy proxy) throws Throwable {System.out.println("买车前开始找车源。。。。");proxy.invokeSuper(obj, args);System.out.println("买车后办理手续。。。。");return null;}}

(3)最后是进行测试

public class Test {public static void main(String[] args) {Myself myself= new Myself();MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor ();//cglib 中加强器,用来创建动态代理Enhancer enhancer = new Enhancer();//设置要创建的代理类enhancer.setSuperclass(myself.getClass());// 设置回调,这里相当于是对于代理类上所有方法的调用enhancer.setCallback(myMethodInterceptor );// 创建代理类Myself proxy =(Myself)enhancer.create();proxy.buyCar();}
}

总结来说cglib是一个强大的、高性能的Code生产类库,在Spring中就是通过cglib方式继承要被代理的类,重写父类的方法,实现Aop编程。

cglib创建动态代理对象的性能时机要比jdk动态代理的方式高很多,但是创建对象所花的时间却要比jdk动态代理方式多很多。

在应用方面单例模式更适合用cglib,无需频繁的创建对象,相反,则使用jdk动态代理的方式更加合适。

程序员专栏 扫码关注填加客服 长按识别下方二维码进群近期精彩内容推荐:   知乎热议:阿里 P8 员工 1.6 w 招私人助理 在字节跳动做码农6年后 28岁郭宇宣布退休! Python 为什么推荐蛇形命名法? 从零开发一个 Java Web 项目要点在看点这里好文分享给更多人↓↓

Java动态代理和Cglib动态代理最强王者阵容相关推荐

  1. Java动态代理的两种实现方法:JDK动态代理和CGLIB动态代理

    Java动态代理的两种实现方法:JDK动态代理和CGLIB动态代理 代理模式 JDK动态代理 CGLIB动态代理 代理模式 代理模式是23种设计模式的一种,指一个对象A通过持有另一个对象B,可以具有B ...

  2. Java中的原生动态代理和CGLIB动态代理的原理,我不信你全知道!

    作者:CarpenterLee cnblogs.com/CarpenterLee/p/8241042.html 动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询 ...

  3. JAVA 进阶篇 动态代理 JDK动态代理和CGlib动态代理

    JDK动态代理和CGlib动态代理 JDK动态代理: 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理. CGlib动态代理: 利用ASM(开源的Java ...

  4. Java两种动态代理JDK动态代理和CGLIB动态代理

    目录 代理模式 JDK动态代理 cglib动态代理 测试 代理模式 代理模式是23种设计模式的一种,他是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式.为了对外开放协议,B往往实现了一个 ...

  5. Java中动态代理的两种方式JDK动态代理和cglib动态代理以及区别

    视频功能审核通过了,可以看视频啦!记得点关注啊~ 注意:因为网络原因,视频前一两分钟可能会比较模糊,过一会儿就好了 记得点关注啊,视频里的wx二维码失效了,wx搜索:"聊5毛钱的java&q ...

  6. 什么是代理模式?代理模式有什么用?通过一个小程序分析静态代理和动态代理。自己简单实现动态代理。JDK动态代理和CGLIB动态代理的区别。

    1. 代理模式有什么用 ①功能增强,在实现目标功能的基础上,又增加了额外功能.就像生活中的中介一样,他跟两边客户会有私下的交流. ②控制访问,代理不让用户直接和目标接触.就像中间商一样,他们不会让我们 ...

  7. 利用代码分别实现jdk动态代理和cglib动态代理_面试之动态代理

    大家好!我是CSRobot,从今天开始,我将会发布一些技术文章,内容就是结合春招以来的面试所遇到的问题进行分享,首先会对知识点进行一个探讨和整理,在最后会给出一些面试题并作出解答,希望可以帮助到大家! ...

  8. JDK动态代理和CGLib动态代理简单演示

    JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...

  9. cglib动态代理jar包_Java中的原生动态代理和CGLIB动态代理的原理,我不信你全知道!...

    作者:CarpenterLee cnblogs.com/CarpenterLee/p/8241042.html 动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询 ...

  10. Spring AOP之---基于JDK动态代理和CGLib动态代理的AOP实现

    AOP(面向切面编程)是OOP的有益补充,它只适合那些具有横切逻辑的应用场合,如性能监测,访问控制,事物管理,日志记录等.至于怎么理解横切逻辑,敲完实例代码也就明白了. 为什么要使用AOP,举个栗子: ...

最新文章

  1. 触发器实现两表之间的INSERT,DELETE,UPDATE
  2. SAP MM初阶事务代码MEK1维护PB00价格
  3. DVWA1.9平台XSS小结
  4. 二维大地电磁有限元数值模拟矩形+线性插值
  5. HDU 3613 Best Reward 正反两次扩展KMP
  6. codeforces 528D. Fuzzy Search 快速傅里叶变换
  7. 小学计算机打字基础知识,浅谈小学计算机教学技巧5篇
  8. [Spring]-各种标注-零配置
  9. 使用.Net 1.1的项目,TreeView控件不能正常显示
  10. 无法打开计算机上的event log服务,Win7系统下启用Windows event log服务发生4201错误的正确解决方法...
  11. 嘉益仕(Litns)带您读懂MES系统:选型篇
  12. java项目log4j_java项目测试log4j
  13. Win11任务栏图标重叠怎么办 Win11任务栏图标重叠的解决方法
  14. 玩客云pc端_移动端灵活弹性云电销平台解决方案
  15. oracle查看定时任务
  16. Quartz开发-插件开发
  17. 网络安全工作及其配套法律法规和规范性文件汇总目录
  18. fgo最新服务器,《FGO》:现在世界上其他地区的服务器近况如何,一起来看看吧!...
  19. single cell 数据分析流程及原理
  20. 与 AI 博弈:从 AlphaGo 到 MuZero(三)

热门文章

  1. IT程序员的常见病:颈椎病、肩周炎
  2. (转链接)Linux 正则表达式
  3. 涨知识系列:爆款短视频拍摄技巧之一,构图
  4. 微电影在市场上越来越多,你不知道的微电影拍摄技巧有哪些?
  5. 买理财撤单显示服务器忙,银行卖理财品存陷阱 默认连投理财品无法撤单
  6. nodejs环境变量配置
  7. 各品牌机及独立主板、笔记本电脑的BIOS热键大全
  8. matlab实现在原图上勾画mask
  9. 【WOJ 4078】吃蛋糕
  10. 如何使用JS将两个数组合并为一个数组