zoukankan      html  css  js  c++  java
  • 设计模式 代理模式

    代理模式:为其他对象提供一种代理以控制对这个对象的访问 - 访问者通过代理对象间接访问被代理对象,使代理对象对被代理对象实现控制
    实现方式:(组合)控制类(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方法。

  • 相关阅读:
    axios,ajax,xhr 请求携带Cookie
    js中reduce的方法和去重
    H5图片预览
    网页唤起qq加群
    tab切换中嵌套swiper轮播
    CantOS的安装
    共享文件夹或文件
    Vue中,iframe中的子网页,调用父网页的方法
    禁止未使用的变量 ( `no-unused-vars`)
    vite + vue3 + ts搭建项目
  • 原文地址:https://www.cnblogs.com/kiqi/p/14037901.html
Copyright © 2011-2022 走看看