写点什么

从源码的角度搞懂 Java 代理模式,那些面试中你最容易忽略的细节

用户头像
极客good
关注
发布于: 刚刚
  • 收中介费

  • Process finished with exit code 0


在这个过程中,客户接触的是中介,看不到房东,但是依旧租到了房东的房子。同时房东省了心,客户省了事。


静态代理享受代理模式的优点,同时也具有代理模式的缺点,那就是一旦实现的功能增加,将会变得异常冗余和复杂,秒变光头。


为了保护头发,就出现了动态代理模式!


动态代理


====


动态代理的出现就是为了解决传统静态代理模式的中的缺点。


具备代理模式的优点的同时,巧妙地解决了静态代理代码冗余,难以维护的缺点。


在 Java 中常用的有如下几种方式:


  • JDK 原生动态代理

  • cglib 动态代理

  • javasist 动态代理


JDK 原生动态代理


=========


上例中静态代理类中,中介作为房东的代理,实现了相同的租房接口。


例子


首先实现一个 InvocationHandler,方法调用会被转发到该类的 invoke()方法。


然后在需要使用 Rent 的时候,通过 JDK 动态代理获取 Rent 的代理对象。



客户使用动态代理调用



运行结果和前例相同


上述代码的核心关键是 Proxy.newProxyInstance 方法,该方法会根据指定的参数动态创建代理对象。


它三个参数的意义如下:


  1. loader,指定代理对象的类加载器

  2. interfaces,代理对象需要实现的接口,可以同时指定多个接口

  3. handler,方法调用的实际处理者,代理对象的方法调用都会转发到这里


Proxy.newProxyInstance 会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给 InvocationHandler.invoke()方法。


因此,在 invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等等等等……


小结


==


显而易见,对于静态代理而言,我们需要手动编写代码让代理实现抽象角色的接口。


而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现抽象角色接口的代理,而不需要去单独定义这个类,代理对象是在程序运行时产生的,而不是编译期。


对于从 Object 中继承的方法,JDK Proxy 会把 hashCode()、equals()、toString()这三个非接口方法转发给 InvocationHandler,其余的 Object 方法则不会转发。


CGLIB 动态代理


=========


JDK 动态代理是基于接口的,如果对象没有实现接口该如何代理呢?CGLIB 代理登场


CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。


使用 cglib 需要引入 cglib 的 jar 包,如果你已经有 spring-core 的 jar 包,则无需引入,因为 spring 中包含了 cglib。



来看示例,假设我们有一个没有实现任何接口的类 Landlord:



因为没有实现接口,所以使用通过 CGLIB 代理实现如下:


首先实现一个 MethodInterceptor,方法调用会被转发到该类的 intercept()方法



客户通过 CGLIB 动态代理获取代理对象



运行输出结果和前例相同


对于从 Object 中继承的方法,CGLIB 代理也会进行代理,如 hashCode()、equals()、toString()等,但是 getClass()、wait()等方法不会,因为它是 final 方法,CGLIB 无法代理。


其实 CGLIB 和 JDK 代理的思路大致相同


上述代码中,通过 CGLIB 的 Enhancer 来指定要代理的目标对象、实际处理代理逻辑的对象。


最终通过调用 create()方法得到代理对象,对这个对象所有非 final 方法的调用都会转发给


MethodInterceptor.intercept()方法。


在 intercept()方法里我们可以加入任何逻辑,同 JDK 代理中的 invoke()方法


通过调用 MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是 Landlord 的具体方法。CGLIG 中 MethodInterceptor 的作用跟 JDK 代理中的 InvocationHandler 很类似,都是方法调用的中转站。


final 类型


=======


CGLIB 是通过继承的方式来实现动态代理的,有继承就不得不考虑 final 的问题。我们知道 final 类型不能有子类,所以 CGLIB 不能代理 final 类型,遇到这种情况会抛出类似如下异常:


java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete


同样的,final 方法是不能重载的,所以也不能通过 CGLIB 代理,遇到这种情况不会抛异常,而是会跳过 final 方法只代理其他方法。


其他方案


  • 使用 ASM 在被代理类基础上生成新的字节码形成


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


代理类


  • 使用 javassist 在被代理类基础上生成新的字节码形成代理类

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
从源码的角度搞懂Java代理模式,那些面试中你最容易忽略的细节