记一次springmvc响应string-json被转义斜杠问题

场景

同样的两个子项目一个controller响应string返回的是正常的json,另一个则是被转义后的加了斜杠的json

public String getHSCounterDataByDate(@RequestParam(value = "startdate", required = false) Integer startdate,
                                   @RequestParam(value = "enddate", required = false) Integer enddate) {
                                   // 略......
        return JSON.toJSONString(result, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty);
    }

上面个controller返回的是被转移的json 类似 :== "{“code”:200

排查

起初怀疑是springmvc底层的json转换类将普通的string进行了再次的json化操作
然后查看了其他子项目发现响应同样是string的情况下并没有被转义

然后我就想从spring的HttpMessageConverter来下手看看到底是怎么回事

通过断点我发现
在这里插入图片描述
spring的源码里面这个地方会填充spring默认的json配置
我打了断点看到列表里面是一个 StringHttpMessageConverter
和 GsonHttpMessageConverter 这时我感觉不太对劲
立马去看了其他子项目的情况发现并没有gson这个转换类而是jackson,这样才是符合预期的,spring默认应该是jackson

然后我就通过断点找了半天…心累

最终我发现了这句话

AnyNestedCondition 1 matched 1 did not; NestedCondition on GsonHttpMessageConvertersConfiguration.PreferGsonOrJacksonAndJsonbUnavailableCondition.JacksonJsonbUnavailable NoneNestedConditions 0 matched 2 did not; NestedCondition on GsonHttpMessageConvertersConfiguration.JacksonAndJsonbUnavailableCondition.JsonbPreferred @ConditionalOnProperty (spring.mvc.converters.preferred-json-mapper=jsonb) found different value in property ‘spring.mvc.converters.preferred-json-mapper’; NestedCondition on GsonHttpMessageConvertersConfiguration.JacksonAndJsonbUnavailableCondition.JacksonAvailable @ConditionalOnBean (types: org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; SearchStrategy: all) did not find any beans of type org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; NestedCondition on GsonHttpMessageConvertersConfiguration.PreferGsonOrJacksonAndJsonbUnavailableCondition.GsonPreferred @ConditionalOnProperty (spring.mvc.converters.preferred-json-mapper=gson) found different value in property ‘spring.mvc.converters.preferred-json-mapper’
本人英语不好自行翻译吧

我猜想大致就是并没有配置jackson和jsonb,而是配了一个不可识别的配置所以导致spring最终选用了gson转换类
这时我赶紧去看配置文件发现

spring.mvc.converters.preferred-json-mapper=fastjson

这不纯纯捣乱吗
因为我看了源码并不支持这种配置
spring官方只支持jackson,gson,jsonb三种
其他的需要实现WebMvcConfigurer接口将对应的转换类add进总的列表
如果像上面一样preferred-json-mapper配置了fastjson这个配置,那么最终注入的其实是gson

重点

那为什么gson就会进行转义呢,这个时候就要说到一个header属性了
accept 就是请求头里面告诉服务端你想要接收的类型是什么
一般情况下我们都不会特殊指定这个字段
但是spring的服务端,在响应请求的时候会根据这个数据去获取满足条件的转换类
我们默认传"/*"的时候那么服务端就拿到了所有的满足return类型的转换类
并且根据他的类型进行排序如下图,范围小的在前面

这张是fastjson的时候spring对accept的处理顺序, gson应该也一样,会把application/json放在前面

在这里插入图片描述spring会根据这个进行顺序遍历, 拿到当前服务所有的转换类(注: 转换类按顺序进行遍历),我们的gson会被放在最前面,具体看源码
所以gson对应的application/json满足,那么直接使用gson进行转换,转换后返回,那么结果就是被转义了

而如果使用jackson的话顺序是这样的

在这里插入图片描述
这个排序是由spring提供规则,各自的转换器实现自己的顺序,
不得不说一句,有多大能力干多大事,fastjson和gson处理string会转义就不要把自己放在前面,现实是揽的活多却干不好

转换器列表jackson的情况,可以看到jackson在后面

其他的自己看一下 AbstractMessageConverterMethodProcessor类
在这里插入图片描述
这样的话accept 就命中了text/plain那么就会从转换器列表中按顺序取出StringHttpMessageConverter进行转换数据,那么string就不会被再次转义了

所以最终就是
jsckson的时候响应string并不会再次转义而gson或者fastjson会
所以我想最好不要去改动spring默认的json转换类jsckson
可能平常大家用fastjson比较多,但是即使代码中使用就算了,在mvc这边不建议修改,不然可能会出一些想不到的问题

疑问

可能有的人会问 – 那么如果每次都是text/plain的话如果我直接返回对象不返回string岂不是最终会出问题,
结果是不会的

getProducibleMediaTypes(request, valueType, targetType)

spring的这行代码决定了mediaTypesToUse的列表数据, 这个方法会根据你的controller的实际return类型和你定义的返回类型双重决定最终可以返回的类型列表
如果你定义的返回是对象的情况mediaTypesToUse根本不会存在text/plain

