基于SpringAOP的操作日志记录实现

项目中,我们经常会遇到这样的需求,记录用户一些特殊的操作日志,我遇到过好几次,所以今天我分享下自己的实现。
实现方式有很多种,我是依据SpringAop+SpEL表达式实现的,下面看下代码:

  • 自定义一个用于标注操作日志的注解
    基于SpringAOP的操作日志记录实现_第1张图片

  • 操作类型枚举
    基于SpringAOP的操作日志记录实现_第2张图片

  • 注解应用
    这里写图片描述

  • 注解解析类和切面配置
    实现方法可以是在spring配置文件中配置切面层,也可以是自定义标有Aspect注解的类,接下来我先采用配置方式实现
    解析处理类 SysLogAspect.java

package com.smqi.common.log;

import com.smqi.common.aop.OperInfo;
import com.smqi.common.aop.OperationType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 系统操作日志记录
 * 

* Created by smqi on 2016/11/4. */ public class SysLogAspect { /** * 日志记录器 */ protected final Logger logger = LoggerFactory.getLogger(getClass()); /** * 缓存标注有自定义日志注解的方法参数名称 */ private static Map parameterNameCaches = new ConcurrentHashMap(); /** * 缓存SPEL Expression */ private static Map spelExpressionCaches = new ConcurrentHashMap(); /** * Spel表达式语法分析式 */ private static ExpressionParser parser = new SpelExpressionParser(); /** * 用于获取方法参数名称数组 */ private static LocalVariableTableParameterNameDiscoverer parameterNameDiscovere = new LocalVariableTableParameterNameDiscoverer(); public Object doAroundMethodForCtLog(ProceedingJoinPoint pjp) throws Throwable { //获取当前注解 Method theMethod = getMethod(pjp); if (theMethod != null && theMethod.isAnnotationPresent(OperInfo.class)) { OperInfo annotation = theMethod.getAnnotation(OperInfo.class); // 获取指定注解 String descpTemp = annotation.description(); String descption = executeTemplate(descpTemp, pjp); OperationType type = annotation.type(); logger.info("操作类型:【{}】,日志信息:【{}】",type.getType(),descption); //TODO 调用数据存储层进行入库,此处省略…… } return pjp.proceed(); } /** * 解析执行description中的SPEL模板。 * * @param template * @param joinPoint * @return */ private String executeTemplate(String template, ProceedingJoinPoint joinPoint) { //获取原始对象方法名 String methodLongName = joinPoint.getSignature().toLongString(); String[] parameterNames = parameterNameCaches.get(methodLongName); if (parameterNames == null) { Method method = getMethod(joinPoint); parameterNames = parameterNameDiscovere.getParameterNames(method); parameterNameCaches.put(methodLongName, parameterNames); } StandardEvaluationContext context = new StandardEvaluationContext(); Object[] args = joinPoint.getArgs(); if (args.length == parameterNames.length) { for (int i = 0, len = args.length; i < len; i++) context.setVariable(parameterNames[i], args[i]); } Expression expression = spelExpressionCaches.get(template); if (expression == null) { expression = parser.parseExpression(template, new TemplateParserContext()); spelExpressionCaches.put(template, expression); } return expression.getValue(context, String.class); } /** * 获取当前执行的方法 * * @param joinPoint * @return */ private Method getMethod(ProceedingJoinPoint joinPoint) { Object target = joinPoint.getTarget(); Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); try { method = target.getClass().getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { e.printStackTrace(); } return method; } }

xml文件配置如下:

<!--系统操作日志记录-->
    <bean id="sysLogAspect" class="com.smqi.common.log.SysLogAspect"/>

基于SpringAOP的操作日志记录实现_第3张图片
这里我集成到了数据库读写分离的aop配置里,不要的话可以把第一个aop:aspect配置删掉

下面我在controller层调用demoService.addDemo(user)方法,看下运行结果:
这里写图片描述
上述是基于配置的实现,下面我们再尝试下自定义标有Aspect注解的类
先将原先的配置删除,只需要添加类SysLogAspect2的bean即可

SysLogAspect2.java

package com.smqi.common.log;

import com.smqi.common.aop.OperInfo;
import com.smqi.common.aop.OperationType;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 *
 * Created by smqi on 2016/11/4.
 */
@Aspect
public class SysLogAspect2 {
    /**
     * 日志记录器
     */
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 缓存标注有自定义日志注解的方法参数名称
     */
    private static Map<String, String[]> parameterNameCaches = new ConcurrentHashMap<String, String[]>();
    /**
     * 缓存SPEL Expression
     */
    private static Map<String, Expression> spelExpressionCaches = new ConcurrentHashMap<String, Expression>();
    /**
     * Spel表达式语法分析式
     */
    private static ExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数名称数组
     */
    private static LocalVariableTableParameterNameDiscoverer parameterNameDiscovere =
            new LocalVariableTableParameterNameDiscoverer();

    @Around("execution(* com.smqi.modules..service.impl.*Impl.*(..)) && @annotation(annotation)")
    public Object doAroundMethodForCtLog(ProceedingJoinPoint pjp, OperInfo annotation)
            throws Throwable {
        //获取当前注解
        String descpTemp = annotation.description();
        String descption = executeTemplate(descpTemp, pjp);
        OperationType type = annotation.type();
        logger.info("操作类型:【{}】,日志信息:【{}】", type.getType(), descption);

        //TODO 调用数据存储层进行入库,此处省略……
        return pjp.proceed();
    }

    /**
     * 解析执行description中的SPEL模板。
     *
     * @param template
     * @param joinPoint
     * @return
     */
    private String executeTemplate(String template, ProceedingJoinPoint joinPoint) {
        //获取原始对象方法名
        String methodLongName = joinPoint.getSignature().toLongString();
        String[] parameterNames = parameterNameCaches.get(methodLongName);
        if (parameterNames == null) {
            Method method = getMethod(joinPoint);
            parameterNames = parameterNameDiscovere.getParameterNames(method);
            parameterNameCaches.put(methodLongName, parameterNames);
        }
        StandardEvaluationContext context = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        if (args.length == parameterNames.length) {
            for (int i = 0, len = args.length; i < len; i++)
                context.setVariable(parameterNames[i], args[i]);
        }
        Expression expression = spelExpressionCaches.get(template);
        if (expression == null) {
            expression = parser.parseExpression(template, new TemplateParserContext());
            spelExpressionCaches.put(template, expression);
        }
        return expression.getValue(context, String.class);
    }

    /**
     * 获取当前执行的方法
     *
     * @param joinPoint
     * @return
     */
    private Method getMethod(ProceedingJoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        try {
            method = target.getClass().getMethod(method.getName(),
                    method.getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return method;
    }
}

你可能感兴趣的:(Spring-AOP)