前一篇我們詳細的介紹了一下spring aop的底層原理,這一篇我們介紹一下如何實現aop的自定義註解。
一、 AOP核心概念
1. 橫切關注點
對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之為橫切關注點。
2. 切面(aspect)
類是對物體特徵的抽象,切面就是對橫切關注點的抽象。
3. 連接點(joinpoint)
被攔截到的點,因為Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器。
4. 切入點(pointcut)
對連接點進行攔截的定義。
5. 通知(advice)
所謂通知指的就是指攔截到連接點之後要執行的代碼,通知分為前置、後置、異常、最終、環繞通知五類。
在通知中主要包含一下幾個註解:
@Before: 前置通知, 在方法執行之前執行。
@After: 後置通知, 在方法執行之後執行。
@AfterReturning: 返回通知, 在方法返回結果之後執行。
@AfterThrowing: 異常通知, 在方法拋出異常之後。
@Around: 環繞通知, 圍繞著方法執行,裡面可以包含@Before和@After。
6. 目標對象
代理的目標對象。
7. 織入(weave)
將切面應用到目標對象並導致代理對象創建的過程。
8. 引入(introduction)
在不修改代碼的前提下,引入可以在運行期為類動態地添加一些方法或字段。
二、 具體實現
這裡以註解日誌為例子。
下面是項目的目錄
第一步:創建註解聲明,還有註解的屬性(LogManage.java)
package com.tzword.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LogManage {
/** 模塊 */
String title() default "";
/** 功能 */
String action() default "";
/** 操作人 */
String optName() default "";
/** 操作時間 */
String optTime() default "";
}
其中上面的幾個標籤的含義如下:
@Target
指定註解使用的目標範圍(類、方法、字段等)。
@Documented
指定被標註的註解會包含在javadoc中。
@Retention
指定註解的生命週期(源碼、class文件、運行時)。
@Inherited
指定子類可以繼承父類的註解,只能是類上的註解,方法和字段的註解不能繼承。即如果父類上的註解是@Inherited修飾的就能被子類繼承。
第二步:創建aspect切面實現類(LogAspect.java)
我們的切點設置在對應的
@Pointcut("execution (* com.tzword.service..*.*(..))")
上的。
package com.tzword.aspect;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.tzword.annotation.LogManage;
@Aspect
@Component("LogAspect")
public class LogAspect {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
/**
* 配置織入點
*/
@Pointcut("execution (* com.tzword.service..*.*(..))")
public void logPointCut() {
}
/**
* 前置通知, 在方法執行之前執行。
*
* @param joinPoint
*/
@Before(value = "logPointCut()")
public void doBefore(JoinPoint joinPoint) {
// 獲取類型
String className = joinPoint.getTarget().getClass().getName();
log.info("前置通知==@Before=============方法執行之前獲取到的類名:{}", className);
}
/**
* 後置通知, 在方法執行之後執行 。
*
* @param joinPoint
*/
@After(value = "logPointCut()")
public void doAfter(JoinPoint joinPoint) {
// 獲取方法簽名
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
LogManage logManage = method.getAnnotation(LogManage.class);
String action = logManage.action();
String title = logManage.title();
log.info("後置通知==@After=============模塊名稱:{}", title);
log.info("後置通知==@After=============操作名稱:{}", action);
}
Object[] params = joinPoint.getArgs();// 獲取請求參數
for (Object param : params) {
log.info("後置通知==@After=============監聽到傳入參數為:{}", param);
}
String methodName = signature.getName();
log.info("後置通知==@After=============方法執行之後獲取到的方法名:{}", methodName);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateTime = format.format(new Date());
log.info("後置通知==@After=============操作時間:{}", dateTime);
}
/**
* 攔截異常操作,有異常時執行。
*
* @param joinPoint
* @param e
*/
@AfterThrowing(value = "logPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
log.info("異常操作==@AfterThrowing=============出現異常:{}", e.getMessage());
}
/**
* 後置返回通知, 在方法返回結果之後執行。
*/
@AfterReturning(value = "logPointCut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
log.info("後置返回通知==@AfterReturning=============獲取到的返回值:{}", result);
}
/**
* 環繞執行,在方法執行之前和之後都執行。
*
* @param joinPoint
* @param e
* @throws Throwable
*/
@Around(value = "logPointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 獲取類型
String className = joinPoint.getTarget().getClass().getName();
log.info("環繞執行==@Around=============方法執行之前獲取到的類名:{}", className);
Object object = joinPoint.proceed();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
LogManage logManage = method.getAnnotation(LogManage.class);
String action = logManage.action();
String title = logManage.title();
log.info("環繞執行==@Around=============模塊名稱:{}", title);
log.info("環繞執行==@Around=============操作名稱:{}", action);
}
Object[] params = joinPoint.getArgs();// 獲取請求參數
for (Object param : params) {
log.info("環繞執行==@Around=============監聽到傳入參數為:{}", param);
}
String methodName = signature.getName();
log.info("環繞執行==@Around=============方法執行之後獲取到的方法名:{}", methodName);
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateTime = format.format(new Date());
log.info("環繞執行==@Around=============操作時間:{}", dateTime);
return object;
}
}
這裡我寫的日誌比較多,看起來很亂,將就看
如果我們需要實現一個spring aop的自定義註解,可以通過兩種方式。
① @Before + @After +@AfterReturning+@AfterThrowing
② @Around +@AfterReturning+@AfterThrowing
這裡我為了實驗,都寫上去了。
第三步:在需要標註註解的方法上標註註解(LogAnnotationService.java)
package com.tzword.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.tzword.annotation.LogManage;
import com.tzword.aspect.LogAspect;
@Service
public class LogAnnotationService {
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@LogManage(title="日誌管理",action="管理日誌模塊")
public String getLogInfo(String param1,String param2) {
log.info("進入方法===============");
return param1 + param2;
}
}
第四步:創建入口類(為了測試)(LogAnnotationController.java)
package com.tzword.controller;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tzword.service.LogAnnotationService;
@RestController
@RequestMapping(value = "/logInfo")
public class LogAnnotationController {
@Resource
private LogAnnotationService logAnnotationService;
@PostMapping("/getLog")
public String getLogInfo() {
String param1 ="1";
String param2 ="2";
return logAnnotationService.getLogInfo(param1,param2);
}
}
三、 進行測試
我們通過post做請求
這裡使用兩種方法進行測試
① @Before + @After +@AfterReturning+@AfterThrowing
測試結果:
② @Around +@AfterReturning+@AfterThrowing
測試結果:
可以看出兩種都可以達到我們想要的結果。
四、 總結
從測試的結果很容易看出來他們的執行順序:
第一種的執行順序為: 先@Before,再進入方法,再@After,再@AfterReturning,如果方法內部有異常的話, 先@Before,再進入方法,再@After,再@AfterThrowing
第二種的執行順序為: 先@Around,再進入方法,再@Around ,再@AfterReturning,如果內部有異常的話, 先@Around,再進入方法,再@Around ,再@AfterThrowing
-----------END------------
因為最近有點忙,更新有點慢,謝謝大家的支持。
閱讀更多 JAVA程序人生 的文章