一,背景
在开发过程,如订单创建逻辑,如果方法异常,需要记录相关的日志信息,比如一些重要的业务信息,及异常信息
二,实现技术方案
1,自定义注解
2,AOP拦截注解,后置异常通知
3,使用ThreadLocal,定义,异常单 全局变量,负责不同类的参数传递(应该是方法的参数传递,同一个方法多线程访问,记录线程级别访问数据)
三,代码
1,自定义注解
package com.smcv.yl.order.service.annotation;
import com.smcv.yl.order.service.constant.OrderExceptionType;
import java.lang.annotation.*;
/**
* @author zjq
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OrderExceptionAnnotation {
/**
* 异常单类型
* 可以不用指定,默认其他异常
*/
OrderExceptionType orderException() default OrderExceptionType.OTHER_EXCEPTION;
/**
* 请求快照入参
*/
String requestSnapshot() default "";
/**
* 运力订单号
*/
String ylOrderNo() default "";
/**
* 第三方订单号
*/
String outOrderNo() default "";
/**
* 订单来源
*/
String sourceApp();
}
2.上下文参数传递类
package com.smcv.yl.order.service.service.other;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
*
* 描述:异常单 全局变量,负责不同类的参数传递
*/
public class OrderThreadLocalContext {
private static final ThreadLocal<Map<String, String>> MAP_THREAD_LOCAL = ThreadLocal.withInitial(HashMap::new);
public static final String YL_ORDER_NO = "ylOrderNo";
public static final String OUT_ORDER_NO = "outOrderNo";
public static final String REQUEST_SNAPSHOT = "requestSnapshot";
protected static ThreadLocal<Map<String, String>> getMapThreadLocal() {
return MAP_THREAD_LOCAL;
}
public static Map<String, String> getMap() {
return MAP_THREAD_LOCAL.get();
}
public static void put(String key, String value) {
getMap().put(key, value);
}
public static String get(String key) {
return getMap().get(key);
}
public static void remove() {
MAP_THREAD_LOCAL.remove();
}
public static void setYlOrderNo(String ylOrderNo) {
setOrderNoAndRequestSnapshot(ylOrderNo, null, null);
}
public static void setOutOrderNo(String outOrderNo) {
setOrderNoAndRequestSnapshot(null, outOrderNo, null);
}
public static void setRequestSnapshot(Object requestSnapshot) {
setOrderNoAndRequestSnapshot(null, null, requestSnapshot);
}
public static void setOrderNoAndRequestSnapshot(String ylOrderNo, String outOrderNo, Object requestSnapshot) {
try {
if (StringUtils.isNotBlank(ylOrderNo)) {
put(YL_ORDER_NO, ylOrderNo);
}
if (StringUtils.isNotBlank(outOrderNo)) {
put(OUT_ORDER_NO, outOrderNo);
}
if (null != requestSnapshot) {
put(REQUEST_SNAPSHOT, JSON.toJSONString(requestSnapshot));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.AOP类
package com.smcv.yl.order.service.aop;
import com.smcv.yl.order.service.service.async.AsyncOrderExceptionHandle;
import com.smcv.yl.order.service.service.other.OrderThreadLocalContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* @author zjq
*/
@Order
@Aspect
@Configuration
@Slf4j
public class OrderExceptionAspect {
public static final String YL_ORDER_NO = "ylOrderNo";
public static final String OUT_ORDER_NO = "outOrderNo";
public static final String REQUEST_SNAPSHOT = "requestSnapshot";
@Autowired
private AsyncOrderExceptionHandle asyncExceptionHandle;
public OrderExceptionAspect() {
}
@Pointcut("@annotation(com.smcv.yl.order.service.annotation.OrderExceptionAnnotation)")
public void orderExceptionAnnotation() {
}
/**
* 后置异常通知
*/
@AfterThrowing(value = "orderExceptionAnnotation()", throwing = "throwable")
public void exceptionHandle(JoinPoint joinPoint, Throwable throwable) throws Exception {
String ylOrderNo = OrderThreadLocalContext.get(YL_ORDER_NO);
String outOrderNo = OrderThreadLocalContext.get(OUT_ORDER_NO);
String s = OrderThreadLocalContext.get(REQUEST_SNAPSHOT);
//这里异步处理防止异常影响主业务
asyncExceptionHandle.asyncExceptionHandle(joinPoint, throwable, ylOrderNo, outOrderNo, s);
//清除当前线程绑定的ThreadLocal,防止可能存在的内存泄露
// 防止web服务线程复用导致前一个请求的副本变量被下一个线程所获取
OrderThreadLocalContext.remove();
}
// 在目标方法返回结果时后执行(当方法抛出异常时,无法执行)。
// 调用成功无异常请求最后调用此方法清除线程绑定的副本变量
@AfterReturning(value = "orderExceptionAnnotation()")
public void afterReturningMethod() {
//清除当前线程绑定的ThreadLocal,防止可能存在的内存泄露
// 防止web服务线程复用导致前一个请求的副本变量被下一个线程所获取
OrderThreadLocalContext.remove();
}
@Bean
@ConditionalOnMissingBean(SpelExpressionParser.class)
public SpelExpressionParser spelExpressionParser() {
return new SpelExpressionParser();
}
}
4.业务方法
@Override
@OrderExceptionAnnotation(requestSnapshot = "#receiveOrder.orderInfo",sourceApp = Constant.WX)
public OrderResponse<ESBOrderResponse> commonCreateOrder(ReceiveOrder receiveOrder) {
ESBOrderInfoReq orderInfo = JSON.parseObject(JSON.toJSONString(receiveOrder.getOrderInfo()), new TypeReference<ESBOrderInfoReq>() {
});
ESBRoute route = orderInfo.getRoute();
ESBOrderDTO esbOrder = orderInfo.getEsbOrder();
//异常单业务逻辑,异常感知,全局变量输入
OrderThreadLocalContext.setOutOrderNo(esbOrder.getOutOrderNo());