设计模式(二)--代理模式
代理模式可分为三种,一种是静态代理,一种是动态代理,还有一种是Cglib代理。
一、静态代理
静态代理和动态代理模式本质上一样的,都是在原有类的行为基础上,加入一些多出的行为,甚至完全替换原有的行为。
举一个静态代理的例子,我们都知道,数据库连接是很珍贵的资源,频繁的开关数据库连接是非常浪费服务器的CPU资源已经内存的,所以我们一般都是使用数据库连接池来解决这一问题,即创造一堆等待被使用的连接,等到用的时候就从池里取一个,不用了再放回去,数据库连接在整个应用启动期间,几乎是不关闭的,除非是超过了最大空闲时间。现在要解决的问题是,如何替换connection的close行为,使close方法被调用的时候没有真正的关闭连接,而是将连接归还给连接池。
下面是Connection接口,LZ去掉了很多方法,我们只关心close方法。
import java.sql.SQLException; import java.sql.Statement; import java.sql.Wrapper; /** * connection接口 * @author xiaodongdong * @create 2018-05-02 14:10 **/ public interface Connection extends Wrapper { Statement createStatement() throws SQLException; void close() throws SQLException; }
如何替换现有close方法的行为呢,我们组合复用一个connection对象,不关心的方法交给原connection方法去处理,比如createStatement()方法,真正关心的close方法,我们用自己的逻辑去实现。另外,为了保证对程序猿是透明的,我们实现Connection接口。
import java.sql.SQLException; import java.sql.Statement; public class ConnectionProxy implements Connection{ private Connection connection; public ConnectionProxy(Connection connection) { super(); this.connection = connection; } public Statement createStatement() throws SQLException{ return connection.createStatement(); } public void close() throws SQLException{ System.out.println("不真正关闭连接,归还给连接池"); } }
这个静态代理应该在什么地方使用呢,LZ写了一个简单的数据库连接池,获得连接的方法返回的就是ConnectionProxy,代码如下。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.LinkedList; /** * 数据库连接池 * @author xiaodongdong * @create 2018-05-02 14:54 **/ public class ConnectionPool { private LinkedList<Connection> pool = new LinkedList<Connection>(); static{ try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } private static Connection createNewConnection() throws SQLException { return DriverManager.getConnection("url","username", "password"); } private ConnectionPool(int size) { initialize(size); } private void initialize(int size) { try { if (size > 0) { synchronized (pool) { for (int i = 0; i < size; i++) { pool.add(createNewConnection()); } } } } catch (Exception e) { e.printStackTrace(); } } /** * 释放连接 */ public void releaseConnection(Connection conn) throws InterruptedException, SQLException { if (conn != null) { synchronized (pool) { pool.addLast(conn); // 释放连接后通知所有线程 pool.notifyAll(); } } } /** * 获得连接 */ public Connection getConnection(long millons) throws InterruptedException { if (millons < 0) {// 完全超时 synchronized (pool) { while (pool.isEmpty()) { pool.wait(millons); System.out.println("完全超时"); } return pool.removeFirst(); } } else { synchronized (pool) { long future = System.currentTimeMillis() + millons; long remaining = millons; while (pool.isEmpty() && remaining > 0) { pool.wait(remaining); remaining = future - System.currentTimeMillis(); } Connection result = null; if (!pool.isEmpty()) { //result = pool.removeFirst(); 这是原有的方式,直接返回连接
//,这样可能会被程序员把连接给关闭掉 //下面是使用代理的方式,程序员再调用close时,就会归还到连接池 result = new ConnectionProxy(pool.removeFirst()); } return result; } } } /** * 单例 */ public static ConnectionPool getInstance(){ return DataSourceInstance.dataSource; } private static class DataSourceInstance{ private static ConnectionPool dataSource = new ConnectionPool(20); } }
这样,ConnectionProxy中的close方法就明确了,我们将close方法修改一下。
public void close() throws SQLException{ ConnectionPool.getInstance().releaseConnection(connection); }
至此,连接池返回的连接就全是我们自己实现的静态代理类,只要连接是从我们自己实现的连接池拿的,就算调用close方法,也不会真正的关闭连接,而是把连接返回给连接池保存。
静态代理一般这样实现:
- 代理类一般要持有一个被代理的对象的引用。
- 对于我们不关心的方法,全部委托给被代理对象处理。
- 只处理我们关心的方法
二、动态代理
静态代理的缺点是一次只能代理一个类,而且编码相对麻烦,我们看看利用动态代理如何实现现有功能,动态代理是JDK自带的功能,你需要实现InvocationHandler接口,并且调用Proxy的静态方法产生代理类。我们重写ConnectionProxy类、
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; /** * 动态代理 */ public class ConnectionProxy implements InvocationHandler{ private Connection connection; public ConnectionProxy(Connection connection) { super(); this.connection = connection; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //这里判断是Connection接口的close方法的话 if (Connection.class.isAssignableFrom(proxy.getClass()) && method.getName().equals("close")) { //我们不执行真正的close方法 //method.invoke(connection, args); //将连接归还连接池 ConnectionPool.getInstance().releaseConnection(connection); return null; }else { return method.invoke(connection, args); } } /** * 调用该方法获得被代理对象 */ public Connection getConnectionProxy(){ return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this); } }
连接池稍作修改,将result = new ConnectionProxy(pool.removeFirst());一行改为 result = new ConnectionProxy(pool.removeFirst()).getConnectionProxy();
上面是我们针对connection写的动态代理,InvocationHandler接口只有一个invoke方法需要实现,这个方法是用来在生成的代理类用回调使用的,很显然,动态代理是将每个方法的具体执行过程交给了我们在invoke方法里处理。而具体的使用方法,我们只需要创造一个ConnectionProxy的实例,并且将调用getConnectionProxy方法的返回结果作为数据库连接池返回的连接就可以了。
代码如果这样写,我们从中得不到任何好处,除了能少写点代码以外,因为这个动态代理还是只能代理Connection这一个接口,我们修改代理类,使它能代理多个类。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxy implements InvocationHandler{ private Object source; public DynamicProxy(Object source) { super(); this.source = source; } public void before(){ System.out.println("在方法前做一些事,比如打开事务"); } public void after(){ System.out.println("在方法返回前做一些事,比如提交事务"); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //假设我们切入toString方法,其他其实也是类似的,一般我们这里大部分是针对特定的方法做事情的,通常不会对类的全部方法切入 //比如我们常用的事务管理器,我们通常配置的就是对save,update,delete等方法才打开事务 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,而不再是具体的类,比如刚才的Connection,这些产生的代理类在调用toString方法时会被插入before方法和after方法。
动态代理有一个强制性要求,就是被代理的类必须实现了某一个接口,或者本身就是接口,就像我们的Connection。
三、Cglib代理
如何目标对象没有实现任何接口,自己本身也不是接口应该怎么办呢?Cglib代理可以实现。
Cglib子类代理实现方法:
- 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入
pring-core-x.x.x.jar
即可。 - 引入功能包后,就可以在内存中动态构建子类。
- 代理的类不能为final,否则报错。
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
简单写一个例子。
/** * 目标对象,没有实现任何接口 * @author xiaodongdong * @create 2018-05-02 17:10 **/ public class UserDao { public void save() { System.out.println("----已经保存数据!----"); } }
接下来是代理工厂。
import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 代理工厂 * @author xiaodongdong * @create 2018-05-02 17:11 **/ public class ProxyFactory implements MethodInterceptor { //维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } //给目标对象创建一个代理对象 public Object getProxyInstance(){ //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始事务..."); //执行目标对象的方法 Object returnValue = method.invoke(target, args); System.out.println("提交事务..."); return returnValue; } }
测试类如下。
import org.junit.Test; /** * 测试类 */ public class App { @Test public void test(){ //目标对象 UserDao target = new UserDao(); //代理对象 UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法 proxy.save(); } }
执行结果:
代理模式目前能整理到的也就是这些,欢迎各路大神批评补充,感谢。