Spring?AOP通知類型與實(shí)戰(zhàn)示例講解
更新時(shí)間:2024年11月18日 12:14:59 作者:lzz的編碼時(shí)刻
Spring?AOP提供了五種通知類型:@Before、@After、@AfterReturning、@AfterThrowing和@Around,每種通知類型都有其特定的使用場(chǎng)景和實(shí)現(xiàn)方式,通過(guò)合理使用這些通知類型,可以實(shí)現(xiàn)各種橫切關(guān)注點(diǎn)的模塊化和解耦,感興趣的朋友跟隨小編一起看看吧
1. @Before 前置通知
1.1 基本說(shuō)明
- 在目標(biāo)方法執(zhí)行前執(zhí)行
- 不能阻止目標(biāo)方法執(zhí)行(除非拋出異常)
- 可以獲取目標(biāo)方法的參數(shù)信息
1.2 實(shí)現(xiàn)示例
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(requiresAuth)")
public void checkAuth(JoinPoint joinPoint, RequiresAuth requiresAuth) {
// 獲取當(dāng)前用戶信息
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
// 驗(yàn)證token
if (!tokenService.isValid(token)) {
throw new UnauthorizedException("無(wú)效的認(rèn)證令牌");
}
// 檢查權(quán)限
String requiredRole = requiresAuth.role();
if (!hasRole(token, requiredRole)) {
throw new ForbiddenException("權(quán)限不足");
}
}
}1.3 典型應(yīng)用場(chǎng)景
- 權(quán)限驗(yàn)證
- 參數(shù)驗(yàn)證
- 日志記錄
- 事務(wù)開(kāi)始標(biāo)記
- 緩存預(yù)處理
1.4 獲取參數(shù)
1.4.1 基本參數(shù)獲取
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
// 獲取方法參數(shù)
Object[] args = joinPoint.getArgs();
// 獲取方法簽名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getName();
// 獲取參數(shù)名稱
String[] parameterNames = signature.getParameterNames();
// 獲取參數(shù)類型
Class<?>[] parameterTypes = signature.getParameterTypes();
// 打印參數(shù)信息
for (int i = 0; i < args.length; i++) {
logger.info("Parameter {} ({}) = {}", parameterNames[i], parameterTypes[i].getSimpleName(), args[i]);
}
}1.4.2 獲取注解參數(shù)
@Before("@annotation(logParams)")
public void beforeWithAnnotation(JoinPoint joinPoint, LogParams logParams) {
// 直接獲取注解屬性
String description = logParams.description();
boolean logResult = logParams.logResult();
// 獲取方法參數(shù)
Object[] args = joinPoint.getArgs();
// 根據(jù)注解配置記錄日志
if (logParams.includeParameters()) {
Arrays.stream(args)
.forEach(arg -> logger.info("Parameter value: {}", arg));
}
}2. @After 后置通知
2.1 基本說(shuō)明
- 在目標(biāo)方法執(zhí)行后執(zhí)行(無(wú)論是否拋出異常)
- 不能訪問(wèn)目標(biāo)方法的返回值
- 主要用于清理資源或類似的收尾工作
2.2 實(shí)現(xiàn)示例
@Aspect
@Component
public class ResourceCleanupAspect {
@After("execution(* com.example.service.FileService.*(..))")
public void cleanup(JoinPoint joinPoint) {
try {
// 清理臨時(shí)文件
String methodName = joinPoint.getSignature().getName();
logger.info("Cleaning up resources after method: {}", methodName);
cleanupTempFiles();
// 釋放其他資源
releaseResources();
} catch (Exception e) {
logger.error("Cleanup failed", e);
}
}
private void cleanupTempFiles() {
// 清理臨時(shí)文件的具體實(shí)現(xiàn)
}
private void releaseResources() {
// 釋放資源的具體實(shí)現(xiàn)
}
}2.3 典型應(yīng)用場(chǎng)景
- 資源清理
- 連接關(guān)閉
- 計(jì)數(shù)器更新
- 日志記錄
- 性能監(jiān)控結(jié)束標(biāo)記
2.4 參數(shù)獲取
@After("execution(* com.example.service.*.*(..)) && args(id,name,..)")
public void afterAdvice(JoinPoint joinPoint, Long id, String name) {
// 直接使用參數(shù)
logger.info("Method executed with ID: {} and name: {}", id, name);
// 獲取目標(biāo)類信息
Class<?> targetClass = joinPoint.getTarget().getClass();
// 獲取代理類信息
Class<?> proxyClass = joinPoint.getThis().getClass();
}3. @AfterReturning 返回通知
3.1 基本說(shuō)明
- 在目標(biāo)方法成功執(zhí)行后執(zhí)行
- 可以訪問(wèn)目標(biāo)方法的返回值
- 可以修改返回值(通過(guò)包裝類)
3.2 實(shí)現(xiàn)示例
@Aspect
@Component
public class ResponseHandlerAspect {
@AfterReturning(
pointcut = "execution(* com.example.controller.*.*(..))",
returning = "result"
)
public void handleResponse(JoinPoint joinPoint, Object result) {
if (result instanceof List) {
// 對(duì)集合類型結(jié)果進(jìn)行脫敏處理
List<?> list = (List<?>) result;
for (Object item : list) {
if (item instanceof UserDTO) {
UserDTO user = (UserDTO) item;
user.setPhone(maskPhoneNumber(user.getPhone()));
user.setEmail(maskEmail(user.getEmail()));
}
}
}
}
private String maskPhoneNumber(String phone) {
// 手機(jī)號(hào)碼脫敏邏輯
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
private String maskEmail(String email) {
// 郵箱脫敏邏輯
return email.replaceAll("(\\w{3})\\w+(@\\w+\\.\\w+)", "$1***$2");
}
}3.3 典型應(yīng)用場(chǎng)景
- 返回值修改(如數(shù)據(jù)脫敏)
- 統(tǒng)計(jì)方法成功率
- 緩存結(jié)果
- 結(jié)果格式化
- 數(shù)據(jù)集合包裝
4. @AfterThrowing 異常通知
4.1 基本說(shuō)明
- 在目標(biāo)方法拋出異常時(shí)執(zhí)行
- 可以訪問(wèn)拋出的異常信息
- 可以進(jìn)行異常轉(zhuǎn)換或處理
4.2 實(shí)現(xiàn)示例
@Aspect
@Component
public class ExceptionHandlerAspect {
@AfterThrowing(
pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex"
)
public void handleException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
// 記錄詳細(xì)錯(cuò)誤信息
logger.error("Exception in {}.{}: {}", className, methodName, ex.getMessage());
// 發(fā)送告警
if (ex instanceof DataAccessException) {
alertService.sendDatabaseAlert(className, methodName, ex);
}
// 異常分類統(tǒng)計(jì)
metricService.incrementExceptionCounter(className, methodName, ex.getClass().getSimpleName());
// 如果需要,可以轉(zhuǎn)換異常類型
if (ex instanceof SQLException) {
throw new DatabaseException("數(shù)據(jù)庫(kù)操作失敗", ex);
}
}
}4.3 典型應(yīng)用場(chǎng)景
- 異常記錄
- 異常轉(zhuǎn)換
- 告警通知
- 失敗重試
- 錯(cuò)誤統(tǒng)計(jì)
5. @Around 環(huán)繞通知
5.1 基本說(shuō)明
- 最強(qiáng)大的通知類型,可以完全控制目標(biāo)方法的執(zhí)行
- 可以在方法執(zhí)行前后添加自定義行為
- 可以修改方法的參數(shù)和返回值
- 可以決定是否執(zhí)行目標(biāo)方法
5.2 實(shí)現(xiàn)示例
@Aspect
@Component
public class CacheAspect {
@Autowired
private CacheManager cacheManager;
@Around("@annotation(cacheable)")
public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
// 構(gòu)建緩存key
String key = buildCacheKey(joinPoint, cacheable);
// 嘗試從緩存獲取
Object cachedValue = cacheManager.get(key);
if (cachedValue != null) {
logger.debug("Cache hit for key: {}", key);
return cachedValue;
}
// 執(zhí)行目標(biāo)方法
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed();
// 記錄執(zhí)行時(shí)間
long executionTime = System.currentTimeMillis() - startTime;
logger.debug("Method execution time: {}ms", executionTime);
// 如果執(zhí)行時(shí)間超過(guò)閾值,發(fā)送告警
if (executionTime > 1000) {
alertService.sendPerformanceAlert(joinPoint, executionTime);
}
} catch (Exception e) {
// 異常處理
logger.error("Method execution failed", e);
throw e;
}
// 將結(jié)果放入緩存
if (result != null) {
cacheManager.put(key, result, cacheable.ttl());
}
return result;
}
private String buildCacheKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) {
// 緩存key構(gòu)建邏輯
StringBuilder key = new StringBuilder();
key.append(joinPoint.getSignature().getDeclaringTypeName())
.append(".")
.append(joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
key.append(":");
for (Object arg : args) {
key.append(arg != null ? arg.toString() : "null").append(",");
}
}
return key.toString();
}
}5.3 典型應(yīng)用場(chǎng)景
- 方法緩存
- 性能監(jiān)控
- 事務(wù)處理
- 重試機(jī)制
- 并發(fā)控制
- 限流處理
@Aspect
@Component
public class RateLimiterAspect {
private final RateLimiter rateLimiter = RateLimiter.create(100.0); // 每秒100個(gè)請(qǐng)求
@Around("@annotation(rateLimited)")
public Object limitRate(ProceedingJoinPoint joinPoint, RateLimited rateLimited) throws Throwable {
if (!rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
throw new TooManyRequestsException("請(qǐng)求過(guò)于頻繁,請(qǐng)稍后重試");
}
return joinPoint.proceed();
}
}6. 最佳實(shí)踐
- 選擇合適的通知類型
- 如果只需要前置處理,用@Before
- 如果需要訪問(wèn)返回值,用@AfterReturning
- 如果需要處理異常,用@AfterThrowing
- 如果需要完全控制方法執(zhí)行,用@Around
性能考慮
- 避免在通知中執(zhí)行耗時(shí)操作
- 合理使用緩存
- 注意異常處理的性能影響
代碼組織
- 每個(gè)切面專注于單一職責(zé)
- 通知方法保持簡(jiǎn)潔
- 復(fù)用共同的切入點(diǎn)表達(dá)式
異常處理
- 在通知中要做好異常處理
- 不要吞掉異常
- 適當(dāng)轉(zhuǎn)換異常類型
到此這篇關(guān)于Spring AOP通知類型詳解與實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Spring AOP通知類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成極光推送的實(shí)現(xiàn)代碼
工作中經(jīng)常會(huì)遇到服務(wù)器向App推送消息的需求,一般企業(yè)中選擇用極光推送的比較多,本文就介紹了SpringBoot集成極光推送的實(shí)現(xiàn)代碼,感興趣的可以了解一下2023-08-08
Mybatisplus集成springboot完成分頁(yè)查詢功能(示例代碼)
今天小編給大家分享Mybatisplus集成springboot完成分頁(yè)查詢功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-11-11
Spring Security 安全框架應(yīng)用原理解析
這篇文章主要介紹了Spring Security 安全框架應(yīng)用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
Java使用easyExcel實(shí)現(xiàn)導(dǎo)入功能
這篇文章介紹了Java使用easyExcel實(shí)現(xiàn)導(dǎo)入功能的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10

