• 设计模式(二)--代理模式


      代理模式可分为三种,一种是静态代理,一种是动态代理,还有一种是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();
        }
    }

      执行结果:

     

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

  • 相关阅读:
    实现Vector对象的序列化的例子
    BigDecimal
    java.io.Serializable引发的问题——什么是序列化?在什么情况下将类序列化?
    删除表中重复记录的方法
    使用PreparedStatement为不同的数据库编写可移植的数据库存取方法
    hsqldb介绍
    ant管理项目
    在jsp中点击按钮,在bean中把已经查出的数据,生成csv文件,然后在ie中自动打开
    用JAVA操作日期类型
    ORACLE默认用户的问题?
  • 原文地址:https://www.cnblogs.com/peterxiao/p/8979883.html
Copyright © 2020-2023  润新知