昨天同事用了我写的WSDL对接代码后,整个Spring MVC项目接口总是返回XML格式,而不是以前的JSON格式。我也是第一次遇到这个问题,觉得很好奇,所以帮忙排查了原因。

排查过程:

其中一个接口代码如下:
    @RequestMapping(value = "sms/sendsms")
    @ResponseBody
    public ResultInfo sendSmsHandler(@RequestParam(defaultValue = "") String otherPhones,
    		@RequestParam(defaultValue = "") String msgText, HttpServletRequest request)
            throws Exception{
    	otherPhones = checkAndConvertStr(otherPhones);
    	msgText = checkAndConvertStr(msgText);
    	ResultInfo result = mobileManager.sendSMSByUrlCon(otherPhones,msgText);
        return result;
    }

 

公司的spring mvc项目都是默认返回JSON格式的,因为是前后端分离的架构。但是再集成我的WSDL代码之后,所有标记了@ResponBod注解的接口默认返回的格式变成了XML格式。 所谓的默认是在http请求时,http请求头中的accept为空的时候。如果accept为“application/json” ,则只会返回json格式。
这种问题用排除法是最好的。我先把我的WSDL相关的java代码还原,重新编译后,发现一切又恢复正常了,默认又返回JSON格式了,所以是我的WSDL代码导致的这个问题。这里说下,我的WSDL代码中,只是引入了 jackson-dataformat-xml 这个依赖包来解析XML,实现Bean对象的序列化和反序列化,其他代码没有任何特别,也不涉及Spring配置的修改,pom文件中加入的依赖:
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.8</version>
        </dependency>

 

我猜测是因为pom中引入jackson-dataformat-xml 包导致的这个问题。
 

分析过程

