1. 为什么需要AOP

关于消除重复代码,定义公共父类使用继承的方式实现是一种常见的思路。但是例如性能监控、事务管理,这类重复代码的特点是和业务代码紧密结合在一起,无法通过继承的方式解决。

比如在程序运行中,想计算每个方法的行时间, 就没有办法通过继承的方式去消除这类重复代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UserService {

public void createUser(String username) {
long startTime = System.currentTimeMillis();

// 模拟业务处理
System.out.println("Creating user: " + username);

long endTime = System.currentTimeMillis();
System.out.println("createUser方法执行时间: " + (endTime - startTime) + "ms");
}

public void deleteUser(String username) {
long startTime = System.currentTimeMillis();

// 模拟业务处理
System.out.println("Deleting user: " + username);

long endTime = System.currentTimeMillis();
System.out.println("deleteUser方法执行时间: " + (endTime - startTime) + "ms");
}
}

AOP 就是解决这类重复的方式。

AOP是Aspect Oriented Programing的简称, 面向切面编程。

在AOP中,将重复性代码抽取出来是很容易的,但如何将这些独立的逻辑融合到业务逻辑中完成和原来一样的业务操作,这才是事情的关键,也正是AOP要解决的主要问题。

本文将先介绍Spring 中各种使用AOP的方法,作为后面介绍原理的前置知识。

具体实现原理可以阅读以下文章:
Spring AOP XML配置方式原理详解
Spring AOP 注解方式原理详解

2. 手动配置代理 - ProxyFactoryBean

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
public class UserService {

public void createUser(String username) {

// 模拟业务处理
System.out.println("Creating user: " + username);
}

public void deleteUser(String username) {

// 模拟业务处理
System.out.println("Deleting user: " + username);
}
}

// 定义一个前置增强
public class LoggingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before method: " + method.getName());
}
}

// 定义一个后置增强
public class LoggingAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After method: " + method.getName());
}
}


// 定义一个 环绕增强
public class LoggingMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 方法执行前逻辑
long startTime = System.currentTimeMillis();
System.out.println(invocation.getMethod().getName()+ " 方法开始执行");


// 通过反射执行目标方法
Object result = invocation.proceed();

// 方法执行后逻辑
//System.out.println("After method: " + invocation.getMethod().getName());
long endTime = System.currentTimeMillis();
System.out.println(invocation.getMethod().getName()+ "方法执行时间: " + (endTime - startTime) + "ms");

return result;
}
}

applicationContext.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
34
35
36
37
38
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 定义业务类的 Bean -->
<bean id="userService" class="com.example.codingInAction.service.UserService"/>

<!-- 定义 前置增强 -->
<bean id="loggingBeforeAdvice" class="com.example.codingInAction.aop.LoggingBeforeAdvice"/>

<!-- 定义 后置增强 -->
<bean id="loggingAfterAdvice" class="com.example.codingInAction.aop.LoggingAfterAdvice"/>

<!-- 定义 环绕增强 -->
<bean id="loggingMethodInterceptor" class="com.example.codingInAction.aop.LoggingMethodInterceptor"/>

<!-- 使用 ProxyFactoryBean 配置代理对象 -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标对象 -->
<property name="target" ref="userService"/>
<!-- 拦截器 -->
<property name="interceptorNames">
<list>
<value>loggingMethodInterceptor</value>
<value>loggingBeforeAdvice</value>
<value>loggingAfterAdvice</value>

</list>
</property>
<!-- 强制使用CGLIB代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>

运行程序启动代码,可以得到如下截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CodingInActionApplication{

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

// 获取代理对象
UserService userService = (UserService) context.getBean("userServiceProxy");

// 调用方法,观察 AOP 切面的效果
userService.createUser("john");
userService.deleteUser("john");

}
}

