SpringBoot AOP统一处理请求日志
有一天,项目经理老吴对小吴说:现在有这样一个需求:在SpringBootDemo01项目上的每个方法执行之前之后各打印一句话,例如:方法执行前打印如下内容:
方法执行开始
方法执行后打印:
方法执行完成
然后,小吴心里觉得,SpringBootDemo01项目中才不到10个方法,这个简单呀,好做嘛:直接在每个方法前后各添加一行打印输出代码就可以了。然后直接爽快的答应的,说5分钟给您。
5分钟过去了,老吴过来了,你高高心心的将程序跑起来,演示给老吴看:这是不是您想要的。
老吴说,很好,同样的需求,你把SpringBootDemo02中的每个方法执行前后各打印一句话。
由于有了第一个项目的基础,觉得这个用同样的方法一样的解决嘛,于是又爽快的答应了。说30分钟之后给您看,经理老吴走之后,打开SpringBootDemo02项目,发现里面居然有100个方法也,这该怎么办呢,然后小吴不带思考的重新开始了ctrl+c/ctrl+v。半个小时过去了,做的满头大汗,终于搞定了。于是又高高兴兴的演示个项目经理老吴看,你觉得这样Ok不?
项目经理老吴一看确实满足需求了,是的,就是这样,小吴呀,表现不错呦,工资加500。然后老吴就走了。10分钟之后,老吴回来了对你说:小吴呀,这个需求有一个小小的变动哈,方法执行前后打印的内容有变化如下:
方法执行start
方法执行finish
就这样一个小小的需求变动,你发现居然又要在每个方法中添加修改。
顿时小吴就傻眼了,难道真的要在每个方法中这样来修改将“开始”—>start,将“结束”—>finish。(这里只是为了说明这个道理,而没有考虑将打印的信息定义为常量哈,即使定义了常量,如果需求变为了在每个方法前后各打印一句:XXX方法start/finish)。
于是你就找到了技术搭档wojiushimogui,告诉了他你的困惑,wojiushimogui说呀:你傻呀,直接aop帮你做呀。那aop如何来做呢,具体如下:
第一步:在我们的SpringBoot项目中添加aop相关的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:建立处理文件,并添加两个方法分别在每个方法执行之前和之后打印一句话。
package com.wrh.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* Created by wuranghao on 2017/4/14.
*/
@Aspect
@Component
public class HttpAspect {
/**
* @Before 在方法执行之前执行
* */
@Before("execution(public * com.wrh.controller.StudentController.*(..))")
public void log(){
System.out.println("doBefore");
}
/**
* @After在方法执行之后执行
* */
@After("execution(public * com.wrh.controller.StudentController.*(..))")
public void doAfter(){
System.out.println("doAfter");
}
}
既然使用aop就是避免重复代码来,而在注解@Before和@After中的内容完全一样,也可以提取出来,如何提取,如下:
@Aspect
@Component
public class HttpAspect {
@Pointcut("execution(public * com.wrh.controller.StudentController.*(..))")
public void log(){}
/**
* @Before 在方法执行之前执行
* */
@Before("log()")
public void doBefore(){
System.out.println("doBefore");
}
/**
* @After在方法执行之后执行
* */
@After("log()")
public void doAfter(){
System.out.println("doAfter");
}
}
这样,就是通过使用aop完成了在我们控制器StudentController所有方法执行前后各打印一句话的功能。
额外介绍另一个知识点:一般情况下我们不直接使用sout来进行输出,而是借助于org.slf4j.Logger类来进行相关日志的输出。相关使用代码如下:
@Aspect
@Component
public class HttpAspect {
private final static org.slf4j.Logger logger= org.slf4j.LoggerFactory.getLogger(HttpAspect.class);
@Pointcut("execution(public * com.wrh.controller.StudentController.*(..))")
public void log(){}
/**
* @Before 在方法执行之前执行
* */
@Before("log()")
public void doBefore(){
logger.info("doBefore");
//System.out.println("doBefore");
}
/**
* @After在方法执行之后执行
* */
@After("log()")
public void doAfter(){
logger.info("doAfter");
//System.out.println("doAfter");
}
}
日志输出结果如下,从结果中可以看出,其中包括来日志输出时间/类等相关信息,这明显比System.out.println输出相关信息要好得多。
回到今天来研究的需求:记录http请求,其中包括:http请求的url/请求的方法类型/响应该http请求的类方法/IP地址/请求中的参数
具体实现代码如下:
/**
* @Before 在方法执行之前执行
* */
@Before("log()")
public void doBefore(JoinPoint joinPoint){
logger.info("doBefore");
//记录http请求
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//从request中获取http请求的url/请求的方法类型/响应该http请求的类方法/IP地址/请求中的参数
//url
logger.info("url={}",request.getRequestURI());
//method
logger.info("method={}",request.getMethod());
//ip
logger.info("ip={}",request.getRemoteAddr());
//类方法
logger.info("class_method={}",joinPoint.getSignature().getDeclaringTypeName()+
"."+joinPoint.getSignature().getName());
//参数
logger.info("args={}",joinPoint.getArgs());
}
可用如下的代码来获取响应的相关内容
@AfterReturning(returning = "object",pointcut = "log()")
public void doAfterReturning(Object object){
logger.info("student={}",(Student)object);
}
当借助于Postman工具输入如下的请求时,控制台的相关输出结果如下图所示。
参考资料
1、《慕课网-SpringBoot进阶之Web进阶》