@RequestBody注解原理

1. @RequestBody

/**
 * Annotation indicating a method parameter should be bound to the body of the web request.
 * The body of the request is passed through an {@link HttpMessageConverter} to resolve the
 * method argument depending on the content type of the request. Optionally, automatic
 * validation can be applied by annotating the argument with {@code @Valid}.
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
    /**
     * Whether body content is required.
     * <p>Default is {@code true}, leading to an exception thrown in case
     * there is no body content. Switch this to {@code false} if you prefer
     * {@code null} to be passed when the body content is {@code null}.
     * @since 3.2
     */
    boolean required() default true;
}

從源碼中可以看到洒擦,@RequestBody 用在方法參數(shù)上面,用來將請求參數(shù)綁定到request body中黔攒,通過HttpMessageConverter封裝為具體的JavaBean蘸拔。通俗點講就是你在一個參數(shù)上加上該注解淤毛,spring就會將request body中的json/xml對象解析成該參數(shù)類型的Javabean對象蒜胖。
作為RESTful開發(fā)中經(jīng)常用到的注解消别,研究其原理有利于我們更好地理解并掌握它。
那么spring是如何做到這一點的呢台谢?先來看DispatcherServlet寻狂。
作為springMVC處理請求的中央調(diào)度器,DispatcherServlet本身是一個servlet对碌,所以我們看doService():

  /**
     * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
     * for the actual dispatching.
     */
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }
        //......
        //......
        try {
            doDispatch(request, response);
        }
        finally {
            //......
        }
    }

重點在doDispatch()方法,該方法先找到會找到合適的handler來處理當(dāng)前請求:

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

HandlerAdapter是一個接口荆虱,具體處理方法在RequestMappingHandlerAdapter類中:

RequestMappingHandlerAdapter.png

@RequestMapping(包括@GetMapping@PostMapping等)注釋修飾的接口請求會用該類處理朽们。RequestMappingHandlerAdapter實現(xiàn)了AbstractHandlerMethodAdapter,所以mv = ha.handle(processedRequest, response, mappedHandler.getHandler());實際上調(diào)用的是AbstractHandlerMethodAdapter類的handle()方法:

@Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }

這里又調(diào)用了handleInternal()方法,RequestMappingHandlerAdapter重寫了該方法:
進入該方法诉位,

       if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }

可以看到最終調(diào)用的都是invokeHandlerMethod()方法,此方法會處理@RequestMapping修飾的請求

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);
            //注意,此處往ServletInvocableHandlerMethod中設(shè)置了一系列的參數(shù)解析器
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);

            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                if (logger.isDebugEnabled()) {
                    logger.debug("Found concurrent result value [" + result + "]");
                }
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }
            //具體請求處理方法
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            webRequest.requestCompleted();
        }
    }

進入該方法的invocableMethod.invokeAndHandle(webRequest, mavContainer);骑脱,來到ServletInvocableHandlerMethod,此類繼承了InvocableHandlerMethod,可以處理請求的返回值苍糠。invokeAndHandle()方法:

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);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        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;
        }
    }

重點在Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);,通過請求調(diào)用并產(chǎn)生返回值叁丧。

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "' with arguments " + Arrays.toString(args));
        }
        Object returnValue = doInvoke(args);
        if (logger.isTraceEnabled()) {
            logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
                    "] returned [" + returnValue + "]");
        }
        return returnValue;
    }

getMethodArgumentValues()方法的作用是獲取方法參數(shù),重點就在這里,

private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        //創(chuàng)建一個Object數(shù)組用于存放解析后的參數(shù)列表
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = resolveProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            //判斷當(dāng)前的參數(shù)解析器是否支持解析該參數(shù)
            if (this.argumentResolvers.supportsParameter(parameter)) {
                try {
                    //解析參數(shù)
                    args[i] = this.argumentResolvers.resolveArgument(
                            parameter, mavContainer, request, this.dataBinderFactory);
                    continue;
                }
                catch (Exception ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
                    }
                    throw ex;
                }
            }
            if (args[i] == null) {
                throw new IllegalStateException("Could not resolve method parameter at index " +
                        parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
                        ": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
            }
        }
        return args;
    }

