【Spring MVC】一篇文章带你吃透 SpringMVC 请求

【Spring MVC】一篇文章带你吃透 SpringMVC 请求

🎬 那我掉的头发算什么个人主页
🔥 个人专栏: 《javaSE》《数据结构》《数据库》《javaEE》

⛺️待到苦尽甘来日


在这里插入图片描述

引言

上回说到,SpringMVC主要涉及三个方面——建立连接、请求、响应。建立连接的部分我们已经了解了,创建请求的工具我们也介绍了。下面,我们将详细的去学习请求与响应的具体实现!

文章目录

请求

访问不同的路径, 就是发送不同的请求. 在发送请求时, 可能会带⼀些参数, 所以学习Spring的请求, 主要是学习如何传递参数到后端以及后端如何接收。

传递单个参数

简单的参数传递上一节已经提到过了,诸如下面的代码,运行之后,在url中传递keyword的值,就可以将参数接收到了。

@RequestMapping("/r1")publicStringr1(String keyword){return"接收参数:"+ keyword;}

下面我们来看一些特殊一点的情况:

@RequestMapping("/r3")publicStringr3(Integer number){return"接收参数: "+ number;}@RequestMapping("/r4")publicStringr4(int number){return"接收参数: "+ number;}

这两个方法在实际运行中你觉得有差别吗???
让咱们分不同情况来测试一下吧:
1.正常传递

在这里插入图片描述


在这里插入图片描述


两段代码都可以正常输出。

2.传递错误类型的数据

在这里插入图片描述
在这里插入图片描述

两个方法对应的程序输出错误信息状态码都是400,也就是用户端输入错误。

3.不输入数据

在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


可以看到此时Integer类型的参数被自动赋值null,int类型对应的参数没有被赋值并且程序出现了状态码为500的错误,即服务器端错误,同时在日志中也能看到报错的信息。

SpringMVC 处理接口参数的核心流程是:解析请求参数 → 类型转换 → 赋值给方法参数,不同场景的表现差异都源于这个流程:

  • 场景 1:正常传递正确类型数据
    无论是 int 还是 Integer,SpringMVC 都会将请求中字符串格式的数字(比如 number=100)自动转换为对应的数值类型,赋值给参数,因此两者都能正常输出。
  • 场景 2:传递错误类型数据(比如 number=abc)
    SpringMVC 在「类型转换」阶段就会失败(无法把字符串 abc 转成数字),直接抛出「类型不匹配异常」,并将异常映射为 HTTP 400 状态码(客户端请求参数错误)。这个失败发生在「赋值」之前,因此 int 和 Integer 的表现一致。
  • 场景 3:不传参数(核心差异)
  • Integer 参数:SpringMVC 解析到没有该参数,会将参数值绑定为 null;而 Integer 作为包装类支持 null,因此参数赋值成功,接口正常执行,返回「接收参数: null」。
  • int 参数:SpringMVC 同样尝试将 null 赋值给参数,但 int 作为基本类型无法存储 null,此时会直接抛出 NullPointerException(空指针异常)或 IllegalStateException(参数绑定失败)。
    这个异常发生在服务器端的参数绑定环节,属于服务器内部处理错误,因此返回 HTTP 500 状态码(服务器内部错误)

所以说,在日常使用中或者企业开发中,推荐使用包装类类型。

传递多个参数

@RequestMapping("/r2")publicStringr2(String name,Integer age){return"名字:"+ name +"年龄:"+ age;}
在这里插入图片描述


传递多个参数时对应的关系是参数名,所以参数顺序无所谓,不会影响最终结果。

传递对象

如果参数比较多时, 方法声明就需要有很多形参. 并且后续每次新增⼀个参数, 也需要修改方法声明.我们不妨把这些参数封装为⼀个对象。

定义Userinfo类

packagecom.bite.spring.demo;publicclassUserinfo{publicString name;publicint gender;publicint age;publicUserinfo(String name,int gender,int age){this.name = name;this.gender = gender;this.age = age;}publicStringgetName(){return name;}publicvoidsetName(String name){this.name = name;}publicintgetGender(){return gender;}publicvoidsetGender(int gender){this.gender = gender;}publicintgetAge(){return age;}publicvoidsetAge(int age){this.age = age;}@OverridepublicStringtoString(){return"Userinfo{"+"name='"+ name +'\''+", gender="+ gender +", age="+ age +'}';}}
@RequestMapping("/r5")publicStringr5(Userinfo userinfo){return userinfo.toString();}

