设计模式之代理模式

/ java-design-patterns / 0 条评论 / 293浏览

这是设计模式系列的第一篇文章, 也不知道能写几篇......

代理模式的理解

代理模式和装饰器模式很像,可以像装饰器模式一样理解它。

代理(Proxy)模式, 提供了对目标对象另外的访问方式,即通过代理访问目标对象。这样的好处是可以在目标实现的基础上,增强额外的功能操作。(扩展对象的功能,不入侵目标对象的代码)。 符合设计模式开闭原则。

代理模式

静态代理

关键: 代理对象要实现与目标对象一样的接口。

举例:保存用户的操作,userDao(目标对象)只处理业务相关的保存操作,userDaoProxy(代理对象)负责给保存方法添加事务。

优点: 不修改目标对象的前提下, 对目标对象进行扩展
缺点:

  1. 因为代理对象需要和目标对象实现相同的接口, 所以会有很多代理类
  2. 一旦接口增加方法, 目标对象和代理对象都要维护
  3. 不可协商,比如UserDao有一个查询一个保存方法,只需要对保存方法进行事务处理。但是代理对象必须也实现查询方法,因为Java的接口必须被子类实现。

模拟用户操作DAO接口,目标对象和代理对象都需要实现该接口。

public interface IUserDao {

    /**
     * 保存用户
     */
    void save();

    /**
     * 根据id查询用户
     * @param id 用户id
     */
    void queryUserById(int id);
}

目标对象仅包含数据库业务操作,不干涉事务、日志等其它代码

/**
 * @author imyzt
 * @date 2019/5/28
 * @description 目标对象
 */
public class UserDao implements IUserDao {

    public void save() {
        System.out.println("保存用户");
    }

    public void queryUserById(int id) {
        System.out.printf("id=%d, 用户详情", id);
    }
}

而代理对象需要对目标对象进行扩展,相同的,实现UserDao接口。具有一个私有成员变量保存着目标对象

/**
 * @author imyzt
 * @date 2019/5/28
 * @description 目标对象的静态代理对象,要与目标对象实现一样的接口
 */
public class UserDaoProxy implements IUserDao {

    /**
     * 保存目标对象
     */
    private IUserDao target;

    UserDaoProxy(IUserDao target) {
        this.target = target;
    }

    /**
     * 扩展目标对象的方法, 加上事务
     */
    public void save() {
        PrintUtil.print("开始事务");

        target.save();

        PrintUtil.print("提交事务");
    }

    /**
     * 此方法可见, 静态代理不具备可扩展性. 只需要代理 {@link #save()} 方法时,
     * 因为接口必须继承, 必须将本方法实现
     */
    public void queryUserById(int id) {
        target.queryUserById(id);
    }

}

最后通过测试,展示静态代理是否成功运行

/**
 * @author imyzt
 * @date 2019/5/28
 * @description App
 */
public class App {

    public static void main(String[] args) {

        UserDao userDao = new UserDao();
        // class top.imyzt.learning.dp.proxy.a_static.UserDao
        System.out.println(userDao.getClass());

        UserDaoProxy userDaoProxy = new UserDaoProxy(userDao);
        // class top.imyzt.learning.dp.proxy.a_static.UserDaoProxy
        System.out.println(userDaoProxy.getClass());

        userDaoProxy.save();
    }
}

动态代理

为了解决上面静态代理的问题,随之出现了动态代理。对于动态代理我们熟知的Spring的AOP就是使用了动态代理的方式。

优点:

  1. 代理对象不需要实现接口
  2. 代理对象的生成, 是利用JDKAPI, 动态的在内存中构建代理对象(需要我们指定创建代理对象实现的接口类型)
  3. 动态代理, JDK代理(接口代理), 需要依赖于实现接口的目标对象.

动态代理分为JDK动态代理和CGLIB代理, 都是通过动态生成字节码技术然后加载字节码到JVM中, 它们的主要区别是:

  1. JDK代理依赖目标对象实现接口, CGLIB不需要
  2. 因为CGLIB是继承目标对象, 所以目标对象不能为final类, 而JDK代理不存在这个问题.

JDK动态代理

java.lang.reflect.Proxy#newProxyInstance(ClassLoader loader,    // 目标对象使用的类加载器
                                          Class<?>[] interfaces,                           // 目标对象实现的接口的类型
                                          InvocationHandler h)                             // 事件处理器

JDK动态代理:
代理对象不需要实现接口, 但是目标对象一定要实现接口, 否则不能使用JDK动态代理.

这里只是展示一下核心的代理工厂的代码, 更多代码在文末的github链接中有给出. JDK动态代理使用了JDK的反射功能实现.

/**
 * @author imyzt
 * @date 2019/5/28
 * @description JDK动态代理工厂
 */
public class DynamicProxyFactory<T> {

    private static final Pattern p = Pattern.compile("(query\\w+|find\\w+|get\\w)");

    /**
     * 目标对象
     */
    private T target;

    public DynamicProxyFactory(T target) {
        this.target = target;
    }

    @SuppressWarnings("unchecked")
    public T getProxyInstance() {
        return  (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (Object proxy, Method method, Object[] args) -> {

                    Object returnValue;

                    // 模拟Spring 的事务开启开关, 根据方法名判断是否需要开启事务
                    if (checkMethod(method)) {
                        // 执行目标对象的方法
                        return method.invoke(target, args);
                    }

                    PrintUtil.print("开始事务");

                    // 执行目标对象的方法
                    returnValue = method.invoke(target, args);

                    PrintUtil.print("结束事务");

                    return returnValue;
                });
    }


    /**
     * 检查方法是否需要开启事务. 根据方法名简单判断
     * @param method 方法
     */
    private boolean checkMethod(Method method) {
        String name = method.getName();
        Matcher matcher = p.matcher(name);
        return matcher.find();
    }
}

CGLIB动态代理

CGLIB可以弥补JDK动态代理不能代理没有实现接口的目标对象的情况, 可以对不实现接口的目标对象进行代理. 需要引入CGLIB的jar包. 这里使用Spring-core包封装的CGLIB即可.
演示一个CGLIB对象工厂的代理对象, 通过泛型, 如业务没有改变的情况下, 可以重用的代理对象实现.

/**
 * @author imyzt
 * @date 2019/5/28
 * @description CglibDynamicFactory
 */
public class CglibDynamicFactory<T> implements MethodInterceptor {

    private T target;

    public CglibDynamicFactory(T target) {
        this.target = target;
    }

    @SuppressWarnings("unchecked")
    public T getProxyInstance() {


        // 1. 工具类
        Enhancer enhancer = new Enhancer();

        // 2. 设置父类
        enhancer.setSuperclass(target.getClass());

        // 3. 设置回调函数
        enhancer.setCallback(this);

        return (T) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

        PrintUtil.print("开始事务");

        // 执行目标对象的方法
        Object returnValue = method.invoke(target, args);

        PrintUtil.print("提交事务");

        return returnValue;
    }
}