抽丝剥茧——代理设计模式

Posted MakerStack

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抽丝剥茧——代理设计模式相关的知识,希望对你有一定的参考价值。

代理设计模式

代理设计模式在生活中应该很常见了,现在各种中间商的货物代售方便了我们的生活也增加了我们生活的成本。这种生活中的中间商行为就是一种代理模式。

拿一个品牌来说明:

在编程领域中一般存在两种代理模式

  • 静态代理。(仅仅可以代理一个类的行为,不能随类的变化而变化)
  • 动态代理。(可以代理所有类的行为)

接下来我们先来看静态代理

1. 静态代理

仅仅用来代理一个类的行为。

代码演示一下:

  • 继承实现代理( 不推荐,耦合性大
class NaiKe {

    void run() {
        System.out.println("耐克");
    }
}


//代理类
class ShoesProxy extends NaiKe{
    @Override
    void run() {
        System.out.println("agency shoes before");
        super.run();
        System.out.println("agency shoes after");
    }
}
  • 组合实现代理(推荐)
class NaiKe{

    void run() {
        System.out.println("耐克");
    }
}

class ShoesProxy {

    NaiKe naiKe = new NaiKe();

    void run() {
        System.out.println("agency shoes before");
        naiKe.run();
        System.out.println("agency shoes after");
    }
}
  • 多态实现代理,多个代理嵌套
public class ProxyDesgin {
    public static void main(String[] args) {
        Shoes shoes = new ShoesProxy(new ShoesTimer(new NaiKe()));
        shoes.run();
    }
}

abstract class Shoes{
   abstract void run();
}

class NaiKe extends Shoes{

    @Override
    void run() {
        System.out.println("耐克");
    }
}

class Adi extends Shoes{
    @Override
    void run() {
        System.out.println("阿迪达斯");
    }
}

//代理类
class ShoesProxy extends Shoes {

    Shoes shoes ;

    public ShoesProxy(Shoes shoes){
        this.shoes = shoes ;
    }

    void run() {
        System.out.println("agency shoes before");
        shoes.run();
        System.out.println("agency shoes after");
    }
}


class ShoesTimer extends Shoes {

    Shoes shoes ;

    public ShoesTimer(Shoes shoes){
        this.shoes = shoes ;
    }

    void run() {
        System.out.println("log timer shoes before");
        shoes.run();
        System.out.println("log timer shoes after");
    }
}

画个图瞅瞅静态代理

抽丝剥茧——代理设计模式

这个就是静态代理,兄弟们应该已经发现了它的缺点,只能指定自己想要进行代理的类,而不能对所有的类进行代理,扩展性太差,所以引出了动态代理

2.动态代理

谈到动态代理,脑子里第一个出现的肯定就是Java动态代理了。我们先来聊一下Java动态代理。

2.1 Java动态代理

先来看一个动态代理的案例

NaiKe naiKe = new NaiKe();
        Shoes shoes = (Shoes) Proxy.newProxyInstance(NaiKe.class.getClassLoader(), new Class[]{Shoes.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("begin timer : " + System.currentTimeMillis());
                method.invoke(naiKe,args);
                System.out.println("after timer : " + System.currentTimeMillis());
                return null;
            }
        });
        shoes.run();
  • 第一个参数。 通过动态代理创建的对象被哪个加载器加载,一般使用本类的类加载器即可
  • 第二个参数。 被代理对象要实现的方法
  • 第三个参数。 被代理对象被调用的时候该如何处理逻辑

我们看一下动态代理的源码。

我们可以通过以下方式让JVM将动态生成的代理类保存到我们的项目中

  • JDK1.8使用 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  • JDK1.8以上可以使用 1 System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

生成的代理类如下:

final class $Proxy0 extends Proxy implements Shoes {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
    }

    public final void run() throws  {
        try {
            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  {
    }

    public final int hashCode() throws  {
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("desgin.proxy.Shoes").getMethod("run");
            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());
        }
    }
}

从这个类的结构中,我们可以看出很多的东西

  • 为什么说 JAVA动态代理仅仅只能代理接口。( 类单继承,代理对象默认继承Proxy类
  • 动态代理的第二个参数,接口内部的方法会被代理对象重写,然后调用第三个参数的 invoke方法。

上面两个也是动态代理的原理了。我们来仔细看一下我们的run()方法,也就是我们代理对象要实现的接口

public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
  • 调用了父类的 h,父类的 hInvocationHandler,然后调用了 invoke方法执行了我们的执行逻辑。

这个就是动态代理的全部实现过程

还有一个非常牛逼的点,它怎么生成的这个代理类。来看一下代理的全过程

抽丝剥茧——代理设计模式

图中的ASM就是为我们动态生成一个代理类的工具,它直接操作了Class字节码的二进制,然后创建了一个代理类,返回给我们。

Java动态代理就聊到这里了。下面看一看CGLIbAOP

2.2 CGLIB动态代理

弥补了Java动态代理的不足,CGLIB动态代理可以代理类。它直接创建了一个被代理对象的子类,实现了对其的代理过程。我们来看一下它的代理过程

//打印生成的代理对象,放置于当前项目下
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, ".");
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        //设置目标类的字节码文件
        enhancer.setSuperclass(Tank.class);
        //设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                methodProxy.invokeSuper(o,objects);
                return null;
            }
        });

        //这里的creat方法就是正式创建代理类
        Tank proxyDog = (Tank)enhancer.create();
        //调用代理类的eat方法
        proxyDog.tank();

还是和Java动态代理相似,传入一个需要代理的Class,设置代理的回调函数。然后调用create创建一个代理对象,调用代理对象的方法。

代理第一行可以输出代理对象,会生成三个代理对象。

抽丝剥茧——代理设计模式

查看中间那个,可以看到我们被代理对象的方法

public class Tank$$EnhancerByCGLIB$$a4ec679a extends Tank implements Factory {
    //构造方法
    public Tank$$EnhancerByCGLIB$$a4ec679a() {
        CGLIB$BIND_CALLBACKS(this);
    }
    //被代理方法
    final void tank() {
        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$tank$0$Method, CGLIB$emptyArgs, CGLIB$tank$0$Proxy);
        } else {
            super.tank();
        }
    }
}

在之前的CGLIB动态代理实现中,我们看到了拦截的回调中传入了四个参数,从上面的源码中可以看到对应参数的作用。

  • Object o代表生成的代理对象
  • Method method代表当前代理对象调用的方法
  • Object[] objects代表方法的参数
  • MethodProxy methodProxy我们调用方法的方法代理,它没有使用 Java本身的反射,而是动态生成一个新的类,(继承 FastClass),向类中写入委托类实例直接调用方法的语句。

我们可以看一下superinvoke的源码

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }

private static class FastClassInfo {
        FastClass f1;
        FastClass f2;
        int i1;
        int i2;

        private FastClassInfo() {
        }
    }

一个图理解CgLib动态代理过程

写了这么多,感觉对于代理设计模式讲解的篇幅不是很大,而是着重讲解了动态代理的实现方式。总的而言,代理设计模式与我们日常生活非常的接近,生活中的事物几乎都在被代理,所以这个设计模式应该很好懂,所以着重讲解了动态代理的实现方式。



以上是关于抽丝剥茧——代理设计模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式

马士兵:抽丝剥茧设计模式,3种Java模式(创建型+结构型+行为模式) (价值50元)

抽丝剥茧——原型设计模式

抽丝剥茧Reactor模式

抽丝剥茧!Source Generators原理讲解

Forge Viewer - 如何在场景中访问(或获取渲染/片段代理)克隆的网格?