美文网首页Java服务器端编程程序员Spring Boot
Springboot通过AOP方式(@Aspect)和Javas

Springboot通过AOP方式(@Aspect)和Javas

作者: 三汪 | 来源:发表于2017-11-23 13:48 被阅读53次

    前言

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

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


    本文由作者三汪首发于简书。

    代码示例

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

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

            <!-- aspectj -->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
            </dependency>
    
            <!-- javassist -->
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
            </dependency>
    
            <!-- fastjson -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.39</version>
            </dependency>
    

    代码部分

    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<args.length; k++){
                LOGGER.info("参数名:" + paramNames[k] + ",参数值:" + JSON.toJSONString(args[k]));
            }
        }
    
    
        /**
         * 得到方法参数的名称
         * @param cls 类
         * @param clazzName 类名
         * @param methodName 方法名
         * @return 参数名数组
         * @throws NotFoundException 异常
         */
        private static String[] getFieldsName(Class<?> 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)和Javas

        本文链接:https://www.haomeiwen.com/subject/kbqzvxtx.html