分享人: 元哥
1、代理模式
设计模式中的结构型设计模式
从字面意思理解,代理即A替B完成某件事。例如媒婆帮靓仔找一个广州的女朋友,那么媒婆就是代理方,代理的事情就是帮忙介绍男女朋友;靓仔就是委托方,委托的事情就是找一个广州的女朋友。
静态代理类图
Code实现
代理类和委托类实现了相同的接口,代理类和委托类实现了相同的方法。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
举个例子。上述的需求是靓仔要找个广州的女朋友,那现在假如靓仔还有个特殊的需求,要找个广州的男朋友,委托类Handsome和代理类Proxy都需要实现findBoyFriend()方法。
静态代理类只能为特定的接口服务。如想要为多个接口服务则需要建立很多个代理类。
如上的代码是只为 HandsomeMan 类的访问提供了代理,但是如果还要为其他类如 PrettyGril 类提供代理的话,就需要我们再次添加代理 PrettyGril 的代理类。
在举个开发中的例子:在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能。
当有新使用动态代理
1)使用静态代理,当HandsomeMan类中有新的方法例如findBoyFriend()需要代理,相应findBoyFriend()方法需要在MiddleProxy类中进行实现;而使用动态代理,在MiddleProxy中不需要实现
2)
![image-20191212212233267](/Users/cvter/Library/Application Support/typora-user-images/image-20191212212233267.png)
我们来对比静态代理和动态代理。在静态代理中target对象一个是MiddleProxy类的对象,而在动态代理中target对象是$Proxy0类的对象。
我们知道静态代理的MiddleProxy类是怎么做的,因为是我们声明的;但$Proxy0类原先是不存在,但是他是在调用newProxyInstance方法后生成,而方法中实现的是通过反射类Proxy.newProxyInstance。
public Object newProxyInstance(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } 复制代码
我们来通过特殊的方式,将$Proxy0类的字节码也就是class输出到文件,然后经过idea反编译得到以下
public final class $Proxy0 extends Proxy implements Target { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws {...} public final boolean equals(Object var1) throws {...} public final String toString() throws {...} public final void findGrilFriend() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws {...} static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("proxy.dynamic_proxy.Target").getMethod("findGrilFriend"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } } 复制代码
代码有点儿多我们不要慌。首先我们看到$Proxy0类继承Proxy,并实现Target接口,那么它肯定实现了findGrilFriend方法。
那么我们来大胆猜想,$Proxy0类是通过反射类Proxy.newProxyInstance()生成的,方法接收的参数就是Target接口对象,那么如果方法接收的参数是其他接口对象,例如BTarget接口对象,里面实现的方法findBoyFriend,那么$Proxy0类就会去实现BTarget接口的方法。因此可以认为,动态代理是静态代理的升级版,在程序运行过程中会动态根据传入的接口对象,动态生成指定接口对象的代理类$Proxy0的对象。
接下来,我们继续看findGrilFriend方法,调用的是 super.h.invoke(this, m3, (Object[])null); super.h 是 Proxy类的成员属性(InvocationHandler),在Proxy.newProxyInstance()生成$Proxy0会传this对象。
public $Proxy0(InvocationHandler var1) throws { super(var1); } 复制代码
public class MiddleProxy implements InvocationHandler { public Object newProxyInstance(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } .... } 复制代码
m3是声明的findGrilFriend()方法
总结一下过程:
类图
代理类
动态生成具体的代理类code
public class HandsomeMan$$EnhancerByCGLIB$$596495c6 extends HandsomeMan implements Factory { ...省略很多代码 final void CGLIB$findGrilFriend$0() { super.findGrilFriend(); } public final void findGrilFriend() { 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$findGrilFriend$0$Method, CGLIB$emptyArgs, CGLIB$findGrilFriend$0$Proxy); } else { super.findGrilFriend(); } } } 复制代码
总结一下流程;
1)首先我们通过newProxyInstance动态生成代理类HandsomeMan$$EnhancerByCGLIB$$和对象
public class MiddleProxy implements MethodInterceptor { public Object newProxyInstance(Object target){ //工具类 Enhancer enhancer=new Enhancer(); //设置被代理的对象,也可以理解为设置父类,因为动态代理类是继承了被动态代理类。 enhancer.setSuperclass(target.getClass()); //设置回调函数 enhancer.setCallback(this); //创建子类的动态代理类对象 return enhancer.create(); } .... } 复制代码
复制代码
2)在子类HandsomeMan$$EnhancerByCGLIB$$对象调用findGirlFriend方法时,会调用MiddleProxy类的intercept方法
3)在MiddleProxy类的intercept方法中,会在调用父类方法前后进行增强
public class MiddleProxy implements MethodInterceptor { .... public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("这里有很多单身的人.深圳、广州、东莞你找一个喜欢的。"); Object obj = methodProxy.invokeSuper(o, objects); System.out.println("给你喜欢的这个人的联系方式。"); return obj; } } 复制代码
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK 方式解决不了;
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
但是 CGLib 在创建代理对象时所花费的时间却比 JDK 多得多,所以对于单例的对象,因为无需频繁创建对象,用 CGLib 合适,反之,使用 JDK 方式要更为合适一些。 同时,由于 CGLib 由于是采用动态创建子类的方法,对于 final 方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。 缺点:技术实现相对难理解些。
注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如 hashCode()
、 equals()
、 toString()
等,但是 getClass()
、 wait()
等方法不会,因为它是final方法,CGLIB无法代理。