1. 程式人生 > 實用技巧 >SpringMVC之json是怎麼傳回前端的 @ResponseBody解析

SpringMVC之json是怎麼傳回前端的 @ResponseBody解析

一 序章 

 

  http的請求裡有一個屬性叫accept,它規定了返回值型別,本篇要講的返回值正是跟這個屬性關係緊密

  

二 原始碼分析

  SpringMVC為@RequestBody和@ResponseBody兩個註解實現了統一處理類RequestResponseBodyMethodProcessor,實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler兩個介面。

ServletInvocableHandlerMethod
public
void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)
throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(
true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { 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; } }

  要執行就離不開returnValueHandlers。先講講這些returnValueHandlers怎麼初始化的

  我們先看RequestMappingHandlerAdapter.invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);//設定返回值Handler
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

  注意這裡,每次處理請求的時候,把原始HandlerMethod包裝成一個ServletInvocableHandlerMethod,這裡用的是每次都new一個出來

protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
        return new ServletInvocableHandlerMethod(handlerMethod);
    }

  所以就來看看 RequestMappingHandlerAdapter裡的returnValueHandlers 是怎麼來的

  現在已經知道了RequestMappingHandlerAdapter 會被當成一個bean來進行初始化,而且它還實現了InitializingBean

@Override
    public void afterPropertiesSet() {
        // Do this first, it may add ResponseBody advice beans
        initControllerAdviceCache();

        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.initBinderArgumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
            this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

  這個getDefaultReturnValueHandlers很長,這裡就不貼了,只需要記住一條RequestResponseBodyMethodProcessor也是 這個handlers中的一員。而它恰恰是處理@ResponseBody的關鍵

  

  現在回頭繼續看his.returnValueHandlers.handleReturnValue

HandlerMethodReturnValueHandlerComposite
public
void handleReturnValue(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); }

  這裡就是選擇合適的handler,因為我們的返回值是有@ResponseBody註解的,所以就選出來了RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor
public
boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); }

  最終會呼叫AbstractMessageConverterMethodProcessor.writeWithMessageConverters

  這個方法很長,這裡只看一部分。如果返回值是一個引用型別而不是基礎型別,那就要有jackson相關的類,在這裡messageConverter 就是MappingJackson2HttpMessageConverter,在這裡呼叫write方法就對物件做了序列化,在通過HttpResponse的outputStream寫出去

if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                if (messageConverter instanceof GenericHttpMessageConverter) {
                    if (((GenericHttpMessageConverter) messageConverter).canWrite(
                            declaredType, valueType, selectedMediaType)) {
                        outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,
                                (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(),
                                inputMessage, outputMessage);
                        if (outputValue != null) {
                            addContentDispositionHeader(inputMessage, outputMessage);
                            ((GenericHttpMessageConverter) messageConverter).write(
                                    outputValue, declaredType, selectedMediaType, outputMessage);
                            if (logger.isDebugEnabled()) {
                                logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +
                                        "\" using [" + messageConverter + "]");
                            }
                        }
                        return;
                    }
                }