Java动态代理的介绍与实践——《我的Java打怪日记》

简介: Java动态代理的介绍与实践

1. Spring AOP

Spring是一个轻型容器,Spring整个系列的最最核心的概念当属IoC、AOP。可见AOP是Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础。AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。

关于AOP的基础知识,并不是本文的重点,我们主要来看下AOP的核心功能的底层实现机制:动态代理的实现原理。AOP的拦截功能是由java中的动态代理来实现的。在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。

那么动态代理是如何实现将切面逻辑(advise)织入到目标类方法中去的呢?下面我们就来详细介绍并实现AOP中用到的两种动态代理。

AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。

下面我们分别来示例实现这两种方法。

2. JDK动态代理

2.1 定义接口与实现类

publicinterfaceOrderService {
   publicvoidsave(UUID orderId, String name);

   publicvoidupdate(UUID orderId, String name);

   public String getByName(String name);
}

上面代码定义了一个被拦截对象接口,即横切关注点。下面代码实现被拦截对象接口。

publicclassOrderServiceImplimplementsOrderService {

   private String user = null;

   publicOrderServiceImpl() {
   }

   publicOrderServiceImpl(String user) {
       this.setUser(user);
   }

//...

   @Override
   publicvoidsave(UUID orderId, String name) {
       System.out.println("call save()方法,save:" + name);
   }

   @Override
   publicvoidupdate(UUID orderId, String name) {
       System.out.println("call update()方法");
   }

   @Override
   public String getByName(String name) {
       System.out.println("call getByName()方法");
       return"aoho";
   }
}

2.2 JDK动态代理类

publicclassJDKProxyimplementsInvocationHandler {
//需要代理的目标对象
   private Object targetObject;
   
   public Object createProxyInstance(Object targetObject) {
       this.targetObject = targetObject;
       return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
               this.targetObject.getClass().getInterfaces(), this);
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
    //被代理对象
       OrderServiceImpl bean = (OrderServiceImpl) this.targetObject;
       Object result = null;
       //切面逻辑(advise),此处是在目标类代码执行之前
       System.out.println("---before invoke----");
       if (bean.getUser() != null) {
           result = method.invoke(targetObject, args);
       }
       System.out.println("---after invoke----");
       return result;
   }

//...

}

上述代码实现了动态代理类JDKProxy,实现InvocationHandler接口,并且实现接口中的invoke方法。当客户端调用代理对象的业务方法时,代理对象执行invoke方法,invoke方法把调用委派给targetObject,相当于调用目标对象的方法,在invoke方法委派前判断权限,实现方法的拦截。

2.3 测试

publicclassAOPTest {
   publicstaticvoidmain(String[] args) {
       JDKProxy factory = new JDKProxy();
       //Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例  
       OrderService orderService = (OrderService) factory.createProxyInstance(new OrderServiceImpl("aoho"));
 //由动态生成的代理对象来orderService 代理执行程序
       orderService.save(UUID.randomUUID(), "aoho");
   }

}

结果如下:

---before invoke----
call save()方法,save:aoho
---after invoke----

3. CGLIB字节码生成

3.1 要代理的类

CGLIB既可以对接口的类生成代理,也可以针对类生成代理。示例中,实现对类的代理。

publicclassOrderManager {
   private String user = null;

   publicOrderManager() {
   }

   publicOrderManager(String user) {
       this.setUser(user);
   }

//...

   publicvoidsave(UUID orderId, String name) {
       System.out.println("call save()方法,save:" + name);
   }

   publicvoidupdate(UUID orderId, String name) {
       System.out.println("call update()方法");
   }

   public String getByName(String name) {
       System.out.println("call getByName()方法");
       return"aoho";
   }
}

该类的实现和上面的接口实现一样,为了保持统一。

3.2 CGLIB动态代理类

publicclassCGLibProxyimplementsMethodInterceptor {
    // CGLib需要代理的目标对象
    private Object targetObject;

      public Object createProxyObject(Object obj) {
       this.targetObject = obj;
       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(obj.getClass());
       //回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
       enhancer.setCallback(this);
       //增强后的目标类
       Object proxyObj = enhancer.create();
       // 返回代理对象
       return proxyObj;
   }

   @Override
   public Object intercept(Object proxy, Method method, Object[] args,
                           MethodProxy methodProxy)
throws Throwable {
       Object obj = null;
       //切面逻辑(advise),此处是在目标类代码执行之前
       System.out.println("---before intercept----");
       obj = method.invoke(targetObject, args);
       System.out.println("---after intercept----");
       return obj;
   }
}

上述实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,methodProxy为代理类实例。method.invoke(targetObject, args)通过代理类调用父类中的方法。

3.3 测试

publicclassAOPTest {
   publicstaticvoidmain(String[] args) {
       OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager("aoho"));
       order.save(UUID.randomUUID(), "aoho");
   }

结果如下:

---before intercept----
call save()方法,save:aoho
---after intercept----

4. 总结

