SpringBoot框架中提供了Aspect切面技术来供我们进行使用,Aspect技术是一种通过预编译和运行期动态代理的统一维护技术。
实际的使用过程中,Aspect技术基本能够和业务逻辑相互独立。
简而言之、就是在请求接口的过程管道中横切一下,然后添加增强信息。
@Aspect:(切面)用来将一个类声明成为Aspect类;
@Pointcut:(切点)切面与业务逻辑相交的点叫做切点;
JoinPoint:(连接点)关键节点、通过连接点来获取信息
ProceedingJoinPonit:(进行连接点)关键节点、获取信息;
使用通知之前我们首先需要自定义一个方法、将其作为切入点
/**
* 定义一个切点,后续通知方法将会使用该节点来进行获取
* 将Controller层中的所有方法作为切面与业务逻辑交互点
*/
@Pointcut("execution(public * com.song.*.controller..*Controller.*(..))")
public void controllerPointcut(){}
@Before:在被请求的方法执行之前进行Advice增强。
/**
* 前置通知
* 在执行业务代码之前进行信息处理
*
* @param joinPoint 切点
*/
@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint){
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
// 打印请求信息
logger.info("-----Aspect 开始-----");
logger.info("请求地址:{} {}", request.getRequestURL().toString(), request.getMethod());
logger.info("类名方法:{} {}", signature.getDeclaringTypeName(), name);
logger.info("远程地址:{}", request.getRemoteAddr());
// 从request中获取IP并设置到线程本地变量
RequestContext.setRemoteAddr(getRemoteIp(request));
// 定义请求参数
Object[] args = joinPoint.getArgs();
// logger.info("请求参数:{}", JSONObject.toJSONString(args));
// 循环多个参数
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++){
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile){
continue;
}
arguments[i] = args[i];
}
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "file"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
logger.info("请求参数:{}", JSONObject.toJSONString(arguments, excludefilter));
}
@Around:在整个请求方法过程中执行,获取请求结果等信息。
/**
* 环绕通知
* 业务内容前面执行一些信息、业务内容后面再执行一些信息
*
* @param proceedingJoinPoint
*
* @return 返回请求方法返回的结果
*/
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取返回结果
Object result = proceedingJoinPoint.proceed();
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "file"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
logger.info("返回结果:{}", JSONObject.toJSONString(result, excludefilter));
logger.info("-----Aspect 结束 耗时:{}ms-----", System.currentTimeMillis() - startTime);
return result;
}
package com.song.wiki.aspect;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.song.wiki.utils.RequestContext;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
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;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* ClassName: SpringBoot切面控制类
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
*
* SpringBoot切面(面向切面编程)
*
* @Aspect: 将该类标注为一个切面类
* @Component 为该类添加组件扫描注解、将其交给SpringBoot进行管理
*
**/
@Aspect
@Component
public class LogAspect{
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
/**
* 定义一个切点,后续通知方法将会使用该节点来进行获取
* 将Controller层中的所有方法作为切面与业务逻辑交互点
*/
@Pointcut("execution(public * com.song.*.controller..*Controller.*(..))")
public void controllerPointcut(){}
/**
* 前置通知
* 在执行业务代码之前进行信息处理
*
* @param joinPoint 切点
*/
@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint){
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
// 打印请求信息
logger.info("-----Aspect 开始-----");
logger.info("请求地址:{} {}", request.getRequestURL().toString(), request.getMethod());
logger.info("类名方法:{} {}", signature.getDeclaringTypeName(), name);
logger.info("远程地址:{}", request.getRemoteAddr());
// 从request中获取IP并设置到线程本地变量
RequestContext.setRemoteAddr(getRemoteIp(request));
// 定义请求参数
Object[] args = joinPoint.getArgs();
// logger.info("请求参数:{}", JSONObject.toJSONString(args));
// 循环多个参数
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++){
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile){
continue;
}
arguments[i] = args[i];
}
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "file"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
logger.info("请求参数:{}", JSONObject.toJSONString(arguments, excludefilter));
}
/**
* 使用Nginx做反向代理,需要使用该方法才能获取到真实的远程IP
* 因为前后端分离后,所有用户的操作都会先到nginx中,然后再转给SpringBoot
*
* @param request
*
* @return
*/
private String getRemoteIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 环绕通知
* 业务内容前面执行一些信息、业务内容后面再执行一些信息
*
* @param proceedingJoinPoint
*
* @return 返回请求方法返回的结果
*/
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取返回结果
Object result = proceedingJoinPoint.proceed();
// 排除字段,敏感字段或太长的字段不显示
String[] excludeProperties = {"password", "file"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
excludefilter.addExcludes(excludeProperties);
logger.info("返回结果:{}", JSONObject.toJSONString(result, excludefilter));
logger.info("-----Aspect 结束 耗时:{}ms-----", System.currentTimeMillis() - startTime);
return result;
}
}
使用过滤器来做日志处理时,需要实现Filter接口,覆写Filter里面的init方法和doFilter方法即可。
在实际的使用过程中,我们一般使用Aspect切面技术来进行日志的处理,而不推荐使用Filter来过滤日志信息。
package com.song.wiki.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* ClassName: Wiki日志过滤器
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
*
**/
@Component
public class LogFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 打印请求信息
HttpServletRequest request = (HttpServletRequest) servletRequest;
logger.info("-----LogFilter开启-----");
logger.info("请求地址:{} {}", request.getRequestURL().toString(), request.getMethod());
logger.info("远程地址:{}", request.getRemoteAddr());
long startTime = System.currentTimeMillis();
filterChain.doFilter(servletRequest, servletResponse);
logger.info("-----LogFilter结束耗时:{}ms-----", System.currentTimeMillis() - startTime);
}
}
SpringBoot中提供拦截器功能中,通过自定义拦截器类,实现HandlerInterceptor处理程序拦截器栏进行日志的处理。
自定义拦截器、实现HandlerInterceptor处理程序拦截器,实现其中的preHandle预处理方法与posthandle后处理方法来进行请求接口日志处理。
package com.song.wiki.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ClassName: SpringBoot拦截器
* Author: 挽风
* Date: 2020
* Copyright: 2020 by 挽风1.0版本
* Description:
*
* 作用于登录校验、权限校验、请求日志打印等
*
**/
@Component
public class LogInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 打印请求信息
logger.info("-----LogInterceptor 开始-----");
logger.info("请求地址:{}{}", request.getRequestURL().toString(), request.getMethod());
logger.info("远程地址:{}", request.getRemoteAddr());
long startTime = System.currentTimeMillis();
request.setAttribute("requestStartTime", startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
long startTime = (long) request.getAttribute("requestStartTime");
logger.info("-----LogInterceptor 结束 耗时:{}ms-----", System.currentTimeMillis() - startTime);
}
}