Spring会根据参数名称自动将变量赋值给参数,还是与顺序无关。

在这里插入图片描述


不过由于对象的一些性质,这里有一个特殊情况:

在这里插入图片描述


如果我们加一个无参数的构造函数,此时,我们传递参数时故意不传递参数:

在这里插入图片描述


咱们刚刚说过,int类型变量不传递参数会产生服务器赋值异常,为什么这里没报错????
因为无参数的构造方法在构造对象时会自动赋值,包装类赋值null,基本类型赋值成0。

后端参数重命名(后端参数映射)

某些情况下,前端参数key和后端参数key可能不一样。比如前端为了不让用户直观的知道url中的具体信息,可能将keyword信息表示成q。但是我们后端写代码时还是更加习惯使用keyword这个变量名,此时就会用到@RequestParam来重命名前后端的值。

@RequestMapping("/r6")publicStringr6(@RequestParam(value ="q",required =false)String keyword){return"接收参数:"+ keyword;}

这里的required设置成true时表示务必传参,false时表示可以不传参。

在这里插入图片描述


在这里插入图片描述


注意,前端的映射是q,传参的时候不要搞错了哦!

传递数组

@RequestMapping("/r7")publicStringr7(String[] arr){return"接收参数: "+Arrays.toString(arr);}
在这里插入图片描述


在这里插入图片描述
数组参数:请求参数名与形参数组名称相同且请求参数为多个, 后端定义数组类型形参即可接收参数
http://127.0.0.1:8080/param/m5?
arrayParam=zhangsan&arrayParam=lisi&arrayParam=wangwu
或者使用 http://127.0.0.1:8080/param/m6?listParam=zhangsan%2clisi%2cwangwu
或者使用 http://127.0.0.1:8080/param/m5?arrayParam=zhangsan,lisi,wangwu

传递集合

传递集合的逻辑大致上与数组相同,但是在定义时需要用@RequestParam注解明确标识,因为Spring默认不会自动将「请求中的多个同名参数」封装为集合。

在这里插入图片描述


在这里插入图片描述


修改之后:

@RequestMapping("/r8")publicStringr8(@RequestParamList<String> name){return"接收参数: list = "+ name;}
在这里插入图片描述

传递JSON

JSON就是⼀种数据格式, 有自己的格式和语法,使用文本表示⼀个对象或数组的信息, 因此JSON本质是字符串. 主要负责在不同的语言中数据传递和交换。
语法:
1.数据在 键值对(Key/Value) 中
2. 数据由逗号 , 分隔
3. 对象⽤ {} 表⽰
4. 数组⽤ [] 表⽰
5. 值可以为对象, 也可以为数组, 数组中可以包含多个对象

接收JSON对象,需要使用@RequestBody注解。

@RequestMapping("/r9")publicStringr9(@RequestBodyUserinfo userinfo){return"接收参数: "+ userinfo.toString();}
在这里插入图片描述


使用fiddler观察:

在这里插入图片描述


如果去掉@RequestBody注解,后端就无法正常给予变量赋值:

在这里插入图片描述

获取url中的参数@PathVariable

@RequestMapping("/article/{articleId}")publicStringr10(@PathVariableInteger articleId){return"获取文章ID:"+ articleId;}
在这里插入图片描述
@RequestMapping("/article/{type}/{articleId}")publicStringr11(@PathVariableInteger articleId ,@PathVariableString type){return"文章ID:"+ articleId +"文章类型:"+ type;}
在这里插入图片描述

这种在今日头条这种新闻类网站会用到,大家可以去看看他们的url,大致可以猜到底层的代码逻辑。

上传文件@RequestPart

//上传文件@RequestMapping("/r12")publicStringr12(@RequestPart("file11")MultipartFile file)throwsIOException{System.out.println(file.getOriginalFilename());//文件上传 file.transferTo(newFile("D:\\cctalk"+ file.getOriginalFilename()));return"文件上传成功";}
在这里插入图片描述

获取Cookie/Session

