SpringMVC——响应处理(1)【包含源码分析】

news2024/11/17 13:39:43
@Controller
public class JsonReturnController {


    @ResponseBody
    @GetMapping("/getPet")
    public Pet getPet(){

        Pet pet=new Pet();
        pet.setAge(5);
        pet.setName("lily");
        return pet;
    }
}

项目启动后 浏览器输入 http://localhost:8080/getPet 。

debug DispatcherServlet组件中其中对返回值处理的方法为
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod
中的public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object… providedArgs方法

	  try {
		  //用返回值处理器处理返回的结果
     this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}

step into 进入内部方法继续debug

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

解析的结果正如归纳的步骤而言

1 :获得返回结果处理器 \textcolor{red}{1:获得返回结果处理器} 1:获得返回结果处理器
在这里插入图片描述
查看RequestResponseBodyMethodProcessor的supportsReturnType方法的实现源码如下

public boolean supportsReturnType(MethodParameter returnType) {
   //所在类上有@ResponseBody或返回方法上有@ResponseBody注解,将使用RequestResponseBodyMethodProcessor作为返回结果处理器
   //getPet()方法上包含@ResponseBody注解
   return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
}

2 :返回结果处理器处理返回结果 \textcolor{red}{2:返回结果处理器处理返回结果} 2:返回结果处理器处理返回结果
在这里插入图片描述

写入响应结果的关键步骤:

  • 内容协商(浏览器默认以请求头的方式告知服务器端允许接受的内容类型)

  • 服务器根据自身的能力,决定服务器能生产什么样的内容类型的数据

  • SpringMVC会遍历IOC容器底层的消息转换器HttpMessageConverter 看能否处理

  • HttpMessageConverter 是一个处理消息转换的标准接口,不同的内容类型有具体的实现类去处理



public interface HttpMessageConverter<T> {
    //是否支持指定MediaType和Class的读操作【从请求获得数据】
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    //是否支持指定MediaType和Class的写操作【向响应写入数据】
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    //获得支持的MediaType内容类型列表
	List<MediaType> getSupportedMediaTypes();
    //读,从请求获得数据
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;
    //写,向响应写入数据
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

在这里插入图片描述


  • 使用消息转换器HttpMessageConverter向响应输出写数据
    关键代码逻辑在于writeWithMessageConverters,使用消息转换器来给返回写入返回数据,源码如下:

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

		Object body;
		Class<?> valueType;
		Type targetType;

		if (value instanceof CharSequence) {
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

		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;
       //从响应中获得ContentType信息
		MediaType contentType = outputMessage.getHeaders().getContentType();
		if (contentType != null && contentType.isConcrete()) {
			if (logger.isDebugEnabled()) {
				logger.debug("Found 'Content-Type:" + contentType + "' in response");
			}
           //响应中ContentType不为空时,以响应体中的ContentType为准【意指当前请求已被处理过,告知服务器应该响应的数据类型】
			selectedMediaType = contentType;
		}
		else {
           //获得原生的请求对象
			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<>();
           //遍历请求允许接收的类型,去check 所有的可接受的数据类型是否双方匹配
			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);
			}
		}

		if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
           //遍历IOC容器中的消息转换器,去向Response写数据
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
				if (genericConverter != null ?
                       //判断消息转换器是否支持Class类型与响应结果的MediaType类型间的写操作
						((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) {
							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;
				}
			}
		}

		if (body != null) {
			throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
		}
	}

在解析上面这段代码的时候需要,需要了解一个概念内容协商(浏览器在发送请求的时候告知服务器端将接受怎样的返回数据类型,默认通过在请求头中设定Accept头)
在这里插入图片描述
获得浏览器最适合返回得数据类型时,需要用==消息转换器==去响应Response中写数据
在这里插入图片描述
使用MappingJacksonHttpMessageConveter去写响应数据
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

十、vben框架如何使用table来写报表

在项目开发的过程中&#xff0c;有很多特殊的table样式&#xff0c;有的时候后端会用帆软来写报表&#xff0c;但是有的特殊的报表后端就不能支持实现了&#xff0c;那么前端是如何实现的呢&#xff0c;今天我们就来讲讲。 先上效果图&#xff1a; 本次使用的tsx组件来写的报表…

csapp第一章 --- 计算机系统漫游

重要内容大纲 1.1 C程序实现过程 一个由用户编写的“hello world”源代码到可以在屏幕上看见可执行文件经过一下过程&#xff1a; C语言翻译的阶段介绍 1.预处理cpp&#xff1a;头文件展开、宏定义替换 将 .c 文件&#xff08;源程序&#xff09;变成 .i 文件&#xff08;修改…

一文搞懂如何在 React 中使用 防抖(Debounce)和 节流(Throttle)

在前端的日常开发中&#xff0c;经常会使用到两个函数防抖&#xff08;Debounce&#xff09;和节流&#xff08;Throttle&#xff09;&#xff0c;防抖函数可以有效控制在一段时间内只执行最后一次请求&#xff0c;例如搜索框输入时&#xff0c;只在输入完成后才进行请求接口。…

linux环境下打包c++的Qt应用程序装载vtk和itk

文章目录一、QT安装安装前准备1.1 方法一 源码编译安装(实践缺少xcb依赖)&#xff1a;1.2 方法二 run文件安装&#xff1a;二、迁移windows下qt应用到linux三、VTK安装3.1 安装ccmake 和 VTK 的依赖项3.2 linux下VTK库下载安装四、QT应用基础上引入VTK依赖五、ITK安装六、QT应用…

sikuli+eclipse对于安卓app自动化测试的应用

Sikuli是什么&#xff1f; 下面是来自于官网的介绍&#xff1a;Sikuli is a visual technology to automate and test graphical user interfaces (GUI) using images (screenshots). Sikuli includes Sikuli Script, a visual scripting API for Jython, and Sikuli IDE, an …

入门Java第十五天 线程

一、多线程 1.1进程和线程 进程&#xff1a;进程就是操作系统中运行的每一个应用程序。例如&#xff1a;微信&#xff0c;QQ 线程&#xff1a;线程是进程中的每一个任务。 多线程&#xff1a;在一个进程中&#xff0c;可以同时执行多个线程。同时完成多个任务。 并发&#x…

投票的链接怎么做出来的网上那些投票链接怎么做的网上投票器

用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务&#xff0c;很多用户都很喜欢“活动星投票”这款软件。“活动星投票”小程序在使…

【踩坑记录】使用OpenCV报错“ undefined symbol: TIFFReadRGBATileExt, version LIBTIFF_4.0”

问题描述 例如&#xff1a;使用OpenCV报错&#xff0c;找不到相关动态库&#xff1a; ImportError: /lib/libgdal.so.26: undefined symbol: TIFFReadRGBATileExt, version LIBTIFF_4.0解决方案&#xff1a; 运行前&#xff0c;先执行或者在./bashrc &#xff08;根目录下ctr…

【图像分类】卷积神经网络之ZFNet网络模型结构详解

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 1. 前言 由于AlexNet的提出,大型卷积网络开始变得流行起来,但是人们对于网络究竟为什么能表现的这么好,以及怎…

Python编写GUI界面案例:实现免费下载器

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 本次网站&#xff1a; 本文所有模块\环境\源码\教程皆可点击文章下方名片获取此处跳转 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 模块使用&#xff1a; import parsel >>> pip install parsel…

从0开始学python -41

Python3 命名空间和作用域 命名空间 先看看官方文档的一段话&#xff1a; A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。 命名空间(Namespace)是从名称到对象的映射&#xff0c;大部分的命名空间都是…

蓝牙耳机适合跑步戴吗,五款适合跑步蓝牙耳机推荐

音乐是坚持运动下去的不懈动力&#xff0c;在运动当中佩戴蓝牙耳机&#xff0c;能够让我们远离枯燥无味&#xff0c;运动更有律动感&#xff0c;运动更为畅快。运动当中佩戴的蓝牙耳机&#xff0c;佩戴舒适度以及牢固度是我们十分需要注意的&#xff0c;下面这几款比较热门的运…

IP-GUARD控制台账户输入多次错误密码锁定后该如何解锁?

其他管理员账户给锁定了,暂时只能等其锁定时间到了才可以再次输入,默认是设置是锁定30min; 1、如果急需此账户查看,可以使用admin系统管理员账户登录控制台,在工具-账户中清除这个账户的密码,重新登录设置密码。

idea远程调试线上jar包

有时候本地代码没问题但在线上运行会报错&#xff0c;这时候可以使用idea的remote功能调试线上jar包 步骤1 步骤2&#xff1a;新建remote 步骤3&#xff1a;配置服务器ip和端口 并复制生成的JVM参数供之后使用 步骤4&#xff1a;打jar包&#xff0c;并将生成的jar包放到服务…

想招到实干派程序员?你需要这种面试法

技术招聘中最痛的点其实是不精准。技术面试官或CTO们常常会向我们吐槽&#xff1a; “我经常在想&#xff0c;能不能把我们项目中的代码打印出来&#xff0c;作为候选人的面试题的一部分&#xff1f;” “能不能把一个Bug带上环境&#xff0c;让候选人来试试怎么解决&#xf…

电信网上用户资管理系统的设计与实现

技术&#xff1a;Java、JSP等摘要&#xff1a;在对目前市面上已经拥有的营业厅功能分析和整理后&#xff0c;为了保证营业厅中多种功能的分层次处理设计了一个的电信网上用户自管理系统&#xff0c;以web页面方式实现了与用户的交互&#xff0c;同时保证了移动电话计费管理系统…

数据结构期末复习总结(后章)

作者的话 作为一名计算机类的学生&#xff0c;我深知数据结构的重要性。在期末复习前&#xff0c;我希望通过这篇博客给大家一些复习建议。希望能帮助大家夯实数据结构的基础知识&#xff0c;并能够更好地掌握数据结构和算法的应用。 一、树与二叉树 1.基本概念 结点的度&am…

Google的中国用户注意!黑客正利用搜索结果投放恶意软件

2月16日研究人员发现&#xff0c;Google搜索结果中含有冒充知名软件的恶意软件下载链接。 研究人员通过测试后表示&#xff0c;攻击者为了增加浏览量&#xff0c;甚至通过在Google搜索结果中购买误导性广告&#xff0c;诱骗受害者下载安装恶意程序。 据悉&#xff0c;这些被冒…

【JavaSE】集合(Collection类)

文章目录1.集合框架1.1 collection1.2 map2. collection2.1.集合迭代2.2.contains和remove2.3集合中元素的删除3.List4.ArrayList5.LinkList6.Vector7.泛型7.1泛型初步7.2自动类型推断机制7.3 自定义泛型8.增强for循环9.Set9.1HashSet9.2 TreeSet1.集合框架 1.1 collection 1.…

4G模块DTU网关远程抄表方案(二):DL645/698协议国网电表

4G模块DTU网关远程抄表方案(二):DL645/698协议国网电表 1 DL 645协议简介 DL645协议是一种用于智能电能表的远程抄读通讯标准。制定该标准是为统一和规范多功能电能表与数据终端设备进行数据交换时的物理连接和通信链路及应用技术规范。DL645协议可用于远程监测电力传输和使用…