aspect学习(1)before&after&around

来源:互联网 发布:免费pdf合并软件 编辑:程序博客网 时间:2024/06/06 02:57

 面向Aspect的编程,其实就是AOP,什么是AOP,用过学习过spring的同学都晓得。本人不喜欢也不擅长写概念,更喜欢结合例子慢慢讲解。

       下面的例子是在eclipse下写的,需要先准备好环境。


准备环境

         eclipse安装AJDT插件,安装插件的方法有多种,本人更喜欢在线安装。http://www.eclipse.org/ajdt/downloads/,找到和ecilpse版本相合的ajdt版本,复制update site URL连接,然后在eclipse中按照安装插件的方法安装即可。

    (不会安装插件的同学,可百度,别人有贴图详细说明)。

    下载aspect架包,http://www.eclipse.org/aspectj/downloads.PHP

需求一

     有一Point类,有三个私有属性int x,int y , int z ,当调用setX,setY,setZ进行属性值修改前,打印通知日志。

    注意:eclipse新建项目,必须选择aspectJ project


[java] view plain copy
  1. package com.fei.bean;  
  2.   
  3. public class Point {  
  4.   
  5.     private int x;  
  6.     private int y;  
  7.     private int z;  
  8.       
  9.     public Point(int x, int y, int z) {  
  10.         super();  
  11.         this.x = x;  
  12.         this.y = y;  
  13.         this.z = z;  
  14.     }  
  15.   
  16.     public int getX() {  
  17.         return x;  
  18.     }  
  19.   
  20.     public void setX(int x) {  
  21.         this.x = x;  
  22.     }  
  23.   
  24.     public int getY() {  
  25.         return y;  
  26.     }  
  27.   
  28.     public void setY(int y) {  
  29.         this.y = y;  
  30.     }  
  31.   
  32.     public int getZ() {  
  33.         return z;  
  34.     }  
  35.   
  36.     public void setZ(int z) {  
  37.         this.z = z;  
  38.     }  
  39.       
  40. }  

     如果是新手,他们的做法就是在3个set方法里面加入日志代码,如果日志代码很复杂呢?如果我还有Pont1、Point2类,也需要对他们的set做相同处理呢?难道你在所有需要的地方都复制粘贴那部分日志代码吗?如果某天日志代码有修改呢,这样一来就需要修改N多个地方的代码了。。。。哪怕你是新手,你都觉得恶心了。

     废话不多说了,下面先直接用aspect来做,实现需求,然后再结合代码讲解。

[java] view plain copy
  1. package com.fei.aspect;  
  2.   
  3. import com.fei.bean.*;  
  4. import org.aspectj.lang.Signature;  
  5.   
  6. public aspect LogAspect {  
  7.   
  8.     public pointcut printLog() : call(void Point.set*(int));  
  9.       
  10.     before() : printLog(){  
  11.          Signature signature = thisJoinPoint.getSignature();  
  12.          System.out.println("在调用"+signature.getName());  
  13.     }  
  14. }  



       是不是觉得很神奇,我们用反编译工具来看看编译后的class文件。

       Point.class反编译后得到的源码和原源码可以说是一样的,就不贴了.

      MainTest.class反编译后的源码

[java] view plain copy
  1. package com.fei;  
  2.   
  3. import com.fei.aspect.LogAspect;  
  4. import com.fei.bean.Point;  
  5. import org.aspectj.lang.JoinPoint.StaticPart;  
  6. import org.aspectj.runtime.reflect.Factory;  
  7.   
  8. public class MainTest  
  9. {  
  10.   private static final JoinPoint.StaticPart ajc$tjp_0;  
  11.   private static final JoinPoint.StaticPart ajc$tjp_1;  
  12.   
  13.   static{  
  14.         ajc$preClinit(); }   
  15.         private static void ajc$preClinit() {  
  16.           Factory localFactory = new Factory("MainTest.java", MainTest.class);   
  17.           ajc$tjp_0 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("1""setX""com.fei.bean.Point""int""x""""void"), 9);  
  18.           ajc$tjp_1 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("1""setY""com.fei.bean.Point""int""y""""void"), 10);  
  19.       }  
  20.           
  21.   public static void main(String[] args) {  
  22.     Point p = new Point(102030);  
  23.     LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_0);   
  24.     p.setX(15);  
  25.       
  26.     LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_1);   
  27.     p.setY(25);  
  28.   }  
  29.   
  30.   
  31. }  

   我们发现在执行p.setX(15),p.setY(25)之前,都会先调用LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898这个方法,那这个方法是什么东东呢,我们在来看看LogAspect.aj编译后产生的class文件

