SpringBoot响应处理实现流程详解

目录
  • 1、相关依赖
  • 2、ReturnValueHandlers—返回值处理器
  • 3、HttpMessageConvert—消息转换器
  • 4、开启浏览器参数方式内容协商功能

1、相关依赖

web项目引入的启动器spring-boot-starter-web中含有

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-json</artifactId>
	<version>2.7.0</version>
	<scope>compile</scope>
</dependency>

这个依赖下面又有jackson的相关依赖,用于json的转换

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.13.3</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
  <version>2.13.3</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.13.3</version>
  <scope>compile</scope>
</dependency>

2、ReturnValueHandlers—返回值处理器

之前我们分析了参数解析器argumentResolvers的相关源码,了解了请求中的参数是如何找到合适的参数解析器,并与方法的入参进行绑定的,那么问题来了,响应值的返回值处理器是如何找到的呢?

要分析源码,我们还是得进入DispatcherServlet这个类下的doDiapatch()方法

与之前分析参数解析器一样的流程,我们跟到了RequestMappingHandlerAdapter这个类下的invokeHandlerMethod()方法

通过断点可以看到我们默认支持15种返回值解析器

那么他是怎么从这么多返回值解析器中选出自己支持的那一个呢?

我们可以发现,这些返回值解析器都是实现了HandlerMethodReturnValueHandler接口

package org.springframework.web.method.support;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.NativeWebRequest;
public interface HandlerMethodReturnValueHandler {
    boolean supportsReturnType(MethodParameter returnType);
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}

此接口下只有2个方法,一个是判断是否支持此类型返回值,另一个是处理返回值的方法

是不是和之前的参数解析器的接口类有异曲同工之妙?

没错,我们正是使用这两个方法来寻找适合自己的返回值解析器以及处理我们的返回值

跟着断点一直向下,我们跟到了HandlerMethodReturnValueHandlerComposite类下的handleReturnValue()方法,在这里,我们就找到了对应的解析器并执行了相关方法

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	// 获取自己适合的返回值解析器
    HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    } else {
    	// 调用该返回值解析器中的处理返回值的方法
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
}
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
	// 判断是否是异步的返回值
    boolean isAsyncValue = this.isAsyncReturnValue(value, returnType);
    Iterator var4 = this.returnValueHandlers.iterator();
    HandlerMethodReturnValueHandler handler;
    // 使用do-while进行遍历,找出支持当前返回值类型的解析器
    do {
        do {
            if (!var4.hasNext()) {
                return null;
            }

            handler = (HandlerMethodReturnValueHandler)var4.next();
        } while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler));
    } while(!handler.supportsReturnType(returnType));
    return handler;
}

最终,我们确定使用解析器RequestResponseBodyMethodProcessor可以处理标注了@ResponseBody的返回值

3、HttpMessageConvert—消息转换器

除了寻找合适的返回值解析器之外,我们还有一个问题要思考

为什么我们在控制器类的方法上加一个@ResponseBody注解就能将响应信息转换成json格式呢?

这个转换是怎样实现的呢?

我们接着上面的代码继续往下debug

之前我们已经定位到了使用RequestResponseBodyMethodProcessor来处理@ResponseBody的返回值,所以我们继续深入到这个类下的handleReturnValue()

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
	mavContainer.setRequestHandled(true);
	// 创建输入和输出信息
	ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
	ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
	// 使用消息转换器进行写出操作
	this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

然后我们来分析解析器父类AbstractMessageConverterMethodProcessor下的writeWithMessageConverters()方法

首先我们了解一个概念:什么是内容协商?

浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型,这里的q代表优先级

然后服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据

所以在writeWithMessageConverters()方法中我们获取到浏览器能接受什么内容

以及服务器能产生什么内容

然后我们经过协商,决定返回application/json格式的数据

重点来了:这里有一系列的消息转换器,我们到底使用哪个呢?

随便点进一个消息转换器,我们发现他都是实现了HttpMessageConverter这个接口

