在软件开发中,我们常常会遇到一些与核心业务逻辑无关,但又贯穿于多个业务模块的功能,比如日志记录、事务管理、权限验证等。传统的编程方式会导致这些功能代码分散在各个业务方法中,造成代码的冗余和可维护性降低。AOP(Aspect-Oriented Programming,面向切面编程)应运而生,它可以将这些横切关注点(如日志、事务等)从业务逻辑中分离出来,以一种模块化的方式进行管理。
Spring AOP 提供了多种通知类型,每种通知类型在不同的时机执行。下面我们详细介绍几种常见的通知类型。
前置通知在目标方法执行之前执行,通常用于进行一些预处理操作,如权限验证、参数检查等。
后置通知在目标方法执行之后执行,无论目标方法是否抛出异常都会执行,常用于资源清理等操作。
返回通知在目标方法正常返回后执行,它可以访问目标方法的返回值。
异常通知在目标方法抛出异常时执行,常用于记录异常信息等操作。
环绕通知是最强大的通知类型,它可以在目标方法执行前后都进行操作,甚至可以决定是否执行目标方法。
下面是一个通知类型的总结表格:
| 通知类型 | 执行时机 |
| —— | —— |
| 前置通知 | 目标方法执行之前 |
| 后置通知 | 目标方法执行之后(无论是否抛出异常) |
| 返回通知 | 目标方法正常返回后 |
| 异常通知 | 目标方法抛出异常时 |
| 环绕通知 | 目标方法执行前后 |
在 pom.xml
中添加 Spring AOP 相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
创建一个简单的业务类 UserService
:
package com.example.demo.service;
public class UserService {
public String addUser(String username) {
System.out.println("Adding user: " + username);
return "User added successfully";
}
}
创建一个切面类 LoggingAspect
,包含各种通知类型:
package com.example.demo.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 前置通知
@Before("execution(* com.example.demo.service.UserService.addUser(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature().getName());
}
// 后置通知
@After("execution(* com.example.demo.service.UserService.addUser(..))")
public void afterAdvice(JoinPoint joinPoint) {
System.out.println("After method: " + joinPoint.getSignature().getName());
}
// 返回通知
@AfterReturning(pointcut = "execution(* com.example.demo.service.UserService.addUser(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("Method returned: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "execution(* com.example.demo.service.UserService.addUser(..))", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
System.out.println("Method threw exception: " + ex.getMessage());
}
// 环绕通知
@Around("execution(* com.example.demo.service.UserService.addUser(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around advice before method");
Object result = joinPoint.proceed();
System.out.println("Around advice after method");
return result;
}
}
package com.example.demo.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = "com.example.demo")
@EnableAspectJAutoProxy
public class AppConfig {
}
package com.example.demo;
import com.example.demo.config.AppConfig;
import com.example.demo.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
String result = userService.addUser("John");
System.out.println("Final result: " + result);
context.close();
}
}
LoggingAspect
类使用 @Aspect
注解标记为切面类,@Component
注解将其注册为 Spring 组件。@Before
、@After
、@AfterReturning
、@AfterThrowing
和 @Around
注解分别定义了前置通知、后置通知、返回通知、异常通知和环绕通知。execution(* com.example.demo.service.UserService.addUser(..))
是切入点表达式,表示匹配 UserService
类的 addUser
方法。运行 Main
类,输出结果如下:
Around advice before method
Before method: addUser
Adding user: John
Around advice after method
After method: addUser
Method returned: User added successfully
Final result: User added successfully
从输出结果可以看出,各种通知类型按照预期的顺序执行。环绕通知在目标方法执行前后都有输出,前置通知在目标方法执行前输出,后置通知在目标方法执行后输出,返回通知在目标方法正常返回后输出。
通过本文的介绍和示例代码,你应该对 Spring AOP 的通知类型有了更深入的理解。AOP 可以帮助我们将横切关注点从业务逻辑中分离出来,提高代码的可维护性和可复用性。