2017年3月6日

IntelliJ Idea 下配置 AspectJ,及简单 demo

零、场景

项目中有大量用于事件处理的代码片段(目测几百处),例如 mousePressed,mouseClicked 等。我们想在执行这些回调函数时,得到具体事件的相关信息,执行一段自定义代码(比如记录日志的代码),大致弄清楚这次程序执行过程中,都触发了哪些事件,何时触发的。

一种方法是,直接在 mousePressed 这样的回调函数中,添加我们想要的功能。但这样做有问题:

  • 找到这几百个方法,并添加内容,是一件苦差事
  • 在几百个地方,加入功能相近的代码,显然违背了 DRY 原则
  • 以后编写新的事件处理程序时,还要单独处理一遍
  • ……

前人(帆软前员工 anchore)已经探索过这个问题,可以用 AOP 的思想来解决。

一、AOP 和 AspectJ 简介

面向切面的程序设计(Aspect-Oriented Programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为切面(aspect,又译作方面)的语言构造为基础,切面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。

  • 关注点(concern):对软件工程有意义的小的、可管理的、可描述的软件组成部分,一个关注点通常只同一个特定概念或目标相关联。
  • 主关注点(core concern):一个软件最主要的关注点。

例如,对于一个信用卡应用程序来说,存款、取款、帐单管理是它的主关注点,日志和持久化将成为横切整个对象结构的横切关注点。我们可以切入这些关注点,加入一些额外的操作。

AspectJ 是 AOP 的一种实现,它扩展了 Java 语言,可以理解为 Java 的超集。AspectJ 定义了 AOP 语法(具体查阅外部资料,本文不讨论),所以它有一个专门的编译器用来生成遵守 Java 字节编码规范的 Class 文件。

AspectJ 允许我们在切面(此处是各种回调函数)插入自定义代码,同时提供切面的上下文信息。它不会影响现有项目的源码,只是在编译的过程中,把自定义代码部分插入切面所在的 class 文件。

二、Idea 下配置 AspectJ

网上都是关于 eclipse 的教程,只好自己折腾 Idea。好在 Idea 官方给出了比较详细的文档(直接看这个文档就行了。貌似只有专业版才支持 AspectJ,社区版不行)。

我自己验证过的步骤如下:

1、激活 Idea 自带的 AspectJ 插件官方文档

如图所示,激活“AspectJ Support”和“Spring AOP/@AspectJ”

aspectj1

2、去官网下载并安装 AspectJ(挂上 VPN 速度更快)

Mac 下,默认安装到用户主目录(home)。如图所示:

aspectj2

本文中,我们只需要用到 lib 目录中的两个 jar 包:aspectjrt.jar 和 aspectjtools.jar。

3、添加依赖

把 aspectjrt.jar 添加为工程的依赖库。相对简单,不会的话,可以参考 Idea 的官方文档

4、将项目的编译器配置为 ajc

编译器路径为 aspectjtools.jar 所在路径。如图所示:

aspectj3

点击右侧的 test 按钮,确保配置成功。

注意 Delegate to Javac 选项。如果勾选,会尽量使用 javac 编译项目,不会向切面中插入代码。看官方文档,理论上不应该这样,但实测的确如此。感兴趣的话,可以自己再验证一下。

三、编写 demo

用一个取色器 demo 作为实例,来说明具体用法。

工程配置如图:

aspectj4

创建一个 AspectJ 源码文件(后缀为 .aj)

aspectj5

写入如下代码:

import org.aspectj.lang.reflect.SourceLocation;

import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;

public aspect AspectjDemo {
    //声明一个pointcut,匹配你需要的方法
    pointcut onMouseClicked(MouseEvent e) :
            execution(* mouseClicked(MouseEvent)) && args(e);
    pointcut onMousePressed(MouseEvent e) :
            execution(* mousePressed(MouseEvent)) && args(e);
    pointcut onMouseReleased(MouseEvent e) :
            execution(* mouseReleased(MouseEvent)) && args(e);
    pointcut onActionPerformed(ActionEvent e) :
            execution(* actionPerformed(ActionEvent)) && args(e);

    //before表示之前的意思
    //这整个表示在 mouseXXX(MouseEvent) 方法调用之前,你想要执行的代码
    before(MouseEvent e) : onMouseClicked(e) || onMousePressed(e) || onMouseReleased(e) {
        SourceLocation sl = thisJoinPoint.getSourceLocation();//切面对应的代码位置
        System.out.println(sl);
        System.out.println(e);
        System.out.println(e.getSource());
    }
    //同上
    before(ActionEvent e) : onActionPerformed(e) { // && !within(LogHandlerBar) {
        SourceLocation sl = thisJoinPoint.getSourceLocation();
        System.out.println(sl);
        System.out.println(e);
        System.out.println(e.getSource());
    }
}

切入 mouseClicked,mousePressed,mouseReleased 和 actionPerformed 这几个回调函数,打印出具体事件信息。不讨论语法细节。

直接编译运行就可以了,下面放出效果图:

aspectjdemo

 

附 AspectJ 参考资料:


“以书为舟,遨游尘世”,
最好的免费 kindle 电子书分享站:

You may also like...

No Responses

  1. Renan Rosa说道:

    I cannot thank you enough, my friend. It helped me alot!

  2. Zhang说道:

    为什么按照步骤配置了还是不行呢?compiler 配置并 testok。library 添加了,aspectj facet 也添加了,command + N 也能新建 Aspectj 文件。

    • 桩白墨说道:

      看具体是卡在哪一步了,有没有报错或其他线索。把问题逐步细化之后,再去网上找解决方法,各个击破。

发表回复

您的电子邮箱地址不会被公开。


*