1. AOP(Aspect Oriented Programing),面向切面方程。介绍具体定义前,先看一个例子:

     package com.baobaotao.concept;
    
     public class ForumService {
    private TransactionManager transManager;
    private PerformanceMonitor pmonitor;
    private TopicDao topicDao;
    private ForumDao forumDao; public void removeTopic(int topicId) {
    pmonitor.start();
    transManager.beginTransaction(); topicDao.removeTopic(topicId); transManager.endTransaction();
    pmonitor.end();
    }
    public void CreateForum(Forum forum) {
    pmonitor.start();
    transManager.beginTransaction(); forumDao.create(forum); transManager.endTransaction();
    pmonitor.end();
    }
    }

    上面代码中,10、11、15、16行和19、20、24、25行是重复的。这是一个监视程序,ForumService 中真正工作的方法是第13行和第22行。我们希望把重复的代码逻辑从原来的方法中抽离出来变成一个独立的模块,而工作类被抽离后变成下面这样:

     package com.baobaotao.concept;
    
     public class ForumService {
    
         private TopicDao topicDao;
    private ForumDao forumDao; public void removeTopic(int topicId) {
    //--------------------------------------------
    topicDao.removeTopic(topicId);
    //--------------------------------------------
    }
    public void CreateForum(Forum forum) {
    //-------------------------------------------- forumDao.create(forum);
    //---------------------------------------------
    }
    }

    当第8行或第15行开始工作的时候,又能够把刚才抽离出来的独立的模块插进去,这就是AOP的任务。它就像一个切面的树的年轮,而一层层的圆环就是要抽离出来的东西,真正工作的代码就是圆心。我们把圆环抽出来,在圆心工作的时候,再把圆环插进去。所以,AOP名曰:面向切面方程。

  2. 基本概念
    • 连接点(JoinPoint):抽离后方法开始执行时,第10行上方、第10行下方,第15行上方,第15行下方都需要插入抽离的代码,那么,"第10行上方"、“第10行下方”,“第15行上方”、“第15行下方”这些具有方向性质的点都是连接点。连接点由两个元素确定,即:“第10行(要执行的代码点)”+“上方(方向定位)”。就是代码中红线的地方。
    • 切点(PointCut):AOP使用切点来定位连接点。也就是说,切点是用来寻找连接点的方式。
    • 增强(Advice):就是程序代码执行时要插入代码上下方的抽离代码,就是上例中抽离出去的10、11、15、16行重复代码。
    • 目标对象(Target):就是"增强"要插入的类。在上例中,就是ForumService。
    • 引介(Introduction):一种特殊的增强。一般的增强是在类的方法上下插入增强代码,而引介是为该类增加一些属性和方法(通过增加属性、方法的方式让目标对象功能强大起来)。
    • 织入(Weaving):把增强代码插入目标对象的过程就是织入。织入分为编译期织入、类加载期织入、动态代理织入。

      • 编译期织入。
      • 类加载期织入。
      • 动态代理织入:在运行期,为目标类插入增强,生成子类。(也就是说,插入增强代码后,生成一个子类,由子类来执行父类的方法)。Spring采用动态代理织入。而AspectJ采用编译期织入和类加载期织入。
    • 代理(Proxy):AOP织入增强代码后,生成一个新类,这个新类就是代理。
    • 切面(Aspect):切点和增强合起来就是切面。
  3. AOP的工作重心:把增强代码插入到目标类的连接点上。包括两个工作:
    • 怎样通过切点和增强定位到连接点上。
    • 怎样在增强中编写切面的代码。
  4. Spring的AOP使用了两种机制实现面向切面方程:
    • 基于JDK的动态代理(只提供接口的代理,不支持类的代理)。
    • 基于CGLib的动态代理。
  5. 具体例子(未使用AOP之前)(下面提到的”性能监视“只是一个”增强“的功能例子,并非AOP的专有名词。)。
    • 下面是”目标类“的代码:

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

      第6行和第13行是一个性能监视功能的简单调用。上面的代码很简单,两个方法都有着相同的性能监视代码(可用作增强的代码),而之间包裹着具体的打印代码。

    • 下面是性能监视的简单实现类(使用了ThreadLocal保证线程安全)。

       package com.baobaotao.proxy;
      
       public class PerformanceMonitor {
      private static ThreadLocal<MethodPerformace> performaceRecord = new ThreadLocal<MethodPerformace>();
      public static void begin(String method) {
      System.out.println("begin monitor...");
      MethodPerformace mp = performaceRecord.get();
      if(mp == null){
      mp = new MethodPerformace(method);
      performaceRecord.set(mp);
      }else{
      mp.reset(method);
      }
      }
      public static void end() {
      System.out.println("end monitor...");
      MethodPerformace mp = performaceRecord.get();
      mp.printPerformace();
      }
      }

      从性能监视类可以看出,里面用到了一个记录性能监视信息的辅助类。

    • 下面是记录性能监视信息的类:

       package com.baobaotao.proxy;
      
       public class MethodPerformace {
      private long begin;
      private long end;
      private String serviceMethod;
      public MethodPerformace(String serviceMethod){
      reset(serviceMethod);
      }
      public void printPerformace(){
      end = System.currentTimeMillis();
      long elapse = end - begin;
      System.out.println(serviceMethod+"花费"+elapse+"毫秒。");
      }
      public void reset(String serviceMethod){
      this.serviceMethod = serviceMethod;
      this.begin = System.currentTimeMillis();
      }
      }
    • 从代码中可以看到,ForumServiceImpl 在执行第6行的时候,把removeTopic的方法名传给了性能监视类PerformanceMonitor,而 PerformanceMonitor在调用begin方法时,把removeTopic的方法名又传给了MethodPerformace类。而传过来的唯一作用就是在 PerformanceMonitor执行end()方法时,利用MethodPerformace打印出该方法名。
    • 下面是测试类:
       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...
      模拟删除Topic记录:1012
      end monitor...
      com.baobaotao.proxy.ForumServiceImpl.removeTopic花费20毫秒。
    • 整个代码很容易理解。很明显,根据目标类的6、13、17、24行可以看出,这需要使用AOP来抽离代码。而这个时候就可以使用JDK动态代理了。
  6. 使用JDK动态代理改造
    • 首先,把目标类的重复代码(可增强的代码)抽离出来,变成下面这样:

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

      我把重复代码抽离掉了。

    • 抽离出来的功能不能扔掉,总要有个栖居之地。java.lang.reflect.InvocationHandler就是放抽离代码的地方:
       package com.baobaotao.proxy;
      
       import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      /**实现InvocationHandler*/
      public class PerformaceHandler implements InvocationHandler {
      private Object target;
      public PerformaceHandler(Object target){
      this.target = target;
      }
      /**重写invoke方法
      * @param proxy 最终生成的代理实例(一般不会用到)
      * @param method 是原始目标类(即,被代理类)的某个方法(比如removeForum),通过它,利用反射,来调用目标类的该方法。
      * @param args 是method方法需要的一组入参。
      * */
      public Object invoke(Object proxy, Method method, Object[] args)
      throws Throwable {
      /**只要给了target类和要调用的method方法,就可以用下面的语句给begin传入具体的方法名*/
      PerformanceMonitor.begin(target.getClass().getName()+"."+ method.getName());
      //用method方法使用反射技术调用目标类的具体方法。
      Object obj = method.invoke(target, args);
      PerformanceMonitor.end();
      return obj;
      }
      }

      上面代码就很好的把抽离的代码(增强)与目标类的具体方法(连接点)交织了起来。

    • 下面是创建代理实例来调用目标类的具体方法(根据前面的思路,并不是在目标类现有的基础上强加上增强代码,而是拷贝一份目标类(生成代理),然后把编织过的代码赋予代理类):
       package com.baobaotao.proxy;
      
       import java.lang.reflect.Proxy;
      
       public class TestForumService {
      public static void main(String[] args) {
      // 使用JDK动态代理
      ForumService target = new ForumServiceImpl();
      //新建PerformaceHandler对象,具体的交织过程已经在PerformaceHandler类中加了注释解读。
      PerformaceHandler handler = new PerformaceHandler(target);
      /**下面这一行,生成代理,有三个参数:
      * 第一个参数:目标类的类加载器;
      * 第二个参数:目标类的所有接口;
      * 第三个参数:handler;
      * */
      ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
      target.getClass().getInterfaces(), handler);
      proxy.removeForum(10);
      proxy.removeTopic(1012); }
      }

      在PerformaceHandler类中,我们是需要method参数的。而上面第16行生成代理的方法中的三个参数,足以用来一一遍历目标类的每一个方法,然后生成的代理类 proxy成功的为目标类的每一个方法(removeForum、removeTopic)加入了增强代码。整个过程的时序图如下:

  7. 使用CGLib改造:JDK代理是基于接口的,而现实中很多业务程序都不是基于接口定义方法的,这就要使用CGLib来完成AOP贡功能了。 CGLib采用底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑:
     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);//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);//由proxy代理类调用父类的方法;
    PerformanceMonitor.end();
    return result;
    }
    }

    下面是测试:

     package com.baobaotao.proxy;
    
     public class TestForumService {
    public static void main(String[] args) { //使用CGLib动态代理
    CglibProxy proxy = new CglibProxy();
    //动态生成子类的方式创建代理类,它拥有原目标类的方法和增强代码
    ForumService forumService = (ForumService)proxy.getProxy(ForumServiceImpl.class);
    forumService.removeForum(10);
    forumService.removeTopic(1023); }
    }

    打印结果是一样的。

  8. 上面的JDK动态代理以及CGLib改造只是基础版本,为了说明概念的,但是它们存在以下问题:
    • 目标类的所有方法都加了增强代码(明显实际中我们只想增强部分方法)。
    • 我们死板的总是在业务方法的开始与结束前织入了增强代码(切点的方位不仅仅只有开始、结束前这两个位置,还有很多种可能性)。
    • 如果另一个类也要创建代理,我们就又需要编写另一个类的代理实例。也就是说,上面的栗子无法做到通用性。
  9. Spring AOP的主要工作就是基于上面提出的几个问题而展开的。后期我们将会逐步学习。 

