当前位置 : 主页 > 编程语言 > java >

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

来源:互联网 收集:自由互联 发布时间:2022-10-15
说明: 日志我这里使用的是slf4j+log4j2,具体的使用方式可以参考​​《SpringBoot入门三,添加log4j2支持》​​​与​​《SpringBoot配置多环境log4j2》​​;如果是使用其他日志组件,正常配置即

说明:

日志我这里使用的是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请求日志_Springboot

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

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

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

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

5.2 异常请求

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

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

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

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

6.扩展知识

6.1 execution(切点函数)

除了返回类型模式、方法名模式和参数模式,其它项都是可选的.语法结构:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

整个表达式可以分为五个部分:

1.execution():表达式主体.

2.第一个*号:表示返回类型,*号表示所有的类型.

3.包名:表示需要拦截的包名

4.第二个*号:表示类名, .*号表示当前包所有的类; ..*表示当前包及所有子包的所有的类

5.*(..) :第三个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数.

表达式

说明

execution(public * *(..))

匹配所有目标类的public方法.

第一个*代表返回类型,

第二个*代表方法名,

..代表任意入参的方法

execution(* *Add(..))

匹配目标类所有以Add为结尾的方法.第一个*代表返回类型,

*Add代表任意以Add为结尾的方法

execution(* com.qfx..*(..))

匹配com.qfx包及子孙包所有类所有方法

execution(* com.qfx.*.*(..))

匹配com.qfx包所有类所有方法

execution(* com.qfx.RoleSerImpl.*(..))

匹配com.qfx包RoleSerImpl类所有方法

execution(* com.qfx..*.Dao.find(..))

匹配com.qfx包下类名后缀为Dao,且方法名以find为前缀的方法

execution(* com.qfx.UserSer+.*(..))

匹配com.qfx包下实现UserSer接口的所有类的所有方法

execution(* info(String, int))

匹配info方法中,第一个参数是String,第二个int的方法

execution(* info(String, *)))

匹配info方法中,第一个参数是String,第二个任意类型

execution(* info(String, ..)))

匹配info方法中,第一个参数是String,后面任意参数

execution(* info(Object+)))

匹配info方法中,方法拥有一个入参,且入参是Object类型或该类的子类


网友评论