java中AOP实践
如何在spring boot项目上实现
什么是AOP
- 基本概念,
AOP
的全称是Aspect Oriented Programming,看到这里大家可能会想到我们OOP
面向对象编程,这两者有什么关系和区别呢?两者是不同的编程范式,OOP
是主要使用对象的模式来解决问题,在对象中会定义属性和方法。AOP
主要关注点在crosscutting concerns
。这样的好处就是可以是这些公共的方法模块化,而不是在这里放一点又在那里放一点。AOP可能为能带来的问题就是anti-pattern
,具体的问题就是action at a distance
。- 什么是
crosscutting concerns
,简而述之就是那些需要项目大规模使用的一些方法,比如所日志,监控,全局异常捕获等等。 - 什么是
action at a distance
,eg: 定义了一个人类Class,然后又在这个类中定义了一些人类的基本属性,speaking,learning以及sleeping等等这些。这个speaking行为对于不同的子类又会有不同的行为,比如说,中国人说中文,美国人说英文。这些具体的实现没有在每个子类中实现,而是放在了一个其他的类中,那么当我们在调用某个人的speaking方法,可能用的语言不一样,但是这个在哪里实现的,我们一开始是不知道的。就感觉这些行为像魔法一样,在我们不知道的情况下就执行了。
- 什么是
- AOP中的基本术语
- Aspect,在aspect中就是定义我们需要的cross-cutting functionality,可能会有多个,比如
LogManagementAspect
- JoinPoint,程序运行时的某个时间点,可能是某个方法的执行,某个类的创建
- PointCut,一系列能和JoinPoint匹配的表达式
- Advice,当进去JoinPoint之后执行的一些操作,一个advice一般和一个CutPoint表达式匹配
- Weaving,用不同的方式(compile-time Weaving, Post-Compile Weaving, Load-Time Weaving)将aspect和java相结合
- Aspect,在aspect中就是定义我们需要的cross-cutting functionality,可能会有多个,比如
- AOP的主流框架
- Spring AOP
- AspectJ
- Comparing Spring AOP and AspectJ
如何使用AOP
AspectJ
- 定义AspectJ runTime需要的Aspect的依赖
1
2
3
4
5<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>- 定义Weaver来使用动态代理,
its actually a bytecode weaver which weaves aspects into classes at load time.
1
2
3
4
5<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>- 定义plugin,执行weaver,将aspectj文件编译成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<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8 </encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<build>执行Maven->Plugins->aspectj:compile
定义Aspect,文件的后缀名是
*.aj
,在这个文件中定义切面的方法,在Aspect中可以定义JoinPoint和Advice,编译之后就有.class
文件,对应CutPoint
就可以在程序中可以被匹配,上述方式为Compile-Time Weaving
weaving的AspectJ Development Tools
验证,run
mvn pacakage
, 测试Aspect是否成功mvn -Dtest=AccountTest test
,跑完测试之后发现,Aspect实际上是在操作一个不同的Account,我们使用的是Account对象的地址值来判断是否操作的是同一个对象。这里要注意一下,直接使用intelliJ的test,无法被Aspect捕获1
2
3
4
5
6
7
8
9
10
11Running org.zqf.aop.AspectJService.AccountTest
-------this---------- org.zqf.aop.AspectJService.Account@5ccd43c2
Balance before withdrawal: 20
Account address: org.zqf.aop.AspectJService.Account@5ccd43c2
Withdraw ammout: 5
Balance after withdrawal : 15
Balance before withdrawal: 20
Account address: org.zqf.aop.AspectJService.Account@368239c8
Withdraw ammout: 100
Withdrawal Rejected!
Balance after withdrawal : 20其他的weaving方式
- post weaving,当jar编译打包之后,再把aspect和代码编织起来
- Load-Time weaving,使用Javaagent代理
使用
@Aspect
注解方式来实现Aspect,需要自己定义annotation,定义loading-time weaver需要weave的class和Aspect
Spring AOP
相比于使用aspectj,Spring aop上手简单,定义好aspect,在aspect中定义JoinPoint和Advice就可以使用了
- 定义个Spring boot的component或者service的spring bean
1
2
3
4
5
6
public class XxxService {
public Integer XxxMethod(arg ...) {
... ...
}
}- 定义Aspect,主要定义Advice
- 定义bean的xml,其中要定义java bean的service以及Aspect,对于Aspect还需要定义JoinPoint和Advice
1
2
3
4
5
6
7
8
9
10<aop:config>
<aop:aspect id="aspects" ref="<bean id>">
<aop:pointcut id="point join id"
expression="cut point experssion" />
<aop:after-returning method="Aspect advice method name"
returning="returnValue" pointcut-ref="pointCut id" />
</aop:aspect>
</aop:config>AccountService
使用了CGLIB代理,描述了这个对象的pointCut和Advice- tips:如果要模拟spring-boot application test,需要有个main函数的启动类,加上spring-boot的注解
@SpringBootApplication
,就不需要使用xml的形式注入bean
Spring AOP
相比于使用aspectj,Spring aop上手简单,定义好aspect,在aspect中定义JoinPoint和Advice就可以使用了
- 定义个Spring boot的component或者service的spring bean
1
2
3
4
5
6
public class XxxService {
public Integer XxxMethod(arg ...) {
... ...
}
}- 定义Aspect,主要定义Advice
- 定义bean的xml,其中要定义java bean的service以及Aspect,对于Aspect还需要定义JoinPoint和Advice
1
2
3
4
5
6
7
8
9
10<aop:config>
<aop:aspect id="aspects" ref="<bean id>">
<aop:pointcut id="point join id"
expression="cut point experssion" />
<aop:after-returning method="Aspect advice method name"
returning="returnValue" pointcut-ref="pointCut id" />
</aop:aspect>
</aop:config>AccountService
使用了CGLIB代理,描述了这个对象的pointCut和Advice- tips:如果要模拟spring-boot application test,需要有个main函数的启动类,加上spring-boot的注解
@SpringBootApplication
,就不需要使用xml的形式注入bean
使用注解的形式定义AOP
- 自定义一个annotation
- 在业务场景中使用注解
- 定义Aspect,在Aspect中定义PointCut,使用表达式的模式
@Pointcut("@annotation(secured)")
使用AOP的场景
日志
监控
cache
功能性的业务
日志
监控
cache
功能性的业务
避坑
- 无法命中自己类中的切点,当该切点的方法在当前类的内部的时候
references
- Title: java中AOP实践
- Author: Xiao Qiang
- Created at : 2023-03-14 17:00:27
- Updated at : 2025-03-08 10:49:30
- Link: http://fdslk.github.io/tech/java/aop/2023/03/14/java-aop/
- License: This work is licensed under CC BY-NC-SA 4.0.
Comments