SpringBoot入门三十,使用AOP统一处理Web请求日志

时间:2022-10-11 16:26:15

说明:

日志我这里使用的是slf4j+log4j2,具体的使用方式可以参考​​《SpringBoot入门三,添加log4j2支持》​​与​​《SpringBoot配置多环境log4j2》​​;如果是使用其他日志组件,正常配置即可.

1.pom.xml添加引用

因为需要对web请求做切面来记录日志,所以需要入AOP模块

<!-- 引入aop切面支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.添加日志切面类

import java.util.Arrays;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
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 org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect // 将⼀个java类定义为切⾯类
@Component
public class WebLogAspect {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

ThreadLocal<Long> startTime = new ThreadLocal<>();

/**
* <h5>功能:定义一个切入点,可以是一个规则表达式,也可以是一个注解</h5>
* 这里匹配com.qfx.modules.*.controller包下所有类的所有方法
*/
@Pointcut("execution(* com.qfx.modules.*.controller..*(..))")
public void webLog() {
}

// 根据需要在不同位置切入目标方法:
// @Before 前置通知(在目标方法执行之前执行)
// @After 后置通知(在目标方法执行之后执行,无论目标方法执行成功还是出现异常,都将执行后置方法)
// @AfterReturning 返回通知(在目标方法执行成功后才会执行,如果目标方法出现异常,则不执行;可以用来对返回值做一些加工处理)
// @AfterThrowing 异常通知(只在目标方法出现异常后才会执行,否则不执行;可以获取目标方法出现的异常信息)
// @Around 环绕通知(包含上面四种通知方法,环绕通知的功能最全面.环绕通知需要携带ProceedingJoinPoint类型的参数,且环绕通知必须有返回值,返回值即为目标方法的返回值)

/**
* <h5>功能:前置通知</h5>
*
* @param joinPoint
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
startTime.set(System.currentTimeMillis());

// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
logger.info("-------------------- 前置通知处理开始 --------------------");
logger.info("请求IP :{}", request.getRemoteAddr());
logger.info("请求信息:[{}] {}", request.getMethod(), request.getRequestURL().toString());
logger.info("调用方法:{} {}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
logger.info("接收参数:{}", Arrays.toString(joinPoint.getArgs()));
logger.info("-------------------- 前置通知处理结束 --------------------");
}

/**
* <h5>功能:后置通知</h5>
*
* @param returnInfo 特殊参数(returning):设定使用通知方法参数接收返回值的变量名
*/
@AfterReturning(pointcut = "webLog()", returning = "returnInfo")
public void doAfterReturning(Object returnInfo) {
logger.info("-------------------- 后置通知处理开始 --------------------");
// 处理完请求,返回内容
logger.info("返回信息:{}", returnInfo);

logger.info("耗时:{}秒", (System.currentTimeMillis() - startTime.get())/1000d);
logger.info("-------------------- 后置通知处理结束 --------------------");
}

/**
* <h5>功能:异常通知(一般不使用这个)</h5>
* 通过throwing属性指定目标方法出现的异常信息存储在e变量中,在异常通知方法中就可以从e变量中获取异常信息了
* @param point
* @param e 特殊参数(throwing):设定使用通知方法参数接收原始方法中抛出的异常对象名
*/
@AfterThrowing(pointcut = "webLog()", throwing = "e")
public void afterReturning(JoinPoint joinPoint, Exception e) {
logger.error("-------------------- 异常通知处理开始 --------------------");
logger.error("调用方法:{} {}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
logger.error("接收参数:{}", Arrays.toString(joinPoint.getArgs()));
logger.error("异常信息:", e);
logger.error("-------------------- 异常通知处理结束 --------------------");
}
}

3.添加业务类

import org.springframework.stereotype.Service;

@Service
public class TestService {

public String hello(String userName, int age) {
return userName + "你好,欢迎" + age + "的你来到java的世界!";
}

public String eat(String foodName) {
return "发现了一种名字叫[" + foodName + "]的新食物!";
}
}

4.添加控制类

4.1 控制类一

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.qfx.modules.test.service.TestService;

@RestController
@RequestMapping("test")
public class TestCtl {

@Autowired
TestService testService;

@RequestMapping("hello")
public String hello(String userName, int age) {
return testService.hello(userName, age);
}

@RequestMapping("helloTwo/{userName}")
public String helloTwo(@PathVariable("userName") String userName, int age) {
return testService.hello(userName, age);
}
}

4.2控制类二

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.qfx.modules.test.service.TestService;

@RestController
@RequestMapping("test2")
public class TestCtl2 {

@Autowired
TestService testService;

@RequestMapping("eat")
public String eat(String foodName) {
return "发现了一种名字叫[" + foodName + "]的新食物!";
}

@RequestMapping("calc")
public double calc(int a, int b) {
double c = a/b;
return c;
}
}

5.测试

5.1 正常请求

http://localhost/test/helloTwo/张三?age=16

SpringBoot入门三十,使用AOP统一处理Web请求日志

http://localhost/test/hello?userName=李四&age=17

SpringBoot入门三十,使用AOP统一处理Web请求日志

http://localhost/test2/eat?foodName=面包

SpringBoot入门三十,使用AOP统一处理Web请求日志

5.2 异常请求

http://localhost/test2/calc?a=10&b=0

SpringBoot入门三十,使用AOP统一处理Web请求日志

SpringBoot入门三十,使用AOP统一处理Web请求日志

SpringBoot入门三十,使用AOP统一处理Web请求日志