進入resolveArgument()方法,

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        //找到對應(yīng)解析器
        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
        }
        //解析參數(shù)
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

@RequestBody修飾的參數(shù)會使用RequestResponseBodyMethodProcessor解析拥娄,

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        parameter = parameter.nestedIfOptional();
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = Conventions.getVariableNameForParameter(parameter);

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
            if (arg != null) {
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
                }
            }
            if (mavContainer != null) {
                mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
            }
        }

        return adaptArgumentIfNecessary(arg, parameter);
    }

進入readWithMessageConverters()方法一路順藤摸瓜蚊锹,來到AbstractMessageConverterMethodArgumentResolver的readWithMessageConverters(),

Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
            message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

            for (HttpMessageConverter<?> converter : this.messageConverters) {
                Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
                GenericHttpMessageConverter<?> genericConverter =
                        (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
                if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
                        (targetClass != null && converter.canRead(targetClass, contentType))) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
                    }
                    if (message.hasBody()) {
                        //解析HttpMessage
                        HttpInputMessage msgToUse =
                                getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
                       //使用相應(yīng)的轉(zhuǎn)換器
                        body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                                ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
                        body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
                    }
                    else {
                        body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
                    }
                    break;
                }
            }
        }
        catch (IOException ex) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", ex);
        }

可以看到使用文章開頭提到的HttpMessageConverter解析參數(shù)并返回,而此處的HttpMessageConverter是在RequestMappingHandlerAdapter中設(shè)置解析器的時候添加到每個解析器中的稚瘾。而json格式的數(shù)據(jù)使用AbstractJackson2HttpMessageConverter進行解析牡昆,內(nèi)部使用jackson進行json數(shù)據(jù)的解析。

總結(jié)

請求由DispatcherServlet處理摊欠,找到相應(yīng)的HandlerAdapter進行處理丢烘,RequestMappingHandlerAdapter會處理@RequestMapping注解的請求,設(shè)置一系列參數(shù)解析器進行解析些椒,如果參數(shù)使用@RequestBody注解播瞳,則使用RequestResponseBodyMethodProcessor進行解析,此參數(shù)解析器用HttpMessageConverter將HttpMessage封裝為具體的JavaBean對象免糕,json格式的數(shù)據(jù)使用AbstractJackson2HttpMessageConverter進行解析赢乓,內(nèi)部使用jackson進行json數(shù)據(jù)的解析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末石窑,一起剝皮案震驚了整個濱河市骏全,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尼斧,老刑警劉巖姜贡,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棺棵,居然都是意外死亡楼咳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門烛恤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來母怜,“玉大人,你說我怎么就攤上這事缚柏∑谎” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵币喧,是天一觀的道長轨域。 經(jīng)常有香客問我,道長杀餐,這世上最難降的妖魔是什么干发? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮史翘,結(jié)果婚禮上枉长,老公的妹妹穿的比我還像新娘冀续。我一直安慰自己,他們只是感情好必峰,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布洪唐。 她就那樣靜靜地躺著,像睡著了一般吼蚁。 火紅的嫁衣襯著肌膚如雪凭需。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天桂敛,我揣著相機與錄音功炮,去河邊找鬼。 笑死术唬,一個胖子當(dāng)著我的面吹牛薪伏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粗仓,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼嫁怀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了借浊?” 一聲冷哼從身側(cè)響起塘淑,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚂斤,沒想到半個月后存捺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡曙蒸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年捌治,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纽窟。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡肖油,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臂港,到底是詐尸還是另有隱情森枪,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布审孽,位于F島的核電站县袱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瓷胧。R本人自食惡果不足惜显拳,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搓萧。 院中可真熱鬧杂数,春花似錦、人聲如沸瘸洛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽反肋。三九已至那伐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間石蔗,已是汗流浹背罕邀。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留养距,地道東北人诉探。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像棍厌,于是被迫代替她去往敵國和親肾胯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350