在 Java 开发中,我们经常面临一些分散在应用各处的重复代码,比如日志记录、事务管理、安全验证等。这些代码与业务逻辑交织在一起,不仅增加了维护难度,也降低了代码的可读性。切面编程(AOP)正是为解决这类问题而生的强大工具。
什么是 AOP?
AOP 核心概念
- 前置通知(Before):在连接点执行前执行
- 后置通知(After):在连接点执行后执行,无论是否异常
- 返回通知(AfterReturning):在连接点正常返回后执行
- 异常通知(AfterThrowing):在连接点抛出异常后执行
- 环绕通知(Around):包围连接点执行,可在前后都执行操作
Java 中 AOP 的实现方式
Spring AOP 实战示例
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义业务服务
@Service
public class UserService {
public String getUserById(Long id) {
// 模拟查询用户
return "User-" + id;
}
public void deleteUser(Long id) {
// 模拟删除用户
if (id == null || id <= 0) {
throw new IllegalArgumentException("无效的用户ID");
}
System.out.println("用户" + id + "已删除");
}
}
3. 创建日志切面
@Aspect
@Component
public class LoggingAspect {
// 定义切点:匹配com.example.service包下所有类的所有方法
@Pointcut("execution(* com.example.service..*(..))")
public void serviceMethodPointcut() {}
// 前置通知
@Before("serviceMethodPointcut()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("调用方法: " + methodName + ", 参数: " + Arrays.toString(args));
}
// 返回通知
@AfterReturning(pointcut = "serviceMethodPointcut()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法" + methodName + "返回: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceMethodPointcut()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
System.out.println("方法" + methodName + "抛出异常: " + ex.getMessage());
}
// 环绕通知
@Around("serviceMethodPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 执行目标方法
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
System.out.println("方法" + methodName + "执行时间: " + (endTime - startTime) + "ms");
return result;
}
}
4. 测试 AOP 效果
@SpringBootTest
public class AopDemoTest {
@Autowired
private UserService userService;
@Test
public void testAop() {
userService.getUserById(1L);
userService.deleteUser(1L);
try {
userService.deleteUser(-1L);
} catch (Exception e) {
// 预期会抛出异常
}
}
}
运行测试后,控制台会输出类似以下内容:
调用方法: getUserById, 参数: [1]
方法getUserById返回: User-1
方法getUserById执行时间: 2ms
调用方法: deleteUser, 参数: [1]
用户1已删除
方法deleteUser返回: null
方法deleteUser执行时间: 1ms
调用方法: deleteUser, 参数: [-1]
方法deleteUser抛出异常: 无效的用户ID
方法deleteUser执行时间: 1ms
可以看到,我们没有修改任何业务代码,就为所有服务方法添加了完整的日志记录功能。