通过在SpringMVC上打断点,跟踪HTTP请求的流程,得到以下分析:
对于SpringMVC来说,当一个Controller执行完,会返回一个对象,SpringMVC最终会根据返回的对象来找到对应的HandlerMethodReturnValueHandler实现类,然后调用实现类的handleReturnValue方法来将这个对象渲染成特定的格式,如json、xml等。
对于添加了@ResponseBody的接口,其会调用
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue,
然后调用org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.web.context.request.NativeWebRequest),
最后调用org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse) ,其代码如下:
	/**
	 * Writes the given return type to the given output message.
	 * @param returnValue 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 {@code Accept} header on
	 * the request cannot be met by the message converters
	 */
	@SuppressWarnings("unchecked")
	protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException {

		Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
		HttpServletRequest servletRequest = inputMessage.getServletRequest();
		List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest); //这里根据Request的accept来获取客户端支持的MediaType,如果没有设置accpet,则为"*/*"
		List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);//根据接口的Produces或者MessageConvert来找到能够生成的MediaType,这个一个关键的方法

		Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>(); //取requestedMediaTypes和producibleMediaTypes互相兼容的交集
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
		if (compatibleMediaTypes.isEmpty()) {
			if (returnValue != null) {
				throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
			}
			return;
		}

		List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
        //MediaType列表进行排序
		MediaType.sortBySpecificityAndQuality(mediaTypes);

		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
            //如果mediaType的类型是具体的(不是 "*/*"),则直接返回。由于我们引入了jackson-dataformat-xml包,导致mediaTypes的第一个MediaType就是application/xml;charset=UTF-8
            //所以isConcrete方法返回true,这就决定了返回的MediaType格式是xml格式
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}
		
        //当我们知道了返回的MediaType为application/xml;charset=UTF-8 后,就需要找到对应的MessageConverter,将对象转换为xml格式输出到http客户端,如果找不到对应的MessageConverter,spring就会抛出异常
		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
				if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
					returnValue = this.adviceChain.invoke(returnValue, returnType, selectedMediaType,
							(Class<HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage);
					if (returnValue != null) {
						((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
						if (logger.isDebugEnabled()) {
							logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
									messageConverter + "]");
						}
					}
					return;
				}
			}
		}

		if (returnValue != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

 

this.mediaTypes中的最后排序:
由于我们引入了jackson-dataformat-xml包,导致mediaTypes的第一个MediaType就是application/xml;charset=UTF-8
而 mediaType.isConcrete方法返回true(因为”application/xml;charset=UTF-8″的类型是具体的(不是 “*/*” 通配符类型)),这就决定了返回的MediaType格式是xml格式。所以,getAcceptableMediaTypes和getProducibleMediaTypes方法决定了mediaTypes,这是两个最关键的方法。我们接下来继续分析下这两个方法。

getAcceptableMediaTypes方法:

功能:根据ContentNegotiationManager配置的协商策略,从Request中获取客户端接受的MediaType。
spring提供了 “org.springframework.web.accept.ContentNegotiationManagerFactoryBean” 工厂bean来生成管理(content negotiation)内容协商策略的ContentNegotiationManager 。所谓的内容协商就是后台接口怎么知道前台想要什么格式的,是json,xml还是其他的,而SpringMVC默认的contentNegotiationManager的协商策略只有三种:
  • 使用Request的URL后缀(扩展名)来实现 (eg .xml/.json) , 默认开启
  • 根据Request中的URL参数来实现(eg ?format=json)  , 默认关闭
  • 根据Request的header中的 Accept属性来决定,默认开启
我们再查看ContentNegotiationManagerFactoryBean类的官方文档:
public class ContentNegotiationManagerFactoryBean 
extends Object implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean

 

Factory to create a ContentNegotiationManager and configure it with ContentNegotiationStrategy instances.

This factory offers properties that in turn result in configuring the underlying strategies. The table below shows the property names, their default settings, as well as the strategies that they help to configure:

As of 5.0 you can set the exact strategies to use via setStrategies(List).

Note: if you must use URL-based content type resolution, the use of a query parameter is simpler and preferable to the use of a path extension since the latter can cause issues with URI variables, path parameters, and URI decoding. Consider setting setFavorPathExtension(boolean) to false or otherwise set the strategies to use explicitly via setStrategies(List).

这个ContentNegotiationManagerFactoryBean类负责生成 ContentNegotiationManager对象和设置默认的内容协商策略
org.springframework.web.accept.ContentNegotiationManagerFactoryBean#afterPropertiesSet:
作用:生成ContentNegotiationManager对象,并给它配置好默认的协商策略。同时将默认的mediaTypes赋值给所有协商策略,用于内容协商时计算
    @Override
    public void afterPropertiesSet() {
        List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>();
		// this.mediaTypes表示springmvc初始化后支持的MediaType类型,并且将mediaTypes塞入到各种默认的协商策略对象中,用于内容协商时计算
        if (this.favorPathExtension) {
            PathExtensionContentNegotiationStrategy strategy;
            if (this.servletContext != null) {
                strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
            }
            else {
                strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
            }
            strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
            if (this.useJaf != null) {
                strategy.setUseJaf(this.useJaf);
            }
            strategies.add(strategy);
        }

        if (this.favorParameter) {
            ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
            strategy.setParameterName(this.parameterName);
            strategies.add(strategy);
        }

        if (!this.ignoreAcceptHeader) {
            strategies.add(new HeaderContentNegotiationStrategy());
        }

        if(this.defaultNegotiationStrategy != null) {
            strategies.add(defaultNegotiationStrategy);
        }

        this.contentNegotiationManager = new ContentNegotiationManager(strategies);
    }

 

由于项目使用的默认配置,我们使用idea的find usage功能,查找下只有org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer这个类调用了ContentNegotiationManagerFactoryBean,而且也仅仅是一个帮助设置ContentNegotiationManager的相关属性。
再接着查找谁调用了ContentNegotiationConfigurer类, 我们看到 WebMvcConfigurationSupport最终会设置ContentNegotiationConfigurer对象的mediaTypes属性,如下:
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#mvcContentNegotiationManager:
    /**
     * Return a {@link ContentNegotiationManager} instance to use to determine
     * requested {@linkplain MediaType media types} in a given request.
     */
    @Bean
    public ContentNegotiationManager mvcContentNegotiationManager() {
        if (this.contentNegotiationManager == null) {
            ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
            configurer.mediaTypes(getDefaultMediaTypes()); //这里获取默认的MediaType
            configureContentNegotiation(configurer);
            try {
                this.contentNegotiationManager = configurer.getContentNegotiationManager();
            }
            catch (Exception ex) {
                throw new BeanInitializationException("Could not create ContentNegotiationManager", ex);
            }
        }
        return this.contentNegotiationManager;
    }

 

mvcContentNegotiationManager方法会通过ContentNegotiationManagerFactoryBean工厂bean创建ContentNegotiationConfigurer对象,并且设置自己的MediaType属性,然后返回一个ContentNegotiationManager的bean对象,而ContentNegotiationConfigurer对象的MediaTypes就是决定request到底需要返回何种格式的关键属性之一。如果ContentNegotiationConfigurer的MediaTypes列表中没有xml类型,那么所有的协商策略都会失败(除非自定义了协商策略),此时SpringMVC就会认为Http请求头的accept是 “*/*”
“getDefaultMediaTypes()”会返回默认的MediaType,我们查看该方法的具体代码:
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getDefaultMediaTypes:
    protected Map<String, MediaType> getDefaultMediaTypes() {
        Map<String, MediaType> map = new HashMap<String, MediaType>();
        if (romePresent) {
            map.put("atom", MediaType.APPLICATION_ATOM_XML);
            map.put("rss", MediaType.valueOf("application/rss+xml"));
        }
        if (jaxb2Present || jackson2XmlPresent) {
            map.put("xml", MediaType.APPLICATION_XML);
        }
        if (jackson2Present || gsonPresent) {
            map.put("json", MediaType.APPLICATION_JSON);
        }
        return map;
    }

 

上面方法中的几个xxxPresent的boolean类型变量决定了默认mediaType有哪几种类型,其赋值代码如下:
    private static boolean romePresent =
            ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean jaxb2Present =
            ClassUtils.isPresent("javax.xml.bind.Binder", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) &&
                    ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean jackson2XmlPresent =
            ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean gsonPresent =
            ClassUtils.isPresent("com.google.gson.Gson", WebMvcConfigurationSupport.class.getClassLoader());

 

上面的代码的意思是,从WebMvcConfigurationSupport的classLoader中查找这些接口类,来确定classpath是否存在这些接口类 ,如果存在,就将对象的MediaType加到默认列表中。
这里以XML为例子:
        
        if (jaxb2Present || jackson2XmlPresent) {//只要jaxb2Present或jackson2XmlPresent任意一个为真,则就加入xml格式
            map.put("xml", MediaType.APPLICATION_XML);
        }

 

我们使用idea的ctrl+shift+N快捷键,分别查看classpath是否有这两个接口:
javax.xml.bind.Binder接口时jdk自带的
接着查看”com.fasterxml.jackson.dataformat.xml.XmlMapper”接口确实存在,存在于我们pom引入的jackson-dataformat-xml依赖包中:
由上面的截图可见,javax.xml.bind.Binder接口是jdk自带的,所以jaxb2Present一定为true,所以无论jackson2XmlPresent是否为真,都会将“application/xml”加入到默认的MediaType列表中。
我们接口url是home/sms/getsmsconfiglist ,  其URL后缀并没有指定返回的MediaType,而且我们的Http请求头中accept也是空的,所以SpringMVC调用this.contentNegotiationManager.resolveMediaTypes()方法时,没法根据SpringMVC默认的协商策略知道http客户端那边想要哪些MediaType,所以 getAcceptableMediaTypes最后返回就是MediaType.ALL (即”*/*”),即认为客户端可以接收所有类型的MediaType
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes
	private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) throws HttpMediaTypeNotAcceptableException { 
		List<MediaType> mediaTypes = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
		return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
	}

 

getProducibleMediaTypes方法

前面分析了getAcceptableMediaTypes方法,该方法让SpringMVC认为客户端接收所有的MediaType。但是,客户端虽然接收所有类型的MediaType,不代表SpringMVC能生成所有的MediaType,这是由getProducibleMediaTypes方法来决定的:
	/**
	 * Returns the media types that can be produced:
	 * <ul>
	 * <li>The producible media types specified in the request mappings, or
	 * <li>Media types of configured converters that can write the specific return value, or
	 * <li>{@link MediaType#ALL}
	 * </ul>
	 */
	@SuppressWarnings("unchecked")
	protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> returnValueClass) {
        //判断Controller的@RequestMapping否设置了produces属性,如果设置了,那肯定就只返回设置的MediaType类型
		Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
		if (!CollectionUtils.isEmpty(mediaTypes)) {
			return new ArrayList<MediaType>(mediaTypes);
		}
		else if (!this.allSupportedMediaTypes.isEmpty()) { //判断spring支持的所有的MediaType,那么就需要看下这个allSupportedMediaTypes值是从哪里来的
			List<MediaType> result = new ArrayList<MediaType>();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				if (converter.canWrite(returnValueClass, null)) {
					result.addAll(converter.getSupportedMediaTypes());
				}
			}
			return result;
		}
		else {
			return Collections.singletonList(MediaType.ALL);
		}
	}

 

