需求
结合AOP技术和SpEL表达式实现一个简单的AOP切面
- 计算耗时,并打印日志
- 用户可以在注解中添加指定的字段名称,切面自动解析出值,放在日志里一起打印。
示例:在添加注解的时候,指定bizCode = "#msg"
,那么在方法被调用的时候,就会计算变量articleId
在当前上下文中的值,并在日志中打印出来。
1 2 3 4 5 6
| @MdcDot(bizCode = "#articleId") @RequestMapping("/hello") public String sayHello(String articleId) { System.out.println("Hello, World :" + articleId); return "success"; }
|
测试效果如下:
实现
定义注解MdcDot
1 2 3 4 5 6 7 8 9 10 11 12
| import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MdcDot { String bizCode() default ""; }
|
编写切面Aspect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j @Aspect @Component
public class MdcAspect implements ApplicationContextAware { private ExpressionParser parser = new SpelExpressionParser(); private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Pointcut("@annotation(MdcDot) || @within(MdcDot)") public void getLogAnnotation() { }
@Around("getLogAnnotation()") public Object handle(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); boolean hasTag = addMdcCode(joinPoint); try { Object ans = joinPoint.proceed(); return ans; } finally { log.info("执行耗时: {}#{} = {}ms", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName(), System.currentTimeMillis() - start); MdcUtil.clear(); } } }
private boolean addMdcCode(ProceedingJoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); MdcDot dot = method.getAnnotation(MdcDot.class); if (dot == null) { dot = (MdcDot) joinPoint.getSignature().getDeclaringType().getAnnotation(MdcDot.class); }
if (dot != null) { MdcUtil.add("bizCode", loadBizCode(dot.bizCode(), joinPoint)); return true; } return false; }
private String loadBizCode(String key, ProceedingJoinPoint joinPoint) { if (StringUtils.isBlank(key)) { return ""; }
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(applicationContext)); String[] params = parameterNameDiscoverer.getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod()); Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { context.setVariable(params[i], args[i]); } return parser.parseExpression(key).getValue(context, String.class); }
private ApplicationContext applicationContext;
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
|
MDCUtil工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import org.slf4j.MDC;
public class MdcUtil { public static final String TRACE_ID_KEY = "traceId";
public static void add(String key, String val) { MDC.put(key, val); }
public static void addTraceId() { MDC.put(TRACE_ID_KEY, SelfTraceIdGenerator.generate()); }
public static String getTraceId() { return MDC.get(TRACE_ID_KEY); }
public static void clear() { MDC.clear(); } }
|
修改log4j.properties配置文件
1 2
| log4j.appender.stdout.layout.ConversionPattern=[%t] [%X{bizCode}](%F:%L) - %m%n
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import com.raining.aspect.MdcDot; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@Slf4j @RestController @RequestMapping("alpha") public class AlphaController { @MdcDot(bizCode = "#articleId") @RequestMapping("/hello") public String sayHello(String articleId) { System.out.println("Hello, World :" + articleId); return "success"; } }
|
参考