Java代理模式动态代理详细实现教程

Java动态代理是在运行时通过invocationhandler和proxy类自动生成代理对象,以实现在不修改原有代码的情况下增强方法功能。其核心在于:1. invocationhandler接口负责处理代理对象的方法调用,通过invoke方法拦截并插入前置、后置及异常处理逻辑;2. proxy类用于动态生成代理实例,通过newproxyinstance方法结合类加载器、接口列表和invocationhandler实例创建代理对象;3. 动态代理解决了静态代理的代码冗余、维护困难和扩展性差的问题,适用于统一处理日志、权限、事务等“横切关注点”;4. 使用时需注意性能开销、只能代理接口、无法代理final和private方法、类型转换问题、异常处理及类加载器选择等限制。

Java代理模式动态代理详细实现教程

Java动态代理,说白了,就是在程序运行时,不手动写代理类,而是通过一套机制,根据接口自动帮你生成一个代理对象。它能让你在不修改原有代码的基础上,给目标对象的方法加上一些额外的处理逻辑,比如日志记录、性能监控、事务管理、权限控制等等。这玩意儿的妙处在于,它极大地提高了代码的灵活性和可维护性,尤其是在你需要为一大类或接口提供类似功能时,能帮你省去大量重复性的工作。

Java代理模式动态代理详细实现教程

解决方案

要实现Java动态代理,核心就是java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类。简单来说,你需要一个实现InvocationHandler接口的类,它会负责处理所有对代理对象方法的调用。当代理对象的方法被调用时,实际执行的是这个InvocationHandler的invoke方法。

我们先定义一个接口和它的实现类:

立即学习Java免费学习笔记(深入)”;

Java代理模式动态代理详细实现教程

// 定义一个服务接口 public interface MyService {     void performAction(String actionName);     String retrieveData(int id); }  // 接口的实现类 public class MyServiceImpl implements MyService {     @Override     public void performAction(String actionName) {         System.out.println("MyServiceImpl: Executing action - " + actionName);     }      @Override     public String retrieveData(int id) {         System.out.println("MyServiceImpl: Retrieving data for ID - " + id);         return "Data for ID " + id;     } }

接着,就是我们的核心——代理处理器。它会持有实际的目标对象,并在invoke方法中,在调用目标方法前后插入我们想做的任何事情。

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;  // 动态代理处理器 public class ServiceInvocationHandler implements InvocationHandler {     private Object target; // 目标对象      public ServiceInvocationHandler(Object target) {         this.target = target;     }      /**      * proxy: 代理对象本身,通常我们不直接用它,除非你需要对代理对象进行递归操作。      * method: 当前被调用的方法对象。      * args: 调用方法时传入的参数。      */     @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         // 在方法执行前的逻辑         System.out.println("--- Proxy Log: Method '" + method.getName() + "' is about to be called. Arguments: " + (args != null ? java.util.Arrays.toString(args) : "None"));          Object result = null;         try {             // 实际调用目标对象的方法             result = method.invoke(target, args);         } catch (Exception e) {             System.err.println("--- Proxy Log: An error occurred during method execution: " + e.getMessage());             throw e; // 重新抛出异常         } finally {             // 在方法执行后的逻辑,无论成功失败都会执行             System.out.println("--- Proxy Log: Method '" + method.getName() + "' has finished execution. Return value: " + result);         }         return result;     }      // 这是一个方便创建代理实例的静态方法     @SuppressWarnings("unchecked")     public static <T> T createProxy(T target, Class<?>... interfaces) {         return (T) Proxy.newProxyInstance(                 target.getClass().getClassLoader(), // 类加载器,用于加载代理类                 interfaces.length > 0 ? interfaces : target.getClass().getInterfaces(), // 代理类要实现的接口                 new ServiceInvocationHandler(target) // 我们的代理处理器         );     } }

最后,我们看看怎么使用这个动态代理:

Java代理模式动态代理详细实现教程

public class DynamicProxyDemo {     public static void main(String[] args) {         MyService realService = new MyServiceImpl();          // 创建代理实例         MyService proxyService = ServiceInvocationHandler.createProxy(realService, MyService.class);          // 通过代理对象调用方法         System.out.println("n--- Calling performAction via proxy ---");         proxyService.performAction("doReport");          System.out.println("n--- Calling retrieveData via proxy ---");         String data = proxyService.retrieveData(101);         System.out.println("Client received data: " + data);     } }

运行这段代码,你会发现performAction和retrieveData方法在执行前后都打印出了我们预设的日志信息,这就是动态代理在起作用。

为什么有了静态代理,我们还要费劲去搞动态代理?

这确实是个好问题,初学者可能都会有这样的疑惑。静态代理嘛,就是你手动为每一个需要代理的接口或类写一个代理类,然后这个代理类持有真实对象的引用,并在调用真实方法前后加上自己的逻辑。看起来挺直观的,不是吗?但问题就出在“手动写”和“每一个”上。

试想一下,如果你的系统里有几十个甚至上百个服务接口,每个都需要加统一的日志、权限检查或者事务管理,难道你要手动写几十个甚至上百个代理类吗?那代码量简直是灾难,而且一旦某个接口的方法变了,或者你需要修改代理逻辑,你就得改动所有相关的代理类,这维护成本简直不敢想。

静态代理的痛点在于:

  • 代码冗余和维护困难: 一个接口一个代理类,接口多了,代理类就爆炸式增长。
  • 扩展性差: 如果想对新的接口进行代理,就必须手动创建新的代理类。
  • 耦合度高: 代理逻辑和业务逻辑在编译时就绑定了,不够灵活。

而动态代理,它就像一个“通用代理生成器”。你只需要写一个InvocationHandler,这个处理器就能处理所有你指定接口的方法调用。它在运行时动态生成代理类,这意味着你不需要提前知道所有要代理的接口,也不需要为它们手动编写代理类。你只需要告诉它:“嘿,帮我代理这个接口,所有方法都走我这个处理器!”它就能帮你搞定。这种运行时生成的能力,彻底解决了静态代理面临的扩展性和维护性难题。

那个InvocationHandler里的invoke方法,它到底是怎么回事?

invoke方法是Java动态代理的真正核心,所有通过代理对象调用的方法,最终都会被路由到这个方法来执行。理解它的三个参数至关重要:

  1. Object proxy: 这个参数代表了当前正在被调用的代理实例本身。听起来有点绕,但你可以把它理解为“我就是那个代理对象”。大多数情况下,我们不会直接操作这个proxy对象,因为它可能会导致无限递归调用(你通过代理对象调用方法,又回到了invoke,如果invoke里再用proxy调用自己,就死循环了)。但在某些高级场景,比如需要判断当前调用是否来自代理自身,或者需要把代理对象作为参数传递出去时,它才有用。通常我们关注的是method和args。

  2. Method method: 这个参数是java.lang.reflect.Method类型,它代表了你通过代理对象实际调用的那个方法。比如你调用proxyService.performAction(“doReport”),那么method对象就代表了MyService接口中的performAction方法。通过这个Method对象,你可以获取方法的名称、参数类型、返回类型,甚至它的注解信息。最关键的是,你可以用method.invoke(target, args)来反射调用真实目标对象上的对应方法。

  3. Object[] args: 这个参数是一个对象数组,它包含了调用method时传入的所有参数。比如proxyService.performAction(“doReport”),那么args数组里就只有一个元素,是字符串”doReport”。你可以根据这些参数来做一些前置校验、参数转换,或者在日志中记录参数值。

invoke方法的工作流程,就像一个方法调用的“守门员”:

  • 当客户端代码调用代理对象的方法时,这个调用不会直接到达真实的目标对象。
  • 相反,jvm会截获这个调用,并把它转发给InvocationHandler的invoke方法。
  • 在invoke方法内部,你可以自由地在调用真实方法之前(前置处理)、调用真实方法之后(后置处理)、甚至在真实方法抛出异常时(异常处理)插入你的逻辑。
  • 最后,通过method.invoke(target, args),你把控制权交还给真实的目标对象,让它执行真正的业务逻辑。
  • 真实方法的返回值会作为invoke方法的返回值,最终返回给客户端。

这种机制,使得我们可以在不侵入业务代码的前提下,实现各种“横切关注点”的功能,比如前面提到的日志、事务、权限等等,这正是动态代理的魅力所在。

用动态代理时,有哪些坑是我们需要特别留意的?

动态代理虽然强大,但在实际使用中,也确实有一些需要注意的地方,否则可能会遇到一些意想不到的问题:

  • 性能开销: 动态代理底层依赖Java的反射机制。反射操作相比直接的方法调用,会有一定的性能损耗。因为反射需要解析方法签名、查找方法、进行类型检查等,这些都会增加CPU的负担。对于那些对性能极其敏感、每秒调用次数达到百万级别的核心业务逻辑,你需要权衡这种开销。不过,对于大多数应用场景,这种性能影响通常可以忽略不计。

  • 只能代理接口,不能直接代理类(JDK动态代理): 这是JDK动态代理的一个核心限制。Proxy.newProxyInstance方法要求传入一个接口数组,它生成的代理类会实现这些接口。这意味着如果你想代理一个没有实现任何接口的普通Java类,JDK动态代理就无能为力了。在这种情况下,你可能需要考虑使用CGLIB这样的第三方库,它通过继承目标类来生成代理(因此不能代理final类或final方法)。

  • final方法和private方法无法被代理: 即使是CGLIB,也无法代理final修饰的方法,因为final方法不允许被子类重写。private方法同样不能被代理,因为它们在类外部是不可见的。动态代理只能拦截和增强那些能够被外部访问和重写的方法。

  • 代理对象类型转换问题: Proxy.newProxyInstance返回的是Object类型,你需要将其强制转换为你所代理的接口类型。例如,MyService proxyService = (MyService) ServiceInvocationHandler.createProxy(…)。如果你尝试将其转换为非代理接口的类型,或者转换成实现类的类型,可能会抛出ClassCastException。代理对象只实现了你传入的那些接口,它并不是目标对象的真正实例。

  • 异常处理: 在InvocationHandler的invoke方法中,如果目标方法抛出了异常,你需要决定是捕获并处理,还是直接重新抛出。通常情况下,为了不改变原有的异常传播链,我们会捕获并记录后,再将异常重新抛出。否则,客户端可能无法感知到业务逻辑中发生的错误。

  • 类加载器: Proxy.newProxyInstance需要一个ClassLoader参数,这个类加载器负责加载生成的代理类。通常情况下,使用目标对象的类加载器(target.getClass().getClassLoader())就足够了。但在复杂的模块化或多类加载器环境中,选择正确的类加载器可能变得复杂,错误的类加载器可能导致ClassNotFoundException或其他运行时错误。

理解这些限制和潜在问题,能帮助你在设计和实现动态代理时做出更明智的选择,避免掉进坑里。

以上就是Java代理模式

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享