(spring-第16回【AOP基础篇】)基本概念的更多相关文章

  1. Spring+SpringMVC+MyBatis+easyUI整合基础篇(六)maven整合SSM

    写在前面的话   承接前文<Spring+SpringMVC+MyBatis+easyUI整合基础篇(五)讲一下maven>,本篇所讲述的是如何使用maven与原ssm项目整合,使得一个普 ...

  2. Spring+SpringMVC+MyBatis+easyUI整合基础篇(八)mysql中文查询bug修复

    写在前面的话 在测试搜索时出现的问题,mysql通过中文查询条件搜索不出数据,但是英文和数字可以搜索到记录,中文无返回记录.本文就是写一下发现问题的过程及解决方法.此bug在第一个项目中点这里还存在, ...

  3. Spring+SpringMVC+MyBatis+easyUI整合基础篇(十一)SVN服务器进阶

    日常啰嗦 上一篇文章<Spring+SpringMVC+MyBatis+easyUI整合基础篇(十)SVN搭建>简单的讲了一下SVN服务器的搭建,并没有详细的介绍配置文件及一些复杂的功能, ...

  4. Spring+SpringMVC+MyBatis+easyUI整合基础篇(十二)阶段总结

    不知不觉,已经到了基础篇的收尾阶段了,看着前面的十几篇文章,真的有点不敢相信,自己竟然真的坚持了下来,虽然过程中也有过懒散和焦虑,不过结果还是自己所希望的,克服了很多的问题,将自己的作品展现出来,也发 ...

  5. Spring+SpringMVC+MyBatis+easyUI整合基础篇

    基础篇 Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简介 Spring+SpringMVC+MyBatis+easyUI整合基础篇(二)牛刀小试 Spring+S ...

  6. (spring-第18回【AOP基础篇】) 创建切面

    一.   在创建增强一节中,增强被织入到目标类的所有方法中,假设我们希望有选择地织入到目标类某些特定的方法中,就需要使用切点进行目标连接点的定位. 二.   spring通过org.springfra ...

  7. Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简介

    很久之前就打算开始写一下自己的技术博客了,实在抽不出时间所以计划一直搁置了,最近项目进度渐渐缓了下来,不那么忙了,也因此开始筹备自己的博客.说到这次博客的主角,也是无心插柳找到的,来源于两年前自己写的 ...

  8. Spring+SpringMVC+MyBatis+easyUI整合基础篇(九)版本控制

    日常啰嗦 还好在第一篇文章里就列好了接下来的主线及要写的知识点,不然都不知道要写什么东西了,开篇里已经列了基础篇要讲svn和git的知识点,所以这一篇就写一下版本控制. 项目实际效果展示在这里,账密: ...

  9. 【SSM之旅】Spring+SpringMVC+MyBatis+Bootstrap整合基础篇(一)项目简介及技术选型相关介绍

    试水 一直想去搭建个自己的个人博客,苦于自己的技术有限,然后也个人也比较懒散.想动而不能动,想动而懒得动,就这么一直拖到了现在.总觉得应该把这几年来的所学总结一番,这样才能有所成长. 不知在何时,那就 ...

  10. Spring+SpringMVC+MyBatis+easyUI整合基础篇(一)项目简述及技术选型介绍

    作者:13GitHub:https://github.com/ZHENFENG13版权声明:本文为原创文章,未经允许不得转载. 萌芽阶段 很久之前就开始打算整理一下自己的技术博客了,由于各种原因(借口 ...

