设计模式之代理模式XXOO

2022-09-13 13:16:17 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

定义

代理模式可以分为两种,一种是静态代理,一种是动态代理。 静态代理: 代理类一般会持有一个被代理的对象引用,且对于不关心的方法全部委托给被代理的对象处理。自己处理关心的方法。 这种代理方式是死板的,它不是在运行时动态创建,它就是硬编码,你代码编译前写的是什么,编译后就是什么。 换句话就是你按下CTRL S的那一刻,就会被代理对象生成一个不可动态改变的代理类。 静态代理一般对于代理的对象是单个或者多个固定的类(数量不会太多)使用。效果会比动态代理要好。 动态代理: 动态代理又分为JDK动态代理以及CGLIB动态代理。 JDK动态代理是实现一个InvocationHandler接口,并且调用Proxy的静态方法去产生代理类。主要是运行时动态生成代理类,类似CURD操作添加日志、拦截等等时使用。例如:Spring AOP。

举个栗子

以数据库连接为例,静态代理。

代码语言:javascript复制
public interface Connection extends Wrapper {

    /**
     * 创建连接
     * @return
     * @throws SQLException
     */
    Statement createStatement() throws SQLException;

    /**
     * 关闭连接
     * @throws SQLException
     */
    void close() throws SQLException;
}
代码语言:javascript复制
public class DBUtil {

    private static LinkedList<Connection> connectionList = new LinkedList<Connection>();

    static{
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static Connection createNewConnection() throws SQLException {
        return (Connection) DriverManager.getConnection("url","username", "password");
    }

    private DBUtil(){
        if (connectionList == null || connectionList.size() == 0) {
            for (int i = 0; i < 10; i  ) {
                try {
                    connectionList.add(createNewConnection());
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Connection getConnection() throws Exception{
        if (connectionList.size() > 0) {
            //这是原有的方式,直接返回连接,这样可能会被程序员把连接给关闭掉
            //return connectionList.remove();  
            //下面是使用代理的方式,程序员再调用close时,就会归还到连接池
            return new ConnectionStaticProxy(connectionList.remove());
        }
        return null;
    }

    public void recoveryConnection(Connection connection){
        connectionList.add(connection);
    }

    public static DBUtil getInstance(){
        return DataSourceInstance.dataSource;
    }

    private static class DataSourceInstance{

        private static DBUtil dataSource = new DBUtil();

    }
}
代码语言:javascript复制
@Slf4j
public class ConnectionStaticProxy implements Connection {

    private Connection connection;

    public ConnectionStaticProxy(Connection connection) {
        super();
        this.connection = connection;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return connection.createStatement();
    }

    @Override
    public void close() throws SQLException {
        log.info("不是真正关闭连接,只是归还给连接池");
        DBUtil.getInstance().recoveryConnection(connection);
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

动态代理 这个动态代理的演示只能代理Connection 这一个接口,如果出现这种情况,用静态代理会更好。 在你发现你使用静态代理的时候,需要写一大堆重复代码的时候,就请改用动态代理。

代码语言:javascript复制
/**
 * 描述: 动态代理
 * 这里只是带来了一个类Connection
 * 注:在你发现你使用静态代理的时候,需要写一大堆重复代码的时候,就请改用动态代理
 *
 */
public class ConnectionProxy implements InvocationHandler {

    private Connection connection;

    public ConnectionProxy(Connection connection) {
        super();
        this.connection = connection;
    }

    @Override
    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);
            // 将连接归还连接池
            DBUtil.getInstance().recoveryConnection(connection);
            return null;
        }else {
            return method.invoke(connection, args);
        }
    }

    public Connection getConnectionProxy(){
        return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this);
    }
}

当我们需要代理一系列类的某一些方法,最典型的应用就是springAOP,我们需要创造出一批代理类,切入到一系列类当中的某一些方法中。下面给出一个经常使用的动态代理方式。

代码语言:javascript复制
/**
 * 描述: 动态代理
 * 这个代理类的作用是可以代理任何类,因为它被传入的对象是Object,而不再是具体的类
 *
 * @author: yanglin
 * @Date: 2020-07-07-9:46
 * @Version: 1.0
 */
@Slf4j
public class ConnectionProxyOne implements InvocationHandler {

    private Object source;

    public ConnectionProxyOne(Object source){
        super();
        this.source = source;
    }

    public void before(){
        log.info("before 在方法前做一些事,比如打开事务");
    }

    public void after(){
        log.info("after 在方法返回前做一些事,比如提交事务");
    }

    @Override
    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 getConnectionProxy(){
        return Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this);
    }
}

之前有个疑问?如果被代理的类未实现接口是否可以使用动态代理。答案是可以的。

代码语言:javascript复制
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("接口的方法全部变成这样了");
        // 这里source是TestClass,但是我们不能使用反射调用它的方法,像下面这样,放开这一行会抛异常
        // return method.invoke(source, args);

        /**
         * 只要你确认你传入的类包括了所有你传入的接口的方法,只是没实现这些接口而已,那么你可以在invoke中这样使用
         */

        log.info("before");
        Method sourceMethod = source.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
        sourceMethod.setAccessible(true);
        Object result = sourceMethod.invoke(source, args);
        log.info("after");
        return result;
    }

以上,有兴趣的话可以跟下源码,理解下原理。(Proxy.newProxyInstance这是产生代理的入口)

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/160923.html原文链接:https://javaforall.cn

0 人点赞