以上xml 中的配置可以等价替换成如下代码,运行结果是一致的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UserServiceTest {

@Test
void createUser() {
UserService target = new UserService();

BeforeAdvice beforeAdvice = new LoggingBeforeAdvice();
AfterReturningAdvice afterAdvice = new LoggingAfterAdvice();
MethodInterceptor methodInterceptor = new LoggingMethodInterceptor();

ProxyFactory proxyFactory = new ProxyFactory();

proxyFactory.setTarget(target);
proxyFactory.addAdvice(methodInterceptor);
proxyFactory.addAdvice(beforeAdvice);
proxyFactory.addAdvice(afterAdvice);

UserService proxy = (UserService) proxyFactory.getProxy();
proxy.createUser("john");
proxy.deleteUser("john");
}

通过以上代码来简单介绍一下AOP 中的专业术语

2.1 连接点(Joinpoint)

连接点是程序执行中的某个特定点。Spring AOP中的连接点通常是方法的执行。例如,在UserService类中,createUser方法的执行就是一个连接点。连接点可以是类初始化、字段访问、方法调用等。

UserService类中,方法createUser的执行是一个连接点。

1
2
3
public void createUser(String username) {
System.out.println("Creating user: " + username);
}

2.2 增强(Advice)

通知是围绕连接点执行的代码。Spring AOP支持五种类型的通知:

  • 前置增强(Before Advice):在方法执行之前执行。
  • 后置增强(After Returning Advice):在方法成功执行之后执行。
  • 异常增强(After Throwing Advice):在方法抛出异常后执行。
  • 最终增强(After (finally) Advice):在方法执行之后,无论是否抛出异常都会执行。
  • 环绕增强(Around Advice):在方法执行之前和之后执行。

在前面的代码示例中,就分别定义了前置增强、后置增强和环绕增强

2.3 织入 Advice

在前面的代码示例中,有连接点 joinpoint 和 增强 Advice,看运行结果我们可能注意到一个问题:Advice 增强被默认织入(weaving)了目标类(target)的所有方法中,即interceptorNames 中配置的拦截器会对目标对象userService中所有方法起作用,这是 因为没有定义具体的 Pointcut 来限制拦截范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 使用 ProxyFactoryBean 配置代理对象 -->
<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标对象 -->
<property name="target" ref="userService"/>
<!-- 拦截器 -->
<property name="interceptorNames">
<list>
<value>loggingMethodInterceptor</value>
<value>loggingBeforeAdvice</value>
<value>loggingAfterAdvice</value>

</list>
</property>
<!-- 强制使用CGLIB代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>

为了更加灵活,更有选择地将增强 织入某些类的某些特定的方法中 ,可以定义 PointcutAdvisor。这样可以明确地指定在哪些连接点 joinpoint 上应用Advice 增强。

2.3.1 Pointcut -接口实现

Pointcut 有不同的实现方式

  1. 可以是通过Pointcut接口实现
  2. 也可以是切点表达式

这一节内容先介绍接口实现方式, 后面自动代理机制部分再介绍表达式方式。
Spring通过org.springframework.aop.Pointcut接口描述切点, PointcutClassFilterMethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface Pointcut {

/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();

/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();


/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;

}

我们可以定义一个Pointcut 匹配UserService.create方法。

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
public class LoggingPointcut implements Pointcut {

@Override
public ClassFilter getClassFilter() {
return clazz -> clazz == UserService.class;
}

@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {

@Override
public boolean matches(Method method, Class<?> targetClass) {
if (method.getName().equals("createUser")) {
return true;
}
return false;
}

@Override
public boolean isRuntime() {
return true;
}

@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
if (method.getName().equals("createUser")) {
return true;
}
return false;
}
};
}
}

通过数据库查询的概念,可以这样来理解以上joinpointAdvicepointcut 3个概念。

连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

根据每个查询条件 (切点 pointcut) 找到对应记录(连接点 Joinpoint )后,对记录执行的操作,相当于增强Advice

2.3.2 Advisor 切面

Advisor是一个非常重要的概念,它将切点(Pointcut)和增强(Advice)结合起来,形成一个切面(Aspect)。Advisor包含两个主要部分:

  1. Pointcut:定义在哪些连接点(通常是方法执行)上应用通知。
  2. Advice:定义在连接点上执行的具体操作,可以是在方法执行前、后或环绕方法执行。

在这里我们可以把上面例子中的环绕增强改成Advisor的形式,以给它增加“筛选条件” pointcut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LoggingAdvisor implements PointcutAdvisor {
@Override
public Pointcut getPointcut() {
return new LoggingPointcut();
}

@Override
public Advice getAdvice() {
return new LoggingMethodInterceptor();
}
@Override
public boolean isPerInstance() {
return true;
}
}

修改applicationContext.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
34
35
36
37
38
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 以下内容是使用 ProxyFactoryBean 配置代理对象, 用 Advisor 增加切点-->
<!-- 定义业务类的 Bean -->
<bean id="userService" class="com.example.codingInAction.service.UserService"/>

<!-- 定义 前置增强-->
<bean id="loggingBeforeAdvice" class="com.example.codingInAction.aop.LoggingBeforeAdvice"/>

