aspect学习(1)before&after&around

            面向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

aspect学习(1)before&after&around_第1张图片

package com.fei.bean;

public class Point {

	private int x;
	private int y;
	private int z;
	
	public Point(int x, int y, int z) {
		super();
		this.x = x;
		this.y = y;
		this.z = z;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}

	public int getZ() {
		return z;
	}

	public void setZ(int z) {
		this.z = z;
	}
	
}

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

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

package com.fei.aspect;

import com.fei.bean.*;
import org.aspectj.lang.Signature;

public aspect LogAspect {

	public pointcut printLog() : call(void Point.set*(int));
	
	before() : printLog(){
		 Signature signature = thisJoinPoint.getSignature();
		 System.out.println("在调用"+signature.getName());
	}
}

aspect学习(1)before&after&around_第2张图片


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

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

      MainTest.class反编译后的源码

package com.fei;

import com.fei.aspect.LogAspect;
import com.fei.bean.Point;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.reflect.Factory;

public class MainTest
{
  private static final JoinPoint.StaticPart ajc$tjp_0;
  private static final JoinPoint.StaticPart ajc$tjp_1;

  static{
	    ajc$preClinit(); } 
	  	private static void ajc$preClinit() {
		  Factory localFactory = new Factory("MainTest.java", MainTest.class); 
		  ajc$tjp_0 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("1", "setX", "com.fei.bean.Point", "int", "x", "", "void"), 9);
		  ajc$tjp_1 = localFactory.makeSJP("method-call", localFactory.makeMethodSig("1", "setY", "com.fei.bean.Point", "int", "y", "", "void"), 10);
	  }
	  	
  public static void main(String[] args) {
    Point p = new Point(10, 20, 30);
    LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_0); 
    p.setX(15);
    
    LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_1); 
    p.setY(25);
  }


}

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

package com.fei.aspect;

import java.io.PrintStream;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class LogAspect{
  static{
    try{
      ajc$postClinit(); 
    } catch (Throwable localThrowable) {
    	ajc$initFailureCause = localThrowable;
    } 
  }

  @Before(value="printLog()", argNames="")
  public void ajc$before$com_fei_aspect_LogAspect$1$738e898(JoinPoint.StaticPart thisJoinPointStaticPart) {
    Signature signature = thisJoinPointStaticPart.getSignature();
    System.out.println("在调用" + signature.getName());
  }

  public static LogAspect aspectOf() {
    if (ajc$perSingletonInstance == null)
    	throw new NoAspectBoundException("com_fei_aspect_LogAspect", ajc$initFailureCause); return ajc$perSingletonInstance; } 
    public static boolean hasAspect() { 
	  return ajc$perSingletonInstance != null;
  }
}

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


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


pointcut

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

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看看,有什么不同。

package com.fei.aspect;

import com.fei.bean.*;
import org.aspectj.lang.Signature;

public aspect LogAspect {

	public pointcut printLog() : execution( void Point.set*(int)) ;
	
	before() : printLog(){
		 Signature signature = thisJoinPoint.getSignature();
		 System.out.println("在调用"+signature.getName());
	}
	
	
}

MainTest.class反编译后的源码

package com.fei;

import com.fei.bean.Point;

public class MainTest
{
  public static void main(String[] args)
  {
    Point p = new Point(10, 20, 30);
    p.setX(15);
    p.setY(25);
  }
}

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

  Point.class反编译后的源码

package com.fei.bean;

import com.fei.aspect.LogAspect;
import org.aspectj.lang.JoinPoint.StaticPart;
import org.aspectj.runtime.reflect.Factory;

public class Point
{
  private int x;
  private int y;
  private int z;
  private static final JoinPoint.StaticPart ajc$tjp_0;
  private static final JoinPoint.StaticPart ajc$tjp_1;
  private static final JoinPoint.StaticPart ajc$tjp_2;

  public Point(int x, int y, int z)
  {
    this.x = x;
    this.y = y;
    this.z = z;
  }

  public int getX() {
    return this.x;
  }

  public void setX(int x) {
    LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_0); this.x = x;
  }

  public int getY() {
    return this.y;
  }

  public void setY(int y) {
    LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_1); this.y = y;
  }

  public int getZ() {
    return this.z;
  }

  public void setZ(int z) {
    LogAspect.aspectOf().ajc$before$com_fei_aspect_LogAspect$1$738e898(ajc$tjp_2); this.z = z;
  }

  static
  {
    ajc$preClinit(); } 
  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);
  }
}

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



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


需求二

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


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

package com.fei.aspect;

import java.util.Calendar;
import java.util.Date;

import com.fei.bean.*;

import org.aspectj.lang.Signature;

public aspect LogAspect {

	public pointcut printLog() : call( void Point.set*(int)) ;
	public pointcut timeLog() : call(void Point.set*(..));
	
	before() : printLog(){
		 Signature signature = thisJoinPoint.getSignature();
		 System.out.println("在调用"+signature.getName());
	}
	
	after() : timeLog(){
		Signature signature = thisJoinPoint.getSignature();
		 System.out.println(signature.getName()+"调用完后当前时间:"+new Date().toLocaleString());
	}
	
}
或者

package com.fei.aspect;

import java.util.Calendar;
import java.util.Date;

import com.fei.bean.*;

import org.aspectj.lang.Signature;

public aspect LogAspect {

	public pointcut printLog() : call( void Point.set*(int)) ;
	
	before() : printLog(){
		 Signature signature = thisJoinPoint.getSignature();
		 System.out.println("在调用"+signature.getName());
	}
	
	after() : printLog(){
		Signature signature = thisJoinPoint.getSignature();
		 System.out.println(signature.getName()+"调用完后当前时间:"+new Date().toLocaleString());
	}
	
}


aspect学习(1)before&after&around_第3张图片


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

package com.fei.aspect;

import java.util.Calendar;
import java.util.Date;

import com.fei.bean.*;

import org.aspectj.lang.Signature;

public aspect LogAspect {

	public pointcut printLog() : call( void Point.set*(int)) ;
	
	void around() : printLog(){
		 Signature signature = thisJoinPoint.getSignature();
		 System.out.println("在调用"+signature.getName());
		 proceed();
		 System.out.println("当前时间:"+new Date().toLocaleString());
	}
	
	
}
aspect学习(1)before&after&around_第4张图片








你可能感兴趣的:(JAVA基础)