簡(jiǎn)介
在項(xiàng)目實(shí)際開發(fā)過程中雄坪,需要統(tǒng)計(jì)部分函數(shù)的調(diào)用耗時(shí)仓技,用于協(xié)助查找性能問題,并指導(dǎo)部分系統(tǒng)參數(shù)配置脚线。
本文通過實(shí)現(xiàn)一個(gè)這樣的示例功能,來展示AOP+SpEL結(jié)合帶來的巨大好處弥搞,同時(shí)加深對(duì)Spring Expression Language了解
設(shè)計(jì)目標(biāo)
只需要通過添加注解的方式邮绿,就能快速統(tǒng)計(jì)函數(shù)調(diào)用耗時(shí),同時(shí)還能輸出更豐富的函數(shù)調(diào)用上下文信息攀例,使結(jié)果具有更大的分析價(jià)值船逮。
函數(shù)調(diào)用上下文包含:
- 函數(shù)所屬類實(shí)例
- 函數(shù)入?yún)⒘斜?/li>
- 函數(shù)調(diào)用結(jié)果
可以自行添加更多的上下文信息
實(shí)現(xiàn)步驟
- 實(shí)現(xiàn)自定義注解
- 實(shí)現(xiàn)SpEL計(jì)算接口
- 實(shí)現(xiàn)自定義切面攔截器
- 為函數(shù)添加自定義注解
代碼Review
自定義注解
/**
* @author wurenhai
* @since 2019/1/11 9:00
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimeMeasure {
/**
* expression: target=#{#target}, a0=#{#a0}, result=#{#result}
*/
String value() default "";
}
實(shí)現(xiàn)SpEL計(jì)算接口
/**
* @author wurenhai
* @since 2019/1/11 9:44
*/
public class AspectExpressContext {
private final EvaluationContext context;
private final ExpressionParser parser;
private final ParserContext parserContext;
private AspectExpressContext() {
this(null);
}
private AspectExpressContext(ApplicationContext applicationContext) {
StandardEvaluationContext context = new StandardEvaluationContext();
if (applicationContext != null) {
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
}
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
this.context = context;
this.parser = new SpelExpressionParser(config);
this.parserContext = new TemplateParserContext();
}
public AspectExpressContext(Object target, Object[] args, Object result) {
this();
context.setVariable("target", target);
context.setVariable("result", result);
for (int i = 0; i < args.length; i++) {
context.setVariable("a" + i, args[i]);
}
}
public String getValue(String express) {
Expression expression = parser.parseExpression(express, parserContext);
return expression.getValue(context, String.class);
}
}
自定義切面攔截器
/**
* @author wurenhai
* @since 2019/1/11 9:00
*/
@Aspect
@Component
public class TimeMeasureInterceptor {
private static final Logger logger = LoggerFactory.getLogger(TimeMeasureInterceptor.class);
@Around("@annotation(TimeMeasure)")
public Object around(ProceedingJoinPoint point) throws Throwable {
StopWatch sw = new StopWatch();
sw.start();
Object result = null;
try {
result = point.proceed();
return result;
} finally {
sw.stop();
output(point, result, sw.getLastTaskTimeMillis());
}
}
private void output(ProceedingJoinPoint point, Object result, long timeInMs) {
String taskName = point.getSignature().getDeclaringType().getSimpleName()
+ "." + point.getSignature().getName() + "()";
MethodSignature signature = (MethodSignature)point.getSignature();
TimeMeasure annotation = signature.getMethod().getAnnotation(TimeMeasure.class);
String expression = annotation.value();
String text = expression;
if (StringUtils.hasLength(expression)) {
AspectExpressContext context = new AspectExpressContext(point.getTarget(), point.getArgs(), result);
try {
text = context.getValue(expression);
} catch (ParseException e) {
logger.warn("{} parse[{}] error: {}", taskName, expression, e.getMessage());
} catch (EvaluationException e) {
logger.warn("{} eval[{}] error: {}", taskName, expression, e.getMessage());
}
}
logger.info("{} cost {}(ms): {}", taskName, timeInMs, text);
}
}
為函數(shù)添加自定義注解
@SuppressWarnings("MVCPathVariableInspection")
@TimeMeasure("text1=#{#a0}, text2=#{#a1}, result=#{#result}")
@GetMapping("/demo/echo")
@ResponseBody
public String echo(String text1, String text2) {
return "ECHO: " + text1 + "," + text2;
}
參考文檔
Spring Expression Language
Aspect Oriented Programming with Spring