SpringMVC 源码分析(八)

在上篇SpringMVC 源码分析(七)中,我们了解到客户端请求到服务器后如何经过Spring MVC找到对应的Controller处理方法并反射调用的流程,本篇主要讲一下Spring MVC又是如何处理Controller处理方法返回的结果响应给客户端的。

首先回到反射调用Controller返回的代码处ServletInvocableHandlerMethod#invokeAndHandle,拿到返回值returnValue后,首先判断Controller的处理方法有没有加@ReaponseStatus注解,如果有的话会对其属性进行解析,@ReaponseStatus注解有三个属性(code,value,reason),其中如果reason不为空时会不管程序处理成功与否都会覆盖我们的响应结果,该注解在项目中很少用到,可以不考虑,接下来先对mavContainer中的请求是否被处理的属性标志requestHandled设置为false,该属性主要用于处理视图模型封装与跳转的逻辑,如果Controller类或者方法上有类似@ResponseBody这种需要返回特定格式注解的,就不需要控制跳转,所以该字段一直为false。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //执行调用并返回结果数据
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        //如果Controller对应的方法设置了@ResponseStatus,就设置response的status状态,没有就不处理,一般都不需要额外设置的
        setResponseStatus(webRequest);

        //如果没有返回值就不需要封装,直接返回
        if (returnValue == null) {
            if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                disableContentCachingIfNecessary(webRequest);
                mavContainer.setRequestHandled(true);
                return;
            }
        }
        //如果设置有原因,直接返回
        else if (StringUtils.hasText(getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }
        //默认为false,如果有@ResponseStatus设置,则为true
        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(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }

接下来就是对返回结果的封装处理,进入this.returnValueHandlers.handleReturnValue方法中,发现跟解析请求参数this.resolvers.resolveArgument一样的套路模式,就是选择合适的处理器来解析返回值,因为我写的Demo中Controller类上加了@ResponseBody注解,所以该解析任务也就交给了RequestResponseBodyMethodProcessor来处理,(当然我们以前前后端不分离的时候,前端页面资源都在服务器下面,通过springMVC来跳转页面时,用到的处理器就是ViewMethodReturnValueHandler,里面只有将返回值当作页面路径放到mavContainer的viewName属性中,另外再判断下是不是redirect跳转等等我们不做过多的研究),这里只RequestResponseBodyMethodProcessor解析请求参数的代码。

@Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        //同HandlerMethodArgumentResolverComposite参数解析器选择器一样,选择一个合适的响应结果解析器
        //因为我们Controller方法有@ResponseBody注解,所以这里选择的解析器仍为RequestResponseBodyMethodProcessor解析器
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        }
        //解析返回的记过
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }

解析请求参数this.resolvers.resolveArgument源码,具体可以在SpringMVC 源码分析(七)中查看。

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        //获取一个支持HandlerMethodArgument解析的的解析器,
        //我们Controller处理方法使用@RequestBody接受请求,所以对应RequestResponseBodyMethodProcessor
        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        //RequestResponseBodyMethodProcessor开始解析
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }

接下来我们再进入RequestResponseBodyMethodProcessor的handleReturnValue方法中查看代码逻辑,只是做了简单的封装处理,重点逻辑在writeWithMessageConverters方法中:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        //设置请求已处理,不需要SpringMVC再处理页面跳转逻辑
        mavContainer.setRequestHandled(true);
        //对请求进行二次封装,分别创建请求对象ServletServerHttpRequest和响应对象ServletServerHttpResponse
        ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        // 将返回值,返回值类型,以及请求响应,使用转换器进行写出
        writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

进入writeWithMessageConverters方法,该方法比较长,关键位置已经加上注释,根据注释边可以看懂该部分的处理逻辑,主要就是先获取反射调用后返回的值类型和Controller处理器方法真正的类型,以及内容协商原理,从不同的地方选择最合适的浏览器接收类型,后面会放到header中返回,接着再匹配到合适的HttpMessageConverter,并调用write方法进行返回值value与Controller处理器方法真正类型的封装处理。

通过该图可以更清楚的了解内容协商处理。

