spring aop 与 aspectj

aspectj 是什么?

官网如此描述:

  • 对Java编程语言中的AOP进行无缝扩展(这点很重要)
  • 适用Java平台
  • 易学易用

aspectj如何使用

aspectj 有三种使用方式:

  1. 编译时织入,利用ajc编译器直接将切面织入到源代码中并编译为class
  2. 编译后织入,利用ajc编译器向编译后的class或jar织入切面代码
  3. 运行时织入,不使用ajc编译器,而是利用java agent的能力,在类加载时将切面织入目标代码。

下面我们就看看这三种方式具体如何使用:

编译时织入

现在都是使用Maven管理项目,必然后对应的Maven插件来满足编译时织入的目的:

aspectj-maven-plugin 插件配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>${source.version}</complianceLevel>
<source>${source.version}</source>
<target>${source.version}</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<encoding>UTF-8</encoding>
<outputDirectory>${build.outputDirectory}</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>

要增强的目标类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author jiexiu
* created 2020/5/31 - 16:45
*/
public class HelloApp {

public void say() {
System.out.println("Hello Java!");
}

public static void main(String[] args) {
HelloApp app = new HelloApp();
app.say();
}
}

aspectj文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @author jiexiu
* created 2020/5/31 - 16:49
*/
public aspect AjAspect {

pointcut say():
execution(* HelloApp.say(..));


before(): say() {
System.out.println("before say");
}

after(): say() {
System.out.println("after say");
}
}

mvn compile 后,观察增强后的class文件和切面类文件:

AjAspect.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Aspect
public class AjAspect {
static {
try {
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}

}

public AjAspect() {
}

@Before(
value = "say()",
argNames = ""
)
public void ajc$before$com_leokongwq_springlearn_aop_AjAspect$1$682722c() {
System.out.println("before say");
}

@After(
value = "say()",
argNames = ""
)
public void ajc$after$com_leokongwq_springlearn_aop_AjAspect$2$682722c() {
System.out.println("after say");
}

public static AjAspect aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com_leokongwq_springlearn_aop_AjAspect", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}

public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
}

可见切面描述文件AjAspect.aj 被编译为一个单例类,增强的功能对应一个静态方法。

HelloApp.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

package com.leokongwq.springlearn.aop;

public class HelloApp {
public HelloApp() {
}

public void say() {
try {
AjAspect.aspectOf().ajc$before$com_leokongwq_springlearn_aop_AjAspect$1$682722c();
System.out.println("Hello Java!");
} catch (Throwable var2) {
AjAspect.aspectOf().ajc$after$com_leokongwq_springlearn_aop_AjAspect$2$682722c();
throw var2;
}

AjAspect.aspectOf().ajc$after$com_leokongwq_springlearn_aop_AjAspect$2$682722c();
}

public static void main(String[] args) {
HelloApp app = new HelloApp();
app.say();
}
}

被增强类的方法中直接包含了增强逻辑。

基于注解增强

上面展示了如何基于*.aj文件对目标类进行增强。其实也可以通过基于Aspectj注解的Java类来描述切面和增强逻辑等相关信息,进而对目标类进行源代码级别的增强。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author jiexiu
*/
@Aspect
@Component
public class AopAspect {
@Around("execution(* com.leokongwq.springlearn.aop.HelloApp.sayHi(..))")
public Object watchPerform(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("########### around before invoke ###############");
Object result = joinPoint.proceed();
System.out.println("########### around after invoke ###############");
return result;
}
}

增强后的class文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.leokongwq.springlearn.aop;

import org.aspectj.runtime.reflect.*;
import com.leokongwq.springlearn.component.*;
import org.aspectj.lang.*;

