SpringBoot自定义注解实现切面日志并使用SpEL解析日志模板

2,207 阅读4分钟

参考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,非常好用!