- 面向切面编程AOP
- AOP介绍
- AOP的七大术语
- 切点表达式
- 使用Spring的AOP
- 通知类型
- 切面顺序@Order
- 通用切点表达式@Pointcut
- 连接点JoinPoint
- 全注解开发
- AOP实际案例
十五、★★★面向切面编程AOP
15.1 AOP介绍
AOP(Aspect Oriented Programming)
:面向切面编程,面向方面编程。- 切面:业务流程中,与业务逻辑不挂钩的非业务逻辑通用代码,如:事务。
AOP
是一种编程技术。AOP
是OOP
的补充延伸。
面向切面编程:将和业务逻辑不挂钩的通用代码抽取出来,形成一个独立的组件,形成一个横向的切面,在纵向的业务逻辑代码中,如果需要用到切面时,可以将切面以交叉业务的形式切入业务流程中。可以增强代码的复用性、可维护性、使我们更专注于业务逻辑代码。
AOP
底层是使用动态代理实现的,动态代理是面向切面编程思想的实现。- Spring 的
AOP
使用的动态代理是:JDK 动态代理 + CGLIB 动态代理技术。Spring 在这两种动态代理中灵活切换:- 如果是代理接口,会默认使用 JDK 动态代理。
- 如果要代理某个类,这个类没有实现接口,就会切换使用 CGLIB。
- 当然,可以通过一些配置强制让 Spring 只使用 CGLIB。
- Spring 的
一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务。
纵向的为业务逻辑代码,横向的为交叉业务。

这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。
如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
- 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处,维护成本高。
- 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。
使用
AOP
(动态代理)可以很轻松的解决以上问题。AOP
:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP
。
AOP的优点:
- 第一:代码复用性增强。
- 第二:代码易维护。
- 第三:使开发者更关注业务逻辑。
15.2 AOP的七大术语
- 通知(
Advice
):又叫增强,具体增强的代码,如具体的事务代码、日志代码、安全代码等。- 通知描述的是代码(增强代码)。
- 通知包括:
- 前置通知:通知的代码只放在业务方法之前。
- 后置通知:通知的代码只放在业务方法之后。
- 环绕通知:通知的代码在业务方法之前和之后都有。
- 异常通知:通知代码在捕获异常
catch(){}
的代码块之中。 - 最终终通知:通知代码在
finally{}
的代码块之中。
- 织入(
Weaving
):把通知应用到目标对象上的过程。 - 连接点(
Joinpoint
):在程序的整个执行流程中,可以织入切面的位置。- 方法的执行前后、异常抛出之后、
finally{}
的代码块等。 - 连接点描述的是位置,可以织入切面的位置。
- 方法的执行前后、异常抛出之后、
- 切点(
Pointcut
):在程序执行流程中,真正织入切面的方法。- 切点本质上就是方法,织入切面的方法。
- 切点:要被 “通知” 增强的方法。
- 一个切点对应多个连接点。
- 切面(
Aspect
):切点 + 通知。 - 代理对象(
Proxy
):目标对象被织入通知后产生的新对象。 - 目标对象(
Target
):被织入通知的对象。


