首页 > 其他分享 >springMVC @ResponseBody 原理

springMVC @ResponseBody 原理

时间:2024-07-04 13:57:43浏览次数:18  
标签:converter springMVC outputMessage springframework ResponseBody ex org returnType

  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);
        }
    }
    

标签:converter,springMVC,outputMessage,springframework,ResponseBody,ex,org,returnType
From: https://www.cnblogs.com/cryus/p/18283743

相关文章

  • 基于Java+Jsp+Springmvc+Mybatis3+Mysql实现的SSM个人博客系统设计与实现
    一、前言介绍:1.1项目摘要随着互联网技术的快速发展和普及,个人博客作为一种表达自我、分享知识和经验的方式,受到了越来越多人的青睐。传统的博客平台虽然提供了丰富的功能和便捷的服务,但往往难以满足个性化、定制化的需求。因此,开发一款基于SSM(Spring+SpringMVC+MyBat......
  • 合成孔径雷达原理与应用(三)
    合成孔径雷达原理与应用(三)2.应用2.5.地质应用2.5.1.地质调查2.5.2.地震监测2.6.海洋应用2.6.1.船只识别2.6.2.风场测速2.6.3.海面溢油检测2.6.4.港口监测2.应用2.5.地质应用2.5.1.地质调查如上图2-7所示,对于不同岩相的边界来说,SAR图像一般呈......
  • 机器学习原理之 -- 最近邻算法分类:由来及原理详解
            最近邻算法(k-NearestNeighbors,k-NN)是一种简单且直观的分类算法,广泛应用于分类和回归问题。由于其易于理解和实现,k-NN在数据挖掘、模式识别和机器学习领域中占据重要地位。本文将详细介绍最近邻算法的由来、基本原理、构建过程及其优缺点。二、最近邻算法的由......
  • Python机器学习实战:推荐系统的原理与实现方法
    Python机器学习实战:推荐系统的原理与实现方法作者:禅与计算机程序设计艺术/ZenandtheArtofComputerProgramming关键词:推荐系统,协同过滤,矩阵分解,深度学习,个性化推荐,用户体验1.背景介绍1.1问题的由来随着互联网和电子商务的快速发展,用户面对的信息量呈爆炸......
  • 剪映数字人口播原理终于搞清楚了
    剪映版本升级了,新版本支持数字人定制,于是我赶紧申请了使用资格 目前的价格是49元单个价格/30天 支付49元之后剪映要求上传2.5至10分钟的视频 接着要阅读一段话并录制视频上传 第三步提交,提交完成之后大概两三个小时就会有一个特定数字人形象出现: ......
  • SpringMVC中的视图
    1、常见视图组件InternalResourceView:内部资源视图(SpringMVC框架内置的,专门为JSP模板语法准备的,也是为转发准备的)RedirectView:重定向视图(SpringMVC框架内置的,用来完成重定向效果)ThymeleafView:Thymeleaf视图(第三方的,为Thymeleaf模板语法准备的)FreeMarkerView:FreeMarker视图(......
  • SciTech-EECS-ADC/DAC: 源自Digikey的模数/数模转换文章:原理、类型、特点、应用、推
    ADC/DAC教程作者:PatSagsveen,投稿人:DigiKey,2017-09-13https://www.digikey.cn/zh/articles/adc-dac-tutorial如今的世界充满着各种数字信号和模拟信号。这些信号表现不同,但通常都被用来帮助实现更大的目标。ADC:想象你是负责控制HVAC装置的工程师。无论你打算使......
  • 《智能计算系统》第五章 编程框架原理(上)课程笔记
    《智能计算系统》第五章编程框架原理(上)课程视频链接:https://www.bilibili.com/video/BV1Ei421i7Rg本文源自于B站国科大计算所智能计算系统课程官方账号所公开上传的视频,在原有视频之上,提取了关键帧、将音频转成了文字并进行了校正,以便学习使用。在此,也感谢国科大计算所智能......
  • 编译原理 第六章&编译原理必考大题: 语义分析及中间代码生成&必考大题语句翻译
    第六章语义分析及中间代码生成&必考大题语句翻译文章目录第六章语义分析及中间代码生成&必考大题语句翻译写在最前6.1语义分析6.2中间代码6.2.1逆波兰式6.2.2四元式6.2.3三元式6.3语句翻译(必考大题)6.3.1布尔表达式的翻译6.3.2if语句的翻译6.3.3while语句翻......
  • Docker系列-Docker镜像分层原理
    Docker镜像的分层原理是其实现高效、灵活和可复用性的关键。以下是对Docker镜像分层原理的详细说明:一、基本概念Docker镜像是一种轻量级、可执行的独立软件包,它包含运行某个软件所需要的所有内容,如代码、运行时库、环境变量和配置文件等。镜像通过Dockerfile定义并构建,最终形......