AspectJ 入门

概述

在学习spring aop过程中,发现有个怎么都绕不过去的坎,就是AspectJ的使用。少了这一部分,一些spring aop的源码总觉得少了点什么,看不大懂。所以接下来会写几篇关于AspectJ的入门使用。

安装示例代码

1、首先,下载最新的稳定版本(我自己下的是AspectJ 1.9.2, Released 24 Oct 2018 版本)aspectj-1.9.2.jar
http://www.eclipse.org/aspectj/downloads.php#stable_release

2、安装
aspectJ的安装很简单,运行 java -jar aspectj-1.9.2.jar 然后选下jdk路径及最终aspectj要安装(AspecJ安装路径)到哪里就好

3、安装最后,会提示你要配置环境变量了


按照环境配下PATH变量 和 CLASSPATH变量就好

export PATH=${JAVA_HOME}/bin:$PATH:/Users/hdj/software/aspectj/bin
export CLASSPATH=${CLASSPATH}:/Users/hdj/software/aspectj/lib/aspectjrt.jar

4、编译代码示例

进入 ${aspectJ 安装路径}/doc/examples
执行 ajc -argfile telecom/billing.lst

5、运行示例

java telecom.BillingSimulation

6、运行结果


示例总结

简单总结下:
1)安装配置aspectJ环境
2)使用 ajc -argfile 编译类
3)运行编译后的字节码

上述的例子是aspectJ 官方的例子 ,为了说明如何使用aspectJ的使用,我们看另一个例子

另一个例子

知道如何运行aspectJ给的官方示例后,我们继续学习官网给的另一个例子,例子路径

${aspectJ 路径}/doc/examples/tjp/Demo.java

我们先来看看原始类

public class Demo {
    static Demo d;

    public static void main(String[] args){
        new Demo().go();
    }

    void go(){
        d = new Demo();
        //调用foo方法
        d.foo(1,d);
        //调用bar方法
        System.out.println(d.bar(new Integer(3)));
    }

    void foo(int i, Object o){
        //打印foo
        System.out.println("Demo.foo(" + i + ", " + o + ")\n");
    }

    String bar (Integer j){
        System.out.println("Demo.bar(" + j + ")\n");
        return "Demo.bar(" + j  + ")";
    }
}

再来看看一个随着Demo.java一起被 ajc编译的特殊的类GetInfo.java

package tjp;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;

aspect GetInfo {

  static final void println(String s){ System.out.println(s); }
  
  pointcut goCut(): cflow(this(Demo) && execution(void go()));

  pointcut demoExecs(): within(Demo) && execution(* *(..));

  Object around(): demoExecs() && !execution(* go()) && goCut() {
     println("Intercepted message: " +
         thisJoinPointStaticPart.getSignature().getName());
     println("in class: " +
         thisJoinPointStaticPart.getSignature().getDeclaringType().getName());
     printParameters(thisJoinPoint);
     println("Running original method: \n" );
     Object result = proceed();
     println("  result: " + result );
     return result;
  }

  // 打印参数
  static private void printParameters(JoinPoint jp) {
     println("Arguments: " );
     Object[] args = jp.getArgs();
     String[] names = ((CodeSignature)jp.getSignature()).getParameterNames();
     Class[] types = ((CodeSignature)jp.getSignature()).getParameterTypes();
     for (int i = 0; i < args.length; i++) {
        println("  "  + i + ". " + names[i] +
            " : " +            types[i].getName() +
            " = " +            args[i]);
     }
  }
}

这里和spring aop的使用很多方面非常类似,最后执行的结果如下:


这里说明一下几点:

GetInfo.java 含有的特殊关键词

GetInfo.java含有很多特殊的关键词,这些关键词java是无法识别的,需要通过ajc编译才能织入到字节码中

切入点表达式

spring的切入点表达式我们非常熟悉,使用aspectJ时,也需要配置切入点,需要配置有哪些方法需要被切入

//表示Demo类的go方法调用过程中的方法需要被增强
pointcut goCut(): cflow(this(Demo) && execution(void go()));

//每一个Demo类定义的方法
pointcut demoExecs(): within(Demo) && execution(* *(..));

// 由于 goCut() 这个切入点还包含go()方法自己的调用,因此需要把自己给排掉
Object around(): demoExecs() && !execution(* go()) && goCut();

可能会问,既然为啥要同时配置
demoExecs() 以及 !execution(* go()) && goCut() 单独有一个不够么?

1)demoExecs()表示拦截Demo类的所有方法,如果不加后面的限制,会同时拦截main方法,以及go()方法
2)如果只有!execution(* go()) && goCut(),直接编译时候会报错(warning)执行后直接会报java.lang.StackOverflowError
3)如果只有!execution(* go()) && goCut(),可以执行,但是只会拦截go方法

thisJoinPointStaticPart 和 thisJoinPoint

thisJoinPoint 包含了关于当前切入点的一些信息(通过反射获取的)
thisJoinPointStaticPart 包含一些静态信息,参考官方文档

通过配置切入点,我们实现了不改变Demo.java源码的前提下,往Demo.java方法的前后插入了一段代码。

与Spring 切面写法的对比

对比下之前在学习Spring时候,配置的切面

//声明这是一个组件
@Component
//声明这是一个切面Bean
@Aspect
@Slf4j
public class ServiceAspect {

    //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
    @Pointcut("execution(* com.hdj.learn.spring.aop.service..*(..))")
    public void aspect() {
    }

    /*
     * 配置前置通知,使用在方法aspect()上注册的切入点
     * 同时接受JoinPoint切入点对象,可以没有该参数
     */
    @Before("aspect()")
    public void before(JoinPoint joinPoint) {
        log.info("before " + joinPoint);
    }

    //配置后置通知,使用在方法aspect()上注册的切入点
    @After("aspect()")
    public void after(JoinPoint joinPoint) {
        log.info("after " + joinPoint);
    }

    //配置环绕通知,使用在方法aspect()上注册的切入点
    @Around("aspect()")
    public void around(JoinPoint joinPoint) {
        long start = System.currentTimeMillis();
        try {
            ((ProceedingJoinPoint) joinPoint).proceed();
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
        }
    }

    //配置后置返回通知,使用在方法aspect()上注册的切入点
    @AfterReturning("aspect()")
    public void afterReturn(JoinPoint joinPoint) {
        log.info("afterReturn " + joinPoint);
    }

    //配置抛出异常后通知,使用在方法aspect()上注册的切入点
    @AfterThrowing(pointcut = "aspect()", throwing = "ex")
    public void afterThrow(JoinPoint joinPoint, Exception ex) {
        log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());
    }

}

其实非常类似,有切面、有通知、有目标类等等,切入点的表达式也非常类似。

总结下

初步了解了aspectJ的使用,我们可以了解以下几点:
1)aspectJ的使用是在编译期,通过特殊的编译器可以在不改变代码的前提下织入代码(当然能不能在运行期,我还没有确认)
2)aspectJ的使用,也是配置切入点、通知

问题

到了这里基本了解了aspectJ的使用,但是还有几个问题。
1)我们在spring中并没有看到需要aspectj之类的关键词,而是使用java代码就可以了,这是如何做到的
2)同样,我们也没有使用特殊的编译器
3)Spring源码中与aspectJ 相关的AjType究竟是啥?

这些问题,我们会在下一篇博客里解决

10月份加班比较多,耽搁了写博客,这周开始回复更新,会尽量补上上个月的。= = 虽然没啥人看哈哈哈。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容