Java设计模式(3)--代理模式

来源:互联网 发布:殷叶子演过的网络剧 编辑:程序博客网 时间:2024/06/10 05:25

什么是代理模式,就是我们生成类,替换掉原有类/接口的原有方法,以达到我们所需要的结果。就比如,数据库连接池的应用,一般老说,我们用完数据库连接,将会调用close()方法,将该连接关闭,但是在连接池中,应该是将数据库连接归还给连接池,而不是直接关闭,以达到统一管理的目的,这就需要我们去代理Connection接口,替换掉Connection接口中的close方法,写成归还给数据库连接池。

代理模式分为两种,一种是静态代理模式,也就是我们将代理写死,只能代理某一类/接口,第二种是动态代理,可以代理所有传入的类/接口。

一、静态代理
静态代理就是只代理一种类/接口,在我们实际应用中,数据库连接池经常使用静态代理,因为整个过程中,只需要跟Connection接口打交道,所以只需要代理它即可。
数据库连接池最主要的特点就是,我们关闭数据库连接这个操作,实际上不是将数据库断开,而是将数据库连接归还给数据库连接池。下面看demo,首先写了一个数据库连接池类,为单例模式。

/** * 数据源类,用于管理数据连接 * @author Errol * 定义为单例 */public class DataSource {    //定义数据库连接池    private static LinkedList<Connection> connectList  = new LinkedList<Connection>();    /**     * 静态代码块     * 用于初始化数据库连接     */    static{        try {            System.out.println("加载驱动,Class.forName(...)");            Class.forName("oracle.jdbc.driver.OracleDriver");        } catch (Exception e) {            e.printStackTrace();        }       }    /*     * 构造方法     */    private DataSource(){        /*         * 如果连接池为空,则需要创建好相应的连接数         */        System.out.println("构造方法初始化连接池");        if(connectList.isEmpty()){            for(int i = 0 ; i < 10 ; i++){                //加载10个连接数                try {                    //connectList.add(new InvocationProxy(CreateConnection()).getConnectionProxy());                    connectList.add(new ConnectionProxy(CreateConnection()));                } catch (SQLException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }            }        }    }    /**     *获取     */    public static DataSource getInstance(){        return DataSourceInstance.dataSource;    }    /**     * 静态内部类     */    private static class DataSourceInstance{        private static DataSource dataSource = new DataSource();    }    /**     * 创建新的连接     * @return     * @throws SQLException      */    private Connection CreateConnection() throws SQLException {        System.out.println("创建新的连接");        return DriverManager.getConnection("jdbc:oracle:" + "thin:@127.0.0.1:1521:ORCL" ,                 "tuser" , "tuser");    }    /**     * 外部获取新的连接     */    public Connection getConnection(){        System.out.println("获取连接");        if (connectList.size() > 0){            // 静态代理            return new ConnectionProxy(connectList.remove());//返回一个代理对象         }else{            //如果连接池为空,则说明没有空闲的连接,返回空            return null;        }    }    /**     * 归还连接     */    public void recoveryConnection(Connection conn){        System.out.println("归还连接");        if(conn != null){            connectList.add(conn);        }    }}

数据库连接池类:
1、首先是单例模式设计,为了保证只有一个数据库连接池。
2、数据库连接池用了一个LinkedList,我们在数据库连接池对象创建时将会创建好10个放到其中。
3、类加载时就执行静态代码块(类加载机制),将数据库驱动加载,然后创建连接池对象时,将数据库连接创建好。
4、提供给外部访问的方法有,从连接池中获取一个连接的方法,这里注意,获取的连接是Connection接口的代理类,归还给连接池的方法(主要是提供给代理类使用)
接下来看代理类,代理Connection接口

/** * 数据库连接代理类 * @author Errol * */public class ConnectionProxy implements Connection {    private Connection connect ;    public ConnectionProxy(Connection connect){        this.connect = connect;    }    public Statement createStatement() throws SQLException {        System.out.println("代理获取Statement");        return connect.createStatement();    }    /*     * 代理类的关闭方法     * @see java.sql.Connection#close()     */    public void close() throws SQLException {        System.out.println("调用代理的关闭,而不是Connection接口的关闭,以达到归还给线程池的作用。");        //归还当前连接        DataSource.getInstance().recoveryConnection(connect);    }}

代理类代码很简单,代理类实现了Connection接口,有一些需要重写的方法没有贴出来,构造函数中传入一个Connection对象,重写Connection接口的close方法,方法中注意到,没有将Connection对象关闭,而是调用了数据库连接池对象的归还连接的方法(单例模式好啊,不然这里不就有多个数据库连接池,就不对了),将传入的Connection对象归还给数据库连接池。

然后我们写一个测试类看看,调用close方法是什么效果?

@Testpublic void testJDBC(){    Connection conn = DataSource.getInstance().getConnection();    try {        conn.createStatement();         conn.close();//调用close方法    } catch (SQLException e) {        // TODO Auto-generated catch block        e.printStackTrace();    }}

输出

加载驱动,Class.forName(...)构造方法初始化连接池创建新的连接获取连接代理获取Statement调用代理的关闭,而不是Connection接口的关闭,以达到归还给线程池的作用。归还连接

这样我们就达到了调用close方法不关闭数据库连接而是归还给数据连接池的目的。
静态代理相对简单些,主要是写一个类实现需要代理的接口,重写需要代理的方法,调用接口对象时,调用这个代理类对象,就能够达到目的。总结来说就是
1,代理类一般要持有一个被代理的对象的引用。

2,对于我们不关心的方法,全部委托给被代理的对象处理。

3,自己处理我们关心的方法

静态代理有局限性,只能代理一个类或接口,如果要达到能够代理多个类的话,就需要用到动态代理模式,Spring的AOP(面向切面编程)最重要的技术就是动态代理。

二、动态代理
动态代理是jdk自带的功能,动态代理需要我们去实现一个InvocationHandler接口,通过调用Proxy接口的方法来产生我们的动态代理类,看先看一下代理的代码:

/** * 动态代理类 * @author Errol * */public class InvocationProxy implements InvocationHandler {    private Connection connect;    public InvocationProxy(Connection connect){        this.connect = connect;    }    /*     * 需要被代理的方法的处理在这里实现     */    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        //如果是Connection类的close方法        if(Connection.class.isAssignableFrom(proxy.getClass()) && method.getName().equals("close")){            //我们不执行close方法,而是归还            System.out.println("动态代理close方法");            DataSource.getInstance().recoveryConnection(connect);            return null;        }else{            return method.invoke(connect, args);        }    }    /**     * 获取连接     */    public Connection getConnectionProxy(){        System.out.println("动态代理获取连接");        return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this);    } 

动态代理实现InvocationHandler接口,然后重写了法invoke()方法,这个方法里面就是代理的核心,可以看到代码中的逻辑是:
1、判断代理类是不是Proxy类,并且,是不是调用了close方法。
2、如果说上面的判断通过,则执行逻辑,这里的逻辑相当于静态代理中替换原有方法的逻辑,不再执行原有的close方法
3、判断不通过,回调invoke方法,相当于执行原来的函数,不变。
写完动态代理,我们的数据库连接池也要小改一下,不能再调用静态代理了,应该调用动态代理产生新的连接:、

/* * 构造方法 */private DataSource(){    /*     * 如果连接池为空,则需要创建好相应的连接数     */    System.out.println("构造方法初始化连接池");    if(connectList.isEmpty()){        for(int i = 0 ; i < 10 ; i++){            //加载10个连接数            try {                //静态代理                //connectList.add(new InvocationProxy(CreateConnection()).getConnectionProxy());                connectList.add(new ConnectionProxy(CreateConnection()));            } catch (Exception e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }}/*** 外部获取新的连接 */public Connection getConnection(){    System.out.println("获取连接");    if (connectList.size() > 0){        // 静态代理        //return new ConnectionProxy(connectList.remove());//返回一个代理对象        // 动态        return new InvocationProxy(connectList.remove()).getConnectionProxy();    }else{        //如果连接池为空,则说明没有空闲的连接,返回空        return null;    }}

测试方法测试一下:

获取连接动态代理获取连接动态代理close方法归还连接

同样达到了我们的目的。
上面写的动态代理也只是名字上的动态代理,因为只代理了一个Connection接口,效果跟静态代理是一样的,代码虽然比静态代理少,但是从原理上,效率上是没有静态代理好的,所以,如果使用静态代理和动态代理之后发现效果一直,都只是代理了一类火接口,那么就还是使用静态代理吧。
下面写一个标准版的动态代理。创造出一批代理类,切入到一系列类当中的某一些方法中。

/** *  常规动态代理的写法 * @author Errol * */public class DynamicProxy implements InvocationHandler {    //需要代理的类    private Object source;    /*     * 构造方法     */    public DynamicProxy(Object source){        this.source = source;    }    public void before(){        System.out.println("调用代理方法前做的事, 如打开事务");    }    public void after(){        System.out.println("代理方法之后做的事,如关闭事务");    }    /*     * 代理核心方法     * (non-Javadoc)     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])     */    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        if (method.getName().equals("toString")) {            before();        }        Object result = method.invoke(source, args);        if (method.getName().equals("toString")) {            after();        }        return result;    }    /*     * 获取代理类     */    public Object getProxy(){        return Proxy.newProxyInstance(getClass().getClassLoader(), source.getClass().getInterfaces(), this);    }}

由于这个代理传入的参数类型是Object,所以,它可以代理任何类。
动态代理需要注意的地方是:
动态代理有一个强制性要求,就是被代理的类(source)必须实现了某一个接口,或者本身就是接口,就像我们的Connection。
为什么这么说呢,跟动态代理的原理有关,动态代理生成的代理类,会实现调用Proxy.newProxyInstance方法时传入的所有接口,并且是继承Proxy类的,假设传入的source类没有实现任何接口,即使传入代理成功了,那么生成的代理类值继承于Proxy类,但是Proxy类中的方法均是静态方法,也就是说这个代理类没有任何意义,况且,将代理类强制转型为source类将会报错,无法转型。
下面一些小测试,说了上述情况

/* * 情况1 * 传入代理类的是一个Class,没有实现任何接口 * 代理对象不合法,无意思,无法将动态代理过的类强制转型为传入的类 */@Testpublic void test1(){    ClassA ca = (ClassA)new DynamicProxy(new ClassA()).getProxy();    ca.methodA();}/* * 情况2 * 传入代理为一个B * 执行成功,能够代理一个接口 *   */@Testpublic void test2(){    //Object fb = new DynamicProxy(InterfaceB.class).getProxy();    InterfaceB fb = (InterfaceB)Proxy.newProxyInstance(getClass().getClassLoader(),            new Class[]{InterfaceB.class}, new DynamicProxy(InterfaceB.class));    fb.method1();    fb.method2();}/* * 情况3 * 传入代理为类A,将类A代理为接口B,但是类A没有实现接口B * 执行成功,都变成了代理函数中的输出,无实际意义。 *  */@Testpublic void test3(){    InterfaceB fb = (InterfaceB)Proxy.newProxyInstance(getClass().getClassLoader(),                new Class[]{InterfaceB.class}, new DynamicProxy(new ClassA()));    fb.method1();    fb.method2();}/* * 情况4 * 传入代理类为接口B,去代理没有实现B的类A,且类A包含有于B接口一样的方法。 * 转型失败 */@Testpublic void test4(){    ClassA ca = (ClassA) new DynamicProxy(InterfaceB.class).getProxy();         ca.method1();    ca.method2();}/** * 情况5 * 传入代理为类A,类A实现了接口B,代理为接口B的子类 * 执行成功 */@Testpublic void test5(){    InterfaceB ca = (InterfaceB) new DynamicProxy(new ClassA()).getProxy();    ca.method1();    ca.method2();}

通过上述测试,基本可以验证,动态代理只能代理接口,或者实现接口的类。

下面来大致总结一下动态代理的原理(通过其他博主查看源代码的经验总结而来),源码这里就不贴了,可以自行在MyEclipse中查看:
首先看上面的标准版的动态代理可以发现,生成动态代理类最关键的方法就是Proxy.newInstance()方法,这个方法里面具体实现是:
1、获取需要被代理的Class的引用
2、调用Class的构造方法,构造方法传入了一个参数,InvocationHandler接口。
第1步中获取代理类的Class应用:
Class cl = getProxyClass(loader, interfaces);
那么getProxyClass()方法是如何实现的:
1、首先进行有效性检查,比如传入的接口长度不能大于65535,否则抛出异常
2、定义用于收集代理类缓存的接口名称数组,以及接口Set
3、循环传入的Class数组,加载每个接口运行时的Class信息(Class.forName()方法),然后判断,如果传入的Class与获取出来的Class不一致将抛出异常。重复传入Class也将抛出异常。
4、上面循环中校验通过之后,将该加载的Class存放到Set中。同时,接口名称数组对应的位置也将赋值为该Classde名称。随后循环结束。
5、将接口名称数组转换成List集合,定义缓存Map cache
6、存储每一个类加载器所载入过的代理接口的代理类,放到Map中
7、然后对Map进行处理,判断是否有生成好的代理类,如果已经生成,则直接返回代理类,否则将需要等待。
8、校验传入的接口是否是public,如果存在不是public的,判断是不是在一个包中,如果不是则抛出异常
9、随机生成代理类名,生成代理类的class文件(这一步最关键,上面都是校验之类的准备),生成class文件也即是动态代理的核心原理,通过生成class文件来产生代理类,调用了generateProxyClass方法(后面解释)
10、上一步生成好Class文件之后,获取文件的二进制流,载入到代理类中。
11、最后将代理类的弱引用放到cache中,也即是上面Map中,然后notifyAll(),代理类加载完毕。

来看一下生成Class文件generateProxyClass方法的逻辑,主要总一下Class文件里面写了什么:
1、给Class中放了三个方法,分别是:hashCode,toString和equals
2、将接口与接口下的方法对应起来(有可能传入多个接口,逻辑中将所有方法都放到了一个列表当中),检查代理方法的返回值类型,写到class文件的代理方法都是final类型。
3、加入构造方法,传入参数InvocationHandler接口。
4、给每一个代理方法都添加一个Method属性,属性类型都是private static,将代理方法加入到代理类方法中
5、加入一个静态代码块,作用是将每一个属性初始化。
6、写入一些常规信息,JDK版本号,属性名,通过写类名,父类名(就是extends后面),实现的接口数量,实现的接口名(也就是implements后面),属性个数,属性,方法个数,方法描述,构造方法等信息
7、写class文件完成。

上面总结看起来复杂,其实主要步骤也不多,我们拿一个class文件,反编译为java文件来看一下,对比这些步骤,就能大概明白动态代理的原理,以及生成代理类的步骤了。反编译如下:

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;//public final的,继承Proxy,实现你传入的接口public final class TestProxy extends Proxy  implements TestInterface{  //private static 的Method属性,对应所有方法  private static Method m1;  private static Method m5;  private static Method m3;  private static Method m4;  private static Method m0;  private static Method m2;  //唯一的构造方法,需要一个InvocationHandler接口传入  public TestProxy(InvocationHandler paramInvocationHandler)    throws   {    super(paramInvocationHandler);  }  //重写Object的三个方法  public final boolean equals(Object paramObject)    throws   {    try    {      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final void method3()    throws   {    try    {      this.h.invoke(this, m5, null);      return;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  //代理的三个方法,回调传入的InvocationHandler的invoke方法  public final void method1()    throws   {    try    {      this.h.invoke(this, m3, null);      return;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final void method2()    throws   {    try    {      this.h.invoke(this, m4, null);      return;    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final int hashCode()    throws   {    try    {      return ((Integer)this.h.invoke(this, m0, null)).intValue();    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  public final String toString()    throws   {    try    {      return (String)this.h.invoke(this, m2, null);    }    catch (Error|RuntimeException localError)    {      throw localError;    }    catch (Throwable localThrowable)    {      throw new UndeclaredThrowableException(localThrowable);    }  }  //这个就是刚才this.methods.add(generateStaticInitializer());这一句话所加入的静态初始化块,初始化每一个属性  static  {    try    {//每一个属性所代表的Method都是与上面加入代理方法列表时与固定类绑定的,这是class文件中的格式,方法要与固定的类绑定      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });      m5 = Class.forName("TestInterface").getMethod("method3", new Class[0]);      m3 = Class.forName("TestInterface").getMethod("method1", new Class[0]);      m4 = Class.forName("TestInterface").getMethod("method2", new Class[0]);      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);      return;    }    catch (NoSuchMethodException localNoSuchMethodException)    {      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());    }    catch (ClassNotFoundException localClassNotFoundException)    {      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());    }  }}

代码中每一步的意思都已经注释过了这里不多做解释,这里发现生成的代理类的每一个方法都是去回调InvocationHandler接口的invoke方法
除了Object的三个方法之外,其他的方法均是与被代理类所实现的接口绑定的,也就是说,如果去代理一个没有实现任何接口的类,将无法回调invoke函数,也就是代理没有任何意思,做不了任何事情。

代理模式原理相对前面几个模式较为复杂,学习这个模式花了不少时间,也参考了许多博主对于动态代理模式的理解和总结。总之,设计模式需要多加练习,在了解原理的情况下,多练习可以更为快速的掌握这个模式。

1 0
原创粉丝点击