面向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;
}
}
废话不多说了,下面先直接用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);
}
}
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());
}
}
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);
}
}
通过比较,对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());
}
}