源码:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
            ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
            throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

        Object body;
        //我们的返回值value的类型
        Class<?> valueType;
        //最终要转换写入的数据类型
        Type targetType;
        //根据value的类型进行绑定
        if (value instanceof CharSequence) {
            //我的demo返回的是String类型,所以走到这里
            body = value.toString();
            valueType = String.class;
            targetType = String.class;
        }
        else {
            //如果是自定义java类,则走这里
            body = value;
            valueType = getReturnValueType(body, returnType);
            targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
        }

        //判断返回值是否为Resource类型的,如果是就处理流数据(stream)
        if (isResourceType(value, returnType)) {
            outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
            if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
                    outputMessage.getServletResponse().getStatus() == 200) {
                Resource resource = (Resource) value;
                try {
                    List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
                    outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
                    body = HttpRange.toResourceRegions(httpRanges, resource);
                    valueType = body.getClass();
                    targetType = RESOURCE_REGION_LIST_TYPE;
                }
                catch (IllegalArgumentException ex) {
                    outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
                    outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
                }
            }
        }

        //内容协商
        //选择合适的接收类型返回给浏览器
        MediaType selectedMediaType = null;
        //首先从响应头中获取合适的接受类型
        MediaType contentType = outputMessage.getHeaders().getContentType();
        boolean isContentTypePreset = contentType != null && contentType.isConcrete();
        if (isContentTypePreset) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found 'Content-Type:" + contentType + "' in response");
            }
            //能获取到的话就是用该类型
            selectedMediaType = contentType;
        }
        else {
            //如果响应头里面没有合适的接收类型,从请求头中获取可以接收的类型,比如从header中获取
            HttpServletRequest request = inputMessage.getServletRequest();
            List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);

            //获取服务器可以支持的接收类型
            List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

            if (body != null && producibleTypes.isEmpty()) {
                throw new HttpMessageNotWritableException(
                        "No converter found for return value of type: " + valueType);
            }
            List<MediaType> mediaTypesToUse = new ArrayList<>();

            //循环匹配合适的接收类型
            for (MediaType requestedType : acceptableTypes) {
                for (MediaType producibleType : producibleTypes) {
                    if (requestedType.isCompatibleWith(producibleType)) {
                        mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                    }
                }
            }
            //没有找到合适的接收类型,抛出异常
            if (mediaTypesToUse.isEmpty()) {
                if (body != null) {
                    throw new HttpMediaTypeNotAcceptableException(producibleTypes);
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
                }
                return;
            }

            //将合适的媒体类型进行排序,获取最优的返回媒体类型
            MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

            for (MediaType mediaType : mediaTypesToUse) {
                if (mediaType.isConcrete()) {
                    selectedMediaType = mediaType;
                    break;
                }
                else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                    selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                    break;
                }
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Using '" + selectedMediaType + "', given " +
                        acceptableTypes + " and supported " + producibleTypes);
            }
        }
        // 开始转换返回值的类型,因为demo返回的是String类型,
        // 所以匹配到的转换器是:org.springframework.http.converter.StringHttpMessageConverter
        // 项目一般使用的就是String和自定义的java类,所以其他的也不用考虑,又兴趣的可以深入了解下
        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) {
                            //转换特定返回类型,比如自定义的java封装对象。
                            genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                        }
                        else {
                            //AbstractHttpMessageConverter
                            ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                        }
                    }
                    else {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Nothing to write: null body");
                        }
                    }
                    return;
                }
            }
        }

        if (body != null) {
            Set<MediaType> producibleMediaTypes =
                    (Set<MediaType>) inputMessage.getServletRequest()
                            .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

            if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
                throw new HttpMessageNotWritableException(
                        "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
            }
            throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
        }
    }

进入convert的write方法,此处调用的是父类AbstractGenericHttpMessageConverter的write方法,添加响应设置headers,同时调用抽象方法writeInternal将返回值写出到响应对象中。

public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        //添加header
        final HttpHeaders headers = outputMessage.getHeaders();
        addDefaultHeaders(headers, t, contentType);

        if (outputMessage instanceof StreamingHttpOutputMessage) {
            StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
            streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
                @Override
                public OutputStream getBody() {
                    return outputStream;
                }
                @Override
                public HttpHeaders getHeaders() {
                    return headers;
                }
            }));
        }
        else {
            //将返回值写出到响应对象中
            writeInternal(t, outputMessage);
            //刷空输出流,并输出所有被缓存的字节。由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。
            outputMessage.getBody().flush();
        }
    }