这里面有2个方法,canRead()和canWrite()来帮助我们判断能否支持源类型和目标类型的消息读写

最终我们确定了,使用MappingJackson2HttpMessageConverter这个消息转换器来进行json转换

package org.springframework.http.converter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
public interface HttpMessageConverter<T> {
	// 读取,将我们当前消息转换器支持的对象以某种格式读取进来,例如将json数据读取成Person对象
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
	// 写出,我们当前消息转换器支持的对象以某种格式写出去,例如将Person对象以json格式写出去
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    List<MediaType> getSupportedMediaTypes();
    default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
        return !this.canRead(clazz, (MediaType)null) && !this.canWrite(clazz, (MediaType)null) ? Collections.emptyList() : this.getSupportedMediaTypes();
    }
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}

然后我们在此处调用MappingJackson2HttpMessageConverter父类AbstractGenericHttpMessageConverter中的write()方法

走到AbstractJackson2HttpMessageConverter类下的writeInternal()方法

可以看出,它利用了底层的Jackson的objectMapper进行转换

这样,我们就完成了Person数据到json格式的转换

大概流程如下

  • 判断当前响应头中是否已经有确定的媒体类型,即MediaType
  • 获取客户端(PostMan、浏览器)支持接收的内容类型(获取客户端Accept请求头字段)
  • 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person),找到支持操作Person的converter,把该converter支持的媒体类型统计出来放到集合中,这样就获取到服务端能提供哪些媒体类型
  • 进行内容协商,找到最佳匹配媒体类型
  • 用 支持 将对象转为 最佳匹配媒体类型 的converter,调用它进行转化

4、开启浏览器参数方式内容协商功能

如果需要返回xml格式的数据,那么需要额外导入相关依赖

<dependency>
	<groupId>com.fasterxml.jackson.dataformat</groupId>
	<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

我们可以看到,浏览器是默认支持如下返回格式的,一般情况下,我们无法指定自己需要的返回格式

但是我们可以通过修改配置+添加参数的方式指定我们需要的格式

首先,我们在yaml文件中,开启基于浏览器参数方式内容协商功能

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

一旦此参数设置为true,那么我们的内容协商管理器contentNegotiationManager中,除了原有的从请求头获取媒体类型的策略之外,还多了一个从请求参数中获取媒体类型的策略,它支持xml和json两种媒体类型

然后我们使用http://localhost:8080/testPathVariable/001/split/decade?format=xml或者http://localhost:8080/testPathVariable/001/split/decade?format=json指定我们需要的返回格式

