java 代理模式详解

作者: 程序员徐公 | 来源:发表于2018-08-06 16:26 被阅读31次

    简介

    代理是什么?

    代理也称“委托”,分为静态代理和动态代理,代理模式也是常用的设计模式之一,具有方法增强、高扩展性的设计优势。

    代理的设计理念是限制对象的直接访问,即不能通过 new 的方式得到想要的对象,而是访问该对象的代理类。

    这样的话,我们就保护了内部对象,如果有一天内部对象因为某个原因换了个名或者换了个方法字段等等,那对访问者来说一点不影响,因为他拿到的只是代理类而已,从而使该访问对象具有高扩展性。

    然而,代理类可以实现拦截方法,修改原方法的参数和返回值,满足了代理自身需求和目的,也就是是代理的方法增强性。

    其实代理,就好比是我们日常生活中的海外代购人员一样.


    代理模式 UML

    说到代理,我们先来复习一下代理设计模式 UML:如下图

    image
    • Subject(抽象角色):声明真实对象和代理对象的共同接口;
    • ProxySubject(代理角色):代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
    • (RealSubject)真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。调用 RealSubject 的方法,都要经过 ProxySubject 进行代理。

    静态代理模式的实现

    一般来说,主要有以下几个步骤

    • 抽象一个接口 ISubject
    • 实现该接口 RealSubject
    • 创建代理对象类 ProxySubject
    • 客户端发起调用

    抽象一个接口 ISubject

    public interface ISubject {
        
        void doAction(String action);
    
    }
    

    实现该接口 RealSubject

    public class RealSubject implements ISubject {
        
        
        public void doAction(String action) {
            // TODO Auto-generated method stub
            System.out.println("I am RealSubject, do action "+ action);
        }
    
    }
    

    创建代理对象类 ProxySubject

    public class ProxySubject implements ISubject {
        
        ISubject mRealSubject;
        
        public ProxySubject(ISubject mRealSubject) {
            super();
            this.mRealSubject = mRealSubject;
        }
    
    
    
        public void doAction(String action) {
            // TODO Auto-generated method stub
            preRequest();
            mRealSubject.doAction(action);
            postRequest();
        }
    
    
    
        protected void postRequest() {
            // TODO Auto-generated method stub
            System.out.println("postRequest");
            
        }
    
        protected void preRequest() {
            // TODO Auto-generated method stub
            System.out.println("preRequest");
            
        }
    
    }
    

    客户端发起调用

        private static void testStatical() {
            RealSubject realSubject = new RealSubject();
            ProxySubject proxySubject = new ProxySubject(realSubject);
            proxySubject.doAction("play");
            
        }
    

    将会看到以下 log

    preRequest
    I am RealSubject, do action play
    postRequest
    

    java 动态代理的实现

    动态代理是指在运行时动态生成代理类。不需要我们像静态代理那个去手动写一个个的代理类。生成动态代理类有很多方式:Java动态代理,CGLIB,Javassist,ASM库等。这里主要说一下 Java 动态代理的实现。

    相关类介绍

    java 动态代理的实现,主要涉及到几个类

    • java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    static InvocationHandler getInvocationHandler(Object proxy) 
     
    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
     
    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 
     
    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
        InvocationHandler h)
    
    • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
    // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象
    // 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
    Object invoke(Object proxy, Method method, Object[] args)
    

    java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。

    实现步骤

    • 实现 InvocationHandler 接口,创建自己的调用处理器;
    • 为 Proxy 类指定 ClassLoader 对象和一组 interface ,从而来创建动态代理类;
    • 反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 
     
    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 
     
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 
     
    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
    

    实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下:

    • 实现 InvocationHandler 接口,创建自己的调用处理器;
    • 通过 Proxy.newProxyInstance 生成动态代理类实例
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 
     
    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
         new Class[] { Interface.class }, 
         handler );
    

    实现步骤

    • 创建 ISubject
    • 实现 RealSubject
    • 通过 Proxy.newInstance 生成动态代理对象
    public class DynamicProxyHandler implements InvocationHandler {
    
        private Object target;
    
        public DynamicProxyHandler(Object target) {
            this.target = target;
        }
    
        public <T> T getProxy() {
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("办事之前先收取点费用");
            System.out.println("开始办事");
            Object result = null;
            try {
                result = method.invoke(target, args);
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("办完了");
            return result;
        }
    
        
    
    }
    
    • 客户端调用
    ISubject subject = new RealSubject();
    ISubject proxy = new DynamicProxyHandler(subject).getProxy();
    proxy.doAction("play");
    

    最终将输出以下 log

    办事之前先收取点费用
    开始办事
    I am RealSubject, do action play
    办完了
    

    优缺点

    优点

    1)良好的扩展性。修改被代理角色并不影响调用者使用代理,对于调用者,被代理角色是透明的。

    2)隔离,降低耦合度。代理角色协调调用者和被代理角色,被代理角色只需实现本身关心的业务,非自己本职的业务通过代理处理和隔离。

    缺点

    1)增加了代理类,实现需要经过代理,因此请求速度会变慢。


    代理模式与装饰者模式的区别

    UML类图基本没区别,都是实现同一个接口,一个类包装另一 个类。 两者的定义:

    装饰器模式:能动态的新增或组合对象的行为
    在不改变接口的前提下,动态扩展对象的功能。关于装饰者模式的,可以参考我的这一篇博客 装饰者模式及其应用

    代理模式:为其他对象提供一种代理以控制对这个对象的访问
    在不改变接口的前提下,控制对象的访问

    装饰模式是“新增行为”,而代理模式是“控制访问”。关键就是我们如何判断是“新增行 为”还是“控制访问”。你在一个地方写装饰,大家就知道这是在增加功能,你写代理,大家就知道是在限制。


    相关推荐

    观察者设计模式 Vs 事件委托(java)

    装饰者模式及其应用

    建造者模式(Builder)及其应用

    二次封装图片第三方框架——简单工厂模式的运用

    Android 二次封装网络加载框架

    java 代理模式详解


    最后的最后,卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。


    image

    相关文章

      网友评论

        本文标题:java 代理模式详解

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