<!-- 定义 后置增强 -->
<bean id="loggingAfterAdvice" class="com.example.codingInAction.aop.LoggingAfterAdvice"/>

<!-- 定义 环绕增强 -->
<bean id="loggingAdvisor" class="com.example.codingInAction.aop.LoggingAdvisor"/>

<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 目标对象 -->
<property name="target" ref="userService"/>
<!-- 拦截器 -->
<property name="interceptorNames">
<list>
<value>loggingAdvisor</value>
<value>loggingBeforeAdvice</value>
<value>loggingAfterAdvice</value>
</list>
</property>
<!-- 强制使用CGLIB代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>

运行前面相同的启动代码,可以得到相同的运行结果。
可以看到,增加了“筛选条件”的环绕增强,如预期一样,如预期一样,环绕增强只在create方法上发挥了作用。

3. 自动代理机制-AbstractAutoProxyCreator

在以上 XML AOP 的实现中,每一个需要被代理的Bean都需要使用一个ProxyFactoryBean进行配置,,并在需要时手动获取代理对象以实现AOP功能。

然而,在大型生产环境中,这种配置方式显得繁琐且不切实际。为了解决这一问题,Spring提供了一种基于AbstractAutoProxyCreator的自动代理机制,使得我们无需为每个Bean手动配置ProxyFactoryBeanAbstractAutoProxyCreator通过自动检测Bean的类型和相应的切面(Aspect)来创建代理对象,从而简化了配置过程。

AbstractAutoProxyCreator 有很多子类,下面将介绍各个子类如何配置完成自动代理机制。

3.1 BeanNameAutoProxyCreator

使用 BeanNameAutoProxyCreator 后,你不再需要手动配置和获取 userServiceProxy 代理对象。Spring 会自动为指定的 Bean 创建代理对象。你只需要获取原始的 userService,Spring 会自动为它应用增强。

修改applicationContext.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
34
35
  <?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 自动代理机制 启用 BeanNameAutoProxyCreator -->
<!-- 定义业务类的 Bean -->
<bean id="userService" class="com.example.codingInAction.service.UserService"/>
<!-- 定义 环绕增强 -->
<bean id="loggingAdvisor" class="com.example.codingInAction.aop.LoggingAdvisor"/>
<!-- 定义 前置增强-->
<bean id="loggingBeforeAdvice" class="com.example.codingInAction.aop.LoggingBeforeAdvice"/>

<!-- 定义 后置增强 -->
<bean id="loggingAfterAdvice" class="com.example.codingInAction.aop.LoggingAfterAdvice"/>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>userService</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>loggingAdvisor</value>
<value>loggingBeforeAdvice</value>
<value>loggingAfterAdvice</value>
</list>
</property>
</bean>
</beans>

修改CodingInActionApplication,不再主动获取代理对象

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

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

// 手动配置并手动获取代理Bean
// UserService userService = (UserService) context.getBean("userServiceProxy");

// 自动代理机制
UserService userService = (UserService) context.getBean("userService");

// 调用方法,观察 AOP 切面的效果
userService.createUser("john");
userService.deleteUser("john");
}
}

运行启动代码,可以得到相同的运行结果

3.2 DefaultAdvisorAutoProxyCreator

AbstractAdvisorAutoProxyCreatorAbstractAutoProxyCreator 的直接子类,它扫描 Spring 容器中的所有 Advisor(一个 Advisor 包含一个 Advice 和一个 Pointcut),并根据 Pointcut 的匹配规则,自动为匹配的 Bean 创建代理对象,并应用对应的 Advice

DefaultAdvisorAutoProxyCreatorAbstractAdvisorAutoProxyCreator 的具体实现类

我们简化下代码,只保留实现了Advisor 的环绕增强来看下运行结果

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

<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"

<!-- 定义业务类的 Bean -->
<bean id="userService" class="com.example.codingInAction.service.UserService"/>

<!-- 定义 环绕增强 -->
<bean id="loggingAdvisor" class="com.example.codingInAction.aop.LoggingAdvisor"/>

<!-- 启用自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>

运行和上面一样的CodingInActionApplication, 可以得到如下结果

3.3 基于AspectJ 实现的自动代理

终于来到日常开发中, 最常见的AOP 配置方式了,基于AspectJ 注解的方式。

以上提到的所有AOP 配置方式中,不论是使用手动配置的ProxyFactoryBean,还是自动代理的BeanNameAutoProxyCreatorDefaultAdvisorAutoProxyCreator,都需要实现PointcutAdvice接口描述切点和增强,并用Advisor整合两者。