public class HelloApp
{
private static final /* synthetic */ JoinPoint$StaticPart ajc$tjp_0;

public void say() {
try {
AjAspect.aspectOf().ajc$before$com_leokongwq_springlearn_aop_AjAspect$1$682722c();
System.out.println("Hello Java!");
}
catch (Throwable t) {
AjAspect.aspectOf().ajc$after$com_leokongwq_springlearn_aop_AjAspect$2$682722c();
throw t;
}
AjAspect.aspectOf().ajc$after$com_leokongwq_springlearn_aop_AjAspect$2$682722c();
}

public String sayHi() {
final JoinPoint jp = Factory.makeJP(HelloApp.ajc$tjp_0, (Object)this, (Object)this);
return (String)sayHi_aroundBody1$advice(this, jp, AopAspect.aspectOf(), (ProceedingJoinPoint)jp);
}

public static void main(final String[] args) {
final HelloApp app = new HelloApp();
app.say();
app.sayHi();
System.out.println(Math.abs(-10));
}

static {
ajc$preClinit();
}

private static final /* synthetic */ String sayHi_aroundBody0(final HelloApp ajc$this, final JoinPoint joinPoint) {
System.out.println("Hi java");
return "Hi";
}

private static final /* synthetic */ Object sayHi_aroundBody1$advice(final HelloApp ajc$this, final JoinPoint thisJoinPoint, final AopAspect ajc$aspectInstance, final ProceedingJoinPoint joinPoint) {
System.out.println("########### around before invoke ###############");
final Object result = sayHi_aroundBody0(ajc$this, (JoinPoint)joinPoint);
System.out.println("########### around after invoke ###############");
return result;
}

private static /* synthetic */ void ajc$preClinit() {
final Factory factory = new Factory("HelloApp.java", (Class)HelloApp.class);
ajc$tjp_0 = factory.makeSJP("method-execution", (Signature)factory.makeMethodSig("1", "sayHi", "com.leokongwq.springlearn.aop.HelloApp", "", "", "", "java.lang.String"), 13);
}
}

可以class文件的内容可以看到sayHi方法被增强了。

编译后织入

这个功能主要是用来面向依赖的第三方类库的。因为通常我们不能修改源代码,但是还有对它进行增强的需求。那么只能依赖于编译后织入,或者在类加载时transform。

maven配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>${source.version}</complianceLevel>
<source>${source.version}</source>
<target>${source.version}</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<encoding>UTF-8</encoding>
<!--<aspectDirectory>src/main/java/com/leokongwq/springlearn/aop</aspectDirectory>-->
<outputDirectory>${build.outputDirectory}</outputDirectory>
<!-- 对依赖的包进行织入 -->
<weaveDependencies>
<weaveDependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</weaveDependency>
</weaveDependencies>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>

示例代码:

1
2
3
4
5
6
7
@Around("execution(* com.google.gson.Gson.toJson(..))")
public Object watchtoJson(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("########### around before toJson ###############");
Object result = joinPoint.proceed();
System.out.println("########### around after toJson ###############");
return result;
}

测试代码:

1
2
3
4
5
6
public static void main(String[] args) {
Gson gson = new Gson();
Map<String, String> result = new HashMap<>();
result.put("code", "SUCCESS");
System.out.println(gson.toJson(result));
}

输出:

1
2
3
4
5
6
7
8
9
########### around before toJson ###############
########### around before toJson ###############
########### around before toJson ###############
########### around before toJson ###############
########### around after toJson ###############
########### around after toJson ###############
########### around after toJson ###############
########### around after toJson ###############
{"code":"SUCCESS"}

为啥有多行呢?答案留给读者朋友。

加载时增强

加载时增强和编译后增强本质上是一样的。不同点在于将增强的时机延迟到JVM加载类的时候。

maven配置:

1
2
3
4
5
6
7
8
9
10
11
12
 <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.10</version>
<configuration>
<argLine>
-javaagent:${user.home}/.m2/repository/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
</argLine>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>

这个配置主要是用来添加命令行参数。当然了,你也可以在idea的VM options配置框里面填写参数,或者在项目启动脚本中配置JVM参数。 这里需要指定的参数是-javaagent:/path/to/agent.jar

只是指定了javaagent还不够,还需要指定需要对那些类进行什么样的增强。

在将项目的resources目录下面建一个子目录META-INF,在该目录中创建配置文件aop.xml,文件内如下:

1
2
3
4
5
6
7
8
<aspectj>
<aspects>
<aspect name="com.leokongwq.springlearn.component.GsonAspect"/>
<weaver options="-verbose -showWeaveInfo">
<include within="com.google.gson.*"/>
</weaver>
</aspects>
</aspectj>

到此就配置好了。启动单元测试,就能看到结果。

aspectj 与Spring AOP的关系

从上一篇文章和上面的内容可知:

  1. 在日常的Spring AOP使用过程中我们并没有用到Aspectj的织入能力,使用到的大都是Aspectj提供的注解和切面相关的语法。
  2. Spring AOP的实现底层是基于JDK代理和CGLIB代理实现的。
  3. AOP的实现可以基于织入,也可以基于代理。
文章目录
  1. 1. aspectj 是什么?
  2. 2. aspectj如何使用
    1. 2.1. 编译时织入
      1. 2.1.1. 基于注解增强
    2. 2.2. 编译后织入
    3. 2.3. 加载时增强
  3. 3. aspectj 与Spring AOP的关系
|