前言:
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();
}