随机推荐

  1. Python中pip版本升级error:You are using pip version 7.1.2, however version 8.1.1 is available.

    使用pip install安装命令时,会报错:You are using pip version 7.1.2, however version 8.1.1 is available. 尝试了推荐解决办 ...

  2. cssText

    cssText 的本质就是设置 HTML 元素的 style 属性值 cssText 的方便之处在于一次可以写很多属性,而且变更 CSS 样式不必变 JS 代码,只需变样式字符串.但它有个缺点,就是它 ...

  3. IDE显示无法打开源文件时解决方案

      解决方法:加上具体地址信息将 #include <qwidgetresizehandler_p.h> 改为 #include <C:\Users\INnoVation\Deskt ...

  4. C/C++源代码到可执行程序的过程详解

    编译,编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序. 源代码-- ...

  5. Unique Paths II [LeetCode]

    Follow up for "Unique Paths": Now consider if some obstacles are added to the grids. How m ...

  6. DataTable 中Distinct操作

    DataTable dt = ds.Tables[]; DataView dataView = dt.DefaultView; DataTable dtDistinct = dataView.ToTa ...

  7. 创建PO/SO

    IF P_ZY EQ 'X'."直营订单 调拨单 PERFORM FRM_INIT_PO_HEADER. PERFORM FRM_INIT_PO_ITEM. PERFORM FRM_INIT ...

  8. C#窗体计算器

    使用窗体写的计算器小程序,不是十分完善,键盘输入只能输入数字键,其他需要换档键的键的输入没有搞懂,先发上来左右,以作留存. 界面截图 主要使用的是TextBox和button控件 using Syst ...

  9. 10+优秀“分步引导”jQuery插件(转)

    很 多时候一个网站或者一个Web应用出品,为了让你的用户知道你的站点(或应用)有些什么?如何操作?为了让你的用户有更好的体验.往往这个时候都会给你的 站点(应用)添加一个分步指引的效果.然而这样的效果 ...

  10. [转]Java8-本地缓存

    这里我将会给大家演示用ConcurrentHashMap类和lambda表达式实现一个本地缓存.因为Map有一个新的方法可以在key为Null的时候自动计算一个新的value值.非常完美的实现cach ...