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