参考Spring Cache org.springframework.context.expression.CachedExpressionEvaluator
,卡卡西一个支持模板的CachedTemplateExpressionEvaluator
重点在于getExpression()
方法的getParser().parseExpression(expression)
变成getParser().parseExpression(expression,new TemplateParserContext(EXPRESSION_PREFIX,EXPRESSION_SUFFIX))
然后它就是模板解析了
package com.logging.evaluator;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import java.util.Map;
/**
* {@link CachedExpressionEvaluator}的模板解析版本
*/
public abstract class CachedTemplateExpressionEvaluator {
private final SpelExpressionParser parser;
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private static final String EXPRESSION_PREFIX = "#{";
private static final String EXPRESSION_SUFFIX = "}";
/**
* 使用指定的 {@link SpelExpressionParser} 创建一个新实例。
*/
protected CachedTemplateExpressionEvaluator(SpelExpressionParser parser) {
Assert.notNull(parser, "SpElExpressionParser must not be null");
this.parser = parser;
}
/**
* 使用默认的 {@link SpelExpressionParser} 创建一个新实例。
*/
protected CachedTemplateExpressionEvaluator() {
this(new SpelExpressionParser());
}
/**
* 返回要使用的 {@link SpelExpressionParser}。
*/
protected SpelExpressionParser getParser() {
return this.parser;
}
/**
* 返回一个共享参数名称发现器,它在内部缓存数据。
*/
protected ParameterNameDiscoverer getParameterNameDiscoverer() {
return this.parameterNameDiscoverer;
}
/**
* 返回指定 SpEL 值的 {@link Expression}
* 使用默认的 #{ 前缀和 } 后缀创建一个新的 TemplateParserContext。
* <p>如果还没有解析表达式。
* @param cache 要使用的缓存
* @param elementKey 定义表达式的元素
* @param expression 要解析的表达式
*/
protected Expression getExpression(Map<ExpressionKey, Expression> cache,
AnnotatedElementKey elementKey, String expression) {
ExpressionKey expressionKey = createKey(elementKey, expression);
Expression expr = cache.get(expressionKey);
if (expr == null) {
expr = getParser().parseExpression(expression,new TemplateParserContext(EXPRESSION_PREFIX,EXPRESSION_SUFFIX));
cache.put(expressionKey, expr);
}
return expr;
}
private ExpressionKey createKey(AnnotatedElementKey elementKey, String expression) {
return new ExpressionKey(elementKey, expression);
}
/**
* 一个表达式键。
*/
protected static class ExpressionKey implements Comparable<ExpressionKey> {
private final AnnotatedElementKey element;
private final String expression;
protected ExpressionKey(AnnotatedElementKey element, String expression) {
Assert.notNull(element, "AnnotatedElementKey must not be null");
Assert.notNull(expression, "Expression must not be null");
this.element = element;
this.expression = expression;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ExpressionKey)) {
return false;
}
ExpressionKey otherKey = (ExpressionKey) other;
return (this.element.equals(otherKey.element) &&
ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
}
@Override
public int hashCode() {
return this.element.hashCode() * 29 + this.expression.hashCode();
}
@Override
public String toString() {
return this.element + " with expression \"" + this.expression + "\"";
}
@Override
public int compareTo(ExpressionKey other) {
int result = this.element.toString().compareTo(other.element.toString());
if (result == 0) {
result = this.expression.compareTo(other.expression);
}
return result;
}
}
}
SpEL需要的上下文信息:
package com.logging.evaluator;
import com.logging.Logging;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.ParameterNameDiscoverer;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 日志SpEl解析时的上下文信息<br>
* 凡是在${@link LogEvaluationContext#setVariable(String, Object)}中加入的变量,都可在${@link Logging} 注解的SpEL表达式中使用
*/
public class LogEvaluationContext extends MethodBasedEvaluationContext {
/**
* 保存结果对象的变量的名称。
*/
public static final String RESULT_VARIABLE = "result";
public static final String ERROR_MSG_VARIABLE = "errorMsg";
/**
* 把方法的参数都放到 SpEL 解析的 RootObject 中
*/
public LogEvaluationContext(Object rootObject, Method method, Object[] args,
ParameterNameDiscoverer parameterNameDiscoverer) {
super(rootObject, method, args, parameterNameDiscoverer);
}
/**
* 把方法的返回值放到 RootObject 中
* @param result 返回值
*/
public void addResult(Object result) {
//把方法的返回值放到 RootObject 中
setVariable(RESULT_VARIABLE, result);
}
/**
* 把方法的ErrorMsg放到 RootObject 中
* @param errorMsg 错误信息
*/
public void addErrorMsg(String errorMsg) {
setVariable(ERROR_MSG_VARIABLE, errorMsg);
}
}
然后扩展这个抽象类:
package com.logging.evaluator;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 解析Log中的SpEL表达式
*/
public class LogExpressionEvaluator extends CachedTemplateExpressionEvaluator {
private final Map<ExpressionKey, Expression> messageCache = new ConcurrentHashMap<>(64);
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<ExpressionKey, Expression> unlessCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
/**
* 解析指定表达式。
*/
public <T> T parse(String expression, AnnotatedElementKey methodKey, EvaluationContext evalContext, Class<T> tClass) {
return getExpression(this.messageCache, methodKey, expression).getValue(evalContext, tClass);
}
/**
* 为指定方法的指定事件处理创建合适的 {@link EvaluationContext}。
*/
public EvaluationContext createEvaluationContext(Method method, Object[] args, Object target, Class<?> targetClass,
@Nullable Object result, @Nullable String errorMsg, @Nullable BeanFactory beanFactory) {
ExpressionRootObject rootObject = new ExpressionRootObject(method, args, target, targetClass);
LogEvaluationContext logEvaluationContext = new LogEvaluationContext(rootObject, getTargetMethod(targetClass, method), args, getParameterNameDiscoverer());
logEvaluationContext.addResult(result);
logEvaluationContext.addErrorMsg(errorMsg);
if (beanFactory != null) {
logEvaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return logEvaluationContext;
}
/**
* 获取真正的具体的方法并缓存
*
* @param targetClass 目标class
* @param method 来自接口或者父类的方法签名
* @return 目标class实现的具体方法
*/
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
@Getter
@AllArgsConstructor
public static class ExpressionRootObject {
private final Method method;
private final Object[] args;
private final Object target;
private final Class<?> targetClass;
/**
* 使用#{#root.methodName}时调用
*/
public String getMethodName() {
return this.method.getName();
}
}
}
基本能用了,但是还不方便,我们封装一下:
package com.logging.evaluator;
import com.logging.Logging;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
import java.util.function.Supplier;
/**
* 包装参数发给{@link LogExpressionEvaluator}解析的工具类
*/
@Component
public class LogValueParser implements BeanFactoryAware {
@Nullable
private BeanFactory beanFactory;
private final LogExpressionEvaluator logExpressionEvaluator = new LogExpressionEvaluator();
@Override
public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public <T> T parser(ProceedingJoinPoint point, Supplier<String> getExpression, @Nullable Object proceed, @Nullable String errorMsg, Class<T> parserResultClass) {
return parserOf(logExpressionEvaluator::parse, getExpression, parserResultClass).parser(point, proceed, errorMsg);
}
private <T> ParserByAop<T> parserOf(ParserTo parser, Supplier<String> getExpression, Class<T> parserResultClass) {
return (point, proceed, errorMsg) -> {
ExpressionArgs expressionArgs = getExpressionArgs(point, proceed, errorMsg);
return parser.parser(getExpression.get(), expressionArgs.getMethodKey(), expressionArgs.getContext(), parserResultClass);
};
}
private ExpressionArgs getExpressionArgs(ProceedingJoinPoint point, @Nullable Object proceed, @Nullable String errorMsg) {
Method method = ((MethodSignature) point.getSignature()).getMethod();
Class<?> targetClass = point.getTarget().getClass();
EvaluationContext evaluationContext = logExpressionEvaluator.createEvaluationContext(method, point.getArgs()
, point.getTarget(), targetClass, proceed, errorMsg, beanFactory);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
return new ExpressionArgs(evaluationContext, methodKey);
}
@Getter
@AllArgsConstructor
private static class ExpressionArgs {
private final EvaluationContext context;
private final AnnotatedElementKey methodKey;
}
@FunctionalInterface
private interface ParserByAop<T> {
T parser(ProceedingJoinPoint point, @Nullable Object proceed, @Nullable String errorMsg);
}
@FunctionalInterface
private interface Parser<T> {
T parser(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext);
}
@FunctionalInterface
private interface ParserTo {
<T> T parser(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext, Class<T> parserResultClass);
}
}
自定义注解:
package com.logging;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Logging {
String successMsg() default "";
String failMsg() default "";
}
切面:
package com.logging;
import com.logging.evaluator.LogValueParser;
import lombok.RequiredArgsConstructor;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Aspect
@Component
@RequiredArgsConstructor
public class LogAspect {
private final LogValueParser logValueParser;
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
@Pointcut("@annotation(com.logging.Logging)")
private void pointCut(){}
@Around("pointCut() && @annotation(logging)")
public Object around(ProceedingJoinPoint point,Logging logging) throws Throwable {
Object proceed;
try {
proceed = point.proceed();
log.info(logValueParser.parser(point,logging::successMsg,proceed,null,String.class));
} catch (Throwable throwable) {
log.info(logValueParser.parser(point,logging::failMsg,null,throwable.getMessage(),String.class));
throw throwable;
}
return proceed;
}
}
使用方法:
@Logging(successMsg = "方法:#{#root.methodName},参数:#{#p0},返回值:#{#result}")
public String test1(String str) {
return str + "test1";
}
@Logging(successMsg = "方法:#{#root.methodName},参数:#{#p0},返回值:#{#result}"
, failMsg = "#{T(org.springframework.util.StringUtils).hasText(#p0).toString()}阿巴阿巴")
public void test4() {
throw new DepartmentNotFoundException(100L);
}
SpEL可以调用静态方法,可以调用Bean,也可以实现类似@Cacheable(condition = "#p0==null")
这种条件记录log,非常好用!