背景

自从学习spring起,​​AOP​​​这个词几乎是天天挂在嘴边,好像一切都是天经地义的。
尤其在springboot里面,经常见到的​​​@Before​​​ ​​@After​​​等之类的内容,一般更是没有多少解释,通常被告知"这是aop的用法"。
能有人特别说明一下下面这点信息就算不错的了。

  • ​@Before​​​​@After​​​等这些的实现,底层是​​aspectj​​​ 。官网地址:​​aspectJ官网​
  • ​aspectj​​​并不天然是​​spring framework​​​的一部分,人家本是一个独立项目,以优秀的方式实现了aop;还有一些其他的aop实现,如​​Jboss Aop​​什么的。
  • ​spring framework​​​有自己的aop实现,但难用,麻烦;后来spring官方索性将 优秀的​​aspectj​​​ 集成到了​​spring framework​​源码中。

从此以后就成了现在我们看到的​​spring framework​​​的aop似乎天然就是​​aspectj​​的样子。

但我总觉得不对劲,距离透彻的理解,似乎总差着一段距离。
于是今天势必追查清楚,求个究竟。

引出具体问题

首先,明确一个基本事实,就是 :

​aspectj​​​并不天然是​​spring framework​​的一部分,它本是一个独立项目,它以自己的方式实现了aop;

那么,我们就不仅仅是一定要把它用在spring项目中;比如我想把它用在我的一个简单的控制台项目中,该怎么做呢?

显然地,网上的文章一大抄,基本上一搜都是教你什么是 pointcut,什么是 joinpoint等等,然后如何用在​​springboot​​​项目中。
这个太简单,太程式化,已经麻木了,不值一提。

实现步骤

经过一些搜索、尝试,起初我的项目并没有如预期一样执行,输出的指定的 before after之类的内容。
经过更多的搜索和思考,想明白了原理之后,一切都变得水到渠成了。

其中,在外网搜到一个有用的文章,给了我很多启发。
​​​How to create a Hello World with IntelliJ and Aspect J​​ 里面的步骤也挺详细的。

正式进入实现步骤

1. 安装编译器

想要正常使用aspectj将自己写好的pointcut等内容织入到对应的class文件,这是一个类似于编译的过程。就像编译C语言需要gcc编译器,编译java需要javac编译器一样(当然也可能是别的编译器)。我们需要下载​​aspectj​​,并安装在我们的电脑中。

下载和安装步骤依次如下图:

下载地址:

​https://www.eclipse.org/aspectj/downloads.php​

怎样在普通java项目中使用aspectj_控制台aspectj


怎样在普通java项目中使用aspectj_aspectj示例_02


怎样在普通java项目中使用aspectj_aspectj示例_03


怎样在普通java项目中使用aspectj_控制台aspectj_04


怎样在普通java项目中使用aspectj_非spring aspectj_05


怎样在普通java项目中使用aspectj_aspectj应用_06


怎样在普通java项目中使用aspectj_aspectj示例_07


截至此处,已经下载并安装了 aspectj到本地。

2.配置环境变量

同样地,类似我们安装了jdk到本地,但cmd命令行里并不能直接使用​​javac xxx.java​​​命令,就是因为我们没有把​​javac​​​可执行文件配置到我们的​​path​​环境变量里。

这个aspect编译器也一样,同样需要配置环境变量。

如下图:

我只配置了​​CLASSPATH​​​,并且只把​​C:\aspectj1.9\lib\aspectjrt.jar​​配置进去就最终成功了。

怎样在普通java项目中使用aspectj_aspectj应用_08

3. 验证一下

随便试了一下,​​ajc xxx​​,别管它报错不报错,有反应就行。说明这个ajc编译器已经配成功地配置并且可用了。

怎样在普通java项目中使用aspectj_aspectj示例_09


至此,系统级环境配置结束。

开始设法将​​aspectj​​用在常规项目中。


IDEA里新建一个最简单的项目

1. 新建一个空maven项目

怎样在普通java项目中使用aspectj_aspectj示例_10

2. 添加aspectj依赖

怎样在普通java项目中使用aspectj_非spring aspectj_11

3. 随便写个mai方法

怎样在普通java项目中使用aspectj_aspectj应用_12

4.新增一个AspectJ文件

怎样在普通java项目中使用aspectj_非spring aspectj_13


我猜测这里之所以能这么新增,是因为我的IDEA已经安装了aspectj插件。来看一下。

怎样在普通java项目中使用aspectj_非spring aspectj_14


应该是上图两个插件中的第一个,不去验证了。

5. aspectj文件里添加pointcut等内容

这个时候就可以在其他网页里随便抄一段了。如

怎样在普通java项目中使用aspectj_非spring aspectj_15


显然,这里我是试图在 main方法调用之前拦截到的。

