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

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

  • 自定义一个用于标注操作日志的注解
    这里写图片描述

  • 操作类型枚举
    这里写图片描述

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

  • 注解解析类和切面配置
    实现方法可以是在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;

/**
 * 系统操作日志记录
 * <p>
 * Created by smqi on 2016/11/4.
 */
public class SysLogAspect {
    /**
     * 日志记录器
     */
    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();

    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"/>

这里写图片描述
这里我集成到了数据库读写分离的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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值