簡介
在項目實際開發過程中,需要統計部分函數的調用耗時,用于協助查找性能問題,并指導部分系統參數配置。
本文通過實現一個這樣的示例功能,來展示AOP+SpEL結合帶來的巨大好處,同時加深對Spring Expression Language了解
設計目標
只需要通過添加注解的方式,就能快速統計函數調用耗時,同時還能輸出更豐富的函數調用上下文信息,使結果具有更大的分析價值。
函數調用上下文包含:
- 函數所屬類實例
- 函數入參列表
- 函數調用結果
可以自行添加更多的上下文信息
實現步驟
- 實現自定義注解
- 實現SpEL計算接口
- 實現自定義切面攔截器
- 為函數添加自定義注解
代碼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 "";
}
實現SpEL計算接口
/**
* @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);
}
}
為函數添加自定義注解
@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