Cookie是http部分我们就讲过的东西,不熟悉的大佬们可以回去看一下我这篇博客:
https://blog.ZEEKLOG.net/2301_81614213/article/details/155863011?spm=1001.2014.3001.5501

Session:(会话)
在计算机领域, 会话是⼀个客户与服务器之间的不中断的请求响应. 对客户的每个请求,服务器能够识别出请求来自于同⼀个客户. 当⼀个未知的客户向Web应用程序发送第⼀个请求时就开始了⼀个会话.当客户明确结束会话或服务器在⼀个时限内没有接受到客户的任何请求时,会话就结束了.
服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是属于哪个用户, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与用户的信息的对应关系.
Session是服务器为了保存用户信息⽽创建的⼀个特殊的对象。

在这里插入图片描述


Session的本质就是⼀个 “哈希表”, 存储了⼀些键值对结构. Key 就是SessionID, Value 就是用户信息(用户信息可以根据需求灵活设计).

在这里插入图片描述

1.当用户登陆的时候, 服务器在 Session 中新增⼀个新记录, 并把 sessionId返回给客户端. (通过HTTP 响应中的 Set-Cookie 字段返回).
2. 客户端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的Cookie 字段带上).
3. 服务器收到请求之后, 根据请求中的 sessionId在 Session 信息中获取到对应的用户信息, 再进行后续操作.找不到则重新创建Session, 并把SessionID返回。

在这里插入图片描述


Cookie 和 Session 的区别

  • Cookie 是客户端保存用户信息的⼀种机制. Session 是服务器端保存用户信息的⼀种机制.
  • Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session 之间的桥梁
  • Cookie 和 Session 经常会在⼀起配合使用. 但是不是必须配合.
  • 完全可以⽤ Cookie 来保存⼀些数据在客户端. 这些数据不⼀定是用户⾝份信息, 也不⼀定是SessionId
  • Session 中的sessionId 也不需要非得通过 Cookie/Set-Cookie 传递, 比如通过URL传递

代码实战:
首先来教大家如何发送一个带有cookie的请求:

在这里插入图片描述
在这里插入图片描述


在这里插入图片描述


send发送请求之后,在下面的cookies里面能找到自己设置的cookie表示成功。

接收cookie代码:

@RequestMapping("/r13")publicStringr13(HttpServletRequest request){Cookie[] cookies = request.getCookies();if(cookies!=null){for(Cookie cookie:cookies){System.out.println(cookie.getName()+":"+ cookie.getValue());}}return"获取cookie成功";}

使用HttpServletRequest实例化对象,虽然Servlet的使用已经是过去式,但是这个类很好用,通过这个类可以获得一个请求中的很多信息。

使用注解:

在这里插入图片描述


我们发送请求之后,日志中会有代码里面打印的数据。

@RequestMapping("/r14")publicStringr14(@CookieValue("java")String java){return"从cookie中获取java的值:"+ java;}
在这里插入图片描述

Session的处理
设置Session

