Springboot通过AOP方式(@Aspect)和Javassist优雅地进行日志输出管理详细示例

前言

在日常工作中,我们免不了要打印很多log。而大部分需要输出的log又是重复的(例如传入参数,返回值)。
因此,通过AOP方式来进行日志管理可以减少很多代码量,也更加优雅。

主要使用技术:Aspect,Javassist
(本文旨在提供实现示例,因此不做过多的原理说明。)


本文由作者三汪首发于。

代码示例

在Sringboot环境下,将本文提供的代码及依赖复制到你的项目中,可直接使用。

Maven依赖
(不包含slf4j部分依赖)

        
        
            org.aspectj
            aspectjrt
        
        
            org.aspectj
            aspectjweaver
        

        
        
            org.javassist
            javassist
        

        
        
            com.alibaba
            fastjson
            1.2.39
        

代码部分

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

@Aspect
@Component
public class MethodLogAop {

    private static final Logger LOGGER = LoggerFactory.getLogger(MethodLogAop.class);

    /**
     * 切点
     * 配置需要添加切面通知的包路径
     */
    @Pointcut("(execution(* com.wolfgy.demo.service..*.*(..)))")//||(execution(* com.wolfgy.demo.web..*.*(..)))
    public void webLog(){}

    /**
     * 前置通知
     * @param joinPoint 切点
     * @throws Throwable 异常
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        String classType = joinPoint.getTarget().getClass().getName();
        Class clazz = Class.forName(classType);
        String clazzName = clazz.getName();
        LOGGER.info("类名:" + clazzName);
        String methodName = joinPoint.getSignature().getName();
        LOGGER.info("方法名:" + methodName);
        String[] paramNames = getFieldsName(this.getClass(), clazzName, methodName);
        Object[] args = joinPoint.getArgs();
        for(int k=0; k cls, String clazzName, String methodName) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);

        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        String[] paramNames = new String[cm.getParameterTypes().length];
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < paramNames.length; i++){
            paramNames[i] = attr.variableName(i + pos); //paramNames即参数名
        }
        return paramNames;
    }

    /**
     * 后置通知
     * 打印返回值日志
     * @param ret 返回值
     * @throws Throwable 异常
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable {
        String classType = joinPoint.getTarget().getClass().getName();
        Class clazz = Class.forName(classType);
        String clazzName = clazz.getName();
        LOGGER.info("类名:" + clazzName);
        String methodName = joinPoint.getSignature().getName();
        LOGGER.info("方法名:" + methodName);
        LOGGER.info("返回值 : " + JSON.toJSONString(ret));
    }
}

代码效果

2017-11-23 13:17:28.865  INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop     : 类名:com.wolfgy.demo.service.DemoService
2017-11-23 13:17:28.867  INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop     : 方法名:save
2017-11-23 13:17:29.025  INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop     : 参数名:project,参数值:{"name":"demo"}

2017-11-23 13:17:29.273  INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop     : 类名:com.wolfgy.demo.service.DemoService
2017-11-23 13:17:29.273  INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop     : 方法名:save
2017-11-23 13:17:29.273  INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop     : 返回值 : null

补充说明

execution语法:

语法表达式:
execution(<修饰符> <返回类型> <类路径> <方法名>(<参数列表>) <异常模式> )
其中,修饰符和异常是可选的,如果不加类路径,则默认对所有的类生效。
常用实例:
1.通过方法签名、返回值定义切点:
execution(public * *Service(..)):定位于所有类下返回值任意、方法入参类型、数量任意,public类型的方法
execution(public String *Service(..)):定位于所有类下返回值为String、方法入参类型、数量任意,public类型的方法
2.通过类包定义切点:
execution(* com.yc.controller.BaseController+.*(..)):匹配任意返回类型,对应包下BaseController类及其子类等任意方法。
execution(* com.*.(..)):匹配任意返回类型,com包下所有类的所有方法
execution(* com..*.(..)):匹配任意返回类型,com包、子包下所有类的所有方法
(注意:.表示该包下所有类,..则涵括其子包。)
3.通过方法入参定义切点
(这里*表示任意类型的一个参数,..表示任意类型任意数量的参数)
execution(* speak(Integer,*)):匹配任意返回类型,所有类中只有两个入参,第一个入参为Integer,第二个入参任意的方法
execution(* speak(..,Integer,..)):匹配任意返回类型,所有类中至少有一个Integer入参,但位置任意的方法。


以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,
但我能保证它们都是我的理解和感悟以及拒绝直接复制黏贴(确实需要引用的部分我会附上源地址)。
有什么意见、见解或疑惑,欢迎留言讨论。

你可能感兴趣的:(Springboot通过AOP方式(@Aspect)和Javassist优雅地进行日志输出管理详细示例)