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

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>

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/strivezxq/article/details/43114063
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