@RequestMapping("/setSession")publicStringsetSession(HttpServletRequest request){//从cookie中获取sessionId, 根据sessionId 获取Session对象//如果sessionId不存在, 则创建HttpSession session = request.getSession();//默认存储在内存中//登录的用户名称 session.setAttribute("userName","zhangsan"); session.setAttribute("age",17);return"设置session成功";}

getSession方法参数默认为true,从cookie中获取sessionId, 根据sessionId 获取Session对象,如果sessionId不存在, 则创建。
为false时,如果不存在sessionid,设置为null。

获取Session

//获取session@RequestMapping("/getSession")publicStringgetSession(HttpServletRequest request){//从cookie中获取sessionId, 根据sessionId 获取Session对象HttpSession session = request.getSession(false);//如果用户登录, session 有值, 未登录, session为nullif(session==null){return"用户未登录";}else{//从session中获取登录用户的信息String userName =(String)session.getAttribute("userName");return"登录用户为: "+ userName;}}//获取session@RequestMapping("/getSession2")publicStringgetSession2(HttpSession session){//从session中获取登录用户的信息String userName =(String)session.getAttribute("userName");return"登录用户为: "+ userName;}//获取session@RequestMapping("/getSession3")publicStringgetSession3(@SessionAttribute("userName")String userName){return"登录用户为: "+ userName;}
在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


直接调用getSession方法的话,因为还没有设置,此时显示用户未登录。需要先使用setSession。建立了Session后,可以在Cookie里面看到SessionId。

对于SessionId:
✅ 不同客户端 → 不同服务器SessionId 完全不同。客户端和服务器都是独立的,两边的会话没有任何关联。
✅ 同一客户端 → 不同服务器SessionId 不同。每个服务器都会独立生成自己的 SessionId,客户端会为不同服务器保存不同的 Cookie。
✅ 不同客户端 → 同一服务器SessionId 不同。服务器会为每个新客户端分配唯一的 SessionId,确保会话隔离。
✅ 同一客户端不同时刻 → 同一服务器SessionId 分情况:
如果 Cookie 未过期、未被清除 → SessionId 相同
如果 Cookie 失效 / 被清除 → 服务器生成新的 SessionId,因此不同

在这里插入图片描述


在这里插入图片描述


就比如咱们的postman和浏览器就是两个不同的客户端,他们的SessionId就是不一样的。

在这里插入图片描述


如果使用fiddler抓包的话,可以看到设置SessionId时响应中有Set-cookie的部分。

获取Header

@RequestMapping("/getHeader")publicStringgetHeader(HttpServletRequest request){String userAgent = request.getHeader("User-Agent");return"从header中获取userAgent:"+ userAgent;}@RequestMapping("/getHeader2")publicStringgetHeader2(@RequestHeader("User-Agent")String userAgent){return"从header中获取userAgent:"+ userAgent;}
在这里插入图片描述


在这里插入图片描述


当然我们使用HttpServletRequest能获取到的请求头中Header肯定不止UserAgent这一个,肯定还有其他的部分。

以上就是本篇博客全部内容!

Read more

Neo4j 知识讲解与在线工具使用教程

图数据库领域的核心工具 ——Neo4j,同时详细拆解其在线预览控制台(https://console-preview.neo4j.io/)的使用方法,以及查询工具(https://console-preview.neo4j.io/tools/query)的模块功能。 一、Neo4j 核心知识铺垫 在使用工具前,我们需要先理解 Neo4j 的本质和核心概念,这是后续操作的基础。 1. 什么是 Neo4j? Neo4j 是世界上最流行的原生图数据库(Native Graph Database),专门用于存储、查询和分析 “实体之间的关联关系”。它与我们熟悉的 MySQL 等关系型数据库的核心差异的是: * 关系型数据库(MySQL):用 “表 + 行 + 外键” 间接表示关联,查询多表关联时需频繁 JOIN,效率低; * 图数据库(Neo4j)

By Ne0inhk
【无人机】无人机路径规划算法

【无人机】无人机路径规划算法

目录 一、引言:无人机与路径规划算法 二、路径规划算法基础 (一)定义与重要性 (二)规划目标与约束条件 三、常见路径规划算法详解 (一)A * 算法 (二)Dijkstra 算法 (三)RRT(快速扩展随机树)算法 (四)蚁群算法 四、算法应用实例与效果展示 (一)不同场景下的算法应用 (二)算法性能对比数据 五、算法的优化与发展趋势 (一)现有算法的优化策略 (二)结合新技术的发展方向 六、挑战与展望 (一)面临的技术挑战 (二)未来应用前景 七、结论 一、引言:无人机与路径规划算法 在科技飞速发展的今天,无人机作为一种极具创新性的技术产物,已深度融入我们生活的方方面面,

By Ne0inhk

简单理解:单片机怎么和FPGA通信

了解单片机与 FPGA 之间的通信方式,这是嵌入式系统中非常常见的硬件交互场景,核心是要根据传输速率、硬件资源、开发复杂度选择合适的通信协议。 一、主流通信方式及实现方案 单片机和 FPGA 通信主要分为并行通信和串行通信两大类,下面按从易到难、从低速到高速的顺序介绍: 1. 通用 IO 口(GPIO)自定义协议(最简单) 适合低速、短距离、数据量小的场景(如按键、状态交互),完全自定义通信规则,开发灵活。 * 硬件连接: * 单片机:1 个输出引脚(发送) + 1 个输入引脚(接收) * FPGA:1 个输入引脚(接收) + 1 个输出引脚(发送) * 需共地,建议加 10K 上拉电阻提高稳定性。 * 单片机端(C 语言,

By Ne0inhk