1. 程式人生 > >spring MVC 註解處理分析(一) @ResponseBody

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 註解處理分析() @ResponseBody

spring的Controller的方法總的來說返回方式有兩種:一種是view, 另一種是converter,通過@ResponseBody來註解方法, 這裡主要講述註解方式,@RequestMapping的 headers 和produces引數設定不同,返回處理略有不同

Spring mvc請求處理流程詳解()之檢視解析

前言   Spring mvc框架相信很多人都很熟悉了,關於這方面的資料也是一搜一大把。但是感覺講的都不是很細緻,讓很多初學者都雲裡霧裡的。本人也是這樣,之前研究過,但是後面一段時間不用發現又忘記了。所以決定寫下來,以備後用。   本系列文基於spring-

Spring MVC請求處理流程分析

一、簡介 Spring MVC框架在工作中經常用到,配置簡單,使用起來也很方便,很多書籍和部落格都有介紹其處理流程,但是,對於

Spring MVC新手教程(

erp -s sil troy .html 解釋 rand rtu wire 直接幹貨 model 考慮給用戶展示什麽。關註支撐業務的信息構成。構建成模型。 control 調用業務邏輯產生合適的數據以及傳遞數據給視圖用於呈獻; view怎樣對數據進行布局,以

Spring MVC異常處理實例

bsp ips etag label 視圖 uri _id integer ive 以下內容引用自http://wiki.jikexueyuan.com/project/spring/mvc-framework/spring-exception-handling-examp

Spring MVC 註解相關

註解 com tst file null key comment mvc ppi // required=false表示不傳的話,會給參數賦值為null,required=true就是必須要有 @ResponseBody

Spring MVC異常處理SimpleMappingExceptionResolver

bean pri 分享圖片 ESS bsh trace 內部實現 ont per Spring MVC異常處理SimpleMappingExceptionResolver【轉】 (2012-12-07 13:45:33) 轉載▼ 標簽: 雜談 分類: 技術

Spring MVC 異常處理 - ResponseStatusExceptionResolver

執行 代碼 pin ces val col resolv use turn 作用在類和方法上面 更改返回的代碼和錯誤消息 類上 通過throw new UserName***Exception()拋出 @ResponseStatus(value=HttpStatus.FO

Spring系列(七) Spring MVC 異常處理

nco 部分 給定 uri too ebo intended 路徑 onf Servlet傳統異常處理 Servlet規範規定了當web應用發生異常時必須能夠指明, 並確定了該如何處理, 規定了錯誤信息應該包含的內容和展示頁面的方式.(詳細可以參考servlet規範文檔)

實戰 :Spring MVC + 註解 +SqlServer 框架搭建及詳解

原始碼下載:http://download.csdn.NET/detail/u010469432/6786687 https://blog.csdn.net/u010469432/article/details/17587699 先說一下Spring3 MVC的優點: spring&nb

Java Spring MVC專案搭建()——Spring MVC框架整合

轉自:https://www.cnblogs.com/eczhou/p/6287852.html 1、Java JDK及Tomcat安裝 我這裡安裝的是JDK 1.8 及 Tomcat 8,安裝步驟詳見:http://www.cnblogs.com/eczhou/p/6285248.html

spring mvc(4)處理模型資料

處理模型資料 Spring MVC 提供了以下幾種途徑輸出模型資料: – ModelAndView: 處理方法返回值型別為 ModelAndView時, 方法體即可通過該物件新增   模型資料 – Map 及 Model: 入參為org.springframework.ui.Mo

Spring重要註解用法分析

Table of Contents Spring註解 @Bean 宣告一個bean Bean依賴 - Bean Dependencies 獲取bean的生命週期回撥: 指定Bean的scope @Configuration 注入bean間依賴關係 有關基於Ja

Spring MVC 註解(上傳)筆記

一 對於spring mvc來說2.0以後大量使用註解確實簡單很多,最近在一個專案使用spring mvc遇到上傳檔案問題,由於使用了註解所以網上沒有找到相關使用註解上傳檔案的。官方文件又沒有更新都是老的,看了一些原始碼這才解決。 使用註解很簡單。 寫個例子:控制器類 FileUplo

Spring MVC 註解型別

Spring 2.5 引入了註解 基於註解的控制器的優勢 1. 一個控制器類可以處理多個動作,而一個實現了 Controller 介面的控制器只能處理一個動作 2. 基於註解的控制器的請求對映不需要儲存在配置檔案中,使用 RequestMapping 註解型別,可以對一個方法進行請求處理。 Contr

spring MVC提交處理帶檔案和非檔案表單

<form action="" method="post" enctype="multipart-form-date"> <input type="file" name="file"/> <input type="user.userName"/&g

spring mvc註解大全

1、@Controller 在SpringMVC 中,控制器Controller 負責處理由DispatcherServlet 分發的請求,它把使用者請求的資料經過業務處理層處理之後封裝成一個Model ,然後再把該Model 返回給對應的Vie

Spring MVC 註解 @RequestParam解析

在Spring MVC 後臺控制層獲取引數的方式主要有兩種,一種是requset.getParameter(“name”),另一種是用註解@Resquest.Param直接獲取。 一、基本使用獲取提交資料 後臺程式碼: @AuthPassport @RequestMapping("/publishe

Spring基於註解的方式

Spring基於註解的方式一 Spring註解簡介 之前的時候我們學習的Spring都是基於Spring配置檔案的形式來編寫,現在很多的情況下使用SpringBoot的時候是基於註解的形式,這裡我們首先要了解的是Java中的註解是什麼意思。對於註解和註釋要做一定的區別。 首先我們

Spring MVC非同步處理-DeferedResult使用

DeferedResult處理流程 Spring mvc的控制層接收使用者的請求之後,如果要採用非同步處理,那麼就要返回DeferedResult<>泛型物件。在呼叫完控制層之後,立即回返回DeferedResult物件,此時驅動控制層的容器主執行緒,可以處理更多的請求。