我使用的demo是返回String,所以调用的是StringHttpMessageConverter的writeInternal,进入该方法,可以看到主要是将返回值拷贝到响应流中。

protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
        HttpHeaders headers = outputMessage.getHeaders();
        if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
            headers.setAcceptCharset(getAcceptedCharsets());
        }
        // 获取字符集
        Charset charset = getContentTypeCharset(headers.getContentType());
        // 将返回值拷贝到输出流中
        StreamUtils.copy(str, charset, outputMessage.getBody());
    }

当然我们也可以返回自定义的java类,那么writeWithMessageConverters方法中的convert为AbstractGenericHttpMessageConverter类,调用writeInternal为子类AbstractJackson2HttpMessageConverter,Spring MVC主要是借助jackson提供的功能将返回值与自定义的java类结合并放入响应流中,有兴趣可以去看下相关源码。

writeInternal方法看完,那么this.returnValueHandlers.handleReturnValue就分析完了,也就是invokeAndHandle分析完了,那么返回上一层调用invokeAndHandle的方法org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod中,因为代码太长,中间省去了一些代码,具体可以看SpringMVC 源码分析(七)

进入getModelAndView,主要处理视图,因为前面调用的是处理@ResponseBody的RequestResponseBodyMethodProcessor类,直接将isRequestHandled设置为true,所以这里直接就返回了null。如果是需要处理视图的,比如我们以前前后端不分离的项目,就需要设置ModelAndView映射关系,以及redirect跳转处理。

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

        modelFactory.updateModel(webRequest, mavContainer);
        // 判断是否需要返回视图
        // 如果是@RequestBody注解对应的处理器时,返回的为json对象,不需要返回视图,就会设置mavContainer.isRequestHandled()为true
        // 后续流程中就不需要视图渲染
        if (mavContainer.isRequestHandled()) {
            return null;
        }
        // 需要返回视图的话,例如我们的Controller对应的处理器没有指定返回什么类型,则根据处理器方法返回对应的视图。
        ModelMap model = mavContainer.getModel();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            if (request != null) {
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
        }
        return mav;
    }

看完getModelAndView方法后,invokeHandlerMethod也分析完毕,继续返回上一层,最后一直接着org.springframework.web.servlet.DispatcherServlet#doDispatch中的ha.handle方法往下走:

applyDefaultViewName顾名思义就是设置默认的视图,查看代码是根据mv是否有视图来设置,因为我们在getModelAndView方法中分析过了,我们通过@ResponseBody对应的处理器来处理,不需要视图,所以mv为null,这里就不需要设置默认视图。

接下来执行mappedHandler.applyPostHandle后置处理,空实现,留给开发自定义实现。

再接下来执行processDispatchResult方法,主要是渲染视图,如果异常会返回异常视图,我们可以进去看下代码

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

        //是否展示错误页面标记
        boolean errorView = false;
        //如果有异常,则设置errorView为true
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
            }
            else {
                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }

        // 判断当前Controller的@RequestMapping是否需要返回一个视图
        // 比如像有@ResponseBody注解这种,不需要返回视图的,mv为null,就不会走进来
        if (mv != null && !mv.wasCleared()) {
            //渲染视图
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace("No view rendering, null ModelAndView returned.");
            }
        }
        //主要用作SPI,一般应用不会走这里
        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            return;
        }

        if (mappedHandler != null) {
            // 视图处理完毕,调用拦截器对应的Controller的@RequestMapping最终完成的开放接口,用户可以自己实现
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }

再到后面就是try-catch中的finally模块,主要是清除之前在线程中设置的信息,以便不影响后续线程的数据。

这个返回过程中有人可能会疑惑,发现处理Controller返回值的handleReturnValue方法无返回值,那么response怎么最终返回给客户端的呢,其实可以沿着代码顺序多仔细观察会发现response其实被封装成一个个对象,可以底层的引用还是response对象,所以最终输出流outputStream还是response的,最终一路返回到tomcat这样的web容器,由web容器返回给客户端。

好啦~Spring MVC源码分析到此结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值