文章来源:http://www.iteye.com/topic/1123293,整理在我的博客有两个目的:一个是原文确实很不错,通俗易懂,督促自已将博主的这一系列关于Spring文章都学完;另一个原因是为免原文被博主删除,在此记录,方便以后查找阅读。
Spring AOP使用动态代理技术在运行期织入增强的代码,为了揭示Spring AOP底层的工作机理,有必要对涉及到的Java知识进行学习。Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。
一.带有横切逻辑的实例
下面代码实现了性能监视横切逻辑,并通过动态代理技术对此进行改造。在调用每一个目标类方法时启动方法的性能监视,在目标类方法调用完成时记录方法的花费时间。
ForumService.java:包含性能监视横切代码
package com.baobaotao.proxy; public interface ForumService { public void removeTopic(int topicId); public void removeForum(int forumId); }
ForumServiceImpl.java
package com.baobaotao.proxy; public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { // ①-1开始对该方法进行性能监视 PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic"); System.out.println("模拟删除Topic记录:" + topicId); try { Thread.currentThread().sleep(20); } catch (Exception e) { throw new RuntimeException(e); } // ①-2结束对该方法进行性能监视 PerformanceMonitor.end(); } public void removeForum(int forumId) { // ②-1开始对该方法进行性能监视 PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum"); System.out.println("模拟删除Forum记录:" + forumId); try { Thread.currentThread().sleep(40); } catch (Exception e) { throw new RuntimeException(e); } // ②-2结束对该方法进行性能监视 PerformanceMonitor.end(); } }
代码中有注释的代码表示的代码就是具有横切逻辑特征的代码,每个Service类和每个业务方法体的前后都执行相同的代码逻辑:方法调用前启动PerformanceMonitor,方法调用后通知PerformanceMonitor结束性能监视并给记录性能监视结果。
PerformanceMonitor是性能监视的实现类,我们给出一个非常简单的实现版本,其代码如下所示:
PerformanceMonitor.java
package com.baobaotao.proxy; public class PerformanceMonitor { // ①通过一个ThreadLocal保存调用线程相关的性能监视信息 private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); // ②启动对某一目标方法的性能监视 public static void begin(String method) { System.out.println("begin monitor..."); MethodPerformance mp = new MethodPerformance(method); performanceRecord.set(mp); } public static void end() { System.out.println("end monitor..."); MethodPerformance mp = performanceRecord.get(); // ③打印出方法性能监视的结果信息。 mp.printPerformance(); } }
ThreadLocal是将非线程安全类改造为线程安全类的法宝,PerformanceMonitor提供了两个方法:通过调用begin(String method)方法开始对某个目标类方法的监视,method为目标类方法的全限定名;而end()方法结束对目标类方法的监视,并给出性能监视的信息。这两个方法必须配套使用。
用于记录性能监视信息的MethodPerformance类的代码如所示:
MethodPerformance.java
package com.baobaotao.proxy; public class MethodPerformance { private long begin; private long end; private String serviceMethod; public MethodPerformance(String serviceMethod) { this.serviceMethod = serviceMethod; // ①记录目标类方法开始执行点的系统时间 this.begin = System.currentTimeMillis(); } public void printPerformance() { // ②获取目标类方法执行完成后的系统时间,并进而计算出目标类方法执行时间 end = System.currentTimeMillis(); long elapse = end - begin; // ③报告目标类方法的执行时间 System.out.println(serviceMethod + "花费" + elapse + "毫秒。"); } }
通过下面的代码测试拥有性能监视能力的ForumServiceImpl业务方法:
TestForumService.java
package com.baobaotao.proxy; public class TestForumService { public static void main(String[] args) { ForumService forumService = new ForumServiceImpl(); forumService.removeForum(10); forumService.removeTopic(1012); } }
我们得到以下输出信息:
begin monitor... 模拟删除Forum记录:10 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeForum花费47毫秒。 begin monitor... 模拟删除Topic记录:1012 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeTopic花费16毫秒。
正如代码实例所示,当某个方法需要进行性能监视,就必须调整方法代码,在方法体前后分别添加上开启性能监视和结束性能监视的代码。这些非业务逻辑的性能监视代码破坏了ForumServiceImpl业务逻辑的纯粹性。我们希望通过代理的方式,将业务类方法中开启和结束性能监视的这些横切代码从业务类中完全移除。并通过JDK动态代理技术或CGLib动态代理技术将横切代码动态织入到目标方法的相应位置。
二.JDK动态代理
JDK 1.3以后,Java提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在我们终于发现动态代理是实现AOP的绝好底层技术。
JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。这样讲一定很抽象,我们马上着手使用Proxy和InvocationHandler这两个魔法戒对性能监视代码进行革新。
首先,我们从业务类ForumServiceImpl中删除性能监视的横切代码,使ForumServiceImpl只负责具体的业务逻辑,如下代码所示:
移除性能监视横切代码ForumServiceImpl.java
package com.baobaotao.proxy; public class ForumServiceImpl implements ForumService { public void removeTopic(int topicId) { //① System.out.println("模拟删除Topic记录:"+topicId); try { Thread.currentThread().sleep(20); } catch (Exception e) { throw new RuntimeException(e); } //① } public void removeForum(int forumId) { //② System.out.println("模拟删除Forum记录:"+forumId); try { Thread.currentThread().sleep(40); } catch (Exception e) { throw new RuntimeException(e); } //② } }
在如上代码的①和②处,原来的性能监视代码被移除了,我们只保留了真正的业务逻辑。
从业务类中移除的性能监视横切代码当然不能漂浮在空气中,它还得找到一个安身之所,InvocationHandler就是横切代码的安家乐园,我们将性能监视的代码安置在PerformanceHandler中,如下代码所示:
PerformanceHandler.java
package com.baobaotao.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class PerformanceHandler implements InvocationHandler {// ①实现InvocationHandler private Object target; public PerformanceHandler(Object target) { // ②target为目标的业务类 this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) // ③ throws Throwable { PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());// ③-1 Object obj = method.invoke(target, args);// ③-2通过反射方法调用业务类的目标方法 PerformanceMonitor.end();// ③-1 return obj; } }
③处invoke()方法中粗体所示部分的代码为性能监视的横切代码,我们发现,横切代码只出现一次,而不是原来那样星洒各处。③-2处的method.invoke()语句通过Java反射机制间接调用目标对象的方法,这样InvocationHandler的invoke()方法就将横切逻辑代码(③-1)和业务类方法的业务逻辑代码(③-2)编织到一起了,所以我们可以将InvocationHandler看成是一个编织器。下面,我们对这段代码做进一步的说明。
首先,我们实现InvocationHandler接口,该接口定义了一个 invoke(Object proxy, Method method, Object[] args)的方法,proxy是最终生成的代理实例,一般不会用到;method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;args是通过被代理实例某一个方法的入参,在方法反射调用时使用。
此外,我们在构造函数里通过target传入希望被代理的目标对象,如②处所示,在InvocationHandler接口方法invoke(Object proxy, Method method, Object[] args)里,将目标实例传给method.invoke()方法,调用目标实例的方法,如③所示。
下面,我们通过Proxy结合PerformanceHandler创建ForumService接口的代理实例,如下代码所示:
创建代理实例TestForumService.java
package com.baobaotao.proxy; import java.lang.reflect.Proxy; public class TestForumService { public static void main(String[] args) { // ①希望被代理的目标业务类 ForumService target = new ForumServiceImpl(); // ②将目标业务类和横切代码编织到一起 PerformanceHandler handler = new PerformanceHandler(target); // ③根据编织了目标业务类逻辑和性能监视横切逻辑的InvocationHandler实例创建代理实例 ForumService proxy = (ForumService) Proxy.newProxyInstance(target .getClass().getClassLoader(), target.getClass().getInterfaces(), handler); // ④调用代理实例 proxy.removeForum(10); proxy.removeTopic(1012); } }
上面的代码完成业务类代码和横切代码的编织工作并生成了代理实例。在②处,我们让PerformanceHandler将性能监视横切逻辑编织到ForumService实例中,然后在③处,通过Proxy的newProxyInstance()静态方法为编织了业务类逻辑和性能监视逻辑的handler创建一个符合ForumService接口的代理实例。该方法的第一个入参为类加载器;第二个入参为创建代理实例所需要实现的一组接口;第三个参数是整合了业务逻辑和横切逻辑的编织器对象。
按照③处的设置方式,这个代理实例实现了目标业务类的所有接口,即Forum ServiceImpl的ForumService接口。这样,我们就可以按照调用ForumService接口实例相同的方式调用代理实例,如④所示。运行以上的代码,输出以下信息:
begin monitor... 模拟删除Forum记录:10 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeForum花费1313毫秒。 begin monitor... 模拟删除Topic记录:1012 end monitor... com.baobaotao.proxy.ForumServiceImpl.removeTopic花费4172毫秒。
我们发现,程序的运行效果和直接在业务类中编写性能监视逻辑的效果一致,但是在这里,原来分散的横切逻辑代码已经被我们抽取到PerformanceHandler中。当其他业务类(如UserService、SystemService等)的业务方法也需要使用性能监视时,我们只要按照如上代码相似的方式,分别为它们创建代理对象就可以了。下面,我们通过时序图描述通过创建代理对象进行业务方法调用的整体逻辑,以进一步认识代理对象的本质,如下图所示。
上图中使用虚线的方式对通过Proxy创建的ForumService代理实例加以凸显,ForumService代理实例内部利用PerformaceHandler整合横切逻辑和业务逻辑。调用者调用代理对象的removeForum()和removeTopic()方法时,上图的内部调用时序清晰地告诉我们实际上所发生的一切。
三.CGLib动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理实例,这一点我们可从Proxy的接口newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)的方法签名中就看得很清楚:第二个入参interfaces就是需要代理实例实现的接口列表。虽然面向接口编程的思想被很多大师级人物(包括Rod Johnson)推崇,但在实际开发中,许多开发者也对此深感困惑:难道对一个简单业务表的操作也需要老老实实地创建5个类(领域对象类、Dao接口,Dao实现类,Service接口和Service实现类)吗?难道不能直接通过实现类构建程序吗?对于这个问题,我们很难给出一个孰好孰劣的准确判断,但我们确实发现有很多不使用接口的项目也取得了非常好的效果(包括大家所熟悉的SpringSide开源项目)。
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。下面,我们采用CGLib技术,编写一个可以为任何类创建织入性能监视横切逻辑代理对象的代理创建器,如下代码所示:
CglibProxy.java
package com.baobaotao.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz); // ① 设置需要创建子类的类 enhancer.setCallback(this); return enhancer.create(); // ②通过字节码技术动态创建子类实例 } // ③拦截父类所有方法的调用 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());// ③-1 Object result = proxy.invokeSuper(obj, args); // ③-2 PerformanceMonitor.end();// ③-1通过代理类调用父类中的方法 return result; } }
在上面代码中,用户可以通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象通过扩展clazz创建代理对象。在这个代理对象中,我们织入性能监视的横切逻辑(③-1)。intercept(Object obj, Method method, Object[] args,MethodProxy proxy)是CGLib定义的Interceptor接口的方法,它拦截所有目标类方法的调用,obj表示目标类的实例;method为目标类方法的反射对象;args为方法的动态入参;而proxy为代理类实例。
下面,我们通过CglibProxy为ForumServiceImpl类创建代理对象,并测试代理对象的方法,如下代码所示:
测试Cglib创建的代理类TestForumService.java
package com.baobaotao.proxy; public class TestForumService { public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);// ① forumService.removeForum(10); forumService.removeTopic(1023); } }
在①中,我们通过CglibProxy为ForumServiceImpl动态创建了一个织入性能监视逻辑的代理对象,并调用代理类的业务方法。运行上面的代码,输出以下信息:
begin monitor... 模拟删除Forum记录:10 end monitor... com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$76852a06.removeForum花费47毫秒。 begin monitor... 模拟删除Topic记录:1023 end monitor... com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$76852a06.removeTopic花费16毫秒。
观察以上的输出,除了发现两个业务方法中都织入了性能监控的逻辑外,我们还发现代理类的名字是com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0,这个特殊的类就是CGLib为ForumServiceImpl动态创建的子类。
四.代理知识小结
Spring AOP的底层就是通过使用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。在这里,我们对前面两节动态创建代理对象作一个小结。
我们虽然通过PerformanceHandler或CglibProxy实现了性能监视横切逻辑的动态织入,但这种实现方式存在三个明显需要改进的地方:
1)目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
2)我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
3)我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。
以上三个问题,在AOP中占用重要的地位,因为Spring AOP的主要工作就是围绕以上三点展开:Spring AOP通过Pointcut(切点)指定在哪些类的哪些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)。此外,Spring通过Advisor(切面)将Pointcut和Advice两者组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。
JDK动态代理所创建的代理对象,在JDK 1.3下,性能强差人意。虽然在高版本的JDK中,动态代理对象的性能得到了很大的提高,但是有研究表明,CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为无须频繁创建代理对象,所以比较适合用CGLib动态代理技术,反之适合用JDK动态代理技术。值得一提的是,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final方法进行代理。
相关推荐
学习Spring必学的Java基础知识(含数据库事务基础知识)
主题:学习Spring必学的Java基础知识(1)----反射主题:学习Spring必学的Java基础知识(1)----反射主题:学习Spring必学的Java基础知识(1)----反射
在深入学习Spring框架之前,掌握基础的Java知识是至关重要的,特别是关于`PropertyEditor`的部分。`PropertyEditor`是Java中用于类型转换的关键组件,它允许我们自定义数据类型的转换规则,这对于处理用户输入或者在...
学习Spring基础知识对Java开发者至关重要,它能帮助你构建高效、可维护的后端应用,并为后续深入学习Spring全家桶打下坚实基础。在实际项目中,结合其他Spring模块和最佳实践,可以构建出满足现代企业需求的复杂系统...
### 学习Java必看的书籍 在Java学习过程中,选择合适的书籍对于深入理解这门语言至关重要。根据提供的部分信息,我们将重点介绍三本被广泛推荐的经典Java书籍:《Thinking in Java》、《Java Collections》以及...
在Spring Cloud的学习过程中,Java基础知识是必不可少的一部分。Java作为Spring Cloud的基础语言,其核心概念和技术对理解Spring Cloud的实现机制至关重要。以下将详细介绍Java在Spring Cloud中的应用以及相关的知识...
这份资料集主要涵盖Java基础知识、初学者入门指南以及常见的Java面试题,旨在帮助学习者系统地理解和掌握Java的核心概念。 首先,Java基础知识是理解任何Java程序的基础。这包括: 1. **Java环境搭建**:学习如何...
"学习Java必看"这个压缩包文件显然包含了一系列与Java学习相关的资源,可能是教程文档、代码示例或者教学视频,旨在帮助初学者和有经验的开发者深化对Java的理解。以下是基于这个主题的Java知识点详细说明: 1. **...
AOP是通过预编译和运行时动态代理实现的,可以将横切关注点与业务逻辑分离。Spring的AOP支持通过XML和注解两种方式进行。 - AOP的概述和底层实现原理是理解Spring AOP的基础。 - AspectJ注解开发和AOP通知类型是...
Java学习指导和面向对象编程基础知识整理是帮助初学者和进阶学习者更好地理解和掌握Java语言的一份宝贵资料。Java作为一门面向对象的编程语言,其学习过程可以分为几个重要阶段,包括理解Java原理、学习Java语言基础...
接着,深入学习Java的基础知识。这包括变量、数据类型、控制结构(如if-else、switch、for、while循环)、类与对象、封装、继承、多态等面向对象编程的概念。同时,要掌握异常处理、文件I/O、集合框架(如ArrayList...
首先,Java学习的起点通常是理解其基础知识,包括基本语法、数据类型、变量、运算符以及控制流程(如条件语句和循环)。这些构成了编程的基础,让你能够编写简单的程序。Java的类和对象概念是面向对象编程的核心,你...
这些知识点构成了从Java基础知识到企业级应用开发的完整学习体系,适用于从初学者到有一定经验的开发者逐步进阶。对于想要成为架构师的同学,还需要在系统设计、性能优化、高可用架构等方面进行深入研究和实践。
最后,别忘了学习Java的进阶主题,如Java.IO和NIO(非阻塞I/O)、JavaFX或Swing用于桌面应用开发、Spring框架用于企业级应用、以及JDBC(Java Database Connectivity)进行数据库操作。这些都会使你的Java技能更加...
下面将详细阐述可能包含在《Java2学习指南2.pdf》中的关键知识点。 ### Java2的核心概念 1. **面向对象编程(OOP)**:Java是一种完全支持面向对象编程的语言,这意味着所有代码都是围绕类和对象构建的。学习如何...
确保这些基础知识牢固,能够帮助你在后续的学习中更快速地理解和应用。 接下来,SQL和数据库知识是必备的。SQL是用于操作和管理数据库的语言,掌握基本的SQL查询、表关系、索引、事务处理等概念,可以帮助你有效地...
最后,深入学习一些高级话题,如反射、动态代理、注解和泛型。这些特性让Java更加灵活和强大,也常用于框架开发。 此外,了解并实践Maven或Gradle这样的构建工具,以及Spring Boot、MyBatis等流行框架,可以帮助你...
Java 基础核心总结 java全方面基础知识 java开发人员必备 通过带着读者手写简化版 Spring 框架,了解 Spring 核心原理。在手写Spring 源码的过程中会摘取整体框架中的核心逻辑,简化代码实现过程,保留核心功能,...
Java基础学习知识笔记涵盖了从环境配置到基本理论的全面内容,非常适合初学者进行自我学习。以下是对这些知识点的详细说明: 1. **Java程序设计**:Java是一种面向对象的编程语言,它的设计目标是具有良好的可移植...