深入理解设计模式之代理模式
各位小伙伴可能会经常听身边人或同事提起代理模式,什么是代理模式?使用代理模式的好处是什么?接下来,小刘同学将会为你一一梳理。如果文章不错,记得点个赞哦。
什么是代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
概念太晦涩难懂了,翻译成人话其实就是:我想买酒,而现在刚好有一个制酒厂,我不能每次买酒都要去厂里面吧,于是我需要一个卖酒的柜台,我每次到柜台来买酒,柜台再负责和制酒厂进行联系。这就是代理模式。
代理模式的好处
看了上面这个例子,小伙伴们就能够知道代理模式的好处了,就是我(客户端)在每次买酒时(调用目标对象),不再需要直接和厂家(目标对象)打交道了,而是通过卖酒的柜台(代理对象),柜台(代理对象)再去和厂家(目标对象)沟通。我(客户端)并不关心非自身以外的事务。
如果不使用代理模式
在这里举一个例子,用代码来演示一下没有代理模式的弊端
首先,定义了一个接口,接口内的方法为移动。
public interface Movable {
void move();
}
新建一个坦克类,实现了此接口。
public class Tank implements Movable {
@Override
public void move() {
System.out.println("坦克移动中,claclacla");
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此时我想要记录坦克移动的方法的执行时间,作为我判断程序性能的指标,如果没有代理模式,我应该怎么做呢?
我可以使用继承的方式,新建一个类继承Tank类,然后实现记录方法执行时间的需求。
/**
* 想要记录坦克的移动时间,演示错误的方法,
* */
public class WrongWay extends Tank{
@Override
public void move() {
long start = System.currentTimeMillis();
super.move();
long end = System.currentTimeMillis();
System.out.println("移动共耗费了:"+(end-start)+"秒");
}
public static void main(String[] args) {
WrongWay wrongWay = new WrongWay();
wrongWay.move();
}
}
代码看起来并没有问题,而且也达到了需求,但真的没有问题吗?有,而且很大!
如果现在产品经理给你新增了一个需求,让你记录一下Tank类中move()方法的日志,你就要接着创建一个类,继承Tank然后在类中实现相关逻辑。产品经理又提了一个需求。。。。。在可爱的产品经理提了无数个需求后,可怕的事情发生了,我们会发现此时项目代码会变得十分繁杂,而且耦合度极高。改动一处,上千行代码也要跟着改。这时,代理模式就来拯救我们了。
静态代理
静态代理就是在程序运行前,我们就指定好代理类和委托类关系了。还是上面坦克的例子,接口和Tank类没有变,这里就不再贴出来了。
/**
* 演示记录移动时间的正确做法,静态代理
* */
public class RightWay implements Movable {
Movable movable;
public RightWay(Movable movable) {
this.movable = movable;
}
@Override
public void move() {
long start = System.currentTimeMillis();
movable.move();
long end = System.currentTimeMillis();
System.out.println("静态代理:移动共耗费了:"+(end-start)+"秒");
}
public static void main(String[] args) {
RightWay rightWay = new RightWay(new Tank());
rightWay.move();
}
}
现在有一个Car类同样实现了Movable接口。
public class Car implements Movable {
@Override
public void move() {
System.out.println("汽车移动中,引擎轰鸣声");
try {
Thread.sleep(new Random().nextInt(20000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们想要用RightWay这个代理类去记录一下Car中move方法的执行时间,我们要怎么做呢?要去修改RightWay的源代码。
//修改RightWay类中的main方法
public static void main(String[] args) {
RightWay rightWay = new RightWay(new Car());
rightWay.move();
}
如果现在有新增了一个飞机Plane类,我们还要去修改RightWay的源代码。每次都要修改源码,很麻烦,耦合度太高。有没有一种好的办法可以不去修改代理类的源码就可以实现呢?答案就是动态代理。
动态代理
动态代理就可以不用在代码运行前去指定代理类和委托类的关系了,而是在运行时确定的。代理类并不关心具体的业务逻辑。这样的话,不管你是汽车还是飞机,不需要每次去修改代理类的源码就可以实现类的复用了。
JDK动态代理
JDK动态代理一般用于客户端实现了接口的情况下,它有一定的局限性,对于没有实现接口的对象无能为力,我们接着上面坦克的例子讲。
/**
* 动态代理:对代理类进行复用
*/
public class Tank implements Movable {
@Override
public void move() {
System.out.println("坦克移动中,claclacla");
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Tank tank = new Tank();
Movable m = (Movable) Proxy.newProxyInstance(
tank.getClass().getClassLoader(),
new Class[]{Movable.class},
new TimeProxy(tank)
);
m.move();
}
}
记录方法执行时间的代理类
public class TimeProxy implements InvocationHandler {
Object object;
public TimeProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object o = method.invoke(object, args);
long end = System.currentTimeMillis();
System.out.println("jdk动态代理=======>此方法共耗费了:" + (end - start)/1000 + "秒");
return o;
}
}
运行结果:
坦克移动中,claclacla
jdk动态代理=======>此方法共耗费了:4秒
假设业务经理此时提了一个需求,新增一个飞行接口和飞机类,并且也要记录飞机的方法执行时间,要怎么做呢?
/**
* 定义一个飞行接口
* */
public interface Fly {
void fly();
}
/**
* 飞机类,实现飞行接口
* */
public class Plane implements Fly {
@Override
public void fly() {
System.out.println("飞机正在飞行");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Plane plane = new Plane();
Fly fly = (Fly) Proxy.newProxyInstance(plane.getClass().getClassLoader(),
new Class[]{Fly.class},
new TimeProxy(plane));
fly.fly();
}
}
可以看到,使用了动态代理之后可以对代理类进行复用,相比于静态代理中每个代理类都有自己固定的业务逻辑,动态代理就灵活的多了。那么JDK动态代理是怎么做到可以灵活使用的呢?我们在调用Proxy.newInstance()
方法前加上一行代码System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
ps:使用的JDK版本为1.8,更新的版本可能不会生效。
由于使用JDK动态代理时,生成的代理类是在内存中的,我们是看不见的,因此加上这行代码将动态生成的代理类保存下来。
保存后的.class文件会在这个位置,下面就来看看它究竟是何方神圣。
/**在坦克类中,调用 Movable m = (Movable) Proxy.newProxyInstance(
tank.getClass().getClassLoader(),
new Class[]{Movable.class},
new TimeProxy(tank)
);
时动态生成的代理类
**/
//该代理类对应的其实就是m,m==$Proxy0
public final class $Proxy0 extends Proxy implements Movable {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
//var1对应的就是实现了InvocationHandler接口的子类
//也就是TimeProxy
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//重点在这里,代理类的move()方法
public final void move() throws {
try {
//调用父类中的invocationHandler的invoke方法
//也就是TimeProxy的invoke()方法
//m3对应的就是Movable接口中的move()方法
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.lxy.proxy.Movable").getMethod("move");
m2 = Class.forName("java.lang.Object").getMethod("toString");
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中move()方法的super.h.invoke()
传入m3,也就是接口类的move()方法?我把TimeProxy中重写的invoke()方法部分参数替换了一下,方便小伙伴们查看。
@Override
public Object invoke(Object $proxy0, Method m3, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object o = m3.invoke(tank, args);
long end = System.currentTimeMillis();
System.out.println("jdk动态代理=======>此方法共耗费了:" + (end - start)/1000 + "秒");
return o;
}
Method类中的invoke()方法作用是调用实现了此方法的子类的方法。也就是说,m3.invoke()调用的就是实现了Movable接口中move()方法的Tank类中的move()方法。这样,JDK动态代理是怎么调用Tank中的move()方法也就可以说通了。
CGLIB动态代理
对比于JDK动态代理能够代理的对象必须实现接口这一短板,CGLIB代理显得要比JDK代理灵活性更高,使用CGLIB代理的对象并不需要实现接口,不过被代理的对象类不能用final
修饰,为什么这么说?往下看你就懂了。
还是来个坦克类:
public class Tank {
public void move() {
System.out.println("坦克移动中,claclacla");
try {
Thread.sleep(new Random().nextInt(20000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用cglib的代理类
class LogInterceptor implements MethodInterceptor {
/**
*
* 参数说明
* @param obj "this", 代理类
* @param method 拦截的方法
* @param args 参数数组;
* @param proxy 被用来调用父类(没有被拦截的方法)
*
*
* */
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("坦克准备移动。。。");
Object result = null;
result = proxy.invokeSuper(obj, args);
System.out.println("坦克结束移动。。");
return result;
}
}
测试类,实现对Tank类的move()方法进行日志的记录。
public class CglibProxy {
public static void main(String[] args) {
//Enhancer 增强者
Enhancer enhancer = new Enhancer();
// 将enhancer的父类设置为Tank类,这里有点意思了,为什么要把enhancer的父类设置为Tank呢,难道是通过继承的方式实现日志记录的功能?
enhancer.setSuperclass(Tank.class);
//为enhancer设置回调函数为我们写的日志记录的功能类LogInterceptor
enhancer.setCallback(new LogInterceptor());
Tank tank = (Tank) enhancer.create();
//在这里输出一下,证明我们的猜想
System.out.println(tank.getClass().getSuperclass().getName());
tank.move();
}
}
输出结果为:
com.lxy.proxy.dynamic.cglib.Tank
坦克准备移动。。。
坦克移动中,claclacla
坦克结束移动。。
输出结果的第一行证明了我们的猜想,使用enhancer.create()生成的类果然是Tank的子类。光看到这个还不够,还要更深入一点。
使用arthars对生成的代理类进行反编译,结果代码如下(部分代码已省略,感兴趣的小伙伴可以自己去调试)
public class Tank$$EnhancerByCGLIB$$7d8f62e0
extends Tank
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$move$0$Method;
private static final MethodProxy CGLIB$move$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;
//设置回调,这里的callback对应的就是我们的LogInterceptor
@Override
public void setCallback(int n, Callback callback) {
switch (n) {
case 0: {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
break;
}
}
}
//重写的move方法
@Override
public final void move() {
//methodInterceptor就是LogInterceptor
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
if (methodInterceptor == null) {
Tank$$EnhancerByCGLIB$$7d8f62e0.CGLIB$BIND_CALLBACKS(this);
methodInterceptor = this.CGLIB$CALLBACK_0;
}
//如果methodInterceptor不为空,就要对我们实现了MethodInterceptor接口的LogInterceptor的intercept方法设置相关参数
if (methodInterceptor != null) {
//第一个this,也就是代理类,指本类Tank$$EnhancerByCGLIB$$7d8f62e0
//第二个CGLIB$move$0$Method,就是要拦截的方法
//第三个为拦截的方法参数
//第四个参数的作用是被用来调用父类没有被拦截的方法,也就是父类Tank的move方法
//到此结束
Object object = methodInterceptor.intercept(this, CGLIB$move$0$Method, CGLIB$emptyArgs, CGLIB$move$0$Proxy);
return;
}
super.move();
}
}
“动”“静”,体现在哪里?
通过上面的讲解,相信小伙伴们对代理模式的理解也已经差不多了,这里再来总结一下,代理模式中的“动”和“静”,到底体现在哪里?
-
静
静,指代理类与被代理对象的业务逻辑密切,往往不能实现对代理类的复用。
-
动
动,指代理类与被代理对象的业务逻辑并没有太大关系,甚至毫无关系,可以重用代理类。
好了,这就是文章的全部内容了,如果对你有帮助,记得给个小小的赞,您的支持是我更新的动力!