文章

Java代理简介

代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

image-20250303191807737

代理的实现通常有两种方式:

  • 静态代理:代理类需要手动显示编写,代理关系在编译时就已经确定了,一个代理类只能代理一个特定的接口或类
  • 动态代理:代理类通过Java的反射机制自动生成,一个动态代理类可以代理多个不同的接口或类,只需在运行时指定,代理关系在运行时动态确定

静态代理

前面已经提到了静态代理的特点:

  • 手动创建:代理类需要手动编写,通常实现与目标类相同的接口,并在代理类中调用目标类的方法。
  • 灵活性低:每个代理类只能代理一个特定的接口或类,若要代理多个类,需为每个类编写单独的代理类。
  • 编译时确定:代理关系在编译时就已经确定。

静态代理实现步骤:

  1. 定义一个接口及其实现类;

    1
    2
    3
    4
    
    // 定义发送短信的接口
    public interface SmsService {
        String send(String message);
    }
    
  2. 创建一个代理类同样实现这个接口

    1
    2
    3
    4
    5
    6
    7
    
    // 实现发送短信的接口
    public class SmsServiceImpl implements SmsService {
        public String send(String message) {
            System.out.println("send message:" + message);
            return message;
        }
    }
    
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    // 创建代理类,并同样实现发送短信接口。它要接收一个被代理对象
    public class SmsProxy implements SmsService {
       
        private final SmsService smsService;
       
        public SmsProxy(SmsService smsService) {
            this.smsService = smsService;
        }
       
        @Override
        public String send(String message) {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method send()");
            smsService.send(message);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method send()");
            return null;
        }
    }
    
  4. 实际使用

    1
    2
    3
    4
    5
    6
    7
    
    public class Main {
        public static void main(String[] args) {
            SmsService smsService = new SmsServiceImpl();
            SmsProxy smsProxy = new SmsProxy(smsService);
            smsProxy.send("java");
        }
    }
    

==本质上就是一层Wrapper!!!==高级了说这是静态代理,低级了说这是脱裤子放屁(在不考虑有很强的扩展功能时)

动态代理

相比于静态代理来说,动态代理更加灵活。它具有以下特点:

  • 自动生成:代理类在运行时通过Java反射机制动态生成,无需手动编写。
  • 灵活性高:一个动态代理类可以代理多个不同的接口或类,只需在运行时指定。
  • 运行时确定:代理关系在运行时动态确定。

应用:Spring AOP、RPC 等等。动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

动态代理听起来很吓人,其实就是对整套静态代理流程的封装。因为静态代理的过程太固定了,就是创建一个代理类,在想使用原始对象的地方,使用代理对象。

原理

可以想一下,如果要手动实现静态代理的话,在整个代理过程中,变化的是什么,不变的又是什么。不变的是过程:

  1. 创建一个接口类
  2. 实现该接口类
  3. 创建一个代理类,同样也要实现该接口类,但同时要持有该接口类属性(这个不是必须的,后面会解释)
  4. 当想要使用原始类时,使用代理类替换

变的是:

  1. 接口类的内容:在不同场景下,我们需要实现不同的功能
  2. 代理的行为:在不同场景下,可能需要在原始功能上扩展不同的行为,如有的需要日志,有的要安全检查

当我们知道这两个内容时,其实我们可以编写一个自动化代码,来实现自动的生成代理类。过程一般如下:

  1. 编写好接口
  2. 编写好代理的功能
  3. 自动化程序读取这些信息,然后生成一个代理类
  4. 自动化程序,自动用代理类替换掉原始类
  5. 重新编译运行

这么一下来,就实现了一个 “伪动态代理”。可以发现有明显的问题:需要编译运行两次。而真正的动态代理,是在运行时生成代理类的,不需要编译运行两次。我们仍然只需要定义好被代理的接口和代理的行为。

前面还提到了 “但同时要只有该接口类属性”,必须是接口吗?

当然不是,代理类要保持原始类的行为,因此它需要来存储原始行为,又因为原始类实现了一个接口,所以可以用该接口“对象”来存储原始类对象。以后都使用代理类对象,当想进行某个行为时,由于代理类存储了原始类的行为信息,所以可以在执行扩展行为后,调用原始行为,这样就实现了对原始行为的扩展。其实,我们存储原始“对象”行为时,不一定非要用接口对象,用类对象同样可以,因为类对象也又完整的行为(方法)描述。原生的JDK动态代理仅支持对接口的代理,而CGLIB支持对接口和类的代理,各有各的好处。

它们是怎样实现,一次编译的动态代理的呢?Java反射技术,接下来详细介绍 原生JDK方式和CGLIB实现动态代理的方式。

疑问:为什么代理类不直接存储原始类的类信息。

