java代理模式_静态代理与动态代理

最近在研究SpringAOP,当然要学习AOP就要知道这么健硕、强大的功能的背后究竟隐藏着怎样不可告人的“秘密”?

接下来就是查阅了许多资料详细的研究了一下Java的代理模式,感觉还是非常非常重要的,我们作为一个有“内涵的”程序员就更应该掌握啦!(本文需要细心、带有审视的目光来甄别其中的内容)

在学习代理模式的时候我首先要提出几个问题:

1、什么是代理模式?

举个例子吧:我们生活中的租房问题。假如我们去租个房子,我们大多数情况下是不会知道房主(就是真正租房,一手货源)的,我们是不是都是先去某些租房平台,或者去找当地的中介去询问何时的房子。我们通过九牛二虎之力在中介那里找到了个物美价廉的房子后,你的租金是不是交给了中介,中介还会收取一些额外的推荐费啦,押金啦、手续费等之类的,那么好,这样的一小段,就已经出来了其中两大核心对象了。

房主(把房子交给中介的人):被代理对象

中介(租给你房子的人):代理对象

2、代理模式有哪些作用? 

1.可以隐藏目标的的具体实现(还是拿上面租房的例子来说,房主把房子交给了中介,并和中介谈好了价格我 7你3啊。然后当我们去租房子的时候,是中介正面把房子租给了我们,而真正背后卖房子的并未出面,这样就隐藏了背后人的信息和提高了背后人的安全)

2.可以在不修改目标类代码的情况下,对其增加新的功能。(上面例子来说:房东把房子交给中介的时候价格可能只有1000,但是房东可以卖到5000,然后卖出去后在把1000给房东,自己收入4000,这样原房东不但收到了应由的钱,中介还收入了更多的额外费用)。

3、代理模式有哪几种?分别都有什么不同?

    

在我们Java程序中代理模式分为:静态代理和动态代理(动态代理又分为:JDK动态代理和CGLIB动态代理)

至于什么不同,接下来正式我们着重要学习的内容

 

静态代理

什么是静态代理呢?

顾名思义,就是静态的,死的,一旦创建了就不允许修改了或者说很难修改(指的是程序运行期间)

专业一些的解释

如若代理类在程序运行之前就已经存在,那么这种代理方式被称为静态代理。这种情况下的代理类通常都是我们在Java代码中定义的。

通常情况下静态代理类和目标类通常都会实现同一个接口或者派生自同一父类  

具体实现:

被代理类与代理类共同实现的接口

package cn.arebirth.staticproxy;/** * 需要实现的共同接口 * 因为要保证代理类要不改变被代理类原来功能的基础上增加新的功能 */public interface RentalHouse {    /**     * 出租房子     */    void rent();}

被代理类(房东)

package cn.arebirth.staticproxy;/** * 房东(目标类、被代理类) */public class Host implements RentalHouse {    @Override    public void rent() {        System.out.println("我是房东,出租500平米的大房子");    }}

代理类(中介)

