小伙伴们看过来!代理模式的原理原来这么好理解

深入理解设计模式之代理模式


各位小伙伴可能会经常听身边人或同事提起代理模式,什么是代理模式?使用代理模式的好处是什么?接下来,小刘同学将会为你一一梳理。如果文章不错,记得点个赞哦。

什么是代理模式

代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

概念太晦涩难懂了,翻译成人话其实就是:我想买酒,而现在刚好有一个制酒厂,我不能每次买酒都要去厂里面吧,于是我需要一个卖酒的柜台,我每次到柜台来买酒,柜台再负责和制酒厂进行联系。这就是代理模式。

代理模式的好处

看了上面这个例子,小伙伴们就能够知道代理模式的好处了,就是我(客户端)在每次买酒时(调用目标对象),不再需要直接和厂家(目标对象)打交道了,而是通过卖酒的柜台(代理对象),柜台(代理对象)再去和厂家(目标对象)沟通。我(客户端)并不关心非自身以外的事务。

如果不使用代理模式

在这里举一个例子,用代码来演示一下没有代理模式的弊端

首先,定义了一个接口,接口内的方法为移动。

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();
    }
}

“动”“静”,体现在哪里?

通过上面的讲解,相信小伙伴们对代理模式的理解也已经差不多了,这里再来总结一下,代理模式中的“动”和“静”,到底体现在哪里?

  • 静,指代理类与被代理对象的业务逻辑密切,往往不能实现对代理类的复用。

  • 动,指代理类与被代理对象的业务逻辑并没有太大关系,甚至毫无关系,可以重用代理类。

好了,这就是文章的全部内容了,如果对你有帮助,记得给个小小的赞,您的支持是我更新的动力!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值