当前位置 博文首页 > 韩超的博客 (hanchao5272):spring-mvc引入jackson-dataformat-x

    韩超的博客 (hanchao5272):spring-mvc引入jackson-dataformat-x

    作者:[db:作者] 时间:2021-09-05 16:14

    概述

    工程是一个spring-mvc,使用jackson作为RestController序列化组件,一切都相安无事。
    直到有一天添加依赖的时候,间接引入了jackson-dataformat-xml,惊奇的发现部分接口的返回竟然从json变成了xml….

    带着满脸奔跑的草泥马,不禁提出了3个问题:

    • 为什么原来是好的?
    • 为什么只有部分接口的json变成了xml?
    • 我该怎么解决这个问题?

    问题1 为什么原来是好的

    既然突然变成了xml,我又没有修改spring序列化相关配置,那么肯定和spring的自动发现有关系了。

    打开pom准备看看有没有新增的xml相关的组件,果然有一个jackson-dataformat-xml

    继续探寻问题的本源…

    问题2 为什么只有部分接口的json变成了xml

    统计了一下异常接口和正常接口的规律,发现正常接口都是以.json结尾,异常接口没有任何后缀,那么6,给个后缀spring就知道我要返回json?

    那没理由不给后缀猜不到啊!跟了一下spring的源码,发现spring确实尽力了…

    打开org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor,让我们来看第180行至第210行。

    版本说明:spring-webmvc-4.3.22.RELEASE.jar

    	/**
    	 * Writes the given return type to the given output message.
    	 * @param value the value to write to the output message
    	 * @param returnType the type of the value
    	 * @param inputMessage the input messages. Used to inspect the {@code Accept} header.
    	 * @param outputMessage the output message to write to
    	 * @throws IOException thrown in case of I/O errors
    	 * @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated
    	 * by the {@code Accept} header on the request cannot be met by the message converters
    	 */
    	@SuppressWarnings("unchecked")
    	protected <T> void writeWithMessageConverters(T value, MethodParameter returnType,
    			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
    			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
    		//...
    		//Line 183 : 从HttpServletRequest中获取媒体类型
    		List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
    		//Line 184 : 从request mappings中获取媒体类型
    		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
    
    		if (outputValue != null && producibleMediaTypes.isEmpty()) {
    			throw new IllegalArgumentException("No converter found for return value of type: " + valueType);
    		}
    
    		//Line 189 : 遍历两个媒体类型集合,构成 兼容的媒体类型
    		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
    		for (MediaType requestedType : requestedMediaTypes) {
    			for (MediaType producibleType : producibleMediaTypes) {
    				//Line 193 : 兼容判断
    				if (requestedType.isCompatibleWith(producibleType)) {
    					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
    				}
    			}
    		}
    		if (compatibleMediaTypes.isEmpty()) {
    			if (outputValue != null) {
    				throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
    			}
    			return;
    		}
    
    		List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
    		//Line 206 : 根据MediaType的特异性(主要标准)和质量(次要标准)进行排序
    		MediaType.sortBySpecificityAndQuality(mediaTypes);
    
    		MediaType selectedMediaType = null;
    		for (MediaType mediaType : mediaTypes) {
    			if (mediaType.isConcrete()) {
    				selectedMediaType = mediaType;
    				break;
    			}
    			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
    				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
    				break;
    			}
    		}
    

    为什么后缀带.json的接口的没问题呢?

    因为第183行,由接口后缀推断出了请求的requestedMediaTypesapplication/json,而没有任何后缀,只能推断出来都接受。

    那为什么加上依赖之后就都有问题了呢?

    因为之前第193行并不包括application/xml,所以无论什么接口,能返回json的都返回json

    加上依赖之后,在第206行排序过程中,虽然application/jsonapplication/xml是相等的,但由于这个排序是稳定排序(相等值顺序不变),而且application/xml的记载顺序更先,因此总是选中了application/xml

    问题3 我该怎么解决这个问题呢

    方式一

    指定一个默认配置,比如这样

    <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/> 
    
    <bean id="contentNegotiationManager" 
          class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> 
        <property name="defaultContentType" value="application/json" /> 
    </bean>
    

    方式二

    如果不需要这个包,可以直接通过排除依赖(exclusion)的方式避免此问题。


    原文链接:https://blog.csdn.net/liang16286/article/details/80091466

    cs