看到这里不得不感叹一句,spring牛逼

最终我删掉了preferred-json-mapper这个配置,响应的结果就正常了,此时spring就走了默认的jackson

要说为什么随便配一个东西spring的json框架就变成了gson
这个我想可能是历史原因吧, 毕竟最开始spring好像支持的是gosn如果我没记错的话

同样的不建议在controller中直接返回string,类型的json,这不是脱裤子放屁吗
但是由于是历史原因,这些代码已经写了很久了,我并不能去修改它,
而上个版本进行了框架结构的大升级,另一位同事配了preferred-json-mapper=fastjson 最终屎盆子要扣到我的网关中台头上,我气不过于是就翻了翻看看

如果非要选择fastjson或者gson, 并且要保重string类型正常返回

先上代码

@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat("yyyy-MM-dd");
        config.setCharset(Charset.forName("UTF-8"));
        config.setSerializerFeatures(
                //输出类名
                SerializerFeature.WriteClassName,
                //输出map中value为null的数据
                SerializerFeature.WriteMapNullValue,
                //json格式化
                SerializerFeature.PrettyFormat,
                //输出空list为[],而不是null
                SerializerFeature.WriteNullListAsEmpty,
                //输出空string为"",而不是null
                SerializerFeature.WriteNullStringAsEmpty
        );
        converter.setFastJsonConfig(config);
        ArrayList<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(mediaTypes);
        for (int i = 0; i < converters.size(); i++) {
            HttpMessageConverter<?> httpMessageConverter = converters.get(i);
            if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
                converters.remove(i);
                converters.add(i,converter);
            }
        }
    }
}

必须通过实现的这种方式来添加fastjson, 另一种注入的方式由于spring不支持fastjson最终会导致在排序的时候fastjson总是在前边,这样方式是追加在列表后面,提换jackson
具体参见源码MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
会涉及到排序,因为string和fastjson的q是一样的(理解为参与排序的值)
那么谁原本在前面,排序之后还是在前边,
所以我们上面就是为了让fastjson排在string后面

上面说的spring不支持fastjson才这么操作,gson可以用规范的方式

那么spring支持gson这么操作不够规范

1 实际上需要排除jackson的相关包

com.fasterxml.jackson.databind.ObjectMapper

2 添加对应配置

spring.mvc.converters.preferred-json-mapper=gson

这两步即可,这个我没有实践,理论上是可以的,
我尝试了一下,由于引用jackson的jar的地方太多了,不止spring相关的,所以我最后放弃了,这样排除jar下去最终可能引起其他问题,上面这种方式比较靠谱影响面较小

最后说一下传统mvc项目的处理办法供参考,可以对应boot项目

bean顺序很重要

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
		<property name="messageConverters">
			<list>
				<bean class="org.springframework.http.converter.StringHttpMessageConverter">
					<property name="supportedMediaTypes">
						<list>
							<value>text/plain;charset=utf-8</value>
							<value>application/json;charset=UTF-8</value>
							<value>application/soap+xml; charset=utf-8</value>
							<value>text/html;charset=UTF-8</value>
							<value>application/x-www-form-urlencoded;charset=UTF-8</value>
						</list>
					</property>
				</bean>
				<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
			</list>
		</property>
	</bean>

Respect !!!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很抱歉,我是一名语言模型AI,无法进行实际的编程工作。但是,我可以提供一些指导和建议,希望能帮到你。 首先,我们需要了解一下 SpringMVC、MyBatis-Plus、Layui 这三个技术的基本概念和用途。 SpringMVC 是一种基于 MVC 设计模式的 Web 框架,它可以帮助我们快速开发 Web 应用程序,并能够很好地处理请求和响应。 MyBatis-Plus 是 MyBatis 的增强工具,它在 MyBatis 的基础上提供了更加方便的 CRUD 操作、分页、逻辑删除等功能。 Layui 是一款基于 Web 的前端 UI 框架,它提供了丰富的组件和样式,使得我们能够快速地搭建出漂亮的页面。 在搭建一个后台管理系统时,我们可以按照如下步骤进行: 1. 创建 SpringMVC 项目,引入需要的依赖和配置文件。 2. 配置 MyBatis-Plus,创建数据库表和实体类,编写 DAO 层和 Service 层的代码。 3. 创建 Controller 层,处理请求和响应,并与 Service 层进行交互。 4. 使用 Layui 框架,搭建前端页面,与后台进行交互。 在实现过程中,我们需要注意一些问题,如: 1. 数据库的设计和表结构的优化,以提高系统的性能和稳定性。 2. 后台代码的安全性和可靠性,如 SQL 注入、XSS 攻击等问题。 3. 前端页面的用户体验和可用性,如响应速度、布局合理性、交互效果等问题。 以上是一个简单的编程思路,具体实现还需要参考官方文档和相关教程。希望这些指导和建议能够帮到你,祝你编写成功!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值