设计模式之代理模式(Proxy Pattern)

时间:2022-09-21 20:38:47

1.1、介绍

概念

代理模式(Proxy Pattern)给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用 。

代理模式是常用的结构型设计模式之一,当直接访问某些对象存在问题时可以通过一个代理对象来间接访问。

用途:

  • 当提供服务方不想让用户访问真正角色时,采用代理模式
  • 当需要横切一些业务时,为了不破坏原有的类,也可采用代理模式

作用:

  1. 功能增强: 在你原有的功能上,增加了额外的功能。 新增加的功能,叫做功能增强。
  2. 控制访问: 代理类不让你访问目标,例如商家不让用户访问厂家。

实现代理的方式

  • 静态代理
  • 动态代理(两种JDK和CGLIB)

1.2 JDK动态代理

在Java的动态代理机制中,有一个接口InvocationHandler(Interface)和另一个类 Proxy(Class),二者可谓之中流砥柱。

InvocationHandler

在使用动态代理的时候,每一个代理类需要实现InvocationHandler接口,并且重写其接口中唯一的方法invoke()。

动态代理工具类

public class DynamicProxy implements InvocationHandler {  // 实现调用处理接口

    //被代理的接口对象
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //创建动态代理对象用Proxy类中的newProxyInstance()方法
    // 参数信息:
    // 	参数一:通过反射得到类加载器
    // 	参数二:需要实现的接口
    // 	参数三:处理者  这个参数需要一个InvocationHandler(接口)的对象,
    // 我们这个自定义代理类实现了InvocationHandler接口,所以用this调用自己
    // 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
    //Proxy.newProxyInstance因为与IllegalArgumentException相同的原因而Proxy.getProxyClass
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // 处理代理实例,并返回结果
    // method这个参数其实就是我们要增强的方法,也就是需要代理类去调用的方法,
    // 通过这个参数调用invoke()方法 invoke翻译:调用
    // method.invoke()通过反射去调用我们target接口里的方法 动态之所以就在这
    // 该方法会在动态代理过程中通过反射被执行,具体执行过程在下面解释
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());

        return method.invoke(target, args);
    }

    private void log(String msg){
        System.out.println("我是代理类添加的消息,使用了"+msg+"方法!!!");
    }

}

动态代理的步骤

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器(动态代理类);

  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;

  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

    1. //获取代理对象的构造方法(也就是$Proxy0(InvocationHandler h)) 
      final Constructor<?> cons = cl.getConstructor(constructorParams);
      
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    1. //生成代理类的实例并把InvocationHandlerImpl的实例传给它的构造方法,InvocationHandler h
      return cons.newInstance(new Object[]{h})
      

JDK动态代理步骤

JDK动态代理分为以下几步:

  1. 拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
  2. 通过JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类所实现的所有的接口。
  3. 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用。
  4. 编译新生成的 Java 代码.class。
  5. 将新生成的Class文件重新加载到 JVM 中运行。

参考:https://blog.csdn.net/jiankunking/article/details/52143504

1.3 CGLIB动态代理

JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

注意:因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private方法则无法被重写,也就是无法被代理。

1、JDK动态代理具体实现原理:

通过实现InvocationHandler接口创建自己的调用处理器;

通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;

通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;

通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

2、CGLib动态代理:

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3、两者对比:

JDK动态代理是面向接口的。

CGLib动态代理是通过字节码底层继承要代理类来实现,因此如果被代理类被final关键字所修饰,会失败。

4、使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);

如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

2、AOP的实现

基于动态代理