随着软件开发技术的不断进步,面向切面编程(AOP)作为一种重要的编程思想,已经在现代开发中占据了重要地位。它通过将横切逻辑从业务逻辑中分离出来,使得代码更加清晰、易于维护。Spring AOP 作为 Spring 框架的核心模块之一,为开发者提供了简单且强大的 AOP 支持,使我们能够以更加优雅的方式处理日志记录、安全控制、事务管理等常见的横切逻辑。本篇内容将引导您深入理解 Spring AOP 的基本概念、核心原理以及实战操作,帮助您在实际项目中更加得心应手地运用这一强大工具。
文章目录
- 1、Spring-Aop 模块介绍
- 1.1、Spring-Aop 模块概述
- 1.2、Spring-Aop 模块依赖
- 1.3、Spring-Aop 模块作用
- 2、Spring-Aop 模块补充
- 2.1、AOP 前置概念
- 2.2、Spring AOP的实现
- 2.3、通知类型
- 2.3.1、前置通知(Before Advice)
- 2.3.2、后置通知(After Returning Advice)
- 2.3.3、后置异常通知(After Throwing Advice)
- 2.3.4、最终通知(After Advice)
- 2.3.5、环绕通知(Around Advice)
- 3、基于注解的 Spring AOP 开发
- 3.1、依赖引入
- 3.2、Spring 业务类
- 3.3、Spring 切面类
- 3.4、Spring 配置类
- 3.5、Spring 主函数
- X、后记
1、Spring-Aop 模块介绍
1.1、Spring-Aop 模块概述
Spring AOP 模块,是 Spring 提供的一个面向切面编程(Aspect Oriented Programming,AOP)的模块。
Spring AOP 通过灵活的配置和强大的功能,可以轻松地将横切关注点从业务逻辑中分离出来,提高代码的可维护性和可扩展性。
1.2、Spring-Aop 模块依赖
Spring-AOP 模块的依赖有两个,分别是 Spring-Beans 模块和 Spring-Core 模块。
其中 Spring Beans 模块是对 Spring Bean 进行定义,实现 IOC 基础功能的模块。而 Spring-Core 是 Spring 中的基础模块,它提供了框架运行所必需的核心功能。
1.3、Spring-Aop 模块作用
Spring AOP 模块本身不具备完整的 AOP 能力,需要依赖其他模块和工具(例如 AspectJ 或代理机制)来实现 AOP 功能。这是因为 Spring AOP 本质上是一个基于代理的 AOP 实现框架,它利用 Spring 容器和其他技术来实现横切逻辑。Spring AOP 模块本身不具备完整的 AOP 能力,需要依赖其他模块和工具(例如 AspectJ 或代理机制)来实现 AOP 功能。这是因为 Spring AOP 本质上是一个基于代理的 AOP 实现框架,它利用 Spring 容器和其他技术来实现横切逻辑。
2、Spring-Aop 模块补充
2.1、AOP 前置概念
首先,我们来确认一些 Spring 使用核心的 AOP 概念和术语。Spring 官网的原话是:这些术语并非 Spring 独有,遗憾的是,AOP 术语并不是特别直观。但是,如果 Spring 使用自己的术语,那就更加令人困惑了。
核心术语(逻辑层面,些概念描述的是 AOP 的基本组成部分):
- 横切(Cross-cutting Concern):横切是指在程序的多个模块中反复出现的功能或逻辑,通常与业务逻辑无直接关系,但又不可或缺。这些功能通常是 “横向” 作用于程序的各个模块,而不是某个模块的核心业务逻辑的一部分,因此被称为横切关注点。
- 切面(Aspect):切面是 AOP 处理横切关注点的核心单元,它是对 横切逻辑的模块化封装,它包含切点和通知(切面 = 切点 + 通知)。切面的本质是一个包含横切逻辑的类,定义了横切逻辑执行的时机(通知)和适用的位置(切点)。其目的是让横切逻辑独立于业务代码,提高代码模块化程度。换句话说横切是问题,切面是解决方案。
- 连接点(Join Point):连接点就是程序中可以插入切面逻辑的具体点。例如,一个类中的有多个方法,那么每个方法都是潜在的连接点。
- 通知(Advice): 通知是在特定连接点处执行的动作。有多种类型的通知,如前置通知、后置通知、环绕通知等。
- 切点(Pointcut):切点从所有切入点中筛选出需要增强的具体点的过滤条件。通过定义一套过滤条件,决定哪些连接点将会被增强(即在这些连接点执行通知)。这些连接点是程序中可以插入横切逻辑的位置(例如方法调用)。
核心术语(实现层面,这些概念描述的是 AOP 的技术实现方式和执行过程):
- 引入(Introduction): 动态地为目标类添加新的接口或功能,是一种特殊的增强方式。例如,为一个类增加性能监控的能力,不改变原始代码。
- 织入(Weaving):将切面逻辑(通知、引入)和目标类的代码结合的过程,生成增强后的类对象。织入可以在编译时、类加载时或运行时进行。
2.2、Spring AOP的实现
Spring AOP 基于代理实现,主要有两种方式:
- JDK 动态代理:适用于接口代理。
- CGLIB 代理:适用于类代理。
JDK 动态代理:基于 Java 的内置动态代理机制。代理目标对象的接口,而不是类本身。当目标对象实现了一个或多个接口时,Spring 会默认选择 JDK 动态代理。
- 实现方式:创建一个实现了目标接口的代理类,并在方法调用前后插入增强逻辑。使用
java.lang.reflect.Proxy
和InvocationHandler
。 - 优点:无需引入额外的库(纯 JDK 实现)。代理生成速度较快。
- 局限性:目标类必须有接口。
CGLIB 代理:基于字节码生成技术,由 CGLIB
(Code Generation Library)库支持。通过继承目标类生成代理类,覆盖目标类中的方法来实现增强。当目标类没有实现任何接口时,Spring 会使用 CGLIB 代理。
- 实现方式:通过字节码操作生成一个目标类的子类,并对其方法进行拦截。
- 优点:可以代理没有实现接口的类。代理范围更广(包括普通类)。
- 局限性:目标类或方法不能声明为
final
,否则无法被继承和代理。需要引入额外的 CGLIB 库。代理生成速度较慢,但运行时性能较好。
2.3、通知类型
Spring AOP 提供了以下几种通知类型,每种通知用于在不同的时间点插入增强逻辑。
通知类型 | 执行时机 | 注解 |
---|---|---|
前置通知 | 在目标方法执行前 | @Before |
后置通知 | 在目标方法成功返回后 | @AfterReturning |
后置异常通知 | 在目标方法抛出异常后 | @AfterThrowing |
最终通知 | 在目标方法执行后(无论成功与否) | @After |
环绕通知 | 在目标方法前后都执行,可控制目标方法的执行 | @Around |
2.3.1、前置通知(Before Advice)
在目标方法执行之前执行。
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
}
2.3.2、后置通知(After Returning Advice)
在目标方法成功执行之后执行。
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After returning: " + joinPoint.getSignature().getName());
System.out.println("Result: " + result);
}
2.3.3、后置异常通知(After Throwing Advice)
在目标方法抛出异常后执行。
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("Exception in method: " + joinPoint.getSignature().getName());
System.out.println("Exception: " + error);
}
2.3.4、最终通知(After Advice)
在目标方法执行之后执行,无论方法是否成功执行。
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
2.3.5、环绕通知(Around Advice)
在目标方法执行前后都执行,可以完全控制目标方法的执行。
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before method: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("After method: " + joinPoint.getSignature().getName());
return result;
}
3、基于注解的 Spring AOP 开发
3.1、依赖引入
虽然 Spring AOP 是一个模块,但它需要依赖以下组件来完整实现 AOP 功能:
- Spring 核心模块:必须依赖 Spring 的核心模块(如
spring-context
和spring-beans
),用于 Bean 的创建和管理。如果没有容器,Spring AOP 只能手动配置,使用起来非常受限。 - 代理机制:依赖 JDK 动态代理或 CGLIB 来生成代理对象。
- AspectJ(可选):Spring AOP 支持 AspectJ 注解,但必须引入
aspectjweaver
库。不依赖 AspectJ 的编译时或加载时织入器。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.39</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.39</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22.1</version>
</dependency>
</dependencies>
3.2、Spring 业务类
package com.lizhengi.example;
import org.springframework.stereotype.Component;
/**
* 用户业务类
*/
@Component // 标记为 Spring 管理的 Bean
public class UserService {
public void createUser(String name) {
System.out.println("User " + name + " created.");
}
public void deleteUser(String name) {
System.out.println("User " + name + " deleted.");
}
}
3.3、Spring 切面类
package com.lizhengi.example;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect // 声明这是一个切面
@Component // 标记为 Spring 管理的 Bean
public class LoggingAspect {
@Before("execution(* com.lizhengi.example.UserService.*(..))") // 定义切点
public void logBefore() {
System.out.println("Logging before method execution");
}
}
说明:
@Aspect
:标记为切面。@Before
:定义一个前置通知,在方法执行前触发。- 切点表达式:
execution(* com.example.service.UserService.*(..))
表示匹配UserService
中的所有方法。
3.4、Spring 配置类
package com.lizhengi.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration // 声明为配置类
@ComponentScan(basePackages = "com.lizhengi") // 自动扫描指定包
@EnableAspectJAutoProxy // 启用 AOP
public class AppConfig {}
说明:
@Configuration
:标记这是一个 Spring 配置类。@ComponentScan
:指定要扫描的包,加载所有带有@Component
的类。@EnableAspectJAutoProxy
:启用 AOP 的自动代理。
3.5、Spring 主函数
package com.lizhengi;
import com.lizhengi.example.AppConfig;
import com.lizhengi.example.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 加载 Spring 应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取 UserService 的代理对象
UserService userService = context.getBean(UserService.class);
// 调用方法,触发 AOP 增强
userService.createUser("Alice");
userService.deleteUser("Bob");
// 关闭上下文
context.close();
}
}
运行结果:
Logging before method execution
User Alice created.
Logging before method execution
User Bob deleted.
X、后记
在学习和实践 Spring AOP 的过程中,我们不仅能深入了解其实现机制,还能感受到面向切面编程对开发效率和代码质量的提升作用。从初识 AOP 的基础概念到动手实现切面逻辑,每一步都在为我们构建更加清晰、健壮的应用体系奠定基础。希望本篇内容能为您打开通往 AOP 世界的大门,也期待您在实际工作中灵活运用所学,将代码的复杂度隐藏于优雅的设计之中,为团队和项目带来更大的价值。如果您有任何疑问或见解,欢迎随时交流与探讨!