深入理解 Spring AOP

AOP,Aspect Oriented Programming,面向切面编程,是指在运行时,动态地将代码切入到类的指定方法、指定位置上的一种编程技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑与横切关注点(cross-cutting concerns,例如日志记录,事务处理)进行隔离,从而使得业务逻辑与横切关注点的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 目的是为了解耦,它可以使一组类共享相同的行为。面向对象编程(OOP)目的也是为了提高代码的可重用性,那么 AOP 和 OOP 有什么不同之处呢?对于 OOP 来说,只能通过继承类和实现接口这种方式来实现,但这会使代码的耦合程度增强,不利于代码的维护。AOP 正是为了弥补了 OOP 的不足而出现。

Spring 框架提供了 AOP 的丰富支持,允许开发人员通过分离应用程序的业务逻辑与横切关注点从而进行内聚性的开发。举例来说,日志记录,性能统计,安全控制,事务处理,异常处理,这些称之为横切关注点的功能,对于应用程序来说是必须的。AOP 允许将这些横切关注点从业务逻辑代码中划分出来,从而改变这些横切关注点的代码不影响业务逻辑的代码。

AOP 的核心概念

我们先来看一下 AOP 涉及的几个核心概念。

1. Aspect

Aspect 是一个类,这个类实现上述横切关注点的功能,例如日志记录,事务处理,等。

2. Join Point

Join point 是程序中某个执行点,例如方法执行,属性访问等。对于 Spring AOP 来说,join point 通常是指方法执行。

3. Advice

Advice 是指在某个特定 join point 执行的动作,当某个 join point 匹配 pointcut 时,advice 指定的方法会被执行。

4. Pointcut

Pointcut 是一个表达式,当 join point 匹配 pointcut 的表达式时,advice 指定的方法就会被执行。

AOP Advice 类型

以执行的时间点划分,可以将 advice 划分为以下几种类型。

1. Before Advice

Before advice 是指 advice 在一个方法执行之前执行,可以使用@Before注解来将一个 advice 标记为 before advice。

2. After Advice

After advice 是指 advice 在一个方法执行之后执行(不管这个方法是正常执行还是抛出异常),可以使用@After注解来将一个 advice 标记为 after advice。

3. After Returning Advice

After returning advice 是指 advice 在一个方法在正常执行完毕后执行,可以使用@AfterReturing注解来标记一个方法为 after returning advice。

4. After Throwing Advice

After throwing advice 是指 advice 在一个方法抛出异常后执行,可以在 advice 中处理回滚等操作。可以使用@AfterThrowing注解来将一个方法标记为after throwing advice。

5. Around Advice

最重要也是最强大的 advice, advice 包裹了方法,在方法调用之前和调用之后执行自定义的行为。

Spring AOP 例子

为了深入理解 Spring AOP 的使用,我们以一个完整的例子来说明如何利用 AOP 来实现日志记录。Spring 支持 @AspectJ 注解式和基于 XML 配置的 AOP 实现,我们以基于 XML 配置的 AOP 作为例子。

使用 IntelliJ IDEA 创建一个 Maven quickstart 项目,并添加以下依赖。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>

接下来添加 aspect,该 aspect 实现日志记录功能,代码如下Logging.java

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
package me.leehao;

public class Logging {
/**
* This is the method which I would like to execute
* before a selected method execution.
*/
public void beforeAdvice(){
System.out.println("Going to setup student profile.");
}
/**
* This is the method which I would like to execute
* after a selected method execution.
*/
public void afterAdvice(){
System.out.println("Student profile has been setup.");
}
/**
* This is the method which I would like to execute
* when any method returns.
*/
public void afterReturningAdvice(Object retVal){
System.out.println("Returning:" + retVal.toString() );
}
/**
* This is the method which I would like to execute
* if there is an exception raised.
*/
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("There has been an exception: " + ex.toString());
}
}

下面是Student.java的代码:

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 me.leehao;

public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age );
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name );
return name;
}
public void printThrowException(){
System.out.println("Exception raised");
throw new IllegalArgumentException();
}
}

程序入口MainApp.java的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package me.leehao;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Student student = (Student) context.getBean("student");
student.getName();
System.out.println();
student.getAge();
System.out.println();
student.printThrowException();
}
}

添加 resources 目录,并在 resources 目录添加Beans.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<aop:config>
<aop:aspect id="log" ref="logging">
<aop:pointcut id="selectAll"
expression="execution(* me.leehao.*.*(..))"/>
<aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
<aop:after pointcut-ref="selectAll" method="afterAdvice"/>
<aop:after-returning pointcut-ref="selectAll"
returning="retVal"
method="afterReturningAdvice"/>
<aop:after-throwing pointcut-ref="selectAll"
throwing="ex"
method="AfterThrowingAdvice"/>
</aop:aspect>
</aop:config>

<!-- Definition for student bean -->
<bean id="student" class="me.leehao.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>

<!-- Definition for logging aspect -->
<bean id="logging" class="me.leehao.Logging"/>
</beans>

我们重点说一下Beans.xml里面的内容。

  • aspect
    使用aop:aspect标签定义一个 aspect,该 aspect 亦即类Logging

  • pointcut
    使用aop:pointcut标签定义一个 pointcut,该 pointcut 的表达式为execution(* me.leehao.*.*(..)),表明me.leehao包下的所有方法都匹配。

  • advice
    可以使用aop:beforeaop:afteraop:after-returningaop:after-throwing等标签定义 advice。通过method属性指定 advice 执行的方法,这些方法在匹配的方法执行前或后会被自动调用。

完整代码构成如下图所示:

运行程序,输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Going to setup student profile.
Name : Zara
Student profile has been setup.
Returning:Zara

Going to setup student profile.
Age : 11
Student profile has been setup.
Returning:11

Going to setup student profile.
Exception raised
Student profile has been setup.
There has been an exception: java.lang.IllegalArgumentException

可以看到,Student类的方法在调用时,advice方法被自动执行,输出了对应的日志。

小结

本文介绍了 AOP 的含义以及其涉及的基本概念,并以一个完整的代码例子说明了如何在 Spring 实现 AOP 的编程。

参考资料

  1. https://www.journaldev.com/2583/spring-aop-example-tutorial-aspect-advice-pointcut-joinpoint-annotations
  2. https://www.zhihu.com/question/24863332
  3. https://baike.baidu.com/item/AOP/1332219
  4. https://www.javatpoint.com/spring-aop-tutorial