15.3 切点表达式
切点是要在其前后织入通知的方法。
切点表达式,可以匹配切点方法的表达式,用于定义通知往哪些方法上切入。
切点表达式语法:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
- 切点表达式以
execution(
开始,以)
结束。 - 访问控制权限修饰符:
public
|protected
|private
|default
- 可选项。没写就是 4 个访问控制权限都包括。
- 写
public
就表示只包括公开的方法。
- 返回值类型:
- 必填项。
*
表示返回值类型任意。- 如果
String
,返回值类型就是String
。
- 全限定类名:
- 带包名的类名。
- 可选项。
- 两个点
..
代表当前包以及子包下的所有类。com..
表示com
包以及子包下的所有类。 - 全限定类名省略时表示所有的类。
- 方法名:
- 必填项。
*
表示所有方法。set*
表示所有以set
开头的方法。
- 形式参数列表:
- 必填项。
()
表示没有参数的方法。(..)
表示参数类型和个数随意的方法。(*)
表示只有一个参数的方法。(*, String)
表示第一个参数类型随意,第二个参数是String
的方法。
- 异常:
- 可选项。
- 省略时表示任意异常类型。
- 切点表达式以
切点表达式实例:
execution(public * com.powernode.mall.service.*.delete*(..))
:service
包下所有的类中以delete
开始的所有公共的任意返回值的方法。execution(* com.powernode.mall..*(..))
:mall
包下所有的类的所有的方法。execution(* *(..))
:所有类的所有方法。
15.4 使用Spring的AOP
Spring 对
AOP
的实现包括以下 3 种方式:- 第一种方式:Spring 框架结合
AspectJ
框架实现的AOP
,基于注解方式。 - 第二种方式:Spring 框架结合
AspectJ
框架实现的AOP
,基于 XML 方式。AspectJ
框架是专门做AOP
的框架。
- 第三种方式:Spring 框架自己实现的
AOP
,基于 XML 配置方式。
实际开发中,都是使用 Spring +
AspectJ
来实现AOP
,所以重点学习第一种和第二种方式。- 其中最重点的就是第一种方式。我们先学习这种方式。
- 第一种方式:Spring 框架结合
15.4.1 准备工作
使用 Spring+
AspectJ
的AOP
需要引入的依赖如下:spring context
依赖、spring aop
依赖、spring aspects
依赖。- 不需要自己手动引入
spring aop
依赖,因为在spring context
依赖中已经依赖了spring 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
44
45
46
47
48
49
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.f</groupId>
<artifactId>spring6-008-spring-aspectj-aop-anno</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!--仓库-->
<repositories>
<repository>
<id>repository.spring.milestone</id>
<name>Spring Milestone Repository</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
<!--依赖-->
<dependencies>
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.8</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.8</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>基于注解的方式需要组件扫描,需要
context
命名空间。需要使用aop
,需要aop
命名空间。所以在 Spring 配置文件中添加
context
命名空间和aop
命名空间。1
2
3
4
5
6
7
8
9
10
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
15.4.2 目标类
1 | package com.f.spring6.service; |
15.4.3 切面
- 切面 = 通知 + 切点。
1 | package com.f.spring6.service; |
15.4.4 配置文件
1 |
|
15.4.5 测试
1 | package com.f.spring6.test; |
输出结果:
1
2我是一段前置通知...
系统正在登录...
15.5 通知类型
通知类型包括:
- 前置通知:
@Before
目标方法执行之前的通知。 - 后置通知:
@AfterReturning
目标方法执行之后的通知。 - 环绕通知:
@Around
目标方法执行之前添加通知,同时目标方法执行之后添加通知。 - 异常通知:
@AfterThrowing
发生异常之后执行的通知。 - 最终终通知:
@After
放在finally
语句块中的通知。
- 前置通知:
接下来,编写程序来测试这几个通知的执行顺序:
- 在前面代码的基础上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.f.spring6.service;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/25 15:01
*/
public class UserService { // 目标类
public void login() { // 目标方法
System.out.println("系统正在登录...");
if (true) {
throw new RuntimeException("运行时异常...");
}
}
}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
54package com.f.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/25 15:03
*/
// 切面类是需要使用@Aspect注解进行标注的
public class LogAspect { // 切面
// 切面 = 通知 + 切点
// 通知就是具体要编写的增强代码
// @Before注解标注的方法就是一个前置通知
// @Before(切点表达式)
// 前置通知
public void beforeAdvice() {
System.out.println("我是一段前置通知...");
}
// 后置通知
public void afterReturningAdvice() {
System.out.println("我是一段后置通知...");
}
// 环绕通知(环绕通知是最大的通知,前环绕在前置通知之前,后环绕在所有通知之后)
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 前面的增强代码
// 这段增强代码在前置通知之前执行
System.out.println("前环绕...");
// 切点
joinPoint.proceed();
// 后面的增强代码
// 这段增强代码在后置通知之后执行
System.out.println("后环绕...");
}
// 异常通知
public void afterThrowingAdvice() {
System.out.println("我是一段异常通知");
}
// 最终通知(finally语句块中的通知)
public void afterAdvice() {
System.out.println("我是一段最终通知...");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package com.f.spring6.test;
import com.f.spring6.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author fzy
* @date 2024/1/25 15:17
*/
public class SpringAOPTest {
public void testAdvice() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login();
}
}输出结果:
1
2
3
4
5
6
7
8前环绕...
我是一段前置通知...
系统正在登录...
我是一段异常通知
我是一段最终通知...
java.lang.RuntimeException: 运行时异常...
......当没有异常发生时,输出结果:
1
2
3
4
5
6前环绕...
我是一段前置通知...
系统正在登录...
我是一段后置通知...
我是一段最终通知...
后环绕...
15.6 切面顺序@Order
使用
@Order
注解可以指定切面的执行顺序。- 数字越小,优先级越高。
下面的代码是别人笔记里的,不是自己写的。
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
56
57
58
59
60
61
62
63package cw.spring.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* ClassName: SecurityAspect
* Package: cw.spring.service
* Description:
*
* @Author tcw
* @Create 2023-05-17 9:32
* @Version 1.0
*/
public class SecurityAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("优先级 1 前置通知...");
}
// 后置通知
public void afterReturningAdvice() {
System.out.println("优先级 1 后置通知...");
}
/**
* 环绕通知
*
* @param joinPoint 连接点
*/
public void aroundAdvice(ProceedingJoinPoint joinPoint) {
// 前环绕
System.out.println("优先级 1 前环绕...");
// 调用执行目标
try {
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
// 后环绕
System.out.println("优先级 1 后环绕...");
}
// 最终通知
public void afterAdvice() {
System.out.println("优先级 1 最终通知...");
}
// 异常通知
public void afterThrowingAdvice() {
System.out.println("优先级 1 异常通知...");
}
}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
56
57
58
59
60
61
62
63
64
65package cw.spring.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* ClassName: LogAspect
* Package: cw.spring.service
* Description:
* 切面 = 通知 + 切点
*
* @Author tcw
* @Create 2023-05-16 16:03
* @Version 1.0
*/
// 标注为切面类
public class LogAspect {
// 前置通知
public void beforeAdvice() {
System.out.println("优先级 0 前置通知...");
}
// 后置通知
public void afterReturningAdvice() {
System.out.println("优先级 0 后置通知...");
}
/**
* 环绕通知
*
* @param joinPoint 连接点
*/
public void aroundAdvice(ProceedingJoinPoint joinPoint) {
// 前环绕
System.out.println("优先级 0 前环绕...");
// 调用执行目标
try {
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
// 后环绕
System.out.println("优先级 0 后环绕...");
}
// 最终通知
public void afterAdvice() {
System.out.println("优先级 0 最终通知...");
}
// 异常通知
public void afterThrowingAdvice() {
System.out.println("优先级 0 异常通知...");
}
}输出结果:
1
2
3
4
5
6
7
8
9
10
11优先级 0 前环绕...
优先级 0 前置通知...
优先级 1 前环绕...
优先级 1 前置通知...
登录中...
优先级 1 后置通知...
优先级 1 最终通知...
优先级 1 后环绕...
优先级 0 后置通知...
优先级 0 最终通知...
优先级 0 后环绕...前置通知,优先级越高越先执行。
后置通知,优先级越高越后执行。
环绕通知,优先级越高越外层执行。
在切点执行之前执行的通知和在切点执行之后执行的通知,同一优先级执行完,再执行下一级优先级。
15.7 通用切点表达式@Pointcut
使用通用切点表达式可以简化切点表达式,使切点表达式的代码得到复用。
在定义通用切点表达式的切面类中使用
"通用切点表达式对应方法的方法名()"
即可。在其他切面类中使用:
"包名.定义通用切点表达式的切面类.通用切点表达式对应方法的方法名()"
可以使切点表达式得到复用。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
32package com.f.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/25 15:03
*/
// 切面类是需要使用@Aspect注解进行标注的
public class LogAspect { // 切面
......
// 最终通知(finally语句块中的通知)
//@After("execution(* com.f.spring6.service..*(..))")
public void afterAdvice() {
System.out.println("我是一段最终通知...");
}
// 通用切点,解决切点表达式复用问题
// 定义通用的切点表达式
public void commonPointCut() {
// 这个方法只是一个标记,方法名随意,方法体中也不需要写任何代码
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package com.f.spring6.service;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/25 15:47
*/
public class SecurityAspect { // 安全切面
//@Before("execution(* com.f.spring6.service..*(..))")
public void beforeAdvice() {
System.out.println("安全切面的前置通知...");
}
}
15.8 连接点JoinPoint
在环绕通知中,Spring 在调用通知方法时,会传入
ProceedingJionPoint
对象(连接点对象)。除了环绕通知以外的其他通知,Spring 在调用时,会传入
JoinPoint
对象(连接点对象)。- 通过
JoinPoint
对象(连接点对象),我们可以获取目标方法的相关信息。
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
30package com.f.spring6.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/25 15:47
*/
public class SecurityAspect { // 安全切面
//@Before("execution(* com.f.spring6.service..*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 这个JoinPoint joinPoint,在Spring容器调用这个方法的时候会自动传过来。
// 我们可以用这个joinPoint获取目标方法的相关信息
// 获取目标方法的签名,即 public void login() 方法的修饰符列表、方法名等
// 方法的签名:访问权限修饰符开始到方法名
Signature signature = joinPoint.getSignature();
// 通过方法的签名可以获取方法的具体信息
System.out.println("目标方法的方法名:" + signature.getName());
System.out.println("安全切面的前置通知...");
}
}输出结果:
1
2
3
4
5
6
7
8目标方法的方法名:login
安全切面的前置通知...
前环绕...
我是一段前置通知...
系统正在登录...
我是一段后置通知...
我是一段最终通知...
后环绕...
- 通过
15.9 全注解开发
在
12.9
小节中已经提到过全注解开发,这里是把aop
的配置也加进去。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.f.spring6.service;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author fzy
* @date 2024/1/25 16:21
*/
// 代替spring.xml文件
// 组件扫描
// AOP配置
public class Spring6Config {
}1
2
3
4
5
6
public void testNoXML() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login();
}输出结果:
1
2
3
4
5
6
7
8目标方法的方法名:login
安全切面的前置通知...
前环绕...
我是一段前置通知...
系统正在登录...
我是一段后置通知...
我是一段最终通知...
后环绕...
15.10 基于XML配置方式的AOP(了解)
直接看别人的笔记吧:
15.11 AOP实际案例:事务处理
项目中的事务控制是在所难免的。在一个业务流程当中,可能需要多条 DML 语句共同完成,为了保证数据的安全,这多条 DML 语句要么同时成功,要么同时失败。这就需要添加事务控制的代码。
在业务类中的每一个业务方法都是需要控制事务的,而控制事务的代码又是固定的格式,都是:
1
2
3
4
5
6
7
8
9
10
11
12
13try{
// 开启事务
startTransaction();
// 执行核心业务逻辑
//......
// 提交事务
commitTransaction();
}catch(Exception e){
// 回滚事务
rollbackTransaction();
}这个控制事务的代码就是和业务逻辑没有关系的 “交叉业务”。这些交叉业务的代码没有得到复用,在每个业务类的业务方法中,都要开启事务、提交事务以及回滚事务的代码,代码复用低,并且如果这些交叉业务代码需要修改,那必然需要修改多处,难维护,怎么解决?可以采用
AOP
思想解决。把以上控制事务的代码作为环绕通知,切入到目标类的方法当中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.f.spring6.service;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/26 10:56
*/
public class AccountService { // 目标对象
// 目标方法
// 转账的业务方法
public void transfer() {
System.out.println("正在转账...");
// 故意抛出异常
throw new RuntimeException();
}
// 目标方法
// 取款的业务方法
public void withdraw() {
System.out.println("正在取款...");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package com.f.spring6.service;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/26 10:57
*/
public class OrderService { // 目标对象
// 目标方法
// 生成订单的业务方法
public void generate() {
System.out.println("正在生成订单...");
}
// 目标方法
// 取消订单的业务方法
public void cancel() {
System.out.println("正在取消订单...");
}
}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
27package com.f.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @author fzy
* @date 2024/1/26 10:58
*/
public class TransactionAspect {
public void aroundAdvice(ProceedingJoinPoint joinPoint) {
try {
// 前环绕
System.out.println("开启事务");
joinPoint.proceed();
// 后环绕
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务");
}
}
}1
2
3
4
5
6
7
8
9
10
11
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.f.spring6.service"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.f.spring6.test;
import com.f.spring6.service.AccountService;
import com.f.spring6.service.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author fzy
* @date 2024/1/26 11:06
*/
public class AopCaseTest {
public void testTransaction() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transfer();
accountService.withdraw();
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
orderService.cancel();
}
}输出结果:
1
2
3
4
5
6
7
8
9
10
11
12开启事务
正在转账...
回滚事务 // 这里是回滚事务,因为业务方法中抛出异常了
开启事务
正在取款...
提交事务
开启事务
正在生成订单...
提交事务
开启事务
正在取消订单...
提交事务
15.12 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
26package com.f.spring6.biz;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/26 11:18
*/
public class UserService { // 目标对象
public void saveUser() { // 目标方法
System.out.println("新增用户信息");
}
public void deleteUser() { // 目标方法
System.out.println("删除用户信息");
}
public void modifyUser() { // 目标方法
System.out.println("修改用户信息");
}
public void getUser() {
System.out.println("获取用户信息");
}
}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
26package com.f.spring6.biz;
import org.springframework.stereotype.Service;
/**
* @author fzy
* @date 2024/1/26 11:18
*/
public class VipService { // 目标对象
public void saveVip() { // 目标方法
System.out.println("新增会员信息");
}
public void deleteVip() { // 目标方法
System.out.println("删除会员信息");
}
public void modifyVip() { // 目标方法
System.out.println("修改会员信息");
}
public void getVip() {
System.out.println("获取会员信息");
}
}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
45package com.f.spring6.biz;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author fzy
* @date 2024/1/26 11:20
*/
public class SecurityAspect {
// com.f.spring6.biz包下所有以save开头的方法
public void savePointCut() {
}
// com.f.spring6.biz包下所有以delete开头的方法
public void deletePointCut() {
}
// com.f.spring6.biz包下所有以modify开头的方法
public void modifyPointCut() {
}
public void beforeAdvice(JoinPoint joinPoint) {
// 得到当前时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowTime = sdf.format(new Date());
// 记录日志信息
// 如果是WEB项目,用户名字可以从Session中获取
System.out.println(nowTime + ": " + "张三调用了 " +
joinPoint.getSignature().getDeclaringTypeName() + " 的 " +
joinPoint.getSignature().getName() + " 方法");
}
}- 由于我们的目的只是记录进行增删改操作的相关信息,所以先用通用切点表达式指定增删改方法,然后在通知中使用
@Before("savePointCut() || deletePointCut() || modifyPointCut()")
来选择这些通用切点表达式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testSercurityLog() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.saveUser();
userService.deleteUser();
userService.modifyUser();
userService.getUser();
VipService vipService = applicationContext.getBean("vipService", VipService.class);
vipService.saveVip();
vipService.deleteVip();
vipService.modifyVip();
vipService.getVip();
}输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
142024-01-26 11:40:43: 张三调用了 com.f.spring6.biz.UserService 的 saveUser 方法
新增用户信息
2024-01-26 11:40:43: 张三调用了 com.f.spring6.biz.UserService 的 deleteUser 方法
删除用户信息
2024-01-26 11:40:43: 张三调用了 com.f.spring6.biz.UserService 的 modifyUser 方法
修改用户信息
获取用户信息
2024-01-26 11:40:43: 张三调用了 com.f.spring6.biz.VipService 的 saveVip 方法
新增会员信息
2024-01-26 11:40:43: 张三调用了 com.f.spring6.biz.VipService 的 deleteVip 方法
删除会员信息
2024-01-26 11:40:43: 张三调用了 com.f.spring6.biz.VipService 的 modifyVip 方法
修改会员信息
获取会员信息
- 由于我们的目的只是记录进行增删改操作的相关信息,所以先用通用切点表达式指定增删改方法,然后在通知中使用