springMVC @ResponseBody 原理

CyrusHuang發表於2024-07-04
  1. 前面說了介面卡執行 handler 怎麼解析請求引數,現在看怎麼響應引數,還是從具體執行 handler 的方法開始

    // org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
    
        // 這裡在解析引數,並拿到了方法的返回值
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        setResponseStatus(webRequest);
    	...
        try {
            // 這裡處理方法返回值,先獲取返回值的處理物件
            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }
    
  2. 怎麼拿返回值處理物件。邏輯和處理器對映器、處理器介面卡、引數解析器、引數訊息轉換器一致,都是挨個遍歷,看哪個合適

    // org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    	// 選擇返回值處理物件
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        }
        // 開始處理
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
    
    // org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler
    @Nullable
    private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
        boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        // 看過太多這種邏輯了,都是遍歷,找到一個合適的
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
                continue;
            }
            // 這裡可以進去看下,最終是選擇了 RequestResponseBodyMethodProcessor,為什麼是它?看看 supportsReturnType 方法就知道了
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    }
    
    // org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsReturnType
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        // 使用了 @ResponseBody 的註解就使用 RequestResponseBodyMethodProcessor 來處理
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
                returnType.hasMethodAnnotation(ResponseBody.class));
    }
    
  3. 現在知道了 @ResponseBody 的註解就使用 RequestResponseBodyMethodProcessor 來處理,具體怎麼處理?

    // org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    
        // 轉型並寫入響應流(根據原始碼是先獲取 HttpMessageConvert 然後再轉型,最後寫入流)
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }
    
  4. 拿 HttpMessageConvert 同請求引數解析一樣,也要看 content-type 等一系列校驗才能拿到合適的 HttpMessageConvert

    原始碼太多,只保留關鍵資訊,把些判斷都刪掉,引數解析那裡留下的原始碼多些,可以去那裡看

    // org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
        // 巴拉巴拉,王八唸經
    
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                        (GenericHttpMessageConverter<?>) converter : null);
                // 這裡和引數解析一樣,判斷
                if (genericConverter != null ?
                        ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                        converter.canWrite(valueType, selectedMediaType)) {
                    body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                            (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                            inputMessage, outputMessage);
                    if (body != null) {
                        Object theBody = body;
                        LogFormatUtils.traceDebug(logger, traceOn ->
                                "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                        addContentDispositionHeader(inputMessage, outputMessage);
                        if (genericConverter != null) {
                            // 呼叫 HttpMessageConvert 的些操作,把資料寫入響應流
                            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                        }
                        else {
                            ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                        }
                    }
                    else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Nothing to write: null body");
                        }
                    }
                    return;
                }
            }
        }
    
    }
    

    貼個圖便於理解

  5. 有了 HttpMessageConvert,這時開始轉型和寫入流

    // org.springframework.http.converter.GenericHttpMessageConverter#write
    @Override
    public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
            HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    
        final HttpHeaders headers = outputMessage.getHeaders();
        addDefaultHeaders(headers, t, contentType);
    
        if (outputMessage instanceof StreamingHttpOutputMessage) {
            StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
            streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
                @Override
                public OutputStream getBody() {
                    return outputStream;
                }
                @Override
                public HttpHeaders getHeaders() {
                    return headers;
                }
            }));
        }
        else {
            // 這個方法又呼叫了本類的 writeInternal,本類的 writeInternal 又是個模板方法,所以到子類去看實現,然後就到了 jackson 了
            // 這個方法的作用是轉型,方法體在下面
            writeInternal(t, type, outputMessage);
            // 重新整理響應流,over
            outputMessage.getBody().flush();
        }
    }
    
    // org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
    @Override
    protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
    
        MediaType contentType = outputMessage.getHeaders().getContentType();
        JsonEncoding encoding = getJsonEncoding(contentType);
        JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
        try {
            ...
                
            // 這裡在進行些操作,也就是 jackson 的轉型操作,有興趣可以看看自行去 jackson 怎麼做的,但本次的 @ResponseBody 原理已經結束了
            objectWriter.writeValue(generator, value);
            writeSuffix(generator, object);
            
            generator.flush();
        }
        catch (InvalidDefinitionException ex) {
            throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
        }
        catch (JsonProcessingException ex) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getOriginalMessage(), ex);
        }
    }
    

相關文章