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. JS实现会动的小车

    2015-06-05怎么说呢,我想要实现的功能是很简单的,但是过程中,遇到不少问题. 我要实现的功能是页面右侧有辆小车,鼠标滚动或者拉动滚动条,小车消失,在底部点击“返还顶部”按钮后,页面缓慢向上滚动 ...

  2. [HBuilder] 简介

    官网首页: http://www.dcloud.io/runtime.html 特点: 编码比其他工具快5倍 代码输入法:按下数字快速选择候选项 可编程代码块:一个代码块,少敲50个按键 内置emme ...

  3. css--block formatting context

    block formatting context(块级格式化上下文) 如何产生BFC:当一个HTML元素满足下面条件的任何一点,都可以产生Block Formatting Context: float ...

  4. Linux基础:软件安装(rpm,yum,源代码)

    Software Installation on Linux Linux安装分为rpm包(可通过yum或者是rpm命令安装)和源码包(源代码或者是编译过的二进制码)两种. Linux是开源系统,很多应 ...

  5. Linux查看系统信息(操作系统版本,进程,任务,CPU,内存,磁盘等信息)

    查看操作系统: cat /proc/version   # 内核版本 cat /etc/issue   # 发行版本 head -n 1 /etc/issue uname -a lsb_release ...

  6. 初学java之触发响应事件举例子

    设置一个触发响应事件? 比如消息框..... package hello; import javax.swing.*; import project.readerListen; import java ...

  7. IO流--复制picture ,mp3

    import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import ...

  8. 超棒的响应式设计测试书签和工具(bookmarks)(转)

    一.测试书签(bookmarks) Viewport Resizer 这个书签号称拥有158个国家3万多活跃的用户,主要特性: 完全自定制 方便的添加自定义尺寸 手动的横竖屏切换 自动的横竖屏切换 ( ...

  9. BZOJ3928 [Cerc2014] Outer space invaders

    第一眼,我勒个去...然后看到n ≤ 300的时候就2333了 首先把时间离散化,则对于一个时间的区间,可以知道中间最大的那个一定要被选出来,然后把区间分成左右两份 于是区间DP就好了,注意用左开右开 ...

  10. C#泛型接口

    为泛型集合类或表示集合中项的泛型类定义接口通常很有用.对于泛型类,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,这样可以避免值类型的装箱和 ...