Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

之前项目上突然遇到个这个问题,显示后台接口报这个错:

Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.math.BigDecimal from String "1,122.00": not a valid representation at [Source: (PushbackInputStream); line: 23, column: 21] (through reference chain: com.ufgov.ar.modules.bill.bean.vo.ArBillVo["arBill"]->com.ufgov.ar.modules.bill.bean.ArBill["billMoney"])

Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

大致意思就是说:ArBill实体类里的 billMoney 字段是BigDecimal类型,但参数中该值为1,122.00 ,不是数值类型的导致反序列化报错了,遇到这个情况我想着这块是不是可以通过过滤器在后端这块对金额类型的数据进行过滤处理呢?

二、简介

针对上述说的问题,因为我想着采取过滤器进行数据过滤,所以本文主要讲解的是spring-boot过滤器读取body里的数据,因为我这块只针对post并且content-type为application/json的请求,不考虑其他的请求方式。

三、HttpServletRequestWrapper的使用

此处我们可以自定义类继承 HttpServletRequestWrapper 类 (该类继承了ServletRequestWrapper),重写 getReader() 方法进行处理,因为在controller 使用@RequestBody 在反序列化到对象数据读取就是通过该方法读取,所以可以通过在这修改数据达到最终过滤数据的目的。

此处贴两张图可以明确看到接受请求时的执行顺序:

Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容
Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

废话不多说,直接贴过滤器读取body里内容的代码

首先需要在启动类增加@ServletComponentScan 这样过滤器才会生效;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

复制代码

过滤器代码:

import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.HttpMethod;
import java.io.IOException;

/**
 * @ClassName: HttpReplaceFilter
 * @Description: 过滤器
 * @Date: 2020/6/16 14:37
 */
@WebFilter(filterName = "httpReplaceFilter", urlPatterns = "/*")
@Component
public class HttpReplaceFilter implements Filter {

    @Autowired
    private IArBillCharacterService arBillCharacterService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String method = httpServletRequest.getMethod();
        String contentType = httpServletRequest.getContentType();
        if (StrUtil.isNotEmpty(contentType)) {
            contentType = contentType.toLowerCase();
        }
        // 该方法处理 POST请求并且contentType为application/json格式的
        if (HttpMethod.POST.equalsIgnoreCase(method) && StrUtil.isNotEmpty(contentType) && contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
            servletRequest = new BodyRequestWrapper(httpServletRequest);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
复制代码

继承 HttpServletRequestWrapper 重写getReader代码:

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.ws.rs.HttpMethod;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @ProjectName: ar-server
 * @Package: com.ufgov.ar.conf.filter
 * @ClassName: BodyRequestWrapper
 * @Author: tianmengwei
 * @Description: 读取Request body里的内容信息
 * @Date: 2020/6/16 14:42
 */
public class BodyRequestWrapper extends HttpServletRequestWrapper {


    private byte[] body;

    public BodyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        String method = request.getMethod();
        String requestURI = request.getRequestURI();
        requestURI = requestURI.replaceAll("[^A-Za-z//d]", "");

        boolean exist = CommonEnums.FilterUrlEnum.exist(requestURI);
        // 由于request并没有提供现成的获取json字符串的方法,所以我们需要将body中的流转为字符串
        BufferedReader reader = request.getReader();
        StringBuilder stringBuilder = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
        String json = stringBuilder.toString();
        if (exist && HttpMethod.POST.equalsIgnoreCase(method)) {
            // 数据处理
            
        } else {
            body = json.getBytes();
        }
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    /**
     * @param
     * @description 在使用@RequestBody注解的时候,其实框架是调用了getInputStream()方法,所以我们要重写这个方法
     * @date 2020/6/16 14:45
     */
    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);

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

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }

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

}

复制代码

可以在上述里获取的json字符串进行数据过滤然后在返回,即可满足我现在的需求,此处代码只是涉及post请求读取body数据的操作。

四、过滤器中的编码问题:

功能实现一段时间后,突然有项目在实施的时候反馈说接口报错了,如下:

org.springframework.http.converter.HttpMessageConversionException: JSON conversion problem: Invalid UTF-8 start byte 0xb9; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Invalid UTF-8 start byte 0xb9

Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

大致意思就是说:ArBill实体类里的 coName 字段值在过滤器那解析的时候报错了,我刚开始看以为是参数的汉字是乱码了,但看了入参发现都是正常的,这个时候我想可能是启动脚本设置编码有关,实施同学给我发了启动脚本,看了确实是脚本启动设置的编码是-Dfile.encoding=GBK,然后我本地设置了该编码集的参数,复现了这个问题:

idea可以通过在这里配置参数:

Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

此处我将读取请求body里的代码注释了,spring在处理这块的编码集是根据请求头里的UTF-8编码读取,此时我看数据库存储的中文也是正常的;

Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容
Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

在此处我设置编码集合系统设置的一致,数据存储到数据库和过滤器读取这块都正常。

Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容
Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

那么问题来了,其实我过滤器读取这块需要主动设置编码集和系统的一致,否则就会出错,但抛开过滤器这块处理,spring在读取的时候按照UTF-8编码读取,然后在哪做了处理呢,数据存储都是正常的?但我查看源码没有找到此处设置,就感觉让我很费解?有遇到该问题的大佬请求指导!

原文 

https://juejin.im/post/5efc014a5188252e58582b01

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Spring-Boot过滤器通过HttpServletRequestWrapper读取request的body内容

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址