【基础系列】AOP之基本使用姿势小结

文章目录
  1. I. 背景知识
    1. 1. advice
    2. 2. join point
    3. 3. pointcut
    4. 4. aspect
    5. 5. introduction
    6. 6. weaving
  2. II. 使用说明
    1. 1. 基本配置
    2. 2. 代码准备
    3. 3. AOP使用
      1. a. before
      2. b. after
      3. c. after returning
      4. d. around
      5. e. 输出
  3. III. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog
    3. 2. 声明
    4. 3. 扫描关注

一般来讲,谈到Spring的特性,绕不过去的就是DI(依赖注入)和AOP(切面),在将bean的系列中,说了DI的多种使用姿势;接下来看一下AOP的玩法

I. 背景知识

在实际使用之前有必要了解一下什么是AOP,以及AOP的几个基本概念

1. advice

  • before: 在方法执行之前被调用
  • after: 在方法执行之后调用
  • after returning: 方法执行成功之后
  • after throwing: 方法抛出异常之后
  • around: 环绕,自己在内部决定方法的执行时机,因此可以在之前之后做一些业务逻辑

2. join point

连接点,比如方法调用,方法执行,字段设置/获取、异常处理执行、类初始化、甚至是 for 循环中的某个点

但 Spring AOP 目前仅支持方法执行 (method execution)

简单来说,Spring AOP中,PointCut就是那个被拦截的方法

3. pointcut

切点,用来描述满足什么规则的方法会被拦截

  • 正则表达式 : @Before("execution(public * com.git.hui.demo.base.bean.*.*(..))")
  • 注解拦截方式 :@Around("@annotation(parameterCheck)")

4. aspect

切面是切点和通知的结合。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能

5. introduction

引入允许我们向现有的类添加新的方法或者属性

6. weaving

组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

简单来讲就是生成一个代理类,在调用被拦截的方法时,实际上执行的是代理类,这个代理类内部执行切面逻辑

II. 使用说明

1. 基本配置

首先是基本环境的搭建, 先贴上必要的xml配置, 使用aop需要引入包: spring-boot-starter-aop

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<java.version>1.8</java.version>
</properties>


<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

2. 代码准备

首先创建一个被拦截的bean: com.git.hui.boot.aop.demo.DemoBean,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class DemoBean {

/**
* 返回随机的字符串
*
* @param time
* @return
*/
public String randUUID(long time) {
try {
System.out.println("in randUUID before process!");
return UUID.randomUUID() + "|" + time;
} finally {
System.out.println("in randUUID finally!");
}
}
}

接着在启动类中,执行

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class Application {

public Application(DemoBean demoBean) {
String ans = demoBean.randUUID(System.currentTimeMillis());
System.out.println("----- ans: " + ans + "---------");
}

public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}

3. AOP使用

在实际使用之前,需要创建一个切面,用@Aspect声明,其次切面也需要作为bean托付给Spring容器管理

1
2
3
4
@Aspect
@Component
public class AnoAspcet {
}

a. before

在方法调用之前,需要执行一些操作,这个时候可以使用 @Before 注解来声明before advice

一种可使用姿势如下,我们的切点直接在注解中进行定义,使用正则表达式的方式

1
2
3
4
@Before("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void doBefore(JoinPoint joinPoint) {
System.out.println("do in Aspect before method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}

b. after

在方法调用完毕之后,再执行一些操作,这个时候after就可以派上用场,为了考虑切点的通用性,我们可以考虑声明一个切点,使用@Pointcut注解

1
2
3
@Pointcut("execution(public * com.git.hui.boot.aop.demo.*.*(*))")
public void point() {
}

使用pointcut的方式也比较简单,如下

1
2
3
4
@After("point()")
public void doAfter(JoinPoint joinPoint) {
System.out.println("do in Aspect after method called! args: " + JSON.toJSONString(joinPoint.getArgs()));
}

c. after returning

在正常返回结果之后,再次执行,这个也挺有意思的,通常使用这个advice时,一般希望获取返回结果,那么应该怎么处理呢?

  • org.aspectj.lang.annotation.AfterReturning#returning 指定返回结果对应参数name
  • 返回结果作为参数传入,要求类型一致,否则不生效
1
2
3
4
5
6
7
8
9
10
/**
* 执行完毕之后,通过 args指定参数;通过 returning 指定返回的结果,要求返回值类型匹配
*
* @param time
* @param result
*/
@AfterReturning(value = "point() && args(time)", returning = "result")
public void doAfterReturning(long time, String result) {
System.out.println("do in Aspect after method return! args: " + time + " ans: " + result);
}

d. around

这个也比较常见,在方法执行前后干一些事情,比如常见的耗时统计,日志打印,安全控制等,很多都是基于around advice实现的

使用这个advice需要注意的是传入参数类型为 ProceedingJoinPoint,需要在方法内部显示执行org.aspectj.lang.ProceedingJoinPoint#proceed()来表示调用方法

1
2
3
4
5
6
7
@Around("point()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("do in Aspect around ------ before");
Object ans = joinPoint.proceed();
System.out.println("do in Aspect around ------- over! ans: " + ans);
return ans;
}

e. 输出

执行之后输出如下

1
2
3
4
5
6
7
8
do in Aspect around ------ before
do in Aspect before method called! args: [1551433188205]
in randUUID before process!
in randUUID finally!
do in Aspect around ------- over! ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205
do in Aspect after method called! args: [1551433188205]
do in Aspect after method return! args: 1551433188205 ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205
----- ans: 6849544b-160e-464c-80bd-641f2651c6c1|1551433188205---------

从输出结果上,可以看到每个advice的使用范围,当然也带来了一些疑问

  • 可以存在多个同类型的advice,拦截同一个目标吗?(如两个around都拦截methodA方法,那么methodA方法被调用时,两个around advice是否都会执行)
  • 多个advice之间的优先级怎么定义?
  • aop拦截的目标方法有没有限制(对非public的方法可以拦截么?)
  • 被拦截的方法中存在相互调用的时候,会怎样?(如methodA,methodB都可以被拦截,且methodA中调用了methodB,那么在执行methodA时,methodB的各种advice是否会被触发?)
  • 基于注解的aop方式可以怎样用

以上这些问题留在下一篇进行介绍

III. 其他

0. 项目

1. 一灰灰Blog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals


打赏 如果觉得我的文章对您有帮助,请随意打赏。
分享到