阅读 1266

面试官: ThreadLocal是干啥的

代理模式介绍

代理模式是一种设计模式,顾名思义是对某个事物的代理,外界所有对该事物的访问或操作,都会经过该代理。举个例子,如果你有法律方面的纠纷,那么你必然要找律师,此时对于律师而言你就是他的委托人,而对于你而言律师是你的代理人,也就是我们常说的代理律师

在编程世界中,我们可以用委托类和代理类来描述二者之间的关系

代理模式的作用: 提供了对委托类的额外访问方式,即通过代理类访问委托类,这样就可以在不修改委托类前提下,提供额外的功能操作,从而扩展委托类的功能。

简而言之,代理模式就是设置一个中间代理来控制所有对委托类的访问,以达到增强委托类的功能。

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性

代理模式分类

讲解代理模式的分类之前,我们先准备些类和接口

    // DataService接口提供了save方法
    public interface DataService {
      public void save();
    }

    // DataServiceImpl类实现了DataService接口,并重写了save方法
    public class DataServiceImpl implements DataService{
      @Override
      public void save() {
        System.out.println("DataServiceImpl method save is called");
      }
    }
复制代码

静态代理(包装)

    // 代理类
    public class DataServiceProxy implements DataService{
        // 委托类
      DataService dataService = new DataServiceImpl();

      @Override
      public void save() {
        System.out.println("DataServiceProxy method save is called");
        dataService.save();
      }
    }
复制代码

就上述代码分析而言,DataServiceProxy为代理类,DataServiceImpl为委托类

静态代理优缺点分析:

  • 优点
    • 可以在不修改委托类的前提下扩展委托类的功能
  • 缺点
    • 代理类和委托类要实现同样的接口
    • 硬编码,一旦委托类增加了方法,代理类同样要修改

JDK动态代理

利用JDK API动态的在内存中构建代理类的实例对象

动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为:

  • 代理接口 是代理类实现的一个接口。
  • 代理实例 是代理类的一个实例。
  • 每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,并传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果返回

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

  • java.lang.reflect.Proxy: 这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象

Proxy 的静态方法

    // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
    static InvocationHandler getInvocationHandler(Object proxy) 

    // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    static Class getProxyClass(ClassLoader loader, Class[] interfaces) 

    // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
    static boolean isProxyClass(Class cl) 

    // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, 
        InvocationHandler h)

复制代码
  • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问

InvocationHandler 的核心方法

    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是,第二个参数是
     * proxy: 代理类实例
     * method: 被调用的方法对象
     * args: 调用参数
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
     */

    Object invoke(Object proxy, Method method, Object[] args)
复制代码

每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。

  • java.lang.ClassLoader: 这是类加载器,负责将字节码加载到Java虚拟机(JVM)中并为其定义类对象(Class), 然后该类才能被使用。通过Proxy.newProxyInstance 生成的动态代理类同样需要类加载器来进行加载才能使用.它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非存在于某个具体的.class文件中

动态代理机制及其特点

首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

  1. 通过实现 InvocationHandler 接口并实现invoke方法来创建自己的调用处理器
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
    InvocationHandler handler = new InvocationHandlerImpl(..); 

    // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

    // 通过构造函数对象创建动态代理类实例
    Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
复制代码

实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下

    // InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(..); 

    // 通过 Proxy 直接创建动态代理类实例
    Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
         new Class[] { Interface.class }, 
         handler );
复制代码

具体代码实现:

调用处理程序类

    public class DataServiceInvovationHandler implements InvocationHandler {
      DataService dataService;

      public DataServiceInvovationHandler(DataService dataService) {
        this.dataService = dataService;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getDeclaringClass() + " " +  method.getName() + "() is called");
        method.invoke(dataService, args);
        return null;
      }
    }
复制代码

动态代理类

    public class DataServiceDynamicProxy {
      DataService dataService = new DataServiceImpl();
      public void save() {
        DataService dataServiceDynamicProxy =
                (DataService) Proxy.newProxyInstance(dataService.getClass().getClassLoader(), new Class[]{DataService.class}, new DataServiceInvovationHandler(dataService));
        dataServiceDynamicProxy.save();
      }
    }
复制代码

静态代理与动态代理的区别主要在:

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

cglib代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展

cglib特点

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

cglib与动态代理最大的区别就是

  • 使用动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入

使用cglib需要引入cglibjar包, cglibmaven坐标如下:

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
复制代码

代码实现

委托类

    public class DataService {
      public void save() {
        System.out.println("class DataService method save is called");
      }
    }
复制代码

代理类

    public class CglibProxy implements MethodInterceptor {
      private Object target; // 委托类

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

      //为委托类生成代理对象
      public Object getProxyInstance() {
        // 工具类
        Enhancer enhancer = new Enhancer();
        // 设置委托类为父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类对象代理
        return enhancer.create();
      }


      @Override
      public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("DataService cglib  proxy start...");
        // 通过反射执行委托类的方法
        method.invoke(target, objects);
        System.out.println("DataService cglib proxy end...");
        return null;
      }
    }
复制代码

测试结果

总结

  1. 静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
  2. JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
  3. 静态代理在编译时产生class字节码文件,可以直接使用,效率高。
  4. 动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
  5. cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final