spring的Controller的方法总的来说返回方式有两种:一种是view, 另一种是converter,通过@ResponseBody来注解方法,
这里主要讲述注解方式,@RequestMapping的 headers 和produces参数设置不同,返回处理略有不同,这里主要讲述下accept和produces的各种配置及优先级类型。
一. @ResponseBody 注解是如何处理消息的
首先找到ResponseBody的处理类:
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor,
继承了 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor
方法writeWithMessageConverters 处理返回结果:
/**
* 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 = returnValue.getClass();
HttpServletRequest servletRequest = inputMessage.getServletRequest();
// 取请求中accept的类型,无值为*/*
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
// 见下面代码片段
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
// 匹配兼容的类型
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
for (MediaType r : requestedMediaTypes) {
for (MediaType p : producibleMediaTypes) {
if (r.isCompatibleWith(p)) {
compatibleMediaTypes.add(getMostSpecificMediaType(r, p));
}
}
}
// 匹配不到就抛返回类型不支持
if (compatibleMediaTypes.isEmpty()) {
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
}
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
// 按quality 排序
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;
}
}
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> messageConverter : messageConverters) {
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
if (logger.isDebugEnabled()) {
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
messageConverter + "]");
}
return;
}
}
}
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
获取可生产的类型
/**
* 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) {
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<MediaType>(mediaTypes);
}//当请求中accept没有设置,或者@RequestMapping注解中没有配置header的accept和produces,
else if (!allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : messageConverters) {
//取spring配置中所有converter支持的MediaType
if (converter.canWrite(returnValueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}
else {
return Collections.singletonList(MediaType.ALL);
}
}
二. 示例代码:
controller 片段:
@Controller
@RequestMapping(URIConstant.SERVICE_CONFIG)
public class ConfigController {
//设置produces
@RequestMapping(value = "/queryConfig", method = RequestMethod.GET,<strong> produces = { "application/xml",
"application/json" }</strong>)
@ResponseBody
public ResultVo<Config> queryConfig(
@ModelAttribute CommonParameterVo commonParameterVo,
@RequestParam(value = "key", required = true) String key)
throws Exception {
ResultVo<Config> resultVo = new ConfigVo();
Config config = new Config();
config.setConfigKey(key);
config.setConfigValue("test");
resultVo.setData(config);
setLogParameters(resultVo, "{key:" + key + "}");
return resultVo;
}
//设置headers的Accept
@SuppressWarnings("rawtypes")
@RequestMapping(value = "/queryConfigs", method = RequestMethod.GET, <strong>headers = "Accept=<span style="color:#FF9966;">application/json</span>,application/xml"</strong>)
@ResponseBody
public ResultVo queryConfigs(@ModelAttribute CommonParameterVo commonParameterVo) throws Exception {
ResultVo<ListVo> resultVo = new ResultVo<ListVo>();
ListVo<Config> configs = new ListVo<>();
Config config = new Config();
config.setConfigValue("test");
config.setConfigKey("test");
configs.add(config);
resultVo.setData(configs);
setLogParameters(resultVo, configs);
return resultVo;
}
// 没有设置 header accept 或者produces
@SuppressWarnings({ "rawtypes", "unchecked" })
@RequestMapping(value = "/addConfig", method = RequestMethod.POST)
@ResponseBody
public ResultVo addConfig(@ModelAttribute CommonParameterVo commonParameterVo, @RequestBody Config config)
throws Exception {
ResultVo resultVo = new ConfigVo();
// TODO 调用业务处理。 省略
resultVo.setData(config);
// 记录日志
setLogParameters(resultVo, config);
return resultVo;
}
}
当设置 accept 或者produces 优先按设置的处理,
当没有设置 ,按所有converter支持的MediaType来选择处理,
MediaType.sortBySpecificityAndQuality(mediaTypes);
按Quality来排序,由于默认都是1,优先级都是一样, 所以
<strong>produces = { "application/xml", "application/json" }
</strong><pre name="code" class="java"><strong>或者 headers = "Accept=application/xml, application/json"</strong>
都会默认按写前面的来转换返回结果 或者加上q,q值越大的优先处理,比如:
@RequestMapping(value = "/queryConfig", method = RequestMethod.GET, produces = { "application/xml;q=0.4",
"application/json;q=0.5" })
如果请求中没设置accept参数,默认会json格式返回结果。
测试类方法:
设置headers的accept和不设置处理是不同的,
@Test
public void testByGetJson() throws Exception {
UrlBuilder urlBuilder = UrlBuilder.url(URIConstantTest.TEST_URL).append(false, configUrl)
.append(false, "/queryConfig").queryparam(URIConstantTest.USER_PARAM)
.queryparam("&key=test&language=en_US");
System.out.println(urlBuilder.build());
String result = Request.Get(urlBuilder.build())
// .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.execute().returnContent().asString();
System.out.println(result);
}
@Test
public void testByGetsJson() throws Exception {
UrlBuilder urlBuilder = UrlBuilder.url(URIConstantTest.TEST_URL).append(false, configUrl)
.append(false, "/queryConfigs").queryparam(URIConstantTest.USER_PARAM)
.queryparam("&key=test&language=en_US");
System.out.println(urlBuilder.build());
String result = Request.Get(urlBuilder.build())
// .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.execute().returnContent().asString();
System.out.println(result);
}
@Test
public void testAddConfigJson() throws Exception {
String strReq = "{\"configValue\":\"testValu测试\",\"configKey\":\"test\"}";
UrlBuilder urlBuilder = UrlBuilder.url(URIConstantTest.TEST_URL).append(false, configUrl)
.append(false, "/addConfig").queryparam(URIConstantTest.USER_PARAM)
.queryparam("&key=test&language=en_US");
System.out.println(urlBuilder.build());
String result = Request.Post(urlBuilder.build())
// .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.bodyString(strReq, ContentType.APPLICATION_JSON).execute().returnContent().asString();
System.out.println(result);
}
spring mvc 配置文件:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<context:property-placeholder location="classpath:app.properties" />
<context:spring-configured />
<context:annotation-config />
<!-- 定义控制器注解扫描包路径,控制器注解为 @Controller TODO -->
<context:component-scan base-package="com.api.**.controller.**"
use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller"
type="annotation" />
</context:component-scan>
<!-- Turns on support for mapping requests to Spring MVC @Controller methods
Also registers default Formatters and Validators for use across all @Controllers -->
<mvc:annotation-driven validator="validator">
<mvc:message-converters>
<ref bean="stringHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter" />
<ref bean="marshallingHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
<!-- XML view using a JAXB marshaller -->
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="marshallerProperties">
<map>
<entry key="jaxb.formatted.output">
<value type="boolean">true</value>
</entry>
<entry key="jaxb.encoding" value="UTF-8" />
</map>
</property>
<property name="packagesToScan">
<list>
<value>com.api.domain</value>
<value>com.api.web.controller.vo</value>
</list>
</property>
</bean>
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<bean id="jsonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
<bean id="marshallingHttpMessageConverter"
class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxb2Marshaller" />
<!-- <property name="supportedMediaTypes" value="application/xml"></property> -->
<property name="supportedMediaTypes">
<util:list>
<bean class="org.springframework.http.MediaType" c:type="application" c:subtype="xml" c:qualityValue="0.5"/>
</util:list>
</property>
</bean>
</beans>