JDK动态代理

JDK方式实现动态代理由 java.lang.reflect.Proxy类java.lang.reflect.InvocationHandler接口 实现。前者负责根据接口和代理行为动态地生成代理类,后者负责定义代理行为。

  • java.lang.reflect.InvocationHandler接口:负责定义我们想要什么样的代理,也就是我们想要的代理的行为。例子如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    // 定义代理类的行为
    public class DebugInvocationHandler implements InvocationHandler {
        /**
         * 代理类中的真实对象
         */
        private final Object target;
      
        public DebugInvocationHandler(Object target) {
            this.target = target;
        }
      
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method " + method.getName());
            Object result = method.invoke(target, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method " + method.getName());
            return result;
        }
    }
    

    看着很像抽象的代理类,但不要认为它就是,它是抽象的行为扩展,代理类会被真实的动态生成。

  • java.lang.reflect.Proxy类:它负责自动根据生成的代理类,创建代理类对象,然后可以被使用在各个地方

    • 它生成代理类对象的方法为Proxy.newProxyInstance():

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      @CallerSensitive
          public static Object newProxyInstance(ClassLoader loader,
                                                Class<?>[] interfaces,
                                                InvocationHandler h) {
              Objects.requireNonNull(h);
          
              @SuppressWarnings("removal")
              final Class<?> caller = System.getSecurityManager() == null
                                          ? null
                                          : Reflection.getCallerClass();
          
              /*
               * Look up or generate the designated proxy class and its constructor.
               */
              Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
          
              return newProxyInstance(caller, cons, h);
          }
      

      loader:类加载器,用于加载代理对象

      interface:被代理的接口

      h:抽象的行为扩展

    • 使用方法如下:

      1
      2
      3
      4
      5
      
      public class Test {
          ProxyTestInterface proxyInterface = (ProxyTestInterface)Proxy.newProxyInstance(ProxyTestInterface.class.getClassLoader(),
                  new Class[]{ProxyTestInterface.class}, new DebugInvocationHandler(new ProxyTestImpl()));
      }
          
      

总结

使用JDK方式动态代理一般流程如下:

  • 定义一个接口,实现这个接口,实现的行为将会被代理
  • 定义一个类实现 java.lang.reflect.InvocationHandler 接口,它定义了将来的代理类,扩展的行为
  • 在想使用原始类的地方,使用 Proxy.newProxyInstance 获取代理类,在调用原始行为。

CGLIB动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

  • MethodInterceptor:负责定义代理类的扩展行为
  • Enhancer:负责根据定义好的 被代理类,Interceptor,动态创建代理类。

示例:

不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

1
2
3
4
5
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
  1. 实现一个使用阿里云发送短信的类

    1
    2
    3
    4
    5
    6
    7
    8
    
    package github.javaguide.dynamicProxy.cglibDynamicProxy;
       
    public class AliSmsService {
        public String send(String message) {
            System.out.println("send message:" + message);
            return message;
        }
    }
    
  2. 自定义 MethodInterceptor(方法拦截器)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
       
    import java.lang.reflect.Method;
       
    /**
     * 自定义MethodInterceptor
     */
    public class DebugMethodInterceptor implements MethodInterceptor {
       
       
        /**
         * @param o           被代理的对象(需要增强的对象)
         * @param method      被拦截的方法(需要增强的方法)
         * @param args        方法入参
         * @param methodProxy 用于调用原始方法
         */
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("before method " + method.getName());
            Object object = methodProxy.invokeSuper(o, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method " + method.getName());
            return object;
        }
       
    }
    
  3. 获取代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    import net.sf.cglib.proxy.Enhancer;
       
    public class CglibProxyFactory {
       
        public static Object getProxy(Class<?> clazz) {
            // 创建动态代理增强类
            Enhancer enhancer = new Enhancer();
            // 设置类加载器
            enhancer.setClassLoader(clazz.getClassLoader());
            // 设置被代理类
            enhancer.setSuperclass(clazz);
            // 设置方法拦截器
            enhancer.setCallback(new DebugMethodInterceptor());
            // 创建代理类
            return enhancer.create();
        }
    }
    
  4. 实际使用

    1
    2
    3
    4
    5
    6
    7
    
    AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
    aliSmsService.send("java");
       
    ----------------控制台输出---------------
    before method send
    send message:java
    after method send
    

总结

总的来说,我们不使用代理也能实现功能的扩展,只需要修改原始功能代码即可,但是这样会使得原始功能代码越来越繁重,代理的设计主要是为了让原始代码专注与业务逻辑,一些扩展功能,如日志,安全检查等可以通过在代理中实现,便于维护。

本文由作者按照 CC BY 4.0 进行授权