package cn.arebirth.staticproxy;/** * 静态代理类(中介、代理类) * 注意:需要被代理类实现相同的接口 */public class StaticProxy implements RentalHouse {    private RentalHouse rentalHouse;    public StaticProxy(RentalHouse rentalHouse) {        this.rentalHouse = rentalHouse;    }    @Override    public void rent() {        System.out.println("我是中介,收你500推荐费");        //调用被代理类的租房方法        rentalHouse.rent();        System.out.println("我是中介,我又想收你1000块钱!");    }}

测试类

package cn.arebirth.staticproxy;public class Test {    public static void main(String[] args) {        //创建被代理度下行        Host host = new Host();        /**         * 创建代理对象,并把被代理对象传递给代理对象,         * 因为他们都实现相同的接口,实现了相同的方法,这样的话传递的对象可以是房东1 房东2 ...         */        StaticProxy proxy = new StaticProxy(host);        proxy.rent();    }}输出结果:    我是中介,收你500推荐费        我是房东,出租500平米的大房子    我是中介,我又想收你1000块钱!

试想一下,如果有两个房东,三个,四个,甚至更多个房东的话,我们怎么写?

 

被代理类

package cn.arebirth.staticproxy;/** * 房东1(目标类、被代理类) */public class Host1 implements RentalHouse {    @Override    public void rent() {        System.out.println("我是房东1,出租500平米的大房子");    }}package cn.arebirth.staticproxy;/** * 房东2(目标类、被代理类) */public class Host2 implements RentalHouse {    @Override    public void rent() {        System.out.println("我是房东2,出租500平米的大房子");    }}

代理类(中介)

package cn.arebirth.staticproxy;/** * 静态代理类(中介、代理类) * 注意:需要被代理类实现相同的接口 */public class StaticProxy implements RentalHouse{    //什么价位的房子    private int moneuy;    public Agent(int moneuy) {        this.moneuy = moneuy;    }    @Override    public void renting() {        //出租房东的租房        //中介调用的租房方法仍然是房东的租房方法        System.out.println("收取50元推荐费");        if (moneuy <= 800) {//金额小于等于800的时候            Host1 host = new Host1();            host.rent();        } else {            Host2 host = new Host2();            host.rent();        }        System.out.println("收取500元押金费用");    }}

测试类

public class Test{    public static void main(String[] args) {        StaticProxy proxy= new StaticProxy(1000);        proxy.renting();    }}输出结果:   我是房东2,出租500平米的大房子

静态代理的缺点:

我们仔细来观察下,随着我们的被代理对象的增多,也是就是房东越来越多,那我们的被代理类就会越来越冗余,中介的压力也就会越来越大。

 

动态代理

常用的动态代理又分为JDK动态代理和CGLIB动态代理

那么两者的使用场景又是什么呢?

如果目标对象实现了接口,就是上面我们举到的例子,房东和中介共同实现的接口类似,这样的话就采用JDK动态代理,如果目标对象没有实现接口,必须采用CGLIB动态代理

 

具体实现:

 

要实现的共同接口

package cn.arebirth.jdkproxy;/** * 需要实现的共同接口 */public interface RentalHouse {    /**     * 出租房子     */    void rent();}

房东(被代理对象)

package cn.arebirth.jdkproxy;/** * 房东(目标类、被代理类) */public class Host implements RentalHouse {    @Override    public void rent() {        System.out.println("我是房东,出租500平米的大房子");    }}

核心来了!(JDK动态代理实现类)package cn.arebirth.jdkproxy;

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * JDK动态代理类 */public class JdkProxy implements InvocationHandler {  //接收要被代理的类    RentalHouse rentalHouse;    public JdkProxy(RentalHouse rentalHouse) {        this.rentalHouse = rentalHouse;    }    /**     * @return 执行该方法就会产生代理类     */    public Object getProxy() {        /**         * Proxy.getProxyInstance(param1,param2,para3);         * 参数一:传入一个classLoader()对象         * 参数二:传入被代理类所实现的接口的数组,因为代理类底层要根据接口产生         * 参数三:参数类型是InvocationHandler即可,this即当前类,我们当前类实现了此接口         */        Object proxy = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{RentalHouse.class}, this);        return proxy;    }    /**     * 这个方法类似于上面我所讲述的静态代理模式里面的中介类的rent()方法     * @param proxy  代理对象--中介     * @param method 代理对象中的方法     * @param args   代理对象方法中的参数     * @return     * @throws Throwable     */    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        System.out.println("我是中介,收你500推荐费");        Object invoke = method.invoke(rentalHouse, args);        System.out.println("我是中介,我又想收你1000块钱!");        //返回执行方法后的返回值        return invoke;    }}

测试类

package cn.arebirth.jdkproxy;public class Test {    public static void main(String[] args) {        //创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去        JdkProxy jdkProxy = new JdkProxy(new Host());        //获得代理类 这里一定要写他们共同实现的接口  利用java多态的特性,如果直接写真实类型是会报错的        RentalHouse  proxy= (RentalHouse)jdkProxy.getProxy();        proxy.rent();    }}输出结果:  我是中介,收你500推荐费  我是房东,出租500平米的大房子  我是中介,我又想收你1000块钱!

我想当你看到这里一定会感到困惑,为什么我们获取到了代理对象后执行的执行代理对象的方法,明明是房东的方法,怎么显示的好像是JDK动态代理类里面的invoke()方法??或许还有其他的困惑,我们将在下边一一讲解如何实现的原理。

既然想知道如何实现的,那么我们就要从底层出发,来看看,底层的Proxy代理到底帮我们生成了一个怎样的代理类。

再开始之前我希望接下来的代码与操作你是跟着我同步进行的,这样子才会更深刻有更好的理解,当然不排除你是个人脑机器模拟器

开始吧!

 

我们想要看底层如何实现的,那么我们首先就要获得代理类的class文件

下面是我写的一个获取JDKProxy动态代理所生成的代理文件的工具类

package cn.arebirth.jdkproxy;import sun.misc.ProxyGenerator;import java.io.File;import java.io.FileOutputStream;import java.io.FileWriter;import java.io.IOException;public class ProxyUtil {    /**     * @param proxyName  生成文件代理类的名字     * @param interClass 代理类所实现的接口的class数组形式     * @param path       写出路径     */    public static void writeProxyClassToHardDisk(String proxyName, Class[] interClass, String path) {        byte[] bytes = ProxyGenerator.generateProxyClass(proxyName, interClass);        FileOutputStream out = null;        try {            out = new FileOutputStream(path + File.separator + proxyName+".class");            out.write(bytes);            out.flush();        } catch (IOException e) {            e.printStackTrace();        } finally {            try {                if (out != null)                    out.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }}

然后我们在测试类里面使用它

package cn.arebirth.jdkproxy;public class Test {    public static void main(String[] args) {        //创建JdkProxy动态代理对象类,并把需要被代理的对象传递进去        JdkProxy jdkProxy = new JdkProxy(new Host());        //获得代理类        RentalHouse proxy = (RentalHouse) jdkProxy.getProxy();        proxy.rent();        //写入JDK动态代理生成的代理类        ProxyUtil.writeProxyClassToHardDisk(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces(), "F:\\Tools\\DevelopmentTools");    }}

最后生成出来的代理类是一个class字节码文件,我们需要使用反编译工具来查看,我使用的是Luten(这个工具打开方式特殊,百度自行查找)

import cn.arebirth.jdkproxy.*;import java.lang.reflect.*;public final class $Proxy0 extends Proxy implements RentalHouse{    private static Method m1;    private static Method m3;    private static Method m2;    private static Method m0;    public $Proxy0(final InvocationHandler invocationHandler) {        super(invocationHandler);    }    public final boolean equals(final Object o) {        try {            return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });        }        catch (Error | RuntimeException error) {            throw;        }        catch (Throwable t) {            throw new UndeclaredThrowableException(t);        }    }    public final void rent() {//这个就是编译器自动为我们实现了的接口中的方法        try {            super.h.invoke(this, $Proxy0.m3, null); //并调用了invoke        }        catch (Error | RuntimeException error) {            throw;        }        catch (Throwable t) {            throw new UndeclaredThrowableException(t);        }    }    public final String toString() {        try {            return (String)super.h.invoke(this, $Proxy0.m2, null);        }        catch (Error | RuntimeException error) {            throw;        }        catch (Throwable t) {            throw new UndeclaredThrowableException(t);        }    }    public final int hashCode() {        try {            return (int)super.h.invoke(this, $Proxy0.m0, null);        }        catch (Error | RuntimeException error) {            throw;        }        catch (Throwable t) {            throw new UndeclaredThrowableException(t);        }    }//在类第一次加载的时候,给全局变量进行初始化,看看里面有没有我们稍微眼熟的东西    static {        try {            $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));            $Proxy0.m3 = Class.forName("cn.arebirth.jdkproxy.RentalHouse").getMethod("rent", (Class<?>[])new Class[0]);//这里就是我们所实现的接口中的方法            $Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);            $Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);        }        catch (NoSuchMethodException ex) {            throw new NoSuchMethodError(ex.getMessage());        }        catch (ClassNotFoundException ex2) {            throw new NoClassDefFoundError(ex2.getMessage());        }    }}

在看到这里的时候,我们大概能了解到,原来我们在使用我们代理类调用租房的方法的时候,它里面并没有我们写的代码而是执行了一个

super.h.invoke(this, $Proxy0.m3, null);

那么super是谁?就是它自动继承的Proxy类

那么h是什么?就是Proxy类的InvocationHandler

 

那么我们看这个InvocationHandler类是不是感觉有那么一丢丢的眼熟啊,回过头看,这就是我们在写JDK动态代理类的时候实现的那个接口。

然后它又调用了invoke(this, $Proxy0.m3, null);

第一个参数:代理类对象

第二个参数:要执行的代理类对象的方  Method对象,这个Method对象的值已经由我们的编译器帮我们构建好了,我们只需要第一个次加载这个类的时候他就会自动赋值了(static代码块)

第三个参数:方法里面的参数,当没有参数的时候就是NULL

 

带着你的疑惑,从头在开始好好敲一遍代码,捋一遍,相信我,你将有更大的收获!

下面是简略画的思路图,试着用画图来画出你的思维

 

那么说完了这个JDK动态代理,我们有没有发现什么缺点??找茬时间开始

是不是这个JDK动态代理必须要依赖接口才能实现,如果没有接口的话,那么JDK动态代理也就凉凉了对吧!

 

那么好,接下来我们将说一种更加强大的动态代理方式CGLIB,它的实现原理只要我们懂了JDK动态代理,那么下面的就是小儿科啦哈哈~

 

CGLIB的代理将不再需要接口也可以生成代理类,但是它需要导包!

 

当然,你也可以选择其他版本的JAR包!

开始

 

被代理类

package cn.arebirth.cglibproxy;/** * 被代理类 */public class Host {    public void rent() {        System.out.println("Host:rental house");    }}

生成代理对象的cglib类

package cn.arebirth.cglibproxy;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class CglibProxy implements MethodInterceptor {    /**     * 获取代理类     *     * @return     */    public Object getProxy() {        Enhancer enhancer = new Enhancer();        //设置被代理对象        enhancer.setSuperclass(Host.class);        //设置回调方法  当前对象        enhancer.setCallback(this);        //创建代理对象        Object o = enhancer.create();        return o;    }    /**     * @param o           被代理对象     * @param method      被代理对象方法     * @param objects     被代理对象方法中的参数     * @param methodProxy 代理类中的方法     * @return     * @throws Throwable     */    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {        System.out.println("中介收取手续费");        Object o1 = methodProxy.invokeSuper(o, objects);        System.out.println("中介收取中介费");        return o1;    }}

测试类

package cn.arebirth.cglibproxy;public class Test {    public static void main(String[] args) {        CglibProxy cglibProxy = new CglibProxy();        Host proxy = (Host) cglibProxy.getProxy();//强制转换为我们的房主        proxy.rent();    }}输出结果:  中介收取手续费  Host:rental house  中介收取中介费

我们动态代理的最大好处就是,可以在没有接口的情况下,只有一个类,我们就可以动态的代理,在程序运行的时候动态的为他创建代理类

 

总结

代理模式:

静态代理动态代理:JDK动态代理  CGLIB动态代理

代理模式的三个要素A.抽象的类或接口  完成一件怎样的事情B 被代理对象  事情操作具体内容C 代理对象 帮助我们完成事情的同时  可以增加其他的东西

具体的列子:我们找中介租房子A 抽象的类或者接口 租房子B 被代理对象 房东C 代理对象 中介

代理模式的好处A 房东可以安心的做自己的事情 (被代理对象可以做自己的事情)B 我们有了问题可以直接找中介 (被代理对象变得比较安全)C 可以增强代码的扩展性

JDK动态代理和CGLIB动态代理的使用场景

我们可以这样记一下,只要被代理类没有实现接口,我们就必须使用CGLIB动态代理

Arebirth