本文主要讲了Spring Aop动态代理实现的两种方式,并分别介绍了其优缺点。jdk动态代理的应用前提是目标类基于统一的接口。如果没有该前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

JDK动态代理机制是委托机制,不需要以来第三方的库,只要要JDK环境就可以进行代理,动态实现接口类,在动态生成的实现类里面委托为hanlder去调用原始实现类方法;CGLib 必须依赖于CGLib的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

参考

  1. jdk动态代理代理与cglib代理原理探究
  2. AOP的底层实现-CGLIB动态代理和JDK动态代理
目录
相关文章
|
24天前
|
Java Linux C语言
Java——输入输出及实践
Java——输入输出及实践
37 1
Java——输入输出及实践
|
24天前
|
监控 算法 Java
Java内存管理:垃圾收集器的工作原理与调优实践
在Java的世界里,内存管理是一块神秘的领域。它像是一位默默无闻的守护者,确保程序顺畅运行而不被无用对象所困扰。本文将带你一探究竟,了解垃圾收集器如何在后台无声地工作,以及如何通过调优来提升系统性能。让我们一起走进Java内存管理的迷宫,寻找提高应用性能的秘诀。
|
10天前
|
监控 算法 Java
掌握Java的垃圾回收机制:从原理到实践
在Java的世界中,垃圾回收(Garbage Collection,简称GC)是一块神秘的领域,它如同一位默默无闻的清洁工,确保内存中不再使用的对象得到妥善处理。本文将带你走进垃圾回收的大门,探索它的工作原理、常见算法及其在实际应用中的调优策略。无论你是初学者还是有一定经验的开发者,这篇文章都将为你揭开垃圾回收的神秘面纱,让你的Java程序运行得更加高效和稳定。
24 5
|
16天前
|
数据采集 Java 数据挖掘
Java IO异常处理:在Web爬虫开发中的实践
Java IO异常处理:在Web爬虫开发中的实践
|
16天前
|
Java UED 开发者
Java中的异常处理:理解与实践
【9月更文挑战第3天】在Java编程中,异常处理是保持程序健壮性的关键。本文将引导你了解Java的异常机制,从基本的try-catch结构到自定义异常类的创建,以及如何优雅地处理异常情况。我们将一起探讨异常处理的最佳实践,并学习如何在代码中实现它们,以确保你的应用程序能够优雅地处理运行时错误。
13 2
|
21天前
|
Java Devops 持续交付
探索Java中的Lambda表达式:简化代码,提升效率DevOps实践:持续集成与部署的自动化之路
【8月更文挑战第30天】本文深入探讨了Java 8中引入的Lambda表达式如何改变了我们编写和管理代码的方式。通过简化代码结构,提高开发效率,Lambda表达式已成为现代Java开发不可或缺的一部分。文章将通过实际例子展示Lambda表达式的强大功能和优雅用法。
|
20天前
|
Java 调度
Java中的多线程基础与实践
【8月更文挑战第31天】本文将深入浅出地讲解Java中多线程的基础知识,并通过实例展示如何在Java程序中实现多线程。我们将从多线程的基本概念出发,逐步深入到线程的创建、控制以及同步机制,最后通过一个简易版的生产者消费者模型来实践这些知识点。文章旨在帮助初学者快速掌握多线程编程的关键技能,并理解其背后的原理。
|
24天前
|
Java 程序员 编译器
Java编程中的异常处理:理解与实践
【8月更文挑战第28天】在Java的世界中,异常是程序运行中不可避免的一部分。它们像是旅途中的路障,挑战着程序员的智慧和耐心。本文将带你走进Java的异常处理机制,从基础概念到高级应用,我们将一起探索如何优雅地处理这些不请自来的挑战者。你将学习到如何捕获、处理以及预防异常,确保你的代码像经验丰富的旅行者一样,即使在最崎岖的道路上也能从容前行。
|
24天前
|
机器学习/深度学习 人工智能 算法
探索人工智能在医疗诊断中的应用与挑战Java编程中的对象和类:基础与实践
【8月更文挑战第27天】随着人工智能(AI)技术的飞速发展,其在医疗领域的应用日益广泛。本文深入探讨了AI技术在医疗诊断中的具体应用案例,包括图像识别、疾病预测和药物研发等方面,并分析了当前面临的主要挑战,如数据隐私、算法偏见和法规限制等。文章旨在为读者提供一个全面的视角,理解AI在改善医疗服务质量方面的潜力及其局限性。
|
11天前
|
Java 数据库连接 开发者
Java中的异常处理:理解与实践
【9月更文挑战第9天】在Java编程的海洋里,异常处理是一艘不可或缺的救生艇。它不仅保护你的代码免受意外错误的侵袭,还能确保你的应用在遇到困难时能优雅地继续航行。本文将带你深入了解Java的异常处理机制,通过浅显易懂的方式,让你掌握如何捕捉和处理异常,以及如何自定义异常类型来应对特定的业务需求。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的知识和技巧,让你的代码更加健壮和可靠。