前言:
1、實現方式:環繞註解,功能最強大,可以替代方法前註解和方法後註解。學好這一種註解最重要。
2、日誌監控埋點需求:業務方法執行前要拿到時間戳,方法執行後要拿到時間戳,最後在切面內,對耗時情況、方法請求入參情況、方法返回結果情況,做一些分析處理,並打印日誌,引入ELK或其他監控去分析。此處只展示切面收集方法前後信息打印日誌工作。
------先上切面增強業務的接入說明,後面再去看切面實現--------
1、流程泳道圖
2、代碼接入註解
- 接入說明:
如果有業務侵入要求入參實現基礎類(內有統一的必填字段)可用方法1,
如果想通用自定義指定哪個字段是入參請求流水號的情況,直接用方法2
方法3不太推薦,需要改入參類,代碼侵入性不好。
方法1(推薦-規範):
第一步:
返回值response需要繼承com.smy.pcs.dto.BaseResponse,並把關鍵字段賦值再返回,關鍵字段含義如下:
//請求響應狀態-是個枚舉
private Status responseStatus;
//渠道返回信息(如錯誤信息描述)--如"渠道服務不可用"
private String responseMsg;
第二步:
請求參數request需要繼承com.smy.pcs.dto.BaseRequest,賦值關鍵字段:
//請求流水號
private String requestSeq;
第三步:
方法上加上註解.如,
@ThirdResMonitor(trade_code=RouterType.REALTIME_COLLECTION,channel_code= ThirdPartyCode.PAB, uuid = false)
參數含義:
//交易業務類型
RouterType trade_code();
//渠道
ThirdPartyCode channel_code();
//是否隨機生成請求號uuid,默認請用false
boolean uuid();
方法2(通用):
第一步:
如果無法做到方法1第一步,那請把業務塊需要監控的做異常拋出。默認異常當做失敗,其他成功。
第二步:
如果無法做到方法1第二步,那請在方法入參上加入一個註解來指定你的請求流水號是哪個字段的值(支持內嵌字段),如下:
第三步:
方法上加上註解.如方法1第三步。
@ThirdResMonitor(trade_code=RouterType.REALTIME_COLLECTION,channel_code= ThirdPartyCode.PAB, uuid = false)
方法3(部分適用):
其中方法2有個弊端就是字段名變化之後,註解如果不跟着改就找不到指定請求流水號了,所以引用方法3解決這個問題;但方法3在入參是三方sdk包的不可更改類的情況下,不適用。
第一步:
如果無法做到方法1第一步,那請把業務塊需要監控的做異常拋出。默認異常當做失敗,其他成功。
第二步:
如果無法做到方法1第二步,那請在方法入參的類裏面某個字段上,加入一個註解@ReName(reName = "requestSeq")來指定你的請求流水號是哪個字段的值,如下:
第三步:
方法上加上註解.如方法1第三步。
@ThirdResMonitor(trade_code=RouterType.REALTIME_COLLECTION,channel_code= ThirdPartyCode.ZH, uuid = false)
------切面增強處理器--------
處理器裏會對加了註解的業務方法做增加,此類裏按上面三種接入方法,分別應用了三種類型註解,分別是是METHOD、PARAMETER、FIELD。
package com.smy.pcs.aspect;
import com.alibaba.fastjson.JSON;
import com.smy.pcs.annotation.ReName;
import com.smy.pcs.annotation.SpecifyRequestSeq;
import com.smy.pcs.annotation.ThirdResMonitor;
import com.smy.pcs.domain.MonitorModelLog;
import com.smy.pcs.dto.BaseRequest;
import com.smy.pcs.dto.BaseResponse;
import com.smy.pcs.util.LoggerUtil;
import lombok.extern.slf4j.Slf4j;
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.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* ThirdResMonitorAspect類
*
* @author Xiaopeng Jiang
* @date 2020/6/1 16:04
*/
@Slf4j
@Aspect
@Component
public class ThirdResMonitorAspect {
/**
* 渠道外聯監控埋點
*
* @param proceedingJoinPoint 切面
* @return 返回值
* @throws Throwable
*/
@Around(value = "@annotation(com.smy.pcs.annotation.ThirdResMonitor)")
Object toAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("[ThirdResMonitorAspect#toAround] toAround receive with proceedingJoinPoint");
Object object = null;
String requestSeq = null;
MonitorModelLog modelLog = null;
try {
//目標對象方法註解
ThirdResMonitor thirdResMonitorAnnotation = getProxyAnnotation(proceedingJoinPoint);
//----------外聯監控埋點日誌組裝開始節點------start--
modelLog = new MonitorModelLog();
//代理對象中獲取
modelLog.setTrade_code(thirdResMonitorAnnotation.trade_code() == null ? null : thirdResMonitorAnnotation.trade_code().getCode());
modelLog.setChannel_code(thirdResMonitorAnnotation.channel_code() == null ? null : thirdResMonitorAnnotation.channel_code().getCode());
modelLog.setRequest_start_time(System.currentTimeMillis());
try {
//------to proceed-------
object = proceedingJoinPoint.proceed();
//獲取請求號
requestSeq = toGetRequestSeq(proceedingJoinPoint, thirdResMonitorAnnotation);
modelLog.setUnique_id(requestSeq);
modelLog.setRequest_end_time(System.currentTimeMillis());
modelLog.setTime(modelLog.getRequest_end_time() - modelLog.getRequest_start_time());
if (object == null) {
log.error("[ThirdResMonitor#toAround] 調用成功,但返回對象爲空");
modelLog.setTrade_status("SUCCESS");
modelLog.setTrade_message("調用成功,但返回對象爲空");
} else if (object instanceof BaseResponse) {
BaseResponse baseResponse = (BaseResponse) object;
//目標方法baseResponse中獲取,可能成功,可能失敗
modelLog.setTrade_status(baseResponse.getResponseStatus() == null ? "未知狀態" : baseResponse.getResponseStatus().getCode());
modelLog.setTrade_message(baseResponse.getResponseMsg());
} else {
log.error("[ThirdResMonitor#toAround] 監控註解的返回值不按規範要求寫 with 返回值:{}", object);
modelLog.setTrade_status("其他");
modelLog.setTrade_message("返回對象沒有繼承base類,狀態未知");
}
LoggerUtil.channelInterfaceMonitor.info(JSON.toJSONString(modelLog));
return object;
} catch (Throwable throwable) {
log.error("[ThirdResMonitor#toAround] 監控渠道調用異常 with throwable:{}", throwable);
//獲取請求號
requestSeq = toGetRequestSeq(proceedingJoinPoint, thirdResMonitorAnnotation);
modelLog.setUnique_id(requestSeq);
modelLog.setRequest_end_time(System.currentTimeMillis());
modelLog.setTime(modelLog.getRequest_end_time() - modelLog.getRequest_start_time());
//異常置爲失敗
modelLog.setTrade_status("FAIL");
modelLog.setTrade_message(throwable.getMessage());
LoggerUtil.channelInterfaceMonitor.info(JSON.toJSONString(modelLog));
throw throwable;
}
//----------外聯監控埋點日誌組裝開始節點------end--
} catch (Throwable t) {
log.error("[ThirdResMonitorAspect#toAround] toAround Throwable with t: {}", t);
throw t;
} finally {
log.info("[ThirdResMonitorAspect#toAround] toAround finally with 目標方法耗時: {}", modelLog == null ? "未知" : modelLog.getTime());
}
}
private String toGetRequestSeq(ProceedingJoinPoint proceedingJoinPoint, ThirdResMonitor thirdResMonitorAnnotation) throws IllegalAccessException {
String requestSeq;
if (thirdResMonitorAnnotation.uuid()) {
//隨機生成請求號uuid--定製化Y5CardBinServiceImpl.queryYYBin
requestSeq = UUID.randomUUID().toString();
log.info("[ThirdResMonitorAspect#toAround] 查詢到隨機的流水號 getSpecifyRequestSeq with requestSeq: {}", requestSeq);
return requestSeq;
}
//通過參數註解方式拿到指定的請求流水號字段
requestSeq = getSpecifyRequestSeq(proceedingJoinPoint);
if (requestSeq != null) {
log.info("[ThirdResMonitorAspect#toAround] 查詢到參數指定的深度流水號 getSpecifyRequestSeq with requestSeq: {}", requestSeq);
return requestSeq;
}
//獲取目標對象方法的參數,參數值,參數名,默認第一個獲取到return
Object[] args = proceedingJoinPoint.getArgs();
for (Object object2 : args) {
//通過字段註解方式拿到指定的請求流水號字段
requestSeq = getFeiledValueByObject(object2, "requestSeq");
if (requestSeq != null) {
log.info("[ThirdResMonitorAspect#toAround] 查詢到字段註解的流水號 getSpecifyRequestSeq with requestSeq: {}", requestSeq);
return requestSeq;
}
if (object2 instanceof BaseRequest) {
//通用-繼承base
BaseRequest atomicEntry = (BaseRequest) object2;
requestSeq = atomicEntry.getRequestSeq();
log.info("[ThirdResMonitorAspect#toAround] 查詢到繼承base的流水號 getSpecifyRequestSeq with requestSeq: {}", requestSeq);
return requestSeq;
} else {
log.info("###ThirdResMonitorAspect#doAfter#object is not need AtomicEntry#requestSeq={}#object={}###", requestSeq, object2);
}
}
if (requestSeq == null) {
requestSeq = UUID.randomUUID().toString();
log.info("###ThirdResMonitorAspect#doAfter#requestSeq找不到,隨機生成一個#requestSeq={}###", requestSeq);
}
return requestSeq;
}
/**
* 拿到目標方法的ThirdResMonitorAnnotation註解
*
* @param proceedingJoinPoint 連接點
* @return 註解對象
*/
public static ThirdResMonitor getProxyAnnotation(ProceedingJoinPoint proceedingJoinPoint) {
try {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
//拿到目標方法的ThirdResMonitorAnnotation註解
return method.getAnnotation(ThirdResMonitor.class);
} catch (Exception e) {
return null;
}
}
/**
* 獲取 @SpecifyRequestSeqAnnotation 參數上指定的流水號
*
* @param point 切點
* @return
*/
public static String getSpecifyRequestSeq(ProceedingJoinPoint point) throws IllegalAccessException {
//獲取連接點方法運行時的入參列表
Object[] args = point.getArgs();
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
//獲取被註解的方法參數及對應的所有註解,第一個下標表示參數,第二個下標表示該參數對應的多個註解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
// 迭代註解獲取所有的;i表示第幾個參數
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
//每個參數前對應的所有註解開始遍歷
for (Annotation annotation : annotations) {
//只抓取每個參數前對應的註解@SpecifyRequestSeqAnnotation,其他註解這裏不處理
if (SpecifyRequestSeq.class.equals(annotation.annotationType())) {
SpecifyRequestSeq specifyFieldAnnotation = (SpecifyRequestSeq) annotation;
String fieldNameAnnotation = specifyFieldAnnotation.requestSeq();
String[] fieldNames = fieldNameAnnotation.split("\\.");
// List<String> fieldNames = Arrays.asList(s);
String requestSeq = toTraverse(args[i], fieldNames, 0);
log.info("[ThirdResMonitorAspect#toAround] 查詢參數指定的深度流水號 getSpecifyRequestSeq with requestSeq: {}", requestSeq);
return requestSeq;
}
}
}
return null;
}
/**
* @Author: Jiangxiaopeng
* @Description:
* @Date: 2020/6/3
* objectCurrent 當前對象
* fieldNames 當前對象的內嵌性質的字段,示例:a.b.c
**/
private static String toTraverse(Object objectCurrent, String[] fieldNames, int index) throws IllegalAccessException {
//兼容abc農行第三方包的騷操作
if (objectCurrent instanceof Map) {
Map<String, Object> map = (Map) objectCurrent;
//這個已不通用了,不符合標準。
Object obj = map.get(fieldNames[index]);
if (obj != null) {
return obj.toString();
}
}
//-------通用類結構--------------
List<Field> fieldsList = new ArrayList();
Class tempClass = objectCurrent.getClass();
//當父類爲null的時候說明到達了最上層的父類(Object類)
while (tempClass != null) {
//當前類字段
fieldsList.addAll(Arrays.asList(tempClass.getDeclaredFields()));
//抓取父類多層字段
tempClass = tempClass.getSuperclass();
}
for (Field field : fieldsList) {
if (fieldNames.length > 0 && fieldNames[index].equals(field.getName())) {
field.setAccessible(true);
Object o = field.get(objectCurrent);
//無值直接返回了
if (o == null) {
return null;
}
if (fieldNames.length - 1 == index) {
//已至最深內嵌直接返回
return o.toString();
} else {
//繼續找下一層次內嵌
index++;
return toTraverse(o, fieldNames, index);
}
}
}
//無此字段直接返回
return null;
}
/**
* @Author: Jiangxiaopeng
* @Description:獲取指定註解字段的值
* @Date: 2020/6/3
* Object object2 已知對象
* String reNameValue 註解字段指定重命名的名稱
**/
public static String getFeiledValueByObject(Object object2, String reNameValue) throws IllegalAccessException {
Field[] fields = object2.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ReName.class)) {
ReName reNameAnnotation = (ReName) field.getAnnotation(ReName.class);
String reName = reNameAnnotation.reName();
if (reNameValue.equals(reName)) {
field.setAccessible(true);
Object o = field.get(object2);
return o.toString();
}
}
}
return null;
}
/**
* @Author: Jiangxiaopeng
* @Description:獲取指定註解字段的值
* @Date: 2020/6/3
* ProceedingJoinPoint point 通過他拿到方法入參所有對象,然後遍歷
* String reNameValue 註解字段指定重命名的名稱
**/
public static String getFeiledValueByPoint(ProceedingJoinPoint point, String reNameValue) throws IllegalAccessException {
// Field[] fields = clazz.getDeclaredFields();
Object[] args = point.getArgs();
for (Object object : args) {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ReName.class)) {
ReName reNameAnnotation = (ReName) field.getAnnotation(ReName.class);
String reName = reNameAnnotation.reName();
if (reNameValue.equals(reName)) {
field.setAccessible(true);
Object o = field.get(object);
return o.toString();
}
}
}
}
return null;
}
}
------應用到三種類型註解定義--------
METHOD
package com.smy.pcs.annotation;
import com.smy.pcs.enums.RouterType;
import com.smy.pcs.enums.ThirdPartyCode;
import java.lang.annotation.*;
/**
* 外聯監控註解,監控異常失敗(或明確業務異常失敗)、成功
*
* @author Xiaopeng Jiang
* @date 2020/6/1 15:21
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ThirdResMonitor {
//交易業務類型
RouterType trade_code();
//渠道
ThirdPartyCode channel_code();
//是否隨機生成請求號uuid,默認請用false--如,Y5CardBinServiceImpl.queryYYBin用true
boolean uuid();
}
PARAMETER
package com.smy.pcs.annotation;
import java.lang.annotation.*;
/**
* SpecifyFieldAnnotation類
*
* @author Jiang xiaopeng
* @date 2020/6/3
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SpecifyRequestSeq {
//如:requestSeq="name1" 訂單號去取當前目標對象的name1字段的值
String requestSeq();
}
FIELD
package com.smy.pcs.annotation;
import java.lang.annotation.*;
/**
* ReNameAnnotation類
*
* @author Xiaopeng Jiang
* @date 2020/6/3 9:10
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ReName {
//如:reName=requestSeq訂單號
String reName();
}