Springboot升级后@RequestBody封装出现乱码问题的解决

问题分析:

代码没有动过,但是由于近期产品方升级了一次springboot,所以问题可能产生在这里。乱码问题都是字符编码不统一造成的。A系统都是统一默认的UTF8编码,那问题出在B系统推送来的数据上。

然后让B系统的同事检查了下发送的数据,他们说用的数据编码正常,不过请求是用的GBK编码。

What?编码不一样确实会乱码,可是为什么乱码在这个时候出现。那既然这样,我们把request的请求的编码手动设置成UTF8的应该可以了。下面呢,我将分3个阶段,用代码演示一下效果。

刚开始没有问题阶段

demo是用springboot构建的,我忘了没升级前是多少版本了,就找一个比较早的1.5.5.RELEASE做为例子,编码为UTF8。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.5.RELEASE</version>
    <relativePath/> 
</parent>

代码示例1:

演示HelloController.java

@Controller
@RequestMapping("/hello")
public class HelloController {
    @PostMapping("/test")
    @ResponseBody
    public UserDO getUser(@RequestBody UserDO user) {
        System.out.println(user.getUserId());
        System.out.println(user.getUserName());
        return user;
    }
}

UserDO.java

@Data
public class UserDO {
    private Integer userId;
    private String userName;
}

POSTMAN 工具模拟请求

B系统的同事说请求使用的GBK发的,那就模拟当时的场景

在此请求头上加上gbk的编码,现在我们看结果是正常返回的。同样的代码,我们升级了下springboot到2.3.2.RELEASE。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath/>
</parent>

同样的代码不动,发送请求看下结果:

现在是乱码了,这就是生产上出的问题。按照常规思路解决下,在我们的请求上对这个URL进行过滤,设置一下编码格式就ok,那接着加上过滤器:


import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/hello/test", filterName = "CharsetFilter")
public class CharsetFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("UTF-8");
        filterChain.doFilter(servletRequest, servletResponse);//交给下一个过滤器或servlet处理
    }

    public void destroy() {
        /*销毁时调用*/
    }
}

然后在run一下,发现仍然不可以,这个过滤器没有起作用。为什么springboot升级后就不可以了。问题就出在了这里,很明显,springboot升级后,会按照请求头设置的字符编码来对字节流解码,之前并没有这么做。而之前功能是正常的原因其实在B系统上,虽然他们在请求头加上了charset=gbk,但是传过来的是UTF8编码的字符,在springboot没有关注请求头的时候,是按照当前默认的字符解码,这是没问题的,两者都是相同编码。

问题找到了,那就很简单。我们把接收的字符用GBK解码后再用UTF8编码。


@Controller
@RequestMapping("/hello")
public class HelloController {

    @PostMapping("/test")
    @ResponseBody
    public UserDO getUser(@RequestBody UserDO user) throws UnsupportedEncodingException {
        //转换编码
        String name = new String(user.getUserName().getBytes("GBK"), "UTF-8");
        user.setUserName(name);
        System.out.println(user.getUserId());
        System.out.println(user.getUserName());
        return user;
    }
}

现在看结果:

功能恢复正常了。

你以为这就结束了,完美修复了一个bug,太天真。下面看看我不叫“张三”,我叫“张三丰”,会有怎么样的结果呢?

哇塞,太神奇了,这一定是在逗我。

为什么会这样呢,这是跟字符编码有关系。感兴趣的同学可以搜一下,其实乱码的本质就是:读取二进制的时候采用的编码和最初将字符转换成二进制时的编码不一致。

所以这个问题原因就是:

GBK一个字符2个字节,UTF-8一个字符3个字节,当用GBK去读(解码)UTF-8编码后的内容,当UTF-8字符是奇数个的时候,GBK解码之后会多出一位字节,那只能用'?'字节(63)来替换,所以即使再转码也会出现最后一个中文字符是?的乱码问题

所以解决这个问题很简单了,直接改用inputStream直接读byte,之后再转为utf-8。

@Controller
@RequestMapping("/hello")
public class HelloController {
    @PostMapping("/test")
    @ResponseBody
    public UserDO getUser(HttpServletRequest request) throws IOException {
        //读取字节流转成字符串
        String content = charReader(request);
        //json字符串转成java对象
        UserDO user = JSON.parseObject(content, UserDO.class);
        return user;
    }
    //字符串读取
    static String charReader(HttpServletRequest request) throws IOException {
        BufferedReader br = request.getReader();
        String str, wholeStr = "";
        while ((str = br.readLine()) != null) {
            wholeStr += str;
        }
        return wholeStr;
    }
}

至此这个bug才是完全的修改好了,这个尴尬的问题是因为B系统请求头用的说用GBK编码,结果请求体确实UTF8,好比说现在考的中文听力,你给我放英语,然后我在一个字一个字用汉字把英语音洗出来,你说的library,我写的“来不弱瑞”,这怎么乱码呢?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值