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

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

是不是觉得很神奇,我们用反编译工具来看看编译后的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());
-
}
-
-
}

这样做,的确满足了需求,但是却需要多写个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());
-
}
-
-
-
}