AspectJ则采用注解来描述切点、增强。

两者只是表述方式不同,描述内容的本质是完全相同的,这就好比一个用中文、一个用英文讲述同一个伊索寓言一样。

AspectJ 是一个强大的面向切面编程(AOP)框架,独立于 Spring 但可以无缝集成。

3.3.1 AspectJAwareAdvisorAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreatorAbstractAdvisorAutoProxyCreator 的子类,主要用于处理基于 AspectJ 方式的 AOP 配置。

它和 AnnotationAwareAspectJAutoProxyCreator 类似,但主要用于处理通过 XML 配置文件或其他非注解方式配置的 AspectJ 切面。

3.3.2 AnnotationAwareAspectJAutoProxyCreator

AnnotationAwareAspectJAutoProxyCreatorAspectJAwareAdvisorAutoProxyCreator 的子类。

AnnotationAwareAspectJAutoProxyCreator 扫描使用了 AspectJ 注解(如 @Aspect@Before@After 等)的 Bean,并将这些注解配置的切面应用到匹配的 Bean 上。

pointcut-切点表达式

定义一个 LoggingAspect

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

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.JoinPoint;

@Aspect
public class LoggingAspect {
//定义一个匹配userService中所有方法的切点表达式
@Pointcut("execution(* com.example.codingInAction.service.UserService.*(..))")
public void userServiceAllMethod() {
}

// 在方法执行之前执行的通知
@Before("userServiceAllMethod()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}

// 在方法执行之后执行的通知
@After("userServiceAllMethod()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}

// 定义一个只匹配 UserService.createUser 方法的切点表达式
@Around("execution(* com.example.codingInAction.service.UserService.createUser(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() + " 方法开始执行");

Object result = joinPoint.proceed(); // 执行目标方法

long endTime = System.currentTimeMillis();
System.out.println(joinPoint.getSignature().getName() + " 方法执行时间: " + (endTime - startTime) + "ms");

return result;
}
}

借一张来描述下各个部分的使用方式和含义

修改applicationContext.xml,并运行启动代码,运行结果依然和之前保持一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 启用 AspectJ 自动代理 -->
<aop:aspectj-autoproxy/>

<!-- 定义业务类的 Bean -->
<bean id="userService" class="com.example.codingInAction.service.UserService"/>

<!-- 定义切面的 Bean -->
<bean id="loggingAspect" class="com.example.codingInAction.aop.LoggingAspect"/>

</beans>

4. 自动代理机制-基于Schema配置切面

如果项目不能使用JDK 5.0,那么就无法使用基于@AspectJ注解的切面了。但是使用AspectJ切点表达式的大门依旧向我们敞开着,因为Spring提供了基于Schema配置的方法,它完全可以替代基于@AspectJ注解声明切面的方式。
依然是做同一件事的两种不同表达形式

在Spring的XML配置中,<aop:config>标签可以直接定义切点和通知,并将它们应用到目标对象上。这种方式简化了配置,不需要额外的Java类来定义PointcutAdvisor

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
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 定义业务类的 Bean -->
<bean id="userService" class="com.example.codingInAction.service.UserService"/>

<!-- 定义 前置增强-->
<bean id="loggingBeforeAdvice" class="com.example.codingInAction.aop.LoggingBeforeAdvice"/>

<!-- 定义 后置增强 -->
<bean id="loggingAfterAdvice" class="com.example.codingInAction.aop.LoggingAfterAdvice"/>

<bean id="loggingMethodInterceptor" class="com.example.codingInAction.aop.LoggingMethodInterceptor"/>


<!-- 配置AOP -->
<aop:config>
<!-- 定义切点,匹配 UserService 类中的所有方法 -->
<aop:pointcut id="userServiceMethods"
expression="execution(* com.example.codingInAction.service.UserService.*(..))"/>
<aop:pointcut id="userServiceCreateMethod"
expression="execution(* com.example.codingInAction.service.UserService.createUser(..))"/>

<!-- 定义 advisor,将 MethodInterceptor 应用于切点 -->
<aop:advisor advice-ref="loggingMethodInterceptor" pointcut-ref="userServiceCreateMethod"/>
<aop:advisor advice-ref="loggingBeforeAdvice" pointcut-ref="userServiceMethods"/>
<aop:advisor advice-ref="loggingAfterAdvice" pointcut-ref="userServiceMethods"/>
</aop:config>

</beans>

5.切面类型总结

根据以上代码扩充并总结下切面类型