[java] view plain copy
  1. package com.fei.aspect;  
  2.   
  3. import java.io.PrintStream;  
  4. import org.aspectj.lang.JoinPoint.StaticPart;  
  5. import org.aspectj.lang.NoAspectBoundException;  
  6. import org.aspectj.lang.Signature;  
  7. import org.aspectj.lang.annotation.Aspect;  
  8. import org.aspectj.lang.annotation.Before;  
  9.   
  10. @Aspect  
  11. public class LogAspect{  
  12.   static{  
  13.     try{  
  14.       ajc$postClinit();   
  15.     } catch (Throwable localThrowable) {  
  16.         ajc$initFailureCause = localThrowable;  
  17.     }   
  18.   }  
  19.   
  20.   @Before(value="printLog()", argNames="")  
  21.   public void ajc$before$com_fei_aspect_LogAspect$1$738e898(JoinPoint.StaticPart thisJoinPointStaticPart) {  
  22.     Signature signature = thisJoinPointStaticPart.getSignature();  
  23.     System.out.println("在调用" + signature.getName());  
  24.   }  
  25.   
  26.   public static LogAspect aspectOf() {  
  27.     if (ajc$perSingletonInstance == null)  
  28.         throw new NoAspectBoundException("com_fei_aspect_LogAspect", ajc$initFailureCause); return ajc$perSingletonInstance; }   
  29.     public static boolean hasAspect() {   
  30.       return ajc$perSingletonInstance != null;  
  31.   }  
  32. }  

   对于里面的一些不熟悉的方法,我们不需要继续深究,我们只要知道它的处理流程即可。


  我们已经知道执行流程了,那下面我们来学习下aspect的一些语法。语法的使用有很多,这里没法一一详解,需要支持复制操作的,可自行查看官方文档。


pointcut

     pointcut,从字面的意思说的是切面的意思。也就是横切的时候,会有哪些执行点会被识别。只有先识别了,才能执行相应的Advice。

[java] view plain copy
  1. public pointcut printLog() : call(void Point.set*(int));  
    这行代码很容易理解,就是凡是符合call里面说写的东东,你都给我执行pritntLog()这个方法,至于是什么时候执行呢?Point.set*之前还是之后呢?那就看printLog()这方法使用befor()修饰呢还是after()修饰呢?我们上面的代码用的是before,所以在main方面里面执行p.setX(15) 之前会先执行printLog().

call

   call里面是可以使用通配符的,组合一些复杂的东东。

     * 表示任何数量的字符,除了(.) 
     .. 表示任何数量的字符包括任何数量的(.) 
     + 描述指定类型的任何子类或者子接口
    同Java一样,提供了一元和二元的条件表达操作符。
    一元操作符:!
    二元操作符:||和&&
    优先权同java

  

   比如上面的call(void Point.set*(int))就是说Point里面方法是以set开头,并且参数类型是int,返回void类型的方法。

   call(* Point.*X(..))就是说Point里面方法名以X结尾,任意参数类型,返回类型也任意的方法都符合,也就是说void setX(int x),int getX()都符合

   call(void Point.set*(int)) 会捕捉到setX,setY,setZ,那如果我要排除setY呢?那可以这样写call(void Point.set*(int)) && !call(void Point.setY(int));

execution

    上面的源码是pointcut  和call连用,call就是方法调用,所以我们用反编译工具看class文件时,发现都是在调用方法(如上面MainTest.main中的p.setX(15),p.setY(25))前会插入一些代码

    pointcut除了和call连用,还可以和execution连用,execution就是方法执行,我们把上面的LogAspect中的 call改为execution看看,有什么不同。

[java] view plain copy
  1. package com.fei.aspect;  
  2.   
  3. import com.fei.bean.*;  
  4. import org.aspectj.lang.Signature;  
  5.   
  6. public aspect LogAspect {  
  7.   
  8.     public pointcut printLog() : execution( void Point.set*(int)) ;  
  9.       
  10.     before() : printLog(){  
  11.          Signature signature = thisJoinPoint.getSignature();  
  12.          System.out.println("在调用"+signature.getName());  
  13.     }  
  14.       
  15.       
  16. }  

MainTest.class反编译后的源码

[html] view plain copy
  1. package com.fei;  
  2.   
  3. import com.fei.bean.Point;  
  4.   
  5. public class MainTest  
  6. {  
  7.   public static void main(String[] args)  
  8.   {  
  9.     Point p = new Point(10, 20, 30);  
  10.     p.setX(15);  
  11.     p.setY(25);  
  12.   }  
  13. }  

    发现没插入任何额外代码了。那额外的那部分代码哪去了呢?

  Point.class反编译后的源码

[java] view plain copy
  1. package com.fei.bean;  
  2.   
  3. import com.fei.aspect.LogAspect;  
  4. import org.aspectj.lang.JoinPoint.StaticPart;  
  5. import org.aspectj.runtime.reflect.Factory;  
  6.   
  7. public class Point  
  8. {  
  9.   private int x;  
  10.   private int y;  
  11.   private int z;  
  12.   private static final JoinPoint.StaticPart ajc$tjp_0;  
  13.   private static final JoinPoint.StaticPart ajc$tjp_1;  
  14.   private static final JoinPoint.StaticPart ajc$tjp_2;  
  15.   
  16.   public Point(int x, int y, int z)  
  17.   {  
  18.     this.x = x;  
  19.     this.y = y;  
  20.     this.z = z;  
  21.   }  
  22.   
  23.   public int getX() {  
  24.     return this.x;  
  25.   }  
  26.   
  27.   public void setX(int x) {  
  28.     LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_0); this.x = x;  
  29.   }  
  30.   
  31.   public int getY() {  
  32.     return this.y;  
  33.   }  
  34.   
  35.   public void setY(int y) {  
  36.     LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_1); this.y = y;  
  37.   }  
  38.   
  39.   public int getZ() {  
  40.     return this.z;  
  41.   }  
  42.   
  43.   public void setZ(int z) {  
  44.     LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_2); this.z = z;  
  45.   }  
  46.   
  47.   static  
  48.   {  
  49.     ajc$preClinit(); }   
  50.   private static void ajc$preClinit() { Factory localFactory = new Factory("Point.java", Point.class); ajc$tjp_0 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("1""setX""com.fei.bean.Point""int""x""""void"), 20); ajc$tjp_1 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("1""setY""com.fei.bean.Point""int""y""""void"), 28); ajc$tjp_2 = localFactory.makeSJP("method-execution", localFactory.makeMethodSig("1""setZ""com.fei.bean.Point""int""z""""void"), 36);  
  51.   }  
  52. }  

   呵呵,已经跑到Point里面来了。



通过比较,对call和execution的区别就一目了然了。


需求二

     在需求一的基础上,增加,当调用完setX(int),setY(int),setZ(int)方法后,打印当前时间.


   既然有before那当然就有after啊,简单。

[java] view plain copy
  1. package com.fei.aspect;  
  2.   
  3. import java.util.Calendar;  
  4. import java.util.Date;  
  5.   
  6. import com.fei.bean.*;  
  7.   
  8. import org.aspectj.lang.Signature;  
  9.   
  10. public aspect LogAspect {  
  11.   
  12.     public pointcut printLog() : call( void Point.set*(int)) ;  
  13.     public pointcut timeLog() : call(void Point.set*(..));  
  14.       
  15.     before() : printLog(){  
  16.          Signature signature = thisJoinPoint.getSignature();  
  17.          System.out.println("在调用"+signature.getName());  
  18.     }  
  19.       
  20.     after() : timeLog(){  
  21.         Signature signature = thisJoinPoint.getSignature();  
  22.          System.out.println(signature.getName()+"调用完后当前时间:"+new Date().toLocaleString());  
  23.     }  
  24.       
  25. }  
或者

[java] view plain copy
  1. package com.fei.aspect;  
  2.   
  3. import java.util.Calendar;  
  4. import java.util.Date;  
  5.   
  6. import com.fei.bean.*;  
  7.   
  8. import org.aspectj.lang.Signature;  
  9.   
  10. public aspect LogAspect {  
  11.   
  12.     public pointcut printLog() : call( void Point.set*(int)) ;  
  13.       
  14.     before() : printLog(){  
  15.          Signature signature = thisJoinPoint.getSignature();  
  16.          System.out.println("在调用"+signature.getName());  
  17.     }  
  18.       
  19.     after() : printLog(){  
  20.         Signature signature = thisJoinPoint.getSignature();  
  21.          System.out.println(signature.getName()+"调用完后当前时间:"+new Date().toLocaleString());  
  22.     }  
  23.       
  24. }  




     这样做,的确满足了需求,但是却需要多写个after.那能否不需这么麻烦吗?可以,这时around就派上用场了,around字面意思包围。

[java] view plain copy
  1. package com.fei.aspect;  
  2.   
  3. import java.util.Calendar;  
  4. import java.util.Date;  
  5.   
  6. import com.fei.bean.*;  
  7.   
  8. import org.aspectj.lang.Signature;  
  9.   
  10. public aspect LogAspect {  
  11.   
  12.     public pointcut printLog() : call( void Point.set*(int)) ;  
  13.       
  14.     void around() : printLog(){  
  15.          Signature signature = thisJoinPoint.getSignature();  
  16.          System.out.println("在调用"+signature.getName());  
  17.          proceed();  
  18.          System.out.println("当前时间:"+new Date().toLocaleString());  
  19.     }  
  20.       
  21.       
  22. }  



原创粉丝点击