加@ResponseBody注解的controller方法處理流程

SpringMVC根據(jù)方法有無@ResponseBody注解刷喜,將有不同的解析處理流程,本文著重關(guān)注@ResponseBody注解的方法解析處理過程

1.常規(guī)解析流程(不加ResponseBody注解)

SpringMVC中立砸,如果無特殊處理掖疮,無論Controller方法返回值是ModelAndView/String/void,最終Controller方法的返回結(jié)果都會被處理成一個ModelAndView對象仰禽,見org.springframework.web.servlet.HandlerAdapter#handle

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

之后根據(jù)得到的ModelAndView對象氮墨,解析并渲染視圖對象org.springframework.web.servlet.DispatcherServlet#processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

org.springframework.web.servlet.DispatcherServlet#render

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {

2.ResponseBody解析流程

加注了@ResponseBody注解的Controller方法,將返回結(jié)果直接輸出到響應(yīng)流吐葵,無視圖解析器流程规揪。具體處理過程如下:
前端控制器的前期處理基本雷同,暫不贅述温峭,前期執(zhí)行流程:

image.png

首先我們斷點來到org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

    /**
     * Invokes the method and handles the return value through one of the
     * configured {@link HandlerMethodReturnValueHandler}s.
     * @param webRequest the current request
     * @param mavContainer the ModelAndViewContainer for this request
     * @param providedArgs "given" arguments matched by type (not resolved)
     */
    public void invokeAndHandle(ServletWebRequest webRequest,
            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
  // 執(zhí)行controller方法猛铅,得到返回值
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);

        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        else if (StringUtils.hasText(this.responseReason)) {
            mavContainer.setRequestHandled(true);
            return;
        }

        mavContainer.setRequestHandled(false);
        try {
  // 處理返回值
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
            }
            throw ex;
        }
    }

觀察代碼得知在this.returnValueHandlers.handleReturnValue()處理返回值。
查看this.returnValueHandlers

private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

HandlerMethodReturnValueHandlerComposite可以認為是HandlerMethodReturnValueHandler的包裝器類凤藏,持有List<HandlerMethodReturnValueHandler>:

public class HandlerMethodReturnValueHandlerComposite implements AsyncHandlerMethodReturnValueHandler {

    protected final Log logger = LogFactory.getLog(getClass());

    private final List<HandlerMethodReturnValueHandler> returnValueHandlers =
        new ArrayList<HandlerMethodReturnValueHandler>();

handleReturnValue方法:

    /**
     * Iterate over registered {@link HandlerMethodReturnValueHandler}s and invoke the one that supports it.
     * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
     */
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
  // 選擇處理的handler
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
  // 處理返回值
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }

選擇handler方法奸忽,遍歷this.returnValueHandlers,找出支持處理當(dāng)前方法的handler:

private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
        boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                continue;
            }
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    }

我們查看RequestResponseBodyMethodProcessor的supportsReturnType方法org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsReturnType:

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
  // 分別判斷類和方法上的ResponseBody注解
        return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
                returnType.getMethodAnnotation(ResponseBody.class) != null);
    }

最終 @ResponseBody注解的方法(類)會交給RequestResponseBodyMethodProcessor來處理:

@Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

        mavContainer.setRequestHandled(true);

        // Try even with null return value. ResponseBodyAdvice could get involved.
        writeWithMessageConverters(returnValue, returnType, webRequest);
    }

方法writeWithMessageConverters

/**
     * Writes the given return value to the given web request. Delegates to
     * {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
     */
    protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

實際調(diào)用的重載方法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, HttpMessageNotWritableException {
  // controller方法定義的返回類型
        Class<?> returnValueClass = getReturnValueType(returnValue, returnType);
        Type returnValueType = getGenericType(returnType);
        HttpServletRequest servletRequest = inputMessage.getServletRequest();
  // 獲取請求的Accept屬性揖庄,如果為空則返回 */*栗菜,即保存了客戶端需要的哪一種類型的數(shù)據(jù)
        List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
  // 保存該容器可以產(chǎn)生的 Content-Type 類型的數(shù)據(jù)
  // 哪一個HttpMessageConverter可以處理你的Controller方法返回的對象,則把該HttpMessageConverter支持的媒體類型返回
        List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass, returnValueType);

        if (returnValue != null && producibleMediaTypes.isEmpty()) {
            throw new IllegalArgumentException("No converter found for return value of type: " + returnValueClass);
        }

        Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
    // 雙層for循環(huán)蹄梢,找出支持解析的媒體類型疙筹,遍歷結(jié)束如果未找到,拋出異常
        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.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) {
// 選擇合適的HttpMessageConverter禁炒,按照合適的 MediaType 而咆,把數(shù)據(jù)寫入到輸出流。
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                if (messageConverter instanceof GenericHttpMessageConverter) {
                    if (((GenericHttpMessageConverter<T>) messageConverter).canWrite(returnValueType,
                            returnValueClass, selectedMediaType)) {
                        returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                inputMessage, outputMessage);
                        if (returnValue != null) {
                            addContentDispositionHeader(inputMessage, outputMessage);
                            ((GenericHttpMessageConverter<T>) messageConverter).write(returnValue,
                                    returnValueType, selectedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Written [" + returnValue + "] as \"" +
                                        selectedMediaType + "\" using [" + messageConverter + "]");
                            }
                        }
                        return;
                    }
                }
                else if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
                    returnValue = (T) getAdvice().beforeBodyWrite(returnValue, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                            inputMessage, outputMessage);
                    if (returnValue != null) {
                        addContentDispositionHeader(inputMessage, outputMessage);
                        ((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);
        }
    }

關(guān)于SpringMVC的HttpMessageConverter消息轉(zhuǎn)換機制幕袱,下回再探究記錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暴备,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子们豌,更是在濱河造成了極大的恐慌涯捻,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件望迎,死亡現(xiàn)場離奇詭異障癌,居然都是意外死亡,警方通過查閱死者的電腦和手機擂煞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門混弥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趴乡,“玉大人对省,你說我怎么就攤上這事蝗拿。” “怎么了蒿涎?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵哀托,是天一觀的道長。 經(jīng)常有香客問我劳秋,道長仓手,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任玻淑,我火速辦了婚禮嗽冒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘补履。我一直安慰自己添坊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布箫锤。 她就那樣靜靜地躺著贬蛙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谚攒。 梳的紋絲不亂的頭發(fā)上阳准,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機與錄音馏臭,去河邊找鬼野蝇。 笑死,一個胖子當(dāng)著我的面吹牛位喂,可吹牛的內(nèi)容都是我干的浪耘。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼塑崖,長吁一口氣:“原來是場噩夢啊……” “哼七冲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起规婆,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤澜躺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抒蚜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掘鄙,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年嗡髓,在試婚紗的時候發(fā)現(xiàn)自己被綠了操漠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浊伙,靈堂內(nèi)的尸體忽然破棺而出撞秋,到底是詐尸還是另有隱情,我是刑警寧澤嚣鄙,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布吻贿,位于F島的核電站,受9級特大地震影響哑子,放射性物質(zhì)發(fā)生泄漏舅列。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一卧蜓、第九天 我趴在偏房一處隱蔽的房頂上張望帐要。 院中可真熱鬧,春花似錦弥奸、人聲如沸宠叼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冒冬。三九已至,卻和暖如春摩渺,著一層夾襖步出監(jiān)牢的瞬間简烤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工摇幻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留横侦,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓绰姻,卻偏偏與公主長得像枉侧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子狂芋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容