@
目錄
- 概述:
- 特色
- 使用方式
- 注意點
- 程式碼
- 第一步:pom引入AOP
- 第二步:建立自定義註解、Bean實體、列舉、常量類
- 第三步:Controller層方法使用自定義註解標識
- 第四步:新建一個日誌操作類LogAopAction,專門用來處理操作儲存日誌
- 第五步:postman模擬呼叫介面,輸出AOP中ProceedingJoinPoint獲取目標方法,引數,註解
概述:
該SpringBoot專案使用AOP的環繞@Around註解及自定義註解儲存操作日誌到資料庫,自定義註解中會配置日誌模板型別logModelType欄位,透過該欄位去匹配是建立、刪除、修改...等等功能,本案例就是為了模擬現實專案中透過AOP及自定義註解如何儲存操作詳情日誌功能。
特色
form表單除了input輸入框,也會有一些按鈕【Disable/Enable】,這些按鈕就需要轉換數字值然後動態拼接詳情日誌引數,日誌操作類LogAopAction中拼接引數不只是簡單的一堆get、set、if else去拼接,而是根據型別logModelType欄位 =》 去找列舉LogDetailEnums =》透過列舉值找常量類LogDetailConstants,常量類中定義了各種型別操作的佔位符,動態拼接引數使用MessageFormat.format(),這樣使用更加簡單、看起來更加優雅、實現可擴充套件性不用寫一堆程式碼。
使用方式
- 第一步:pom引入AOP
- 第二步:建立自定義註解、Bean實體、列舉、常量類
- 第三步:Controller層方法使用自定義註解標識
- 第四步:新建一個日誌操作類LogAopAction,專門用來處理操作儲存日誌
- 第五步:postman模擬呼叫介面,輸出AOP中ProceedingJoinPoint獲取目標方法,引數,註解
注意點
- 注意點1:日誌操作類LogAopAction必須加兩個註解@Aspect和@Component,其中@Aspect註解代表該類為切面,而@Component為了使該類能讓spring容器掃描到
- 注意點2:@Around註解中配置@annotation註解用來指定生效的自定義註解名字
- 注意點3:該案例描述AOP中ProceedingJoinPoint獲取目標方法,引數,註解
- 注意點4:接收實體Bean要重新toString方法,不然無法轉成json,因為未重寫toString方法中用的是等號 "=" 而不是冒號 ":"
- 注意點5:格式化常量類定義好的佔位符請使用MessageFormat.format(),拼接起來更加方便
程式碼
第一步:pom引入AOP
<!--aop相關的依賴引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
第二步:建立自定義註解、Bean實體、列舉、常量類
自定義註解LogAnnotation
package com.example.demo.config;
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 LogAnnotation {
//日誌模板型別
String logModelType();
}
實體類
package com.example.demo.bean;
import lombok.Data;
/**
* @Author 211145187
* @Date 2022/2/23 09:32
**/
@Data
public class OperateTeacherReq {
//id
private Integer id;
//姓名
private String name;
//開關【0:Disable、1:Enable】
private Integer pstnFlag;
@Override
public String toString() {
return "{" +
"id:" + id +
", name:'" + name + '\'' +
", pstnFlag:" + pstnFlag +
'}';
}
}
列舉類LogDetailEnums
package com.example.demo.enums;
public enum LogDetailEnums {
/**
* 開戶
*/
CREATE_ACCOUNT("CREATE_ACCOUNT", LogDetailConstants.CREATE_ACCOUNT),
/**
* PSTN Flag值轉換
*/
PSTN_ENABLE_FLAG("pstnFlag_1","Enable"),
PSTN_DISABLE_FLAG("pstnFlag_0","Disable");
private String code;
private String message;
LogDetailEnums(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public static String getDetailByCode(String messageCode){
try {
for (LogDetailEnums value : LogDetailEnums.values()) {
if (value.code.equals(messageCode)){
return value.message;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static LogDetailEnums getLogDetailEnum(String code){
if (code==null){
return null;
}
for (LogDetailEnums value : LogDetailEnums.values()) {
if (code.equals(value.code)){
return value;
}
}
return null;
}
}
常量類LogDetailConstants
package com.example.demo.enums;
public class LogDetailConstants {
public static final String CREATE_ACCOUNT = "【Add Account】id:[{0}], User Name:[{1}],pstnFlag:[{2}]";
}
第三步:Controller層方法使用自定義註解標識
package com.example.demo.controller;
import com.example.demo.bean.TeacherReq;
import com.example.demo.config.LogAnnotation;
import com.example.demo.mapper.TeacherMapper;
import com.example.demo.response.Response;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
* @Author 211145187
* @Date 2022/5/7 11:34
**/
@RestController
public class Controller {
@RequestMapping(value = "/operateLog", method = RequestMethod.POST)
@LogAnnotation(logModelType = "CREATE_ACCOUNT")
public Response operateLog(@RequestBody OperateTeacherReq req) {
return Response.success(req);
}
}
第四步:新建一個日誌操作類LogAopAction,專門用來處理操作儲存日誌
package com.example.demo.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.bean.OperateTeacherReq;
import com.example.demo.enums.LogDetailEnums;
import com.example.demo.response.Response;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.text.MessageFormat;
@Aspect
@Component
public class LogAopAction {
/**
* AOP切面儲存操作日誌
* @Author 211145187
* @Around 註解描述的方法為一個環繞通知方法,在此方法中可以新增擴充套件業務邏輯,可以呼叫下一個切面物件或目標方法
* @param point 連線點(此連線點只應用@Around描述的方法)
* @Date 2022/5/16 14:38
* @param point
* @Return Response
**/
@Around("@annotation(com.example.demo.config.LogAnnotation) && execution(* com.example.demo.controller.*.*(..))")
public Response logOperate(ProceedingJoinPoint point) throws NoSuchMethodException {
//獲取類的位元組碼物件,透過位元組碼物件獲取方法資訊
Class<?> targetCls=point.getTarget().getClass();
//獲取方法簽名(透過此簽名獲取目標方法資訊)
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//獲取目標方法上的註解指定的操作名稱
Method targetMethod=
targetCls.getDeclaredMethod(
signature.getName(),
signature.getParameterTypes());
System.out.println("獲取目標方法上的註解指定的操作名稱:"+targetMethod);
LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
String logModelType=logAnnotation.logModelType();
System.out.println("獲取自定義註解引數值logModelType:" + logModelType);
//獲取目標方法名(目標型別+方法名)
String targetClsName=targetCls.getName();
String targetObjectMethodName=targetClsName+"."+method.getName();
System.out.println("獲取目標方法名:" + targetObjectMethodName);
//獲取請求引數
Object[] args = point.getArgs();
//TODO 操作日誌儲存到資料庫中
String logDetailInfo = LogDetailEnums.getDetailByCode(logModelType);
switch (LogDetailEnums.getLogDetailEnum(logModelType)) {
case CREATE_ACCOUNT:
OperateTeacherReq req = JSONObject.parseObject(JSON.toJSONString(args[0]), OperateTeacherReq.class);
System.out.println("獲取請求引數:" + req);
JSONObject jsonObject = JSONObject.parseObject(req.toString());
logDetailInfo = MessageFormat.format(logDetailInfo, req.getId(), req.getName(), LogDetailEnums.getDetailByCode("pstnFlag_" + jsonObject.getString("pstnFlag")));
break;
default:
System.out.println("無該型別!");
break;
}
return Response.success(logDetailInfo);
}
}
第五步:postman模擬呼叫介面,輸出AOP中ProceedingJoinPoint獲取目標方法,引數,註解
常量類詳情日誌佔位符:
public static final String CREATE_ACCOUNT = "【Add Account】id:[{0}], User Name:[{1}],pstnFlag:[{2}]";
postman呼叫介面
postman結果列印
控制檯列印:
獲取目標方法上的註解指定的操作名稱:public com.example.demo.response.Response com.example.demo.controller.Controller.operateLog(com.example.demo.bean.OperateTeacherReq)
獲取自定義註解引數值logModelType:CREATE_ACCOUNT
獲取目標方法名:com.example.demo.controller.Controller.operateLog
獲取請求引數:{id:1, name:'教師1', pstnFlag:1}
專案程式碼路徑圖片