设计模式 - 代理模式

代理模式:为其他对象提供一种代理以控制对这个对象的访问 - 访问者通过代理对象间接访问被代理对象,使代理对象对被代理对象实现控制
实现方式:(组合)控制类(Proxy) + 执行逻辑接口(ISubject),由proxy封装了对subject的访问,在访问subject方法前后可以织入其他逻辑(功能附着)
核心:功能附着,Subject对象复杂本职任务,额外的逻辑交由代理对象,使subject职责相关的逻辑和其他逻辑解耦 - 本质就是:拦截

静态代理

// 代理模式 - (控制类 + 执行逻辑接口的组合)
// 在subject方法的前后织入额外的与subject职责无关的逻辑,使subject的业务逻辑和其他逻辑分离。
public class Proxy implements ISubject{
    // 静态代理:硬编码,手动注入,面向单一接口
    // 动态代理:更强的扩展性,自动生成新的代理类,和被代理类属于用一个继承体系。

    private ISubject subject = null;

    public Proxy(ISubject subject){
        this.subject = subject;
    }

    @Override
    public void work() {
        long startTime = System.currentTimeMillis();
        this.subject.work();
        System.out.println("接口耗时:" + (System.currentTimeMillis() - startTime));
    }
}

动态代理

JDK Proxy

  • Subject,作为被代理类,负责主要的业务逻辑
  • InvocationHandler,作为主要的控制类,负责织入额外的逻辑以及Subject对象的控制
  • Proxy类,在程序运行过程中,动态创建代理对象
  • ISubject接口的子类:动态生成的代理对象,其方法职责是:当被调用时,获取该方法对应的method对象和传入参数params,转交给InvocationHandler对象,由InvocationHandler通过反射调用真实的subject中的方法

JDK Proxy底层的实现原理:程序运行过程中,手写class字节码文件,并由类加载器进行加载和初始化。

// JDK Proxy,代理对象和被代理对象需实现同一个接口,通过接口的方法列表生成Method对象实现代理类到主题类方法之间的关联。
// JDK Proxy,采用反射实现subject方法的调用
public class Test {
    public static void main(String[] args) {
        // 主题类
        ISubject subject = new Subject();

        // 附加逻辑类(用于添加附加逻辑相关的代码)
        InvocationHandler handler = new MyInvocationHandler(subject);

        // 代理类(实现了主题类的接口,接口中方法的实现:获取此方法相关的信息和参数,传递给handler.invoke()方法
        ISubject proxy = (ISubject)Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),handler);

        // 1. 在work方法中获取到被调用方法的信息(Method对象和params),传递给hanlder
        // 2. handler 使用反射 Method.invoke(target,params),实现方法调用
        proxy.work();
    }
}
// InvocationHandler - 提供一个invoke方法,在invoke方法中完成额外逻辑的织入
public class MyInvocationHandler implements InvocationHandler {
    private Object subject = null;

    // 方案1 - 单一职责(由高层模块完成代理对象的创建 - Proxy.newProxyInstance())
    public MyInvocationHandler (Object subject){
        this.subject = subject;
    }

    // 方案2 - 将创建代理对象的职责与invoke的职责合并,高层只关心获取到的proxy对象。
    public MyInvocationHandler (){};

    public Object getProxy(Object subject){
        this.subject = subject;
        Class<?> clazz = subject.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before!");   // 此为织入前置逻辑
        Object result = method.invoke(this.subject,args);   // 反射
        System.out.println("after!");    // 此处织入后置逻辑
        return result;
    }
}
// $Proxy JDK实际在内存中生成的$Proxy0对象字节码反编译。
// 代理对象作用:收集调用对象和传入参数,并传递给invocationHandler对象。
public final class $Proxy0 extends Proxy implements ISubject {
    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  {
        try {
            // 参数2:该方法的method对象  参数3:该方法的调用参数 --- 只需method、被调用方法的实例对象和参数,即可调用实例的方法
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void work() 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  {
        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("top.kiqi.design.pattern.proxy.dynamic_proxy.jdk_proxy.ISubject").getMethod("work");
            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());
        }
    }
}

CGLib proxy

  • Subject,作为被代理类,负责主要的业务逻辑
  • MethodInterceptor,拦截器,负责织入额外的逻辑,然后通过回调MethodProxy.invokeSuper()完成被代理类的控制
  • Enhancer:在程序运行过程中,动态创建代理对象(ASM字节码框架)
  • Subject的实现子类:完成子类方法信息的采集,并调用MethodInterceptor.intercep()方法(参数:Method,args,MethodProxy)
  • MethodProxy:提供给MethodInterceptor的回调对象,会根据方法的信息查找并调用subject方法(非反射,是根据方法信息,通过if else执行的判断) --- 与jdk proxy的主要区别

CGLib Proxy底层的实现原理:程序运行过程中,通过ASM框架生成class字节码文件,并由类加载器进行加载和初始化。

// cglib 通过继承被代理类的方式实现代理。
public class Test {
    public static void main(String[] args) {
        Subject subject = (Subject) new CGlibInterceptor().getProxy(Subject.class);
        subject.work();
    }
}
public class CGlibInterceptor implements MethodInterceptor {
    // cglib创建逻辑,通过生成传入clazz的子类的字节码,实现proxy
    public Object getProxy(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    // intercept方法,加入前后的织入逻辑,然后回调methodProxy.invokeSuper()方法,invokeSupper()方法中会根据方法信息通过if else判断找到并执行subject对应的方法。
    // MethodProxy methodProxy: CGLIB$work$0$Proxy = MethodProxy.create(var1, var0, "()V", "work", "CGLIB$work$0");
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before!");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("after!");
        return result;
    }
}

对比 - JDK proxy 和 CGLib proxy

  1. JDK proxy通过实现同一个接口完成代理,而CGLib proxy通过继承被代理类完成代理
  2. JDK proxy对subject的调用是通过反射实现的,而CGLib proxy中根据方法信息通过if else匹配完成subject对应方法的查找和调用
  • JDK proxy: 代理类结构简单,创建效率更高
  • CGLib proxy: 代理类复杂,执行效率更高
  1. JDK proxy是硬手写字节码实现的,CGLib proxy借用ASM框架完成代理类字节码的生成

PS:CGLib proxy通过继承父类实现,因此无法代理到final方法。

posted @ 2020-11-25 19:16  祁奇  阅读(154)  评论(0编辑  收藏  举报