信息发布→ 登录 注册 退出

浅谈Java动态代理的实现

发布时间:2026-01-11

点击量:
目录
  • 一、代理设计模式
    • 1.1 什么是代理
    • 1.2 代理模式入门
  • 二、Java代理的三种实现
    • 2.1 静态代理
    • 2.2 Java自带的动态代理
    • 2.3 cglib实现动态代理
  • 三、cglib动态代理的实现
    • 四、面试常见问题

      一、代理设计模式

      1.1 什么是代理

      • 考虑真实的编程场景,项目中存在一个访问其他数据源的接口,包含一个query()方法
      • 我们已经针对这个接口,实现了MySQL、Hive、HBase、MongoDB等作为数据源的实现类
      • 但是,在测试过程中,我们发现这些数据源的查询并不是很稳定
      • 最原始的想法: 在所有实现类query()方法中,代码首部获取startTime,代码尾部获取endTime,通过打印日志的方式,知道每次查询的耗时
      long startTime = System.currentTimeMillis();
      logger.info("query mysql start:" + new Date(startTime).toLocaleString());
      // 具体的query代码
      ...
      
      long endTime = System.currentTimeMillis();
      logger.info(String.format("query mysql end: %s, consumed time: %dms", new Date(endTime).toLocaleString(), (endTime-startTime)));
      
      • 直接修改已经实现的方法,存在很多缺点:

      (1)现在是打印日志,代码非常简单,就算query()方法不是你实现的,你也能很好的完成。
      (2)但是如果是其他功能呢?比如,如果查询失败,要求你查询重试

      • 因此,在不改变已经实现好的query()方法前提下,去实现日志打印的功能是最好的方法。
      • 进阶想法: 我为每个实现类创建一个包装类。

      (1)这个包装类与实现类一样,实现了相同的接口。
      (2)在query()方法中,直接调用实现类的query()方法,并在调用前后进行日志打印
      (3)对实现类方法的调用,都改成对包装类方法的调用

      long startTime = System.currentTimeMillis();
      logger.info("query mysql start:" + new Date(startTime).toLocaleString());
      // 使用try-finally
      JSONObject[] data = null;
      try {
          data = mysql.query();
          return data;
      } catch (Exception exception) {
          throw exception;
      } finally {
          long endTime = System.currentTimeMillis();
          logger.info(String.format("query mysql end: %s, consumed time: %dms", new Date(endTime).toLocaleString(), (endTime - startTime)));
      }
      
      • 这时,代理模式的概念就变得非常清晰了:不直接调用实现类的某个方法,而是通过实现类的代理去调用。
      • 这样不仅可以实现调用者与被调用者之间的解耦合,还可以在不修改调用者的情况下,丰富功能逻辑

      1.2 代理模式入门

      代理模式的UML图如下

      1.subject: 抽象主题角色,是一个接口,定义了一系列的公共对外方法

      2.real subject: 真实主题角色,也就是我刚刚提到的实现类,又称委托类。
      委托类实现抽象主题,负责实现具体的业务逻辑

      3.proxy: 代理主题角色,简称代理类。它也实现了抽象主题,用于代理、封装,甚至增强委托类。
      一般通过内含委托类,实现对委托类的封装

      4.client: 当访问具体的业务逻辑时,clinet表面是访问代理类,而实际是访问被代理类封装的委托类

       

      代理模式的应用场景:目前,就我本人所接触的使用场景,就是通过代理去打印日志、增强业务逻辑 😂

      二、Java代理的三种实现

      2.1 静态代理

      • 所谓的静态动态,是相对于字节码的生成时机来说的:

      (1)静态是指字节码,即class文件,在编译时就已经生成。
      (2)动态是指字节码在运行时动态生成,而不是编译时提前生成

      • 刚刚,我们针对数据查询的进阶方法,实际就是静态代理
      • 通过为每个委托类创建对应的代理类,然后编译时就可以得到代理类的字节码

      下面是一个具体的静态代理实例:

      抽象主题:

      public interface Animal {
         void eat();
      }

      委托类:Dog和Cat,实现了抽象接口

      public class Dog implements Animal {
          @Override
          public void eat() {
              System.out.println("I like eating bone");
          }
      }
      
      public class Cat implements Animal {
          @Override
          public void eat() {
              System.out.println("I like eating fish");
          }
      }
      

      代理类:代理类中含有对应的委托类,通过调用委托类的具体实现,来封装委托类

      public class DogProxy implements Animal {
          private Dog dog;
      
          public DogProxy(Dog dog) {
              this.dog = dog;
          }
      
          @Override
          public void eat() {
              System.out.print("I'm a "+dog.getClass().getSimpleName() +". ");
              dog.eat();
          }
      }
      
      public class CatProxy implements Animal {
          private Cat cat;
      
          public CatProxy(Cat cat) {
              this.cat = cat;
          }
      
          @Override
          public void eat() {
              System.out.print("I'm a " + cat.getClass().getSimpleName()+". ");
              cat.eat();
          }
      }
      

      静态代理虽然实现简单、不更改原始的业务逻辑,但是仍然存在以下缺点:

      1.如果存在多个委托类,则需要创建多个代理类,这样则会产生过多的代理类

      2.如果抽象主题增加、删除、修改方法时,委托类和代理类都需要同时修改,不易维护

      2.2 Java自带的动态代理

      • 静态代理存在以上缺点,如果我们能在程序运行时,动态生成对应的代理类,则无需创建并维护过多的代理类
      • 使用Java自带的java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler类,可以实现动态代理
      • 从类的全路径可以看出,Java的动态代理实际利用的是反射机制实现的
      • 其中,Proxy用于创建对应接口的代理类。具体代理的是哪个委托类,是由实现InvocationHandler接口的中介类决定的
      • 代理类、委托类、中介类之间的关系如下

      Java自带的动态代理具体实现:

      1.创建抽象主题 —— 与静态代理类一致,不再展示

      2.创建实现类 —— 与静态代理类一致,不再展示

      3.实现InvocationHandler接口,创建中介类

      public class AnimalInvokeHandler implements InvocationHandler {
          private Animal animal;
      
          public AnimalInvokeHandler(Animal animal) {
              this.animal = animal;
          }
      
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println("proxy class: " + proxy.getClass().getName());
              System.out.printf("proxy instanceof  Animal: %b \n", proxy instanceof Animal);
              System.out.printf("---- Call method: %s, class: %s ----\n", method.getName(), animal.getClass().getSimpleName());
              // 通过反射,调用委托类的方法
              Object result = method.invoke(animal, args);
              return result;
          }
      }
      

      4.通过Proxy创建动态代理类,实现对抽象主题的代理

      public static void main(String[] args) {
          // 通过Proxy.newProxyInstance创建代理类
          // 将代理类转为抽象主题,可以动态的创建实现了该主题的代理类
          // 必须从实现类去获取需要代理的接口
          // 指定中介类,通过中介类实现代理
          Animal proxy = (Animal) Proxy.newProxyInstance(Main.class.getClassLoader(), Dog.class.getInterfaces(), new AnimalInvokeHandler(new Dog()));
          proxy.eat();
      }
      

      执行结果:

      Java原生的动态代理,利用反射动态生成代理类字节码ProxyX.class,然后将其强制转化为抽象主题类型,就能实现对该接口的代理

      jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口

      Java原生动态代理,又叫jdk动态代理,具有以下优缺点

      1.优点: jdk动态代理,避免了静态代理需要创建并维护过多的代理类的

      2.缺点: jdk动态代理只能代理接口,因为Java的单继承原则:代理类本身已经继承了Proxy类,就不能再继承其他类,只能实现委托类的抽象主题接口。

      2.3 cglib实现动态代理

      • jdk动态代理存在只能代理接口的问题,是十分不方便的。
      • 考虑以下场景:

      一个类中有两个方法methodA:转账到其他账户,methodB:查询账户余额。
      如果用户访问methodA,希望先提示用户检查账户信息是否正确;
      如果用户访问methodB,希望在用户查询完余额后,提示用户关注银行的微信公众号。

      • 上述类已经成功用于业务场景了,我们想要实现这些增强功能,最好不要更改其原始代码,而是通过代理实现功能增强
      • 这时,便可以使用cglib实现对类的动态代理 —— 小白看了实现后,可能会倾向于说这就是类似Java web的拦截器 😂

      三、cglib动态代理的实现

      添加maven依赖

      <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.2.11</version>
      </dependency>

      简化版的银行系统类

      public class BankSystem {
          // 转账
          public boolean transferAccount(double amount, String address) {
              System.out.printf("Send %f dollars to account %s", amount, address);
              // 转账成功
              return true;
          }
      
          // 查询账户余额
          public String queryBalance(){
              System.out.printf("Query account balance success");
              return "Account balance: 2400 dollars";
          }
      }
      

      为转账方法创建拦截器

      public class TransferInterceptor implements MethodInterceptor {
          /**
          *
          * @param o,表示要增强的对象,其实就是代理中的委托类
          * @param method,被拦截的方法,其实就是委托类中被代理的方法
          * @param objects,被拦截方法的入参,如果是基本数据类型需要传入包装类型
          * @param methodProxy,对method的代理,通过invokeSuper(),实现对method的调用
          * @return java.lang.Object
          * @author sunrise
          * @date 2025/5/23 10:43 上午
          **/
          @Override
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
              before(objects);
              Object result = methodProxy.invokeSuper(o, objects);
              return result;
          }
      
          private void before(Object[] args){
              System.out.printf("Please check: you will send %.2f dollar to account %s.\n", args[0], args[1]);
          }
      }
      

      为余额查询方法创建拦截器

      public class QueryBalanceInterceptor implements MethodInterceptor {
          @Override
          public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
              // 调用预发插叙方法
              Object result = proxy.invokeSuper(obj, args);
              after();
              return result;
          }
      
          private void after() {
              System.out.printf("Please pay attention to WeChat official account.\n");
          }
      }
      

      创建filter,实现方法与拦截器的映射

      public class BankFilter implements CallbackFilter {
          @Override
          public int accept(Method method) {
              if ("transferAccount".equals(method.getName())) {
                  // 使用拦截器列表中的第1个拦截器
                  return 0;
              }
              // 使用拦截器列表中的第2个拦截器
              return 1;
          }
      }
      

      使用cglib实现动态代理 —— 我认为就是方法拦截 😂

      public static void main(String[] args) {
          // 新建拦截器
          TransferInterceptor transferInterceptor = new TransferInterceptor();
          QueryBalanceInterceptor queryBalanceInterceptor = new QueryBalanceInterceptor();
      
          // 创建工具类
          Enhancer enhancer = new Enhancer();
          // 设置委托类,在cglib中,这是cglib需要继承的超类
          enhancer.setSuperclass(BankSystem.class);
          // 设置多个拦截器
          enhancer.setCallbacks(new Callback[]{transferInterceptor, queryBalanceInterceptor});
          // 实现拦截器和方法的映射,即为不同的方法配置不同的拦截器
          enhancer.setCallbackFilter(new BankFilter());
      
          // 创建代理类
          BankSystem proxy = (BankSystem) enhancer.create();
      
          // 执行转账,调用TransferInterceptor
          boolean ok = proxy.transferAccount(1024.28, "lucy");
          System.out.println("transfer money success: " + ok);
          // 查询余额,调用QueryBalanceInterceptor
          proxy.queryBalance();
      }
      

      cglib总结:

      1.优点1: cglib基于实现动态代理,通过ASM字节码框架动态生成委托类的子类,并使用方法拦截器实现对委托类方法的拦截。

      2.优点2: 基于ASM字节码框架动态生成代理类,比jdk动态生成代理类更加高效

      3.缺点: 通过继承委托类创建动态代理类,因此不能代理final委托类或委托类中的final方法

      四、面试常见问题

      java中代理的实现

      共有三种方法:静态代理、JDK动态代理、cglib动态代理

      • 静态代理: 为每个实现类都创建一个对应的代理类,需要创建并维护大量的代理类
      • jdk动态代理: 通过Proxy.newProxyInstance()为抽象主题创建代理类,被代理的委托类包含在InvocationHandler类中,由InvocationHandler类的invoke方法通过反射实现对委托类方法的调用
      • cglib动态代理:通过ASM字节码框架,继承委托类以创建代理类。代理类通过方法拦截器,实现对委托类方法的拦截

      三种代理方式的比较

      1.静态代理,需要创建并维护大量的委托类

      2.jdk动态代理,避免了静态类的上述缺点,但只能代理接口(Java单继承原则,代理类已经继承了Proxy类)

      3.cglib动态代理,可以实现对类的代理,并通过方法拦截器实现对委托类(父类)方法的拦截;使用强大的ASM字节码框架,更加高效;通过继承实现对类的代理,使其无法代理final类或类中的final方法

      为何调用代理类的方法,会自动进入InvocationHandlerinvoke()方法?

      • 通过newProxyInstance()创建代理类时,会为代理类设置InvocationHandlerh
      • 动态生成的代理类字节码,通过反编译可以发现,它实现了抽象主题中的每个方法
      • 方法的实现,是调用内部成员h.invoke()方法
      this.h.invoke(this, method, args);

      因此,调用代理类的方法时,实际上会调用InvocationHandlerinvoke()方法

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!