如果能拦截到,则控制台会输出绿色字的内容。

下面直接启动一下main方法,看效果。

怎样在普通java项目中使用aspectj_控制台aspectj_16


然而并没有。

不管你再怎么clean,clean install,rebuild,等等等等,都一样,不用再试了。

开始思考和解决aspectj为什么不生效

这段代码居然有了个​​aspectj​​​关键字,居然编译没报错,已经很让我惊讶了。
难道jdk编译的时候认识它?
或者是由于插件的原因,使他显示为蓝色?
但编译为什么没报错呢?
既然没报错那为什么又不生效呢?

经过网上搜索查资料和思考,我悟了。

原因其实也很简单,就是这段代码不应该由jdk来编译,因为jdk又不知道遇到aspect该怎么办;而应该由 ​​aspectj​​​提供的编译功能来编译,也即​​ajc​​​。
(但我也没弄明白为什么有aspect字,jdk编译却不报错)

1. 下面开始给IDEA配置ajc编译器

怎样在普通java项目中使用aspectj_aspectj应用_17


怎样在普通java项目中使用aspectj_非spring aspectj_18


怎样在普通java项目中使用aspectj_java项目aspectj_19


给​​Path to aspectjtools.jar​​配置路径,测试一下,如下图。

怎样在普通java项目中使用aspectj_非spring aspectj_20

2. 此时让我们再编译一下项目

如果配置正确的话,现在我们就是用​​ajc​​编译的了。

3. 然后运行:

怎样在普通java项目中使用aspectj_java项目aspectj_21


立竿见影。

至此完整演示成功。
基本原理,总结起来就是

  1. 安装aspectj,也就有了aspectj编译器
  2. 项目中配置编译器,使用ajc编译器
  3. 项目中新增aspect文件,指定各种切点等。
  4. 使用ajc编译项目,生成如何预期的结果。

几行 的demo,地址如下:
​aspectj-demo.zip


如果此文真的给你解惑了,或者起到了帮助,可考虑打赏。


后记补充

写完之后,我以为就完了,但是后来马上又发现了问题。
就是在前面引入的两个maven依赖,

  <dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.7</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>

它为什么要是两个依赖呢?
我偶然地发现,一个依赖也能正常执行。
更奇怪的是,这两个依赖,只依赖任何一个就可以正常执行了。
已经亲测@2021年8月23日23点04分。

那么,这里又引出了下一个问题

​aspectjrt​​​和​​aspectjweaver​​是什么关系,各自有什么作用

现在来看一下

怎样在普通java项目中使用aspectj_aspectj示例_22


由图可知,这是很明显的,​​aspectjweaver​​​ 包含了​​aspectjrt​​全部的内容。

另外主要多出来的内容,以我的经验,我一眼就看出了重点:(以下是猜测,但我感觉是对的)

后者比前者多出的内容,主要是 ​​asm​​​ 和 ​​weaver​​。

那么,

asm是什么

ASM 是一个 Java 字节码操控框架。

它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

weaver是什么

weaver这个词,就是“织入”的意思啊,字面意思不解释了。

所以,很明显,weaver + asm,实现了可以动态织入 pointcut等内容到字节码中去的。

那么,下一步再可以得出推论。

推论

  1. ​aspectjrt​​​ 功能单纯一些,应该是提供一个​​编译过后的已经织入了pointcut等内容的java字节码的程序的运行环境​​。
    关键词:​​rt​​,也就是 ​​runtime​​。这个概念我们已经比较熟悉了。
  2. ​aspectjweaver​​​ 包含了​​aspectjrt​​全部内容,另外多出了 ​​动态织入的能力​​,主要是​​asm + weaver​​部分的代码。

上面的示例中,之所以需要配置ajc编译器,要编译之后才能正常执行,正是因为推论1的原因。

而推论2
我猜测多出来的这部分能力,就是让java项目可以不用再专门指定ajc编译器,而是使用​​​asm + weaver​​​可以对java代码进行动态编译和织入内容,这样的话直接使用通用的javac编译器就可以了。
当然,还看得出来,也支持了注解的用法。
顺便多数一句,这个情况我已经用代码验证了,符合猜想,大概是对的无疑了

ps: spring项目应用三步曲: ​​aspect​​​(需要标记为@Component) --> ​​pointcut​​​ --> ​​advice​​ 看似不起眼,其实ps:这句话很精髓,懂的自然懂。

所以,项目里,想要使用aspectj注解的写法,需要引入 ​​aspectjweaver​​​ 而不是​​aspectjrt​​​ ,比如​​spring​​里的aop相关的用法。

如果愿意自己指定ajc编译器,则可只引入体积较小的​​aspectjrt​​。

我忽然觉得自己理解得更通透了。