您的位置:首页 > 编程语言 > Java开发

spring MVC 注解处理分析(一) @ResponseBody

2015-01-25 13:40 471 查看
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(
c0da
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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  api json spring mvc xml