分享人: 元哥
1、代理模式
-
设计模式中的结构型设计模式
-
从字面意思理解,代理即A替B完成某件事。例如媒婆帮靓仔找一个广州的女朋友,那么媒婆就是代理方,代理的事情就是帮忙介绍男女朋友;靓仔就是委托方,委托的事情就是找一个广州的女朋友。
1)、静态代理
-
静态代理类图
-
Code实现





- 在这里我们会先声明一个Target接口,声明的方法就是findGirlFriend。
- 然后声明一个类Handsome表示靓仔,实现Target接口的方法findGirlFriend
- 接着声明一个类MiddleProxy表示媒婆,持有一个目标接口Target的对象,并且实现Target接口的方法。在这个方法中调用持有对象的方法的前后进行增强。增强的效果就是本来靓仔得自己一个人去做找女朋友这件事;但有了媒婆就先让她展示她的资源,然后靓仔找一个自己喜欢的广州的女朋友,最后媒婆给联系方式。
优点
- 在调用的时候我们不用知道MiddleProxy是如何实现的,只需要知道被代理即可。对例子而言,靓仔不需要知道媒婆这个代理是怎么帮忙找女朋友的,反正有了媒婆就能找到女朋友。
缺点
-
代理类和委托类实现了相同的接口,代理类和委托类实现了相同的方法。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
-
举个例子。上述的需求是靓仔要找个广州的女朋友,那现在假如靓仔还有个特殊的需求,要找个广州的男朋友,委托类Handsome和代理类Proxy都需要实现findBoyFriend()方法。
-
-
静态代理类只能为特定的接口服务。如想要为多个接口服务则需要建立很多个代理类。
-
如上的代码是只为HandsomeMan类的访问提供了代理,但是如果还要为其他类如PrettyGril类提供代理的话,就需要我们再次添加代理PrettyGril的代理类。
-
在举个开发中的例子:在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能。
-
2)、动态代理
a)、jdk动态代理
- 动态代理类图

- Code实现





- 同样的使用前面的例子,需求是靓仔让媒婆帮忙找女朋友。因此Target接口、Handsome类不变。
- 但在实现MiddleProxy媒婆这个类的时候,会去实现InvocationHandler这个类,实现的方法就是invoke方法和一个newProxyInstance方法。newProxyInstance是通过Java的反射类Proxy生成一个参数传进来对象的代理;invoke方法就是成员属性target中的所有成员方法进行增强。(可能你们会有很多疑问点,会在后面一一描述解决)
解决静态代理的问题
-
当有新使用动态代理
- 对比类图
-
1)使用静态代理,当HandsomeMan类中有新的方法例如findBoyFriend()需要代理,相应findBoyFriend()方法需要在MiddleProxy类中进行实现;而使用动态代理,在MiddleProxy中不需要实现
-
2)

- 使用静态代理。当有新的委托方时,例如靓女也需要找媒婆介绍男朋友,这时候有个PrettyGirl类,那么由于先前的Target接口类中声明的方法是findGrilFriend, 而我们需要的是findBoyFriend,Target接口不再适用,因此会导致MidlleProxy方法不再适用,需要新建新的目标接口和代理类来满足需求。
- 使用动态代理,newProxyInstance无论传进来的对象是什么,持有的对象始终是Object,就只需要再写新的接口,不要需要再写新的代理类。
JDK动态代理是怎么实现的


-
我们来对比静态代理和动态代理。在静态代理中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()方法
-
总结一下过程:
- a)经过反射类Proxy.newProxyInstance()生成的了$Proxy0类和对象,并且$Proxy0对象持有MiddleProxy对象。
- b)当调用findGirlFriend方法,调用的是$Proxy0类中的方法,super.h.invoke()
- c)会再调用MiddleProxy类中的invoke方法,由于动态的传进来的HandsomeMan对象,其中会调用Object obj = method.invoke(target, args);
-
优缺点
- 优点:解决了静态代理中冗余的代理实现类问题。
- 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常
b)、cglib动态代理
-
类图
-
代理类
-
动态生成具体的代理类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无法代理。