Java代理模式

作者: 星星_点灯 | 来源:发表于2018-04-14 15:53 被阅读21次

    什么是代理模式?

    其实代理和我们的生活息息相关,简单来说就是:中介,比如我们要租房子,我们找中介公司就可以了,比如链家,自如等,又比如我们去饭店吃饭,我们通过服务员点菜,而不是直接向厨师要等等;
    因此,代理模式的定义为:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
    从定义里我们知道代理中有三个角色:
    1、被代理对象(我)
    2、代理对象(中介、服务员)
    3、目标对象(房东、厨师)

    在我们的程序中,代理模式有三种角色:
    1、抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
    2、代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
    3、真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

    代理模式的类图如下:


    代理模式类图

    静态代理

    静态代理是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。
    举个例子,以去餐厅吃饭为例,首先定义一个抽象角色(接口)
    抽象角色

    public interface Subject {
        void eat();
    }
    

    真实角色

    public class Person implements Subject {
        @Override
        public void eat() {
            System.out.println("我饿了,我要吃饭");
        }
    }
    

    代理角色

    public class ProxyWaiter implements Subject {
        private Person person;
    
        public ProxyWaiter(Person p) {
            this.person = p;
        }
    
        @Override
        public void eat() {
            person.eat();
            System.out.println("我是服务员,我负责点菜");
        }
    }
    

    使用

      Person person=new Person();
      ProxyWaiter proxyWaiter=new ProxyWaiter(person);
      proxyWaiter.eat();
    

    控制台输出:


    静态代理输出

    静态代理比较简单,比较适用于控制对实际对象的访问,比如我们想在访问实际对象前或后加入其它操作或者过滤等,都可以用静态代理来实现,但是也会有一些问题,就是如果我们的代理的对象方法较多时,如果我们每一个方法前或后都插入相同操作的话,就会产生冗余代码,程序的可读性降低,同时也不利于维护,所以这是我们就要用到动态代理了;

    动态代理

    我们知道,静态代理是我们在java虚拟机编译之前就写好的类,而动态代理,代理类并不是在java代码中定义的,而是在运行时根据我们的代码“指示”动态生成的,相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
    我们还是以上面的例子为例,来看一下动态代理的实现过程
    Subject接口类和Person类和上例一样,动态代理的核心是要实现InvocationHandler接口,这个类随后会详细介绍,实现代码如下:

    public class RentingInvocationHandler<T> implements InvocationHandler {
    
        T target;//持有一个被代理的对象
    
        public RentingInvocationHandler(T t) {
            this.target = t;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是动态代理,代理执行" + method.getName() + "方法");
            method.invoke(target, args);
            return null;
        }
    }
    

    然后执行动态代理

    InvocationHandler waiterHandler = new RentingInvocationHandler<Subject>(person);
    Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, waiterHandler);
    subject.eat();
    

    执行结果:


    动态代理执行结果

    接下来我们来分析一下动态代理,动态代理的代理类创建分为4步:
    第一步:创建一个与代理对象相关联的InvocationHandler

    InvocationHandler subHandler = new RentingInvocationHandler<Subject>(person);
    

    也就是我们上例中的RentingInvocationHandler,它实现了InvocationHandler接口;
    InvocationHandler是调用处理器,每一个被代理的对象都与一个InvocationHandler关联,动态代理中动态生成的代理类,通过InvocationHandler,来调用实际对象的方法;

    第二步:使用Proxy类的getProxyClass方法生成一个动态代理类对象subjectProxyClass;

    Class<?> subjectProxyClass = Proxy.getProxyClass(Subject.class.getClassLoader(), new Class<?>[] {Subject.class});
    

    我们知道,jvm虚拟机在运行时通过读取磁盘上的类文件,在jvm内存中生成该类文件的Class对象,通过该Class对象我们可以通过反射来获取该类的构造函数等;所以以上方法就是在jvm内存中生成我们动态代理类的Class对象;所以下一步就是获取动态代理类的构造函数了;

    第三步:很明显,我们通过Class对象来直接获取动态代理对象的构造函数;

    Constructor<?> constructor = subjectProxyClass.getConstructor(InvocationHandler.class);
    

    第四步:通过构造函数,来创建一个动态代理对象实例subProxy;

    Subject subProxy = (Subject) cons.newInstance(subHandler);
    

    到此,一个动态对象就创建完毕,当然,上面的四个步骤java中已经为我们封装,我们直接使用一下代码即可

    InvocationHandler subHandler = new RentingInvocationHandler<Subject>(person);
    Subject subject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(), new Class<?>[]{Subject.class}, subHandler);
    

    下面为动态代理的原理图:


    动态代理

    接下来我们根据原理图再来总结一下动态代理的创建过程:
    1、首先我们定义个抽象角色即接口,然后创建一个实际对象来实现这个接口,即我们的被代理类(Person)
    2、创建一个InvocationHandler(调用处理器),即我们在上例中的RentingInvocationHandler,它实现了InvocationHandler接口,并在内部持有一个被代理类的实例;
    3、使用Proxy类的getProxyClass方法生成一个动态代理类对象subjectProxyClass,该Class对象主要用来获取动态代理类的构造器;
    4、通过上面的Class对象获取带InvocationHandler参数的构造器,因为我们在上述第2步已经创建了一个InvocationHandler,将我们创建好的InvocationHandler通过该构造器来构造生成动态代理对象,这样就创建了一个动态代理类$Proxy1;
    5、因为动态代理类$Proxy1中我们传入了RentingInvocationHandler,而我们的RentingInvocationHandler中又持有被代理类的实例,所以当我们使用动态代理类调用eat()方法时,是通过RentingInvocationHandler调用了它内部的核心方法invoke(),在invoke方法中我们将被代理对象的实例传进去,最终调用了被代理对象的方法,如上原理图所示;

    至此动态代理的原理就分析结束了,本文只对动态代理做了最上层的原理分析,并未深入源码层进行更深的研究,比如动态代理对象是如何生成,InvocationHandler的invoke方法是如何调用被代理类的方法,感兴趣的小伙伴可以自行去查看源码,本文主要目的是理解动态代理的原理,并为接下来的阅读Retrofit源码提供支持,感谢小伙伴们阅读;

    相关文章

      网友评论

        本文标题:Java代理模式

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