`
prettyzhou
  • 浏览: 34553 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Spring AOP 实现

 
阅读更多

的学习笔记

 
Spring AOP的底层实现技术
 
 

AOP概述

软件的编程语言最终的目的就是用更自然更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,我们看到编程语言在一步步用更自然、更强大的方式描述软件。AOP是软件开发思想的一个飞跃,AOP的引入将有效弥补OOP的不足,OOP和AOP分别从纵向和横向对软件进行抽象,有效地消除重复性的代码,使代码以更优雅的更有效的方式进行逻辑表达。

AOP有三种植入切面的方法:其一是编译期织入,这要求使用特殊的Java编译器,AspectJ是其中的代表者;其二是类装载期织入,而这要求使用特殊的类装载器,AspectJ和AspectWerkz是其中的代表者;其三为动态代理织入,在运行期为目标类添加增强生成子类的方式,Spring AOP采用动态代理织入切面。

Spring AOP使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理,之所以需要两种代理机制,很大程度上是因为JDK本身只提供基于接口的代理,不支持类的代理。

基于JDK的代理和基于CGLib的代理是Spring AOP的核心实现技术,认识这两代理技术,有助于探究Spring AOP的实现机理。只要你愿意,你甚至可以抛开Spring,提供自己的AOP实现。

带有横切逻辑的实例
    
首先,我们来看一个无法通过OOP进行抽象的重复代码逻辑,它们就是AOP改造的主要对象。下面,我们通过一个业务方法性能监视的实例了解横切逻辑。业务方法性能监视,在每一个业务方法调用之前开始监视,业务方法结束后结束监视并给出性能报告:

代码清单 2 ForumService:包含性能监视横切代码

 

package com.baobaotao.proxy;
public class ForumServiceImpl implements ForumService ...{

 public void removeTopic(int topicId) ...{
          //开始性能监视
  PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic");
  System.out.println("模拟删除Topic记录:"+topicId);
  try ...{
   Thread.currentThread().sleep(20);
  } catch (Exception e) ...{
   throw new RuntimeException(e);
  }
  //结束监视、并给出性能报告信息
  PerformanceMonitor.end();
 }

 public void removeForum(int forumId) ...{
  //开始性能监视
PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum");
  System.out.println("模拟删除Forum记录:"+forumId);
  try ...{
   Thread.currentThread().sleep(40);
  } catch (Exception e) ...{
   throw new RuntimeException(e);
  }
         //结束监视、并给出性能报告信息
  PerformanceMonitor.end();
 }
}

 

代码清单 2中粗体表示的代码就是具有横切特征的代码,需要进行性能监视的每个业务方法的前后都需要添加类似的性能监视语句。
    
我们保证实例的完整性,我们提供了一个非常简单的性能监视实现类,如所示代码清单 3所示:

代码清单 3 PerformanceMonitor

 

package com.baobaotao.proxy;

public class PerformanceMonitor {
  //通过一个ThreadLocal保存线程相关的性能监视信息
 private static ThreadLocal<MethodPerformace> performaceRecord =
new ThreadLocal<MethodPerformace>();
 public static void begin(String method) {
  System.out.println("begin monitor...");
  MethodPerformace mp = new MethodPerformace(method);
  performaceRecord.set(mp);
 }
 public static void end() {
  System.out.println("end monitor...");
  MethodPerformace mp = performaceRecord.get();
  mp.printPerformace(); //打印出业务方法性能监视的信息
 }
}

 

PerformanceMonitor提供了两个方法,begin(String method)方法开始对某个业务类方法的监视,method为业务方法的签名,而end()方法结束对业务方法的监视,并给出性能监视的信息。由于每一个业务方法都必须单独记录性能监视数据,所以我们使用了ThreadLocal,ThreadLocal是削除非线程安全状态的不二法宝。ThreadLocal中的元素为方法性能记录对象MethodPerformace,它的代码如下所示:

代码清单 4 MethodPerformace

 

package com.baobaotao.proxy;
public class MethodPerformace {
 private long begin;
 private long end;
 private String serviceMethod;
    public MethodPerformace(String serviceMethod){
       this.serviceMethod = serviceMethod;
       this.begin = System.currentTimeMillis();//记录方法调用开始时的系统时间
    }
    public void printPerformace(){
        //以下两行程序得到方法调用后的系统时间,并计算出方法执行花费时间
        end = System.currentTimeMillis();
        long elapse = end - begin;
        //报告业务方法执行时间
        System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
    }
}

 

#p#

通过下面代码测试这个拥有方法性能监视能力的业务方法:

 

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以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在我们终于发现动态代理是实现AOP的绝好底层技术。
    
JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
   
而Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例。这样讲一定很抽象,我们马上着手动用Proxy和InvocationHandler这两个魔法戒对上一节中的性能监视代码进行AOP式的改造。
    
首先,我们从业务类ForumServiceImpl 中删除性能监视的横切代码,使ForumServiceImpl只负责具体的业务逻辑,如所示:

代码清单 5 ForumServiceImpl:移除性能监视横切代码

 

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);
  }
         ②
 }
}

 

在代码清单 5中的①和②处,原来的性能监视代码被移除了,我们只保留了真正的业务逻辑。
    
从业务类中移除的横切代码当然还得找到一个寄居之所,InvocationHandler就是横切代码的家园乐土,我们将性能监视的代码安置在PerformaceHandler中,如代码清单 6所示:

代码清单 6 PerformaceHandler

 

package com.baobaotao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PerformaceHandler implements InvocationHandler {
    private Object target;
 public PerformaceHandler(Object target){//①target为目标的业务类
  this.target = target;
 }
 public Object invoke(Object proxy, Method method, Object[] args)
   throws Throwable {
  PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName());
  Object obj = method.invoke(target, args);//②通过反射方法调用目标业务类的业务方法
  PerformanceMonitor.end();
  return obj;
 }
}

 

粗体部分的代码为性能监视的横切代码,我们发现,横切代码只出现一次,而不是原来那样星洒各处。大家注意②处的method.invoke(),该语句通过反射的机制调用目标对象的方法,这样InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法就将横切代码和目标业务类代码编织到一起了,所以我们可以将InvocationHandler看成是业务逻辑和横切逻辑的编织器。下面,我们对这段代码做进一步的说明。

#p#

首先,我们实现InvocationHandler接口,该接口定义了一个 invoke(Object proxy, Method method, Object[] args)的方法,proxy是代理实例,一般不会用到;method是代理实例上的方法,通过它可以发起对目标类的反射调用;args是通过代理类传入的方法参数,在反射调用时使用。
    
此外,我们在构造函数里通过target传入真实的目标对象,如①处所示,在接口方法invoke(Object proxy, Method method, Object[] args)里,将目标类实例传给method.invoke()方法,通过反射调用目标类方法,如②所示。
    
下面,我们通过Proxy结合PerformaceHandler创建ForumService接口的代理实例,如代码清单 7所示:

代码清单 7 TestForumService:创建代理实例

 

package com.baobaotao.proxy;
import java.lang.reflect.Proxy;
public class TestForumService {
 public static void main(String[] args) {
  ForumService target = new ForumServiceImpl();//①目标业务类
//② 将目标业务类和横切代码编织到一起
  PerformaceHandler handler = new PerformaceHandler(target);
         //③为编织了目标业务类逻辑和性能监视横切逻辑的handler创建代理类
  ForumService proxy = (ForumService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
 handler);
         //④ 操作代理实例
  proxy.removeForum(10);
  proxy.removeTopic(1012);
 }
}

 

上面的代码完成了业务类代码和横切代码编织和接口代理实例生成的工作,其中在②处,我们将ForumService实例编织为一个包含性能监视逻辑的PerformaceHandler实例,然后在③处,通过Proxy的静态方法newProxyInstance()为融合了业务类逻辑和性能监视逻辑的handler创建一个ForumService接口的代理实例,该方法的第一个入参为类加载器,第二个入参为创建的代理实例所要实现的一组接口,第三个参数是整合了业务逻辑和横切逻辑的编织器对象。

按照③处的设置方式,这个代理实例就实现了目标业务类的所有接口,也即ForumServiceImpl的ForumService接口。这样,我们就可以按照调用ForumService接口的实例相同的方式调用代理实例,如④所示。运行以上的代码,输出以下的信息:

 

begin monitor...
模拟删除Forum记录:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeForum花费47毫秒。

begin monitor...
模拟删除Topic记录:1012
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeTopic花费26毫秒。

 

我们发现,程序的运行效果和直接在业务类中编写性能监视逻辑的效果一致,但是在这里,原来分散的横切逻辑代码已经被我们抽取到PerformaceHandler中。当其它业务类(如UserService、SystemService等)的业务方法也需要使用性能监视时,我们只要按照以上的方式,分别为它们创建代理对象就可以了。下面,我们用时序图描述调用关系,进一步代理实例的本质,如图1所示:


 
图1:代理实例的时序图
    
我们在上图中特别使用虚线阴影的方式对通过代理器创建的ForumService实例进行凸显,该实例内部利用PerformaceHandler整合横切逻辑和业务逻辑。调用者调用代理对象的的removeForum()和removeTopic()方法时,上图的内部调用时序清晰地告诉了我们实际上所发生的一切。

CGLib动态代理
   
使用JDK创建代理有一个限制,即它只能为接口创建代理,这一点我们从Proxy的接口方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)就看得很清楚,第三个入参interfaces就是为代理实例指定的实现接口。虽然,面向接口的编程被很多很有影响力人(包括Rod Johnson)的推崇,但在实际开发中,开发者也遇到了很多困惑:难道对一个简单业务表的操作真的需要创建5个类(领域对象类、Dao接口,Dao实现类,Service接口和Service实现类)吗?对于这一问题,我们还是留待大家进一步讨论。现在的问题是:对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。你可以从http://cglib.sourceforge.net/获取CGLib的类包,也可以直接从Spring的关联类库lib/cglib中获取类包。

#p#
   
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并在拦截方法相应地织入横切逻辑。下面,我们采用CGLib技术,编写一个可以为任何类创建织入性能监视横切逻辑的代理对象的代理器,如代码清单 8所示:

代码清单 8 CglibProxy

 

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());
  Object result=proxy.invokeSuper(obj, args); ③ 通过代理类调用父类中的方法
  PerformanceMonitor.end();
  return result;
 }
}

 

在上面代码中,你可以通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象是指定类clazz的子类。在这个代理对象中,我们织入性能监视的横切逻辑(粗体部分)。intercept(Object obj, Method method, Object[] args,MethodProxy proxy)是CGLib定义的Inerceptor接口的方法,obj表示父类的实例,method为父类方法的反射对象,args为方法的动态入参,而proxy为代理类实例。
    
下面,我们通过CglibProxy为ForumServiceImpl类创建代理对象,并测试代理对象的方法,如代码清单 9所示:

代码清单 9 TestForumService:测试Cglib创建的代理类

 

package com.baobaotao.proxy;
import java.lang.reflect.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$$2a9199c0.removeForum花费47毫秒。
begin monitor...
模拟删除Topic记录:1023
end monitor...
com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0.removeTopic花费16毫秒。

 

观察以上的输出,除了发现两个业务方法中都织入了性能监控的逻辑外,我们还发现代理类的名字是com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$2a9199c0,这个特殊的类就是CGLib为ForumServiceImpl所动态创建的子类。

小结
      
Spring AOP在底层就是利用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。在这里,我们对以上两节动态创建代理对象做一个小结。

在PerformaceHandler和CglibProxy中,有三点值得注意的地方是:第一,目标类的所有方法都被添加了性能监视横切的代码,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些方法织入横切代码;第二,我们手工指定了织入横切代码的织入点,即在目标类业务方法的开始和结束前调用;第三,我们手工编写横切代码。以上三个问题,在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 aop实现原理

    NULL 博文链接:https://zhang-yingjie-qq-com.iteye.com/blog/319927

    Spring AOP实现机制

    **Spring AOP 实现机制详解** Spring AOP(面向切面编程)是Spring框架的核心特性之一,它允许程序员在不修改源代码的情况下,通过“切面”来插入额外的业务逻辑,如日志、事务管理等。AOP的引入极大地提高了代码的...

    Spring AOP实现 项目源码 Myeclipse 直接导入可用

    **Spring AOP 实现详解** 在Java开发中,Spring框架以其强大的功能和灵活性深受开发者喜爱。其中,AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的一个重要特性,它允许开发者将关注点从核心业务...

    理解Spring AOP实现与思想 案例代码

    2. **Spring AOP实现方式** - **代理模式**:Spring AOP使用两种代理方式,JDK动态代理和CGLIB代理。如果目标类实现了接口,Spring会使用JDK动态代理;如果没有实现接口,Spring会使用CGLIB代理生成子类。 - **JDK...

    springAOP实现数据字典.zip

    在这个"springAOP实现数据字典.zip"压缩包中,我们主要关注的是如何利用Spring AOP来实现数据字典的功能。数据字典是系统设计中一个关键的组成部分,它存储了系统中所有数据的描述,包括数据项、数据结构、数据流、...

    JAVA 中Spring aop 实现日志记载

    2. **Spring AOP实现日志记载** - **配置Spring AOP**:首先,我们需要在Spring配置文件中启用AOP代理。通常,对于基于注解的配置,会使用`&lt;aop:aspectj-autoproxy&gt;`标签;对于XML配置,可以使用`&lt;aop:config&gt;`和`...

    Spring  AOP实现方法大全

    【Spring AOP实现方法大全】 在Spring框架中,面向切面编程(Aspect-Oriented Programming,简称AOP)是一种强大的设计模式,它允许我们在不修改业务代码的情况下,插入额外的功能,比如日志记录、事务管理等。在...

    Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现.doc

    Spring AOP 实现原理详解之 AOP 切面的实现 Spring AOP 是基于 IOC 的 Bean 加载来实现的,本文主要介绍 Spring AOP 原理解析的切面实现过程。AOP 切面的实现是将切面类的所有切面方法根据使用的注解生成对应 ...

    spring aop实现日志功能

    标题"spring aop实现日志功能"涉及到的是如何利用Spring AOP来记录和跟踪应用程序中的操作,这对于调试、性能分析和故障排查至关重要。下面我们将详细探讨如何通过Spring AOP来实现日志功能。 首先,理解AOP的基本...

    spring aop 实现权限的简单示例

    在本示例中,我们将深入探讨如何利用Spring AOP实现简单的权限验证。 首先,AOP的核心概念是切面(Aspect),它封装了横切关注点,比如日志、事务管理、权限验证等。在Spring AOP中,切面通过通知(Advice)来定义...

    Java之Spring AOP 实现用户权限验证

    在本文中,我们将重点探讨如何使用Spring AOP实现用户权限验证。 首先,权限验证是任何应用程序中不可或缺的一部分,尤其是涉及到用户登录和访问控制的场景。通过Spring AOP,我们可以将权限验证的逻辑与业务代码...

    spring aop实现接口参数变更前后对比和日志记录

    spring aop实现接口参数变更前后对比和日志记录完整代码,拿到项目代码,只需要做数据库连接的修改即可运行起来使用,代码案例详细,真是可靠,代码原文地址:...

    SpringAOP实现.docx

    【Spring AOP 实现】 Spring AOP,即Spring的面向切面编程,是一种编程范式,用于处理系统中的横切关注点,如日志、事务管理等,它将这些关注点与业务逻辑分离,提高了代码的可维护性和复用性。AOP在Spring框架中的...

    spring aop实现

    Spring AOP实现详解 在Java开发中,Spring框架以其强大的功能和灵活性被广泛采用,而AOP(面向切面编程)是Spring框架的一个重要组成部分。AOP允许开发者将关注点从核心业务逻辑中分离出来,比如日志记录、权限检查...

    spring aop 自定义注解保存操作日志到mysql数据库 源码

    2、能够清楚的知道如何用spring aop实现自定义注解以及注解的逻辑实现 (需要知道原理的请看spring aop源码,此处不做赘述) 3、可在现有源码上快速进行功能扩展 4、spring boot,mybatis,druid,spring aop的使用

    Spring AOP实现

    Spring AOP(面向切面编程)是Spring框架中的一个重要组件...通过理解advice、pointcut和advisor这三个核心概念,开发者可以更有效地利用Spring AOP实现日志记录、事务管理等各种功能,提高代码的可维护性和可扩展性。

    springAOP实现

    在Spring AOP中,切面可以通过编写带有注解的类或者XML配置来实现。切面通常包括一个或多个通知,以及连接点的定义。 2. **通知(Advice)** 通知是在特定的连接点上执行的代码,它可以是前置通知(Before)、后置...

    springAOP配置动态代理实现

    动态代理则是Spring AOP实现的核心技术之一,它允许我们在运行时创建具有额外行为的对象。下面将详细阐述Spring AOP的配置以及动态代理的实现。 一、Spring AOP基础知识 1. **什么是AOP**:AOP是一种编程范式,...

    spring aop切面拦截指定类和方法实现流程日志跟踪

    本节将详细介绍如何使用Spring AOP实现流程日志跟踪,主要关注于如何通过AOP拦截特定的类和方法来进行日志记录。 ##### 3.1 配置Spring AOP 在Spring配置文件中定义切面和切入点表达式是非常关键的一步。一般来说...

Global site tag (gtag.js) - Google Analytics