美文网首页
代理模式

代理模式

作者: more2023 | 来源:发表于2020-06-11 10:56 被阅读0次

    一、什么是代理模式

    代理模式(Proxy pattern):代理模式又叫委托模式,是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是生活中的中介。

    二、为什么要使用代理模式

    1、中介隔离作用
    在某些情况下,一个客户类不想或者不能直接直接引用委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
    2、开闭原则,增加功能
    代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来拓展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类、以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的方法。真正的业务还是由委托类来实现,但是可以在业务功能执行前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要修改已经封装好的委托类。

    三、代理模式的分类

    我们有多种不同的方式来实现代理,如果按照代理创建的时期来进行分类的话,可以分为两种:静态代理、动态代理。

    3.1、静态代理

    静态代理是由程序员创建或特定工具自动生成源码,再对其进行编译。在程序运行之前,代理类.class文件就已经被创建了。

    3.2、动态代理

    动态代理是在程序运行时,代理类.class文件是通过反射动态创建的。
    动态代理实现的两种方式:

    • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvocationHandler来处理。
    • CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
      区别:
    • JDK代理只能对实现接口的类生成代理,而不能针对类;
    • CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法。因为是继承,所以该类或方法最好不要修饰为final。

    四、代理模式的结构

    代理模式结构图.png

    Subject(抽象主题角色):可以是抽象类,也可以是接口。抽象主题是一个普通的业务类型。
    RealSubject(具体主题角色):也叫做被委托角色或被代理角色,是业务逻辑的具体执行者。
    Proxy(代理主题角色):也叫做委托类或代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后处理工作。

    五、代码示例

    5.1、静态代理

    5.1.1、抽象主题类

    public interface Subject {
        /**
         * 接口方法
         */
        public void req();
    }
    

    5.1.2、具体主题类

    public class RealSubject implements Subject {
        /**
         * 具体的业务逻辑实现
         */
        @Override
        public void req() {
            //业务处理逻辑
        }
    }
    

    5.1.3、代理类

    public class Proxy implements Subject {
        /**
         * 要代理的实现类
         */
        private Subject subject = null;
    
        public Proxy(Subject subject) {
            this.subject = subject;
        }
        /**
         * 实现接口方法
         */
        @Override
        public void req() {
            this.preReq();
            this.subject.req();
            this.postReq();
        }
        /**
         * 预处理
         */
        private void preReq() {
            //do something
        }
        /**
         * 后处理
         */
        private void postReq() {
            //do something
        }
    }
    

    5.1.4、客户端类

    public class Client {
        public static void main(String[] args) {
            Subject subject = new RealSubject();
            Proxy proxy = new Proxy(subject);
            proxy.req();
        }
    }
    

    静态代理的优缺点
    1、优点

    • 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需要知道代理即可(解耦合)。
      2、缺点
    • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的重复代码。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加代码维护的复杂度。
    • 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。
    5.2、动态代理(JDK动态代理)

    5.2.1、java.lang.reflect.InvocationHandler接口

    public interface InvocationHandler {
        /**
         * @param   proxy   被代理对象
         * @param   method  要调用的方法
         * @param   args    方法调用时所需要的参数
         * @return  
         */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    

    5.2.2、java.lang.reflect.Proxy类

    public class Proxy implements java.io.Serializable {
    
        private static final long serialVersionUID = -2222568056686623797L;
    
        /** parameter types of a proxy class constructor */
        private static final Class<?>[] constructorParams =
            { InvocationHandler.class };
    
        /**
         * the invocation handler for this proxy instance.
         * @serial
         */
        protected InvocationHandler h;
    
        /**
         * Prohibits instantiation.
         */
        private Proxy() {
        }
    
        protected Proxy(InvocationHandler h) {
            Objects.requireNonNull(h);
            this.h = h;
        }
    
        /**
         * 获取被代理对象实例
         * @param   被代理对象的类加载器
         * @param   被代理对象实现的全部接口
         * @param   InvocationHandler接口的子类的实例(即代理类本身)
         * @return  
         */
        @CallerSensitive
        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    }
    

    5.2.3、抽象主题类

    public interface Subject {
        /**
         * 接口方法
         */
        public void req();
    }
    

    5.2.4、具体主题类

    public class RealSubject implements Subject {
        /**
         * 具体的业务逻辑实现
         */
        @Override
        public void req() {
            //业务处理逻辑
        }
    }
    

    5.2.5、代理类

    @Slf4j
    public class ProxyHandler implements InvocationHandler {
        /**
         * 目标对象
         */
        private Object target;
    
        /**
         * 绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
         * @param target 绑定具体的代理实例
         * @return 动态代理类实例
         */
        public Object newProxyInstance(Object target) {
            this.target = target;
            Object result = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
            return result;
        }
    
        /**
         * 关联的这个实现类的方法被调用时将被执行。InvocationHandler接口的方法。
         * @param proxy  代理
         * @param method 原对象被调用的方法
         * @param args   方法的参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //TODO 原对象方法调用前添加的预处理逻辑
            Object ret = null;
            try {
                //调用目标方法
                ret = method.invoke(target, args);
            } catch (Exception e) {
                log.error("调用{}.{}发生异常", target.getClass().getName(), method.getName(), e);
                throw e;
            }
            //TODO 原对象方法调用后添加的后处理逻辑
            return ret;
        }
    }
    

    5.2.6、客户端类

    @Slf4j
    public class Client {
        public static void main(String[] args) {
            log.info("开始");
            ProxyHandler handler = new ProxyHandler();
            Subject subject = (Subject) handler.newProxyInstance(new RealSubject ());
            subject.req();
            log.info("结束");
        }
    }
    

    5.2.7、生成的代理类

    public final class Subject$proxy0 extends Proxy implements Subject {
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $proxy4(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);
            }
        }
    
        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 void req() throws  {
            try {
                super.h.invoke(this, m3, (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"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m3 = Class.forName("com.fyb.myProxy.Subject").getMethod("req");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    动态代理(JDK方式)的优缺点
    1、优点

    • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke() )。这样无论接口如何增加,都可以灵活处理,而不需要像静态代理那样每一个方法进行中转。动态代理使我们的类职责更加单一,复用性更强。
      2、缺点
    • JDK动态代理无法摆脱仅支持接口的代码。因为动态生成的代理类的继承关系图,已经注定有一个共同的父类叫Proxy。Java的单继承机制注定了这些动态代理类们无法实现对class的动态代理。
    5.3、动态代理(CGlib动态代理)

    (待补充)

    相关文章

      网友评论

          本文标题:代理模式

          本文链接:https://www.haomeiwen.com/subject/vhzltktx.html