到此这篇关于SpringBoot响应处理实现流程详解的文章就介绍到这了,更多相关SpringBoot响应处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot响应处理之以Json数据返回的实现方法

    目录 一.示例代码 二.返回值解析原理 三.源代码分析(debug) 四.内容协商机制 1. 内容协商 2. 内容协商原理重点源代码 3. HttpMessageConverters消息转换器原理 一.示例代码 访问 localhost:8080/jsonTest —— 返回 json 格式的数据 @Controller public class ResponseTestController { @ResponseBody // 标注 -- 自动返回json数据 @GetMapping("/js

  • SpringBoot响应处理实现流程详解

    目录 1.相关依赖 2.ReturnValueHandlers—返回值处理器 3.HttpMessageConvert—消息转换器 4.开启浏览器参数方式内容协商功能 1.相关依赖 web项目引入的启动器spring-boot-starter-web中含有 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</arti

  • SpringBoot解析yml全流程详解

    目录 背景 加载监听器 执行run方法 加载配置文件 封装Node 调用构造器 思考 背景 前几天的时候,项目里有一个需求,需要一个开关控制代码中是否执行一段逻辑,于是理所当然的在yml文件中配置了一个属性作为开关,再配合nacos就可以随时改变这个值达到我们的目的,yml文件中是这样写的: switch: turnOn: on 程序中的代码也很简单,大致的逻辑就是下面这样,如果取到的开关字段是on的话,那么就执行if判断中的代码,否则就不执行: @Value("${switch.turnOn}

  • Springboot实现动态定时任务流程详解

    目录 一.静态 二.动态 1.基本代码 2.方案详解 2.1 初始化 2.2 单次执行 2.3 停止任务 2.4 启用任务 三.小结 一.静态 静态的定时任务可以直接使用注解@Scheduled,并在启动类上配置@EnableScheduling即可 @PostMapping("/list/test1") @Async @Scheduled(cron = "0 * * * * ?") public void test1() throws Exception { Ob

  • SpringBoot自定义Starter实现流程详解

    目录 starter起步依赖 starter命名规则 自定义starter new module 添加依赖 simplebean 自动配置类 META-INF\spring.factories 在spring-boot-mytest中引入mystarter-spring-boot-starter 添加配置 通过@Autowired引用 启动访问 starter起步依赖 starter起步依赖是springboot一种非常重要的机制, 它打包了某些场景下需要用到依赖,将其统一集成到starter,

  • SpringBoot自定义启动器Starter流程详解

    目录 一.背景 二.自定义启动器 1.创建一个启动器的自动配置模块 2.创建一个启动器模块 3.在业务模块中引入启动器 一.背景 虽然Spring官方给我们提供了很多的启动器供我们使用 但有时候我们也会遇到某些特殊场景,这些启动器满足不了 这个时候就需要自定义一个启动器供我们使用 二.自定义启动器 在之前学习Spring Boot的过程中,我们已经对启动器有了一个大致的了解 Spring Boot实现某个功能,一般是引入对应场景的启动器(一般不写代码,只是声明这个启动器需要引用哪些依赖),然后这

  • SpringBoot Session接口验证实现流程详解

    目录 添加pom.xml 创建简单的测试接口 使用过滤器实现 使用拦截器实现 需求:只有用户登录成功后,才能访问其它接口,否则提示需要进行登录 项目仓库地址:https://gitee.com/aiw-nine/springboot_session_verify 添加pom.xml 新建Spring Boot(2.7.2)项目,添加如下依赖: <?xml version="1.0" encoding="UTF-8"?> <project xmlns

  • SpringBoot中的响应式web应用详解

    简介 在Spring 5中,Spring MVC引入了webFlux的概念,webFlux的底层是基于reactor-netty来的,而reactor-netty又使用了Reactor库. 本文将会介绍在Spring Boot中reactive在WebFlux中的使用. Reactive in Spring 前面我们讲到了,webFlux的基础是Reactor. 于是Spring Boot其实拥有了两套不同的web框架,第一套框架是基于传统的Servlet API和Spring MVC,第二套是

  • springboot整合mybatis流程详解

    目录 1.mybatis是什么 2.整合 2.1 导入依赖 2.2 创建包和类 2.3 在application.yaml配置mybatis 3.使用注解版mybaits 4.实战过程 1.mybatis是什么 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射.MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作.MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型.接口和 Java POJO(Plain Old Java

  • Springboot通过lucene实现全文检索详解流程

    目录 建立索引 检索文档 Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具. Lucene全文检索就是对文档中全部内容进行分词,然后对所有单词建立倒排索引的过程.主要操作是使用Lucene的API来实现对索引的增(创建索引).删(删除索引).改(修改索引).查(搜索数据). 假设我们的电脑的目录中含有很多文本文档,我们需要查找哪些文档含有某个关键词.为了实现这种功能,我们首先利用 Lucene 对这个目

  • SpringBoot整合Dozer映射框架流程详解

    目录 1. Dozer 介绍 2. 为什么要使用映射框架 Dozer 3. Dozer 映射框架的使用 1. Dozer 介绍 Dozer 是一个 Java Bean 到 Java Bean 的映射器,它递归地将数据从一个对象复制到另一个对象.Dozer 是用来对两个对象之间属性转换的工具,有了这个工具之后,我们将一个对象的所有属性值转给另一个对象时,就不需要再去写重复的调用 set 和 get 方法. 最重要的是,Dozer 可以确保来自数据库的内部域对象不会渗入外部表示层或外部消费者,它还可

随机推荐