给allSupportedMediaTypes赋值的过程:
	public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
		Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
		this.messageConverters = messageConverters;
		this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters);
	}


	/**
     * springmvc会根据提供的MessageConverter来决定支持哪些MediaType,那么我们就需要知道springmvc有哪些MessageConverter了,看messageConverters这个列表是在哪里赋值的。
	 * Return the media types supported by all provided message converters sorted
	 * by specificity via {@link MediaType#sortBySpecificity(List)}.
	 */
	private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) {
		Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>();
		for (HttpMessageConverter<?> messageConverter : messageConverters) {
			allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
		}
		List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes);
		MediaType.sortBySpecificity(result);
		return Collections.unmodifiableList(result);
	}

 

通过不断的 find usage, 最终可以找到WebMvcConfigurationSupport类的getMessageConverters方法里获取所有的MessageConverter:
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#getMessageConverters
	/**
	 * Provides access to the shared {@link HttpMessageConverter}s used by the
	 * {@link RequestMappingHandlerAdapter} and the
	 * {@link ExceptionHandlerExceptionResolver}.
	 * This method cannot be overridden.
	 * Use {@link #configureMessageConverters(List)} instead.
	 * Also see {@link #addDefaultHttpMessageConverters(List)} that can be
	 * used to add default message converters.
	 */
	protected final List<HttpMessageConverter<?>> getMessageConverters() {
		if (this.messageConverters == null) {
			this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
			configureMessageConverters(this.messageConverters); //第一次this.messageConverters是空的,所以继续往下走
			if (this.messageConverters.isEmpty()) {
				addDefaultHttpMessageConverters(this.messageConverters); //初是化默认的HttpMessageConverter列表
			}
		}
		return this.messageConverters;
	}

 

