代理模式(学习版)

(一)代理模式原理

主要角色:

  • 抽象主题(Subject)类: 声明了真实主题和代理主题的共同接口,这样就可以保证任何使用真实主题的地方都可以使用代理主题,客户端一般针对抽象主题类进行编程。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以在任何时候访问、控制或扩展真实主题的功能。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象
    代理模式图

(二)静态代理

静态代理优缺点:

  1. 优点:可以在不修改目标类的前提下,扩展目标类的功能
  2. 缺点:
    • 冗余,由于代理对象要实现和目标对象一样的接口,会产生很多的代理
    • 不易维护,一旦接口中增加方法,目标对象和代理对象都要进行修改,违反开闭原则
//抽象主题
public interface IUserDao {
    public void save();
}
//真实主题
public class UserDaoImpl implements IUserDao{
    @Override
    public void save() {
        System.out.println("保存数据");
    }
}
//代理类
/**
 * 代理类,静态代理
 */
public class UserProxy implements IUserDao {
    private IUserDao target;

    public UserProxy(IUserDao target) {
        this.target = target;
    }

    @Override
    public void save() {
        System.out.println("开启事务");
        target.save();
        System.out.println("关闭事务");

    }
}
//test
public class TestProxy {
    /**
     * 静态代理:
     *      优点:可以在不修改目标类的前提下,扩展目标类的功能
     *      缺点:
     *          1.冗余,由于代理对象要实现和目标对象一样的接口,会产生很多的代理
     *          2.不易维护,一旦接口中增加方法,目标对象和代理对象都要进行修改,违反开闭原则
     */
    public void testStaticProxy() {
        //创建目标类
        IUserDao dao = new UserDaoImpl();
        //代理对象
        UserProxy proxy = new UserProxy(dao);
        //执行方法
        proxy.save();
    }
}

(三)proxy-动态代理

静态代理与动态代理的区别(注意对比):

  1. 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文件
  2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

1 代码

/**
 * 代理工厂类,动态生成代理对象
 */
public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    //为目标对象生成代理对象
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                //目标类使用的类加载器
                target.getClass().getClassLoader(),
                //目标类实现的接口类型
                target.getClass().getInterfaces(),
                //事件处理器
                new InvocationHandler() {
                    /**
                     *
                     * @param proxy   代理对象
                     * @param method  对应于在代理对象上调用的接口方法实例
                     * @param args    对应代理对象在调用接口方法时传递的实际参数
                     * @return 返回目标对象的方法的返回值,没有返回值返回null即可
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("开启事务");
                        method.invoke(target, args);
                        System.out.println("提交事务");
                        return null;
                    }
                }
        );
    }
}

2 原理

1.Proxy.newProxyInstance三个参数:目标类的类加载器、目标类实现的接口、事件处理器、InvocationHandler

public Object getProxyInstance() {
    //newProxyInstance
    return Proxy.newProxyInstance(
            //目标类使用的类加载器
            target.getClass().getClassLoader(),
            //目标类实现的接口类型
            target.getClass().getInterfaces(),
            //事件处理器
            new InvocationHandler() {
                /**
                 *
                 * @param proxy   代理对象
                 * @param method  对应于在代理对象上调用的接口方法实例
                 * @param args    对应代理对象在调用接口方法时传递的实际参数
                 * @return 返回目标对象的方法的返回值,没有返回值返回null即可
                 * @throws Throwable
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("开启事务");
                    method.invoke(target, args);
                    System.out.println("提交事务");
                    return null;
                }
            }
    );
}

2.Proxy.newProxyInstance源码
通过getProxyClass0获取代理类,然后创建Constructor对象
在这里插入图片描述

将InvocationHandler传递给Constructor的newInstance方法,即使用带参(参数为InstanceHander)的构造器创建对象。
在这里插入图片描述

3.cons.newInstance实际调用代理类的带参构造方法(参数为InvocationHandler)
简化后的代理类如下,构造方法为带参(参数为InvocationHandler)

public final class $Proxy0extends Proxy implements IUserDao {
    private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
         m3 = Class.forName("com.mashibing.proxy.example01.IUserDao").getMethod("save", new Class[0]);
          return;
        }
    }

    public final void save() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
    }
}

整体过程:

  1. 使用Proxy.newProxyInstance方法,传入类加载器、接口、InvocationHandler。
  2. Proxy.newProxyInstance中,通过类加载器与接口获取代理类,后创建Constructor对象,并调用newInstance方法,传入InvocationHandler对象。即调用代理类的带参构造,创建代理对象。
  3. 调用代理对象的方法时,实际是调用InvocationHandler重写的invoke方法。

(四)CGLIB-动态代理

cglib是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理(通过生成目标类的子类),为JDK的动态代理提供了很好的补充。

1 代码

/**
 * 扩展日志信息,在保存前后添加日志信息
 */
public class UserLogProxy implements MethodInterceptor {

    /**
     * 生成CGLIB动态代理类
     *
     * @param target 需要被代理的目标类
     * @return 代理类对象
     */
    public Object getLogProxy(Object target) {
        //增强器类,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //设置代理类的父类字节码对象,因为CGLIB是通过继承目标类实现的,不是实现目标类实现的接口
        enhancer.setSuperclass(target.getClass());
        //设置回调,intercept方法在哪个类就设置哪个,由于在当前类所以传入this
        enhancer.setCallback(this);
        //创建动态代理对象并返回
        return enhancer.create();
    }

    /**
     * 实现回调的方法
     *
     * @param o           代理对象
     * @param method      目标对象中的方法的Method实例
     * @param args        实际的参数
     * @param methodProxy 代理类对象中的方法的method实例
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Calendar instance = Calendar.getInstance();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(format.format(instance.getTime()) + "[ " + method.getName() + " ] 查询用户信息...");
        Object result = methodProxy.invokeSuper(o, args);
        return result;
    }
}


/**
 * 目标类
 */
public class UserServiceImpl {
    //查询功能
    public List<User> findUserList() {
        return Collections.singletonList(new User("tom", 23));
    }
}

(五)代理模式对比

  1. 在jdk1.6前,CLIB采用ASM字节码生成框架,比使用反射的JDK代理效率高。jdk1.6、1.7、1.8对JDK代理进行优化。在jdk1.8中,JDK代理效率高于CGLIB。因此如果目标类有接口使用JDK动态代理,没有接口使用CGLIB代理。
  2. 动态代理与静态代理相比最大的好处就是,拿JDK代理举例,接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(如InvocationHandler)。当接口方法较多时,能够进行灵活处理,不需要像静态代理那样每一个方法进行中转。静态代理每增加一个方法,除了所有实现类要实现这个方法,所有代理类都要实现这个方法,维护起来十分复杂

(六)代理优缺点

优点:

  1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  2. 代理对象可以扩展目标对象的功能
  3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度

缺点:

  1. 增加了系统的复杂度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值