给SpringBoot接口加全局日志(AOP)

company项目差不多到了尾声,leader突然告知我,给所有API接口加上个执行时间放到日志。

我还得把所有接口都得修改,加一个开始时间,加一个结束时间,然后输出到日志。

先来给大家看看我们现在的日志(如下图):

file

感觉乱的一锅粥。根本不知道执行了什么。

一想到,每个接口都需要加一个开始时间和一个结束时间,我就感觉特别烦,就是不想撸代码。

file

回到家准备买个烤冷面吃

file

结果一看钱包

file

又想到老板的脸

file

我还是加一下日志吧,要不饭都没得吃了。

我问我朋友 怎么快速测试每个接口API执行时间。我朋友说你先去看看java的特性。

file

我打开了百度百科:突然想到 java最大的特性就是面向对象。就是所有事情都不用亲力亲为了。所以肯定有好的办法。

我朋友说:NoNoNo 我的意思是说你不适合学java 回家卖烤冷面吧。。

我沉思了许久,终于有一个好的办法 ,就是Spring的特性AOP(面向切面编程)

file

直接就在进入和返回直接加日志:

file

AOP编程思想及术语(摘抄百度):

		AOP是面向切面的编程,其编程思想是把散布于不同业务但功能相同的代码从业务逻辑中抽取出来,封装成独立的模块,这些独立的模块被称为切面,切面的具体功能方法被称为关注点。在业务逻辑执行过程中,AOP会把分离出来的切面和关注点动态切入到业务流程中,这样做的好处是提高了功能代码的重用性和可维护性。

		例如,前面案例的VerifyUser类就是切面,VerifyUser类的beforeAdvice就是关注点。VerifyUser切面的功能就是验证老师身份,可以应用到与老师相关的不同业务流程中。AopEmailNotice是一个业务类,负责发送通知给老师,在发送通知之前需要验证老师的身份。AOP会在AopEmailNotice类的setTeacher方法执行之前,将beforeAdvice关注点切入到AopEmailNotice业务类中,并执行beforeAdvice方法。

		Spring框架提供了@AspectJ 注解方法和基于XML架构的方法来实现AOP。前面的案例是基于XML架构的方法,后面一节会讲述基于@AspectJ 注解的方法。下面结合前面的案例讲述一下AOP的相关术语。

		● Aspect

		表示切面。切入业务流程的一个独立模块。例如,前面案例的VerifyUser类,一个应用程序可以拥有任意数量的切面。

		● Join point

		表示连接点。也就是业务流程在运行过程中需要插入切面的具体位置。例如,前面案例的AopEmailNotice类的setTeacher方法就是一个连接点。

		● Advice

		表示通知。是切面的具体实现方法。可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种。实现方法具体属于哪类通知,是在配置文件和注解中指定的。例如,VerifyUser类的beforeAdvice方法就是前置通知。

		● Pointcut

		表示切入点。用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上。例如,前面案例配置文件的<aop:pointcut>标签。

		● Target

		表示目标对象。被一个或者多个切面所通知的对象。例如,前面案例的AopEmailNotice类。

		● Proxy

		表示代理对象。将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象为目标对象的业务逻辑功能加上被切入的切面所形成的对象。

		● Weaving

		表示切入,也称为织入。将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期。

说时迟那时快直接拿起键盘开撸。 AOP有 前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)和环绕通知(Around)五种

我为了我的业务直接用的环绕通知,其他的可以自行测试哈。

第一步:引入pom文件(jar包)

file

代码如下:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

第二步引入工具类:

file

代码如下:
package cn.cnbuilder.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;

@Component
@Aspect
public class LogAspect {
	//引入日志配置
    private static final Log LOG = LogFactory.getLog(LogAspect.class);

    /**
     * 定义一个切入点 只拦截controller.
     * 解释下:
     * ~ 第一个 * 代表任意修饰符及任意返回值.
     * ~ 第二个 * 定义在web包或者子包
     * ~ 第三个 * 任意方法
     * ~ .. 匹配任意数量的参数.
     */
    @Pointcut("execution(* cn.cnbuilder.controller..*.*(..))")
			public void logPointcut() {
    }

	//around(和上面的方法名一样)
    @org.aspectj.lang.annotation.Around("logPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {    
        LOG.info("=====================================Method  start====================================");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        long start = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
            LOG.info("请求地址:" + request.getRequestURI());
            LOG.info("用户IP:" + request.getRemoteAddr());
            LOG.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            LOG.info("参数: " + Arrays.toString(joinPoint.getArgs()));
            LOG.info("执行时间: " + (end - start) + " ms!");
            LOG.info("=====================================Method  End====================================");
            return result;
        } catch (Throwable e) {
            long end = System.currentTimeMillis();
            LOG.info("URL:" + request.getRequestURI());
            LOG.info("IP:" + request.getRemoteAddr());
            LOG.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
            LOG.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
            LOG.info("执行时间: " + (end - start) + " ms!");
            LOG.info("=====================================Method  End====================================");
            throw e;
        }
    }
}

完事搞定。。。。 ps:这个地方一定要用object 用void没有返回值。

file

是不是超简单,为了奖励我自己,我决定半夜去吃两个烤冷面(贷款吃)犒劳犒劳自己。

file

我们看一下最终效果:有木有感觉自己写的代码很棒,嗯嗯嗯嗯,一加日志我们代码就是所有代码中最靓的码。

file


以上有什么问题可以联系一下我。

文章中提到的leader以及朋友纯属为文章有趣性额外添加。台上做戏,台下做人。加油!!!

本来这篇文章想给大家分享科目二的技巧以及考试要素的。不过数据还在整理,下一篇给大家分享哈。

鼓励作者写出更好的技术文档,就请我喝一瓶哇哈哈哈哈哈哈哈。。你们的赞助决定我更新的速度哦!

微信:

支付宝:


感谢一路支持我的人。。。。。

Love me and hold me
QQ:69673804(16年老号)
EMAIL:itw@tom.com
友链交换
如果有兴趣和本博客交换友链的话,请按照下面的格式在评论区进行评论,我会尽快添加上你的链接。

网站名称:KingYiFan’S Blog
网站地址:http://blog.cnbuilder.cn
网站描述:年少是你未醒的梦话,风华是燃烬的彼岸花。
网站Logo/头像: [头像地址](https://blog.cnbuilder.cn/upload/2018/7/avatar20180720144536200.jpg)

 

posted @ 2019-05-08 14:25  KingYiFan  阅读(1020)  评论(0编辑  收藏  举报