我们看具体的代码:
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHttpMessageConverters:
	/**
	 * Adds a set of default HttpMessageConverter instances to the given list.
	 * Subclasses can call this method from {@link #configureMessageConverters(List)}.
	 * @param messageConverters the list to add the default message converters to
	 */
	protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
		StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
		stringConverter.setWriteAcceptCharset(false);

		messageConverters.add(new ByteArrayHttpMessageConverter());
		messageConverters.add(stringConverter);
		messageConverters.add(new ResourceHttpMessageConverter());
		messageConverters.add(new SourceHttpMessageConverter<Source>());
		messageConverters.add(new AllEncompassingFormHttpMessageConverter());

		if (romePresent) {
			messageConverters.add(new AtomFeedHttpMessageConverter());
			messageConverters.add(new RssChannelHttpMessageConverter());
		}

		if (jackson2XmlPresent) {
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
		}
		else if (jaxb2Present) {
			messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
		}

		if (jackson2Present) {
			messageConverters.add(new MappingJackson2HttpMessageConverter());
		}
		else if (gsonPresent) {
			messageConverters.add(new GsonHttpMessageConverter());
		}
	}

 

上面方法中的几个xxxPresent的boolean类型变量决定了默认mediaType有哪几种类型,其赋值代码如下:
    private static boolean romePresent =
            ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean jaxb2Present =
            ClassUtils.isPresent("javax.xml.bind.Binder", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean jackson2Present =
            ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) &&
                    ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean jackson2XmlPresent =
            ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", WebMvcConfigurationSupport.class.getClassLoader());

    private static final boolean gsonPresent =
            ClassUtils.isPresent("com.google.gson.Gson", WebMvcConfigurationSupport.class.getClassLoader());

 

上面的代码的意思是,从WebMvcConfigurationSupport的classLoader中查找这些接口类,来确定classpath是否存在这些接口类 ,如果存在,就将对象的MediaType加到默认列表中。
这里以jackson2XmlPresent为例子:
        
		if (jackson2XmlPresent) {
			messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
		}

 

我们使用idea的ctrl+shift+N快捷键,分别查看classpath是否有这接口:
查看”com.fasterxml.jackson.dataformat.xml.XmlMapper”接口确实存在,存在于我们pom引入的jackson-dataformat-xml依赖包中:
由上面的截图可见,如果classpath中存在com.fasterxml.jackson.dataformat.xml.XmlMapper接口的实现类,则会将MappingJackson2XmlHttpMessageConverter对象添加到MessageConverter列表中,我们看下MappingJackson2XmlHttpMessageConverter类的代码:
/*
Implementation of HttpMessageConverter that can read and write XML using Jackson 2.x extension component for reading and writing XML encoded data .
By default, this converter supports application/xml, text/xml, and application/*+xml. This can be overridden by setting the supportedMediaTypes property.
The default constructor uses the default configuration provided by Jackson2ObjectMapperBuilder.
Compatible with Jackson 2.1 and higher.
*/

public class MappingJackson2XmlHttpMessageConverter extends AbstractJackson2HttpMessageConverter {

	/**
	 * Construct a new {@code MappingJackson2XmlHttpMessageConverter} using default configuration
	 * provided by {@code Jackson2ObjectMapperBuilder}.
	 */
	public MappingJackson2XmlHttpMessageConverter() {
		this(Jackson2ObjectMapperBuilder.xml().build());
	}

	/**
	 * Construct a new {@code MappingJackson2XmlHttpMessageConverter} with a custom {@link ObjectMapper}
	 * (must be a {@link XmlMapper} instance).
	 * You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
	 * @see Jackson2ObjectMapperBuilder#xml()
	 */
	public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) {
		super(objectMapper, new MediaType("application", "xml", DEFAULT_CHARSET),
				new MediaType("text", "xml", DEFAULT_CHARSET),
				new MediaType("application", "*+xml", DEFAULT_CHARSET));
		Assert.isAssignable(XmlMapper.class, objectMapper.getClass());
	}

	/**
	 * {@inheritDoc}
	 * The {@code objectMapper} parameter must be a {@link XmlMapper} instance.
	 */
	@Override
	public void setObjectMapper(ObjectMapper objectMapper) {
		Assert.isAssignable(XmlMapper.class, objectMapper.getClass());
		super.setObjectMapper(objectMapper);
	}
}

 

根据javadoc的说明,这个类使用Jackson 2.x 来实现xml的读取和写入,默认支持的MediaType为application/xml, text/xml, and application/*+xml 。到此为止,这里我们就算找到问题的关键了。
如果我们pom文件中不引入jackson-dataformat-xml依赖包,则springmvc项目的classpath就找不到com.fasterxml.jackson.dataformat.xml.XmlMapper接口的实现类,则jackson2XmlPresent变量就为false,此时SpringMVC就不会创建并添加MappingJackson2XmlHttpMessageConverter 到messageConverters列表中,   也就不支持生成   application/xml, text/xml, 和application/*+xml 这三种MediaType了
此时的writeWithMessageConverters方法中的mediaTypes列表的如下:

这时”application/json;”是排在第一个,且 isConcrete()返回true,所以最后返回的就会是json格式,而不再是xml格式了。

 

从上面的分析种,我们就能猜到以前SpringMVC项目的接口总是默认返回JSON格式的原因:肯定是因为classpath中有“com.fasterxml.jackson.databind.ObjectMapper (属于jackson-databind包)”和“com.fasterxml.jackson.core.JsonGenerator (属于jackson-core包)”这两个接口的实现类。我们查看公司项目的pom文件,确实发现了引用了jackson的两个包:

            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.6.2</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>2.6.2</version>
            </dependency>

 

解决办法

方法一:
从pom(classpath)去掉jackson-dataformat-xml包,用xstream替代。
方法二:
对于Spring3.2之后的修改协商策略:
XML配置
设置默认的MediaType为“application/json”:
	<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
	<bean id="contentNegotiationManager"
		  class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">

		<property name="defaultContentType" value="application/json" />
	</bean>

 

或者
Java代码配置
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
    }
}

 

参考:
  • https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.html
  • https://www.baeldung.com/spring-httpmessageconverter-rest
  • https://www.baeldung.com/spring-mvc-content-negotiation-json-xml

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.