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函数,也就是代理没有任何意思,做不了任何事情。
代理模式原理相对前面几个模式较为复杂,学习这个模式花了不少时间,也参考了许多博主对于动态代理模式的理解和总结。总之,设计模式需要多加练习,在了解原理的情况下,多练习可以更为快速的掌握这个模式。
- Java设计模式(3)--代理模式
- java设计模式(4)--代理模式
- JAVA设计模式(一)代理模式
- JAVA设计模式- 代理模式(Proxy)
- Java设计模式(代理模式)
- java设计模式(七)---代理模式
- Java设计模式----代理模式(Proxy)
- java设计模式(四)--代理模式
- Java设计模式(八)----代理模式
- JAVA设计模式--代理模式(静态)
- 设计模式之代理模式(java)
- Java设计模式(代理模式)
- java设计模式--代理模式(一)
- java设计模式--代理模式(二)
- java设计模式--代理模式(三)
- 常用Java设计模式系列(3)- 代理模式
- java设计模式-代理模式
- Java设计模式-----代理模式
- sunny ngrok 使用简介
- OPT算法,FIFO算法,LRU算法,LFU算法的java程序
- ReactiveCocoa详解
- windows下硬盘安装centos
- adb命令记录
- Java设计模式(3)--代理模式
- 用HTML创建游戏中的元素用canvas~画方块~画圆:
- spring 源码探索--创建bean实例和初始化
- Linux进程间通信——使用信号
- js跨域的一些解决方法
- seekbar的简单使用
- LBP(三)
- PAT A1051
- 米斯特白帽培训讲义 漏洞篇 XSS