切面(Aspect)的核心机制就是拦截(Interception),但切面的概念比单纯的拦截更加丰富和系统化。
让我用一个更准确的比喻来解释:
🎯 拦截 vs 切面
特性 | 单纯的拦截(Interception) | Spring AOP 切面(Aspect) |
---|---|---|
目的 | 阻止或修改方法调用 | 模块化横切关注点 |
范围 | 通常针对单个方法或类 | 跨越多个类、模块的通用功能 |
设计理念 | 技术实现手段 | 编程范式和解耦设计 |
功能 | 主要关注方法调用前后 | 支持前置、后置、环绕、异常等多种通知类型 |
🔧 切面不仅仅是拦截
切面确实通过拦截机制来实现,但它提供了更完整的AOP解决方案:
@Aspect
@Component
public class ExampleAspect {// 1. 前置通知 - 在方法执行前拦截@Before("execution(* com.example.service.*.*(..))")public void beforeAdvice(JoinPoint joinPoint) {System.out.println("方法执行前: " + joinPoint.getSignature().getName());}// 2. 后置通知 - 在方法执行后拦截(无论成功失败)@After("execution(* com.example.service.*.*(..))")public void afterAdvice(JoinPoint joinPoint) {System.out.println("方法执行完成: " + joinPoint.getSignature().getName());}// 3. 返回通知 - 只在方法成功返回后拦截@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")public void afterReturningAdvice(JoinPoint joinPoint, Object result) {System.out.println("方法返回结果: " + result);}// 4. 异常通知 - 只在方法抛出异常时拦截@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {System.out.println("方法抛出异常: " + ex.getMessage());}// 5. 环绕通知 - 最强大的拦截方式(可以控制是否执行原方法)@Around("execution(* com.example.service.*.*(..))")public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("方法执行前");Object result = joinPoint.proceed(); // 可选择是否执行原方法System.out.println("方法执行后");return result;}
}
🌟 切面的核心价值
切面之所以比单纯拦截更强大,是因为它:
- 模块化横切关注点:将日志、事务、安全、缓存等分散在各个模块中的通用功能集中管理
- 声明式编程:通过注解配置,而不是硬编码在业务逻辑中
- 解耦:业务代码不需要知道增强逻辑的存在
- 可维护性:修改通用功能只需修改切面,而不需要改动所有相关类
📊 实际应用场景
场景 | 拦截内容 | 切面实现的功能 |
---|---|---|
事务管理 | 拦截Service方法 | 自动开启/提交/回滚事务 |
日志记录 | 拦截所有方法调用 | 记录方法入参、出参、执行时间 |
权限校验 | 拦截Controller方法 | 验证用户权限和角色 |
性能监控 | 拦截关键业务方法 | 统计方法执行时间和调用次数 |
缓存处理 | 拦截数据查询方法 | 自动缓存查询结果 |
💡 简单总结
切面 = 拦截 + 增强逻辑 + 模块化管理
所以你说"切面就是拦截"是基本正确的,但更准确的说法是:拦截是切面实现其功能的底层机制,而切面是在拦截基础上提供的一套完整的面向切面编程的解决方案。
这种设计让开发者可以专注于业务逻辑,而将横切关注点(cross-cutting concerns)交给切面来处理,大大提高了代码的质量和可维护性。
demo
Spring Boot AOP 切面 Demo
下面是一个完整的 Spring Boot 切面示例,演示日志记录、性能监控和缓存功能。
1. 项目结构
src/main/java/com/example/demo/
├── DemoApplication.java
├── annotation/
│ ├── Loggable.java
│ ├── PerformanceMonitor.java
│ └── Cacheable.java
├── aspect/
│ ├── LoggingAspect.java
│ ├── PerformanceAspect.java
│ └── CacheAspect.java
└── service/└── UserService.java
2. 自定义注解
Loggable.java
package com.example.demo.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {String value() default "";boolean logParams() default true;boolean logResult() default true;
}
PerformanceMonitor.java
package com.example.demo.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceMonitor {long threshold() default 1000; // 阈值,单位毫秒
}
Cacheable.java
package com.example.demo.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {String keyPrefix() default "";long expireTime() default 300; // 过期时间,单位秒
}
3. 切面实现
LoggingAspect.java
package com.example.demo.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.example.demo.annotation.Loggable;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);@Pointcut("@annotation(loggable)")public void loggableMethod(Loggable loggable) {}@Before("loggableMethod(loggable)")public void logBefore(JoinPoint joinPoint, Loggable loggable) {if (loggable.logParams()) {logger.info("方法执行前: {} - 参数: {}", joinPoint.getSignature().toShortString(), java.util.Arrays.toString(joinPoint.getArgs()));} else {logger.info("方法执行前: {}", joinPoint.getSignature().toShortString());}}@AfterReturning(pointcut = "loggableMethod(loggable)", returning = "result")public void logAfterReturning(JoinPoint joinPoint, Loggable loggable, Object result) {if (loggable.logResult()) {logger.info("方法执行成功: {} - 返回值: {}", joinPoint.getSignature().toShortString(), result);} else {logger.info("方法执行成功: {}", joinPoint.getSignature().toShortString());}}@AfterThrowing(pointcut = "loggableMethod(loggable)", throwing = "ex")public void logAfterThrowing(JoinPoint joinPoint, Loggable loggable, Exception ex) {logger.error("方法执行异常: {} - 异常信息: {}", joinPoint.getSignature().toShortString(), ex.getMessage());}
}
PerformanceAspect.java
package com.example.demo.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.example.demo.annotation.PerformanceMonitor;@Aspect
@Component
public class PerformanceAspect {private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);@Around("@annotation(monitor)")public Object monitorPerformance(ProceedingJoinPoint joinPoint, PerformanceMonitor monitor) throws Throwable {long startTime = System.currentTimeMillis();try {Object result = joinPoint.proceed();return result;} finally {long endTime = System.currentTimeMillis();long duration = endTime - startTime;if (duration > monitor.threshold()) {logger.warn("性能警告: {} 执行耗时: {}ms (阈值: {}ms)", joinPoint.getSignature().toShortString(), duration, monitor.threshold());} else {logger.info("方法执行时间: {} - 耗时: {}ms", joinPoint.getSignature().toShortString(), duration);}}}
}
CacheAspect.java (简单内存缓存示例)
package com.example.demo.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import com.example.demo.annotation.Cacheable;
import java.util.concurrent.ConcurrentHashMap;@Aspect
@Component
public class CacheAspect {// 简单的内存缓存(实际项目中应该用Redis)private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();private final ConcurrentHashMap<String, Long> expireTimes = new ConcurrentHashMap<>();@Around("@annotation(cacheable)")public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {String cacheKey = generateCacheKey(cacheable.keyPrefix(), joinPoint);// 检查缓存是否存在且未过期if (isCacheValid(cacheKey, cacheable.expireTime())) {System.out.println("从缓存获取数据: " + cacheKey);return cache.get(cacheKey);}// 执行原方法Object result = joinPoint.proceed();// 缓存结果cache.put(cacheKey, result);expireTimes.put(cacheKey, System.currentTimeMillis());System.out.println("数据已缓存: " + cacheKey);return result;}private String generateCacheKey(String keyPrefix, ProceedingJoinPoint joinPoint) {StringBuilder key = new StringBuilder(keyPrefix);key.append(":").append(joinPoint.getSignature().getName());for (Object arg : joinPoint.getArgs()) {key.append(":").append(arg != null ? arg.toString() : "null");}return key.toString();}private boolean isCacheValid(String cacheKey, long expireTimeSeconds) {Long cacheTime = expireTimes.get(cacheKey);if (cacheTime == null) {return false;}long currentTime = System.currentTimeMillis();return (currentTime - cacheTime) < (expireTimeSeconds * 1000);}
}
4. 业务服务类
UserService.java
package com.example.demo.service;import org.springframework.stereotype.Service;
import com.example.demo.annotation.Loggable;
import com.example.demo.annotation.PerformanceMonitor;
import com.example.demo.annotation.Cacheable;@Service
public class UserService {@Loggable(logParams = true, logResult = true)@PerformanceMonitor(threshold = 500)@Cacheable(keyPrefix = "user", expireTime = 60)public String getUserInfo(Long userId) {// 模拟数据库查询simulateDatabaseQuery();return "用户信息 - ID: " + userId;}@Loggable@PerformanceMonitorpublic String updateUser(Long userId, String userName) {// 模拟业务逻辑simulateBusinessLogic();return "用户更新成功: " + userName;}private void simulateDatabaseQuery() {try {Thread.sleep(200); // 模拟数据库查询耗时} catch (InterruptedException e) {Thread.currentThread().interrupt();}}private void simulateBusinessLogic() {try {Thread.sleep(100); // 模拟业务逻辑耗时} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}
5. 主应用类
DemoApplication.java
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import com.example.demo.service.UserService;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);// 测试切面功能UserService userService = context.getBean(UserService.class);System.out.println("=== 第一次调用(会执行方法并缓存)===");String result1 = userService.getUserInfo(1L);System.out.println("结果: " + result1);System.out.println("\n=== 第二次调用(从缓存获取)===");String result2 = userService.getUserInfo(1L);System.out.println("结果: " + result2);System.out.println("\n=== 测试更新方法 ===");String updateResult = userService.updateUser(1L, "张三");System.out.println("结果: " + updateResult);}
}
6. 依赖配置 (pom.xml)
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
</dependencies>
7. 运行结果示例
运行应用后,你会在控制台看到类似输出:
=== 第一次调用(会执行方法并缓存)===
方法执行前: UserService.getUserInfo(..) - 参数: [1]
数据已缓存: user:getUserInfo:1
方法执行时间: UserService.getUserInfo(..) - 耗时: 203ms
方法执行成功: UserService.getUserInfo(..) - 返回值: 用户信息 - ID: 1
结果: 用户信息 - ID: 1=== 第二次调用(从缓存获取)===
从缓存获取数据: user:getUserInfo:1
结果: 用户信息 - ID: 1=== 测试更新方法 ===
方法执行前: UserService.updateUser(..)
方法执行时间: UserService.updateUser(..) - 耗时: 102ms
方法执行成功: UserService.updateUser(..) - 返回值: 用户更新成功: 张三
结果: 用户更新成功: 张三
这个 Demo 展示了:
- 日志切面:记录方法调用前后信息
- 性能监控切面:统计方法执行时间并警告超时
- 缓存切面:自动缓存方法结果
- 注解驱动:通过自定义注解控制切面行为
你可以根据需要扩展这些切面,比如添加数据库事务、权限校验等功能。
Spring AOP 切面的完整生命周期
切面的生命周期指的是从方法调用开始到结束的整个过程中,切面各个通知(Advice)的执行顺序和时机。让我详细解释整个生命周期。
📊 切面生命周期全景图
🔄 完整的通知执行顺序
1. 正常执行流程(方法成功完成)
@Aspect
@Component
public class FullLifecycleAspect {// 执行顺序演示@Around("execution(* com.example.service.*.*(..))")public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("1. @Around - 前置部分");try {Object result = joinPoint.proceed(); // 调用原方法System.out.println("5. @Around - 后置部分(正常)");return result;} catch (Exception e) {System.out.println("5. @Around - 后置部分(异常)");throw e;}}@Before("execution(* com.example.service.*.*(..))")public void beforeAdvice(JoinPoint joinPoint) {System.out.println("2. @Before - 方法执行前");}@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")public void afterReturningAdvice(JoinPoint joinPoint, Object result) {System.out.println("4. @AfterReturning - 方法成功返回");}@After("execution(* com.example.service.*.*(..))")public void afterAdvice(JoinPoint joinPoint) {System.out.println("3. @After - 方法执行完成(无论成功失败)");}
}
正常执行输出:
1. @Around - 前置部分
2. @Before - 方法执行前
[执行业务方法]
4. @AfterReturning - 方法成功返回
3. @After - 方法执行完成(无论成功失败)
5. @Around - 后置部分(正常)
2. 异常执行流程(方法抛出异常)
@Aspect
@Component
public class ExceptionLifecycleAspect {@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {System.out.println("4. @AfterThrowing - 方法抛出异常: " + ex.getMessage());}
}
异常执行输出:
1. @Around - 前置部分
2. @Before - 方法执行前
[执行业务方法 - 抛出异常]
4. @AfterThrowing - 方法抛出异常: xxx
3. @After - 方法执行完成(无论成功失败)
5. @Around - 后置部分(异常)
🎯 各个通知的特点
@Before
- 时机:方法执行前
- 特点:无法阻止方法执行,但可以修改参数
- 异常:如果抛出异常,会阻止方法执行
@AfterReturning
- 时机:方法成功执行后
- 特点:可以访问返回值,但不能修改
- 条件:只在方法正常返回时执行
@AfterThrowing
- 时机:方法抛出异常后
- 特点:可以访问抛出的异常对象
- 条件:只在方法抛出异常时执行
@After
- 时机:方法执行完成后(无论成功或失败)
- 特点:类似于 finally 块
- 用途:资源清理、日志记录等
@Around(最强大)
- 时机:包围整个方法执行
- 特点:
- 完全控制是否执行原方法
- 可以修改参数、返回值和异常
- 必须调用
joinPoint.proceed()
来执行原方法
- 能力:可以模拟其他所有通知的功能
💡 实际应用示例
完整的业务切面示例
@Aspect
@Component
@Slf4j
public class BusinessAspect {private final ThreadLocal<Long> startTime = new ThreadLocal<>();@Around("@annotation(com.example.annotation.BusinessLog)")public Object handleBusinessMethod(ProceedingJoinPoint joinPoint) throws Throwable {// 1. 前置处理startTime.set(System.currentTimeMillis());log.info("业务方法开始: {}", joinPoint.getSignature().getName());log.debug("方法参数: {}", Arrays.toString(joinPoint.getArgs()));try {// 2. 执行原方法Object result = joinPoint.proceed();// 3. 成功处理long duration = System.currentTimeMillis() - startTime.get();log.info("业务方法成功: {} - 耗时: {}ms", joinPoint.getSignature().getName(), duration);log.debug("返回结果: {}", result);return result;} catch (BusinessException ex) {// 4. 业务异常处理log.warn("业务异常: {} - 错误码: {}", ex.getMessage(), ex.getErrorCode());throw ex;} catch (Exception ex) {// 5. 系统异常处理log.error("系统异常: {}", ex.getMessage(), ex);throw new BusinessException("系统繁忙,请稍后重试");} finally {// 6. 最终清理startTime.remove();log.debug("业务方法执行完成: {}", joinPoint.getSignature().getName());}}@AfterReturning(pointcut = "@annotation(com.example.annotation.AuditLog)", returning = "result")public void auditLog(JoinPoint joinPoint, Object result) {// 审计日志记录AuditService.logOperation(joinPoint.getSignature().getName(),joinPoint.getArgs(),result,"SUCCESS");}@AfterThrowing(pointcut = "@annotation(com.example.annotation.AuditLog)", throwing = "ex")public void auditLogException(JoinPoint joinPoint, Exception ex) {// 异常审计记录AuditService.logOperation(joinPoint.getSignature().getName(),joinPoint.getArgs(),null,"FAILED: " + ex.getMessage());}
}
🚀 生命周期管理最佳实践
- 使用 ThreadLocal:在 @Around 中存储状态,在其他通知中共享
- 异常处理:在 @Around 中统一处理异常,保证稳定性
- 资源清理:在 @After 或 @Around 的 finally 中清理资源
- 性能监控:在 @Around 中计算执行时间
- 事务管理:结合 @Transactional 使用
📝 总结
切面的生命周期是一个精心设计的执行链:
正常流程:@Around(前) → @Before → 业务方法 → @AfterReturning → @After → @Around(后)
异常流程:@Around(前) → @Before → 业务方法 → @AfterThrowing → @After → @Around(后)
理解这个生命周期对于编写正确的切面逻辑至关重要!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/923109.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!