代理模式

Posted Stay hungry,stay foolish.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了代理模式相关的知识,希望对你有一定的参考价值。

  代理模式可分为三种,一种是静态代理,一种是动态代理,还有一种是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方法,也不会真正的关闭连接,而是把连接返回给连接池保存。

  静态代理一般这样实现:

  1. 代理类一般要持有一个被代理的对象的引用。
  2. 对于我们不关心的方法,全部委托给被代理对象处理。
  3. 只处理我们关心的方法

二、动态代理

  静态代理的缺点是一次只能代理一个类,而且编码相对麻烦,我们看看利用动态代理如何实现现有功能,动态代理是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子类代理实现方法:

  1. 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-x.x.x.jar即可。
  2. 引入功能包后,就可以在内存中动态构建子类。
  3. 代理的类不能为final,否则报错。
  4. 目标对象的方法如果为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();
    }
}

  执行结果:

 

  代理模式目前能整理到的也就是这些,欢迎各路大神批评补充,感谢。

以上是关于代理模式的主要内容,如果未能解决你的问题,请参考以下文章

scrapy按顺序启动多个爬虫代码片段(python3)

用于从 cloudkit 检索单列的代码模式/片段

java代码实现设计模式之代理模式

代理模式(静态代理动态代理)代码实战(详细)

Java设计模式-代理模式之动态代理(附源代码分析)

代理模式(静态代理)