您的位置:首页 > 其它

【实践】切面打印请求参数

2020-05-07 21:03 453 查看

加打印语句,将请求参数打印出来。后面想想,以后可能还会遇到这样的情况,如果每次遇到,我都去对应的方法中加日志打印,就变成重复工作。并且日志打印跟我们的业务本身没有任何关系。

记录日志网上主要有三种方法:

  1. aop
  2. filter
  3. interceptor

我选择了filter。为什么选择它,因为我觉得它相对于定义切点,然后切点前后处理来说,更加方便;相对于 interceptor, 我更加熟悉这种方式。

 

定义Filter

定义一个 LogFilter  。 里面对 HttpServletRequest  进行拦截,根据对应的 content-type 解析请求参数。主要代码如下

/**
* 功能描述: 打印请求参数
* @author lkb
* @date 2020/5/6
* @param
* @return
*/
@Slf4j
public class LogFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

//日志
doLog(request, response);

// 将request 传到下一个Filter
filterChain.doFilter(request, response);
}

private void doLog(HttpServletRequest request,  HttpServletResponse response){
// 输出请求体
log.info("request. uri = {}, method = {}, requestParam = {}", request.getRequestURI(), request.getMethod(), getRequestParam(request));
//todo 返回结果也可以进行处理
}

private String getRequestParam(HttpServletRequest request){
String requestParam = "";

String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
try {
if(StringUtils.isNotEmpty(requestContentType)){
if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE)
|| requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) {
// xml json
requestParam = getRequestBody(request);
}
else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
// 文件表单提交
requestParam = getFormParam(request);
}else if(requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)){
// 普通表单提交
requestParam = toJson(request.getParameterMap());
}
}else{
// 默认普通表单提交
requestParam = toJson(request.getParameterMap());
}
}catch (Exception e){
log.error("getRequestParam error");
log.error(e.getMessage(),e);
}
return requestParam;
}

...
}

 

然后,注册这个filter

@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean logFilter() {
final FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
final LogFilter logFilter = new LogFilter();
filterRegistrationBean.setFilter(logFilter);
return filterRegistrationBean;
}

}

 

上面两步之后,重启项目,可以看到请求过来后会打印出请求的uri、method、param 。

 

InputStream 只能读取一次

本来以为这样就万事大吉了。但是事实并不是如此。加上上面代码后会发现,再controller 加上的 @requestBody 没有效果,取不到任何数据,并抛出异常,告诉我们请求已经被读取过。 为什么呢?

 

原因很简单。因为在 doLog 中获取请求参数的时候,我们已经将请求的 inputStream 给读取了。读取inputStream 时有一个offset,它表示你从哪里开始读取输入流。因为我们读取了一遍 inputStream,所以offset已经在流的最末端了。我们再去读取,就会发现没有东西可以读了。如果想重复读取 inputStream 就需要每次读取后重置 offset 的值。

 

当然为了方便,我并没有去重新inputStream 中的reset 方法。而是选择,在读取请求后,将请求缓存起来。

 

首先,BufferedServletInputStream 继承自 ServletInputStream。

public class BufferedServletInputStream extends ServletInputStream {

private ByteArrayInputStream inputStream;

public BufferedServletInputStream(byte[] buffer) {
this.inputStream = new ByteArrayInputStream( buffer );
}

@Override
<
56c
/span>public int available(){
return inputStream.available();
}

@Override
public int read(){
return inputStream.read();
}

@Override
public int readLine(byte[] b, int off, int len){
return inputStream.read( b, off, len );
}

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady(
227b
) {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}
}

 

然后,BufferedServletRequestWrapper 继承 HttpServletRequestWrapper。

@Slf4j
public class BufferedServletRequestWrapper extends HttpServletRequestWrapper {

private byte[] buffer;

public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buff[] = new byte[1024];
int read;
while ((read = is.read(buff)) > 0) {
baos.write(buff, 0, read);
}
this.buffer = baos.toByteArray();
}

@Override
public ServletInputStream getInputStream() {
return new BufferedServletInputStream(this.buffer);
}
}

里面使用一个 byte[] buffer 数组将请求缓存起来。

 

最后,在 LogFilter 中 doLog 前,对请求进行包装。

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 备份HttpServletRequest
request = new BufferedServletRequestWrapper(request);

//日志
doLog(request, response);

// 将request 传到下一个Filter
filterChain.doFilter(request, response);
}

经过上诉处理,我们就可以愉快地用日志记录请求参数了。

 

总结

最后总结一下:

1. 记录请求参数日志的方式最好采用切面的思想

2. inputStream 默认只能读取一次,多次读取要重新处理inputStream

3. @requestBody 的原理可以了解一下

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: