面試必備-如何實現spring aop的自定義註解呢?

前一篇我們詳細的介紹了一下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)

在不修改代碼的前提下,引入可以在運行期為類動態地添加一些方法或字段。

面試必備-如何實現spring aop的自定義註解呢?

二、 具體實現

這裡以註解日誌為例子。

下面是項目的目錄

面試必備-如何實現spring aop的自定義註解呢?

第一步:創建註解聲明,還有註解的屬性(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);
}
}
面試必備-如何實現spring aop的自定義註解呢?

三、 進行測試

我們通過post做請求

面試必備-如何實現spring aop的自定義註解呢?

這裡使用兩種方法進行測試

① @Before + @After +@AfterReturning+@AfterThrowing

測試結果:

面試必備-如何實現spring aop的自定義註解呢?

② @Around +@AfterReturning+@AfterThrowing

測試結果:

面試必備-如何實現spring aop的自定義註解呢?

可以看出兩種都可以達到我們想要的結果。

面試必備-如何實現spring aop的自定義註解呢?

四、 總結

從測試的結果很容易看出來他們的執行順序:

第一種的執行順序為: 先@Before,再進入方法,再@After,再@AfterReturning,如果方法內部有異常的話, 先@Before,再進入方法,再@After,再@AfterThrowing

第二種的執行順序為: 先@Around,再進入方法,再@Around ,再@AfterReturning,如果內部有異常的話, 先@Around,再進入方法,再@Around ,再@AfterThrowing

面試必備-如何實現spring aop的自定義註解呢?

-----------END------------

因為最近有點忙,更新有點慢,謝謝大家的支持。


分享到:


相關文章: