Spring MVC详解

Spring MVC的定义

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的
正式名称"Spring Web MVC"来⾃其源模块的名称(Spring-webmvc),但它通常被称为"Spring
MVC".

MVC是软件架构的设计模式,与单例模式和工厂模式不同
⽐如去公司⾯试
我们到了公司之后, HR会给我们安排会议室, 根据候选⼈去通知不同的部⻔来安排⾯试, ⾯试结束
后, 由HR来告诉⾯试结果
在这个过程中
HR就是View(视图), 负责接待候选⼈, 并告知候选⼈⾯试结果
不同的部⻔, 就是Controller(控制器), HR根据候选⼈来选择对应的部⻔来进⾏⾯试
⾯试官, 就是Model层, 来处理⾯试这个事情.

什么是Spring MVC

MVC 是⼀种架构设计模式, 也⼀种思想, ⽽ Spring MVC 是对 MVC 思想的具体实现. 除此之外, Spring
MVC还是⼀个Web框架.
总结来说,Spring MVC 是⼀个实现了 MVC 模式的 Web 框架.
所以, Spring MVC主要关注有两个点:

  1. MVC
  2. Web框架
    Spring MVC 全称是 Spring Web MVC
    Spring MVC的大致流程
项目准备

Spring MVC 项⽬创建和 Spring Boot 创建项⽬相同,在创建的时候选择 Spring Web 就相当于创建了
Spring MVC 的项⽬.
Spring MVC 使⽤ Spring Boot 的⽅式创建
创建项⽬时, 勾选上 Spring Web 模块即可,如下图所⽰

学习Spring MVC最重要的三个方面
1. 建⽴连接:

将⽤⼾(浏览器)和 Java 程序连接起来,也就是访问⼀个地址能够调⽤到我们的Spring 程序。

在 Spring MVC 中使⽤ @RequestMapping 来实现 URL 路由映射 ,也就是浏览器连接程序的作⽤

在idea中构造如下代码

通过postman构造网络请求

@RequestMapping相关知识

@RequestMapping 是 Spring Web MVC 应⽤程序中最常被⽤到的注解之⼀,它是⽤来注册接⼝的
路由映射的.
表⽰服务收到请求时, 路径为 /hello 的请求就会调⽤hello这个⽅法的代码.
路由映射: 当⽤⼾访问⼀个 URL 时, 将⽤⼾的请求对应到程序中某个类的某个⽅法的过程就叫路由映射.
@RequestMapping 既可修饰类,也可以修饰⽅法,当修饰类和⽅法时,访问的地址是类路径 + ⽅法路径.
@RequestMapping标识⼀个类:设置映射请求的请求路径的初始信息
@RequestMapping标识⼀个⽅法:设置映射请求请求路径的具体信息

@RequestMapping 是 GET 还是 POST 请求?
我们可以通过构造请求来测试一下

GET请求:
浏览器发送的请求类型都是get, 通过以上案例, 可以看出来 @RequestMapping ⽀持get请求
POST 请求:


从运⾏结果可以看出: @RequestMapping 既⽀持Get请求, ⼜⽀持Post请求. 同理, 也⽀持其他的请
求⽅式.
那如何指定GET或者POST类型呢?
指定GET/POST⽅法类型
我们可以显⽰的指定@RequestMapping 来接收POST的情况

@RestControlle

既然 @RequestMapping 已经可以达到我们的⽬的了, 我们为什么还要加 @RestControlle呢?去掉@RestControlle我们来看一下结果

可以看到, 程序报了404, 找不到该⻚⾯.
这就是 @RestController 起到的作⽤.
⼀个项⽬中, 会有很多类, 每个类可能有很多的⽅法, Spring程序怎么知道要执⾏哪个⽅法呢?
Spring会对所有的类进⾏扫描, 如果类加了注解@RestController, Spring才会去看这个类⾥⾯的⽅法

2. 请求

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

传递单个参数

接收单个参数, 在 Spring MVC 中直接⽤⽅法中的参数就可以,⽐如以下代码:



可以看到, 后端程序正确拿到了name参数的值. Spring MVC 会根据⽅法的参数名, 找到对应的参数, 赋值给⽅法
如果参数不⼀致, 是获取不到参数的


注意事项
使⽤基本类型来接收参数时, 参数必须传(除boolean类型), 否则会报500错误



类型不匹配时, 会报400错误


对于参数可能为空的数据,建议使⽤包装类型

传递多个参数

如何接收多个参数呢? 和接收单个参数⼀样, 直接使⽤⽅法的参数接收即可. 使⽤多个形参.



当有多个参数时,前后端进⾏参数匹配时,是以参数的名称进⾏匹配的,因此参数的位置是不影响后端获取参数的结果.

传递对象

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




当传递对象中的成员变量为基本类型时,不传递参数,会默认为0


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

某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,⽐如前端传递了⼀个userName给后端,⽽后端是使⽤name字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使⽤@RequestParam 来重命名前后端的参数值.



如果使用name来作为参数接收变量的值,就会报这样的错误 equired request parameter 'userName' for method parameter type String is not present,认为userName这个参数不存在,这个required这个方法默认是true,意思是这个参数不能为空,必传参数

  1. 使⽤ @RequestParam 进⾏参数重命名时, 请求参数只能和 @RequestParam 声明的名称⼀
    致, 才能进⾏参数绑定和赋值.
  2. 使⽤ @RequestParam 进⾏参数重命名时, 参数就变成了必传参数.
    可以看到 required 的默认值为true, 表⽰含义就是: 该注解修饰的参数默认为必传
    既然如此, 我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错,如下设置



    设置完之后就可以发现传给参数为空了,而不会报错
    可以看到, 添加required=false之后, time前⾯也加了key, 变成了 value = "time"
    注解属性赋值时, 没有指明key的话, 默认为value属性.
    如果需要有多个属性进⾏赋值时, 需要写上key

传递数组

Spring MVC 可以⾃动绑定数组参数的赋值
后端实现代码如下


数组参数:请求参数名与形参数组名称相同且请求参数为多个, 后端定义数组类型形参即可接收参数

对于form表单来说,只能以post的形式来进行接收参数,不能使用GET方法

传递集合

集合参数:和数组类似, 同⼀个请求参数名有为多个, 且需要使⽤ @RequestParam 绑定参数关系
默认情况下,请求中参数名相同的多个值,是封装到数组. 如果要封装到集合,要使⽤
@RequestParam 绑定参数关系, 请求⽅式和数组类似
如果不使用,会出现以下错误


正确的代码如下

传递JSON数据

JSON概念
JSON:JavaScript Object Notation 【JavaScript 对象表⽰法】
JSON是⼀种轻量级的数据交互格式. 它基于 ECMAScript (欧洲计算机协会制定的js规范)的⼀个⼦集,
采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据.
简单来说:JSON就是⼀种数据格式, 有⾃⼰的格式和语法, 使⽤⽂本表⽰⼀个对象或数组的信息, 因此
JSON本质是字符串. 主要负责在不同的语⾔中数据传递和交换.
JSON与Javascript的关系
没有关系, 只是语法相似, js开发者能更快的上⼿⽽已, 但是他的语法本⾝⽐较简单, 所以也很好学
JSON语法
JSON 是⼀个字符串,其格式⾮常类似于 JavaScript 对象字⾯量的格式

JSON的语法:

  1. 数据在 键值对 (Key/Value) 中
  2. 数据由逗号 , 分隔
  3. 对象⽤ {} 表⽰
  4. 数组⽤ [] 表⽰
  5. 值可以为对象, 也可以为数组, 数组中可以包含多个对象
    JSON的两种结构
  6. 对象: ⼤括号 {} 保存的对象是⼀个⽆序的 键值对 集合. ⼀个对象以左括号 { 开始, 右括号 }
    结束。每个"键"后跟⼀个冒号 : ,键值对使⽤逗号 , 分隔
  7. 数组: 中括号 [] 保存的数组是值(value)的有序集合. ⼀个数组以左中括号 [ 开始, 右中括
    号 ] 结束,值之间使⽤逗号 , 分隔


    使⽤ObjectMapper 对象提供的两个⽅法, 可以完成对象和JSON字符串的互转
    writeValueAsString: 把对象转为JSON字符串
    readValue: 把字符串转为对象
    JSON优点
  8. 简单易⽤: 语法简单,易于理解和编写,可以快速地进⾏数据交换
  9. 跨平台⽀持: JSON可以被多种编程语⾔解析和⽣成, 可以在不同的平台和语⾔之间进⾏数据交换和传输
  10. 轻量级: 相较于XML格式, JSON数据格式更加轻量级, 传输数据时占⽤带宽较⼩, 可以提⾼数据传输速度
  11. 易于扩展: JSON的数据结构灵活,⽀持嵌套对象和数组等复杂的数据结构,便于扩展和使⽤
  12. 安全性: JSON数据格式是⼀种纯⽂本格式,不包含可执⾏代码, 不会执⾏恶意代码,因此具有较⾼的安全性
    基于以上特点, JSON在Web应⽤程序中被⼴泛使⽤, 如前后端数据交互、API接⼝数据传输等
传递JSON对象

接收JSON对象, 需要使⽤ @RequestBody 注解 RequestBody: 请求正⽂,意思是这个注解作⽤在请求正⽂的数据绑定,请求参数必须在写在请求正⽂中

获取URL中参数@PathVariable

path variable: 路径变量
和字⾯表达的意思⼀样, 这个注解主要作⽤在请求URL路径上的数据绑定
默认传递参数写在URL上,SpringMVC就可以获取到
后端实现代码:

上传⽂件@RequestPart

后端代码实现如下:

获取Cookie/Session

回顾 Cookie
HTTP 协议⾃⾝是属于 "⽆状态" 协议.
"⽆状态" 的含义指的是:
默认情况下 HTTP 协议的客⼾端和服务器之间的这次通信, 和下次通信之间没有直接的联系.
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的.
例如登陆⽹站成功后, 第⼆次访问的时候服务器就能知道该请求是否是已经登陆过了

上述图中的 "令牌" 通常就存储在 Cookie 字段中.
⽐如去医院挂号

  1. 看病之前先挂号. 挂号时候需要提供⾝份证号, 同时得到了⼀张 "就诊卡", 这个就诊卡就相当于患
    者的 "令牌".
  2. 后续去各个科室进⾏检查, 诊断, 开药等操作, 都不必再出⽰⾝份证了, 只要凭就诊卡即可识别出当
    前患者的⾝份.
  3. 看完病了之后, 不想要就诊卡了, 就可以注销这个卡. 此时患者的⾝份和就诊卡的关联就销毁了. (类
    似于⽹站的注销操作)
  4. ⼜来看病, 可以办⼀张新的就诊卡, 此时就得到了⼀个新的 "令牌"

    去医院看病首先需要填写信息,然后挂号,此时把你的信息储存在医院的服务器上,此时服务器返回一个SessionId,医院会给你一个就诊卡这个id就是你的就诊卡号,这个就诊卡相对于Cookie,里面记录着这个SessionId,当你去看医生的时候,医生只需要刷一下就诊卡,就诊卡中的SessionId就会与服务器进行通信,建立Session会话,把你存储在服务器中的数据全部返回给医生,医生就知道你的症状,等你就诊完了之后,这个SessionId就会注销,等下次就诊就会重复上述流程
    此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的⽤⼾信息, 这个就是 Session 机制所做的⼯作.
理解Session

我们先来了解⼀下什么是会话.
会话: 对话的意思

在计算机领域, 会话是⼀个客⼾与服务器之间的不中断的请求响应. 对客⼾的每个请求,服务器能够识
别出请求来⾃于同⼀个客⼾. 当⼀个未知的客⼾向Web应⽤程序发送第⼀个请求时就开始了⼀个会话.
当客⼾明确结束会话或服务器在⼀个时限内没有接受到客⼾的任何请求时,会话就结束了.
⽐如我们打客服电话
每次打客服电话, 是⼀个会话. 挂断电话, 会话就结束了
下次再打客服电话, ⼜是⼀个新的会话.
如果我们⻓时间不说话, 没有新的请求, 会话也会结束.
服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是从属于哪个⽤⼾, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与⽤⼾的信息的对应关系.
Session是服务器为了保存⽤⼾信息⽽创建的⼀个特殊的对象

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

SessionId 是由服务器⽣成的⼀个 "唯⼀性字符串", 从 Session 机制的⻆度来看, 这个唯⼀性字符串称为 "SessionId". 但是站在整个登录流程中看待, 也可以把这个唯⼀性字符串称为 "token".
上述例⼦中的令牌ID, 就可以看做是SessionId, 只不过令牌除了ID之外, 还会带⼀些其他信息, ⽐如时间, 签名等

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

    Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.

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

获取Cookie

传统获取Cookie

Spring MVC是基于 Servlet API 构建的原始 Web 框架, 也是在Servlet的基础上实现的
HttpServletRequest , HttpServletResponse 是Servlet提供的两个类, 是Spring
MVC⽅法的内置对象. 需要时直接在⽅法中添加声明即可.
HttpServletRequest 对象代表客⼾端的请求, 当客⼾端通过HTTP协议访问服务器时,HTTP请
求头中的所有信息都封装在这个对象中,通过这个对象提供的⽅法,可以获得客⼾端请求的所有信
息.
HttpServletResponse 对象代表服务器的响应. HTTP响应的信息都在这个对象中, ⽐如向客⼾
端发送的数据, 响应头, 状态码等. 通过这个对象提供的⽅法, 可以获得服务器响应的所有内容
Spring MVC在这两个对象的基础上进⾏了封装, 给我们提供更加简单的使⽤⽅法.
简洁获取Cookie
也有更简洁的⽅式获取Cookie,使用注解@CookieValue("Cookie名称")

获取Session

Session 存储和获取
Session是服务器端的机制, 我们需要先存储, 才能再获取
Session 也是基于HttpServletRequest 来存储和获取的
Session存储

储存到的session
getSession 操作内部提取到请求中的Cookie ⾥的
SessionId, 然后根据SessionId获取到对应的Session 对象, Session 对象⽤HttpSession来描述
获取Session有两种⽅式
HttpSession getSession ( boolean create);
HttpSession getSession ();
HttpSession getSession(boolean create) : 参数如果为 true, 则当不存在会话时新建会话; 参数如果
为 false, 则当不存在会话时返回 null
HttpSession getSession(): 和getSession(true) 含义⼀样, 默认值为true.
void setAttribute(String name, Object value): 使⽤指定的名称绑定⼀个对象到该 session 会话

Session读取

首先需要设置Session

在fiddler中查看HTTP请求和响应情况,可以看到在HTTP响应中,通过Set-Cookie告知客⼾端, 把SessionID存储在Cookie中


通过Fiddler观察Http请求和响应,可以看到, Http请求时, 把SessionId通过Cookie传递到了服务器


简洁获取 Session(2)
通过Spring MVC内置对象HttpSession 来获取

可以看到, Http响应中, 通过Set-Cookie告知客⼾端, 把SessionID存储在Cookie中

获取Header

传统获取 header
获取Header也是从 HttpServletRequest 中获取

简洁获取 Header
使用注解@RequestHeader

3. 响应

执⾏了业务逻辑之后,要把程序执⾏的结果返回给⽤⼾, 也就是响应.
在我们前⾯的代码例⼦中,都已经设置了响应数据, Http响应结果可以是数据, 也可以是静态⻚⾯,也可
以针对响应设置状态码, Header信息等

返回静态⻚⾯

创建前端⻚⾯ index.html(注意路径)




结果却发现, ⻚⾯未正确返回, http响应把 "/index.html" 当做了http响应正⽂的数据
那Spring MVC如何才能识别出来 index.html 是⼀个静态⻚⾯, 并进⾏返回呢?
我们需要把 @RestController 改为 @Controller


@RestController 和 @Controller 有着什么样的关联和区别呢?
咱们前⾯讲了MVC模式, 后端会返回视图, 这是早期时的概念

随着互联⽹的发展, ⽬前项⽬开发流⾏"前后端分离"模式, Java主要是⽤来做后端项⽬的开发, 所以也就不再处理前端相关的内容了
MVC的概念也逐渐发⽣了变化, View不再返回视图, ⽽是返回显⽰视图时需要的数据.
所以前⾯使⽤的 @RestController 其实是返回的数据.
@RestController = @Controller + @ResponseBody
@Controller : 定义⼀个控制器, Spring 框架启动时加载, 把这个对象交给Spring管理.
@ResponseBody : 定义返回的数据格式为⾮视图, 返回⼀个 text/html 信息
@RestController源码

如果想返回视图的话, 只需要把 @ResponseBody 去掉就可以了, 也就是 @Controller

返回数据@ResponseBody

我们上⾯讲到, @ResponseBody 表⽰返回数据,加上 @ResponseBody 注解, 该⽅法就会把 "/index.html" 当做⼀个数据返回给前端

@ResponseBody 既是类注解, ⼜是⽅法注解
如果作⽤在类上, 表⽰该类的所有⽅法, 返回的都是数据, 如果作⽤在⽅法上, 表⽰该⽅法返回的是数据.
也就是说: 在类上添加 @ResponseBody 就相当于在所有的⽅法上添加了 @ResponseBody 注解.
同样, 如果类上有 @RestController 注解时:表⽰所有的⽅法上添加了 @ResponseBody 注
解, 也就是当前类下所有的⽅法返回值做为响应数据
如果⼀个类的⽅法⾥, 既有返回数据的, ⼜有返回⻚⾯的, 就把 @ResponseBody 注解添加到对应的⽅法上即可

返回HTML代码⽚段

后端返回数据时, 如果数据中有HTML代码, 也会被浏览器解析

通过Fiddler观察响应结果, Content-Type 为 text/html

响应中的 Content-Type 常⻅取值有以下⼏种:
• text/html : body 数据格式是 HTML
• text/css : body 数据格式是 CSS
• application/javascript : body 数据格式是 JavaScript
• application/json : body 数据格式是 JSON
如果请求的是js⽂件, Spring MVC会⾃动设置Content-Type为 application/javascript
如果请求的是css⽂件, Spring MVC会⾃动设置Content-Type为 text/css

返回JSON

Spring MVC 也可以返回JSON,后端⽅法返回结果为对象


设置状态码

Spring MVC会根据我们⽅法的返回结果⾃动设置响应状态码, 程序员也可以⼿动指定状态码
通过Spring MVC的内置对象HttpServletResponse 提供的⽅法来进⾏设置

状态码不影响⻚⾯的展⽰
通过Fiddler来观察设置的结果:

约定前后端交互接⼝
概念介绍

约定 "前后端交互接⼝" 是进⾏ Web 开发中的关键环节.
接⼝⼜叫 API(Application Programming Interface), 我们⼀般讲到接⼝或者 API,指的都是同⼀个东西.是指应⽤程序对外提供的服务的描述, ⽤于交换信息和执⾏任务(与JavaSE阶段学习的[类和接⼝]中的接⼝是两回事).
简单来说, 就是允许客⼾端给服务器发送哪些 HTTP 请求, 并且每种请求预期获取什么样的 HTTP 响应.
现在"前后端分离"模式开发, 前端和后端代码通常由不同的团队负责开发. 双⽅团队在开发之前, 会提前
约定好交互的⽅式. 客⼾端发起请求, 服务器提供对应的服务. 服务器提供的服务种类有很多, 客⼾端按照双⽅约定指定选择哪⼀个服务.
接⼝, 其实也就是我们前⾯⽹络模块讲的的"应⽤层协议". 把约定的内容写在⽂档上, 就是"接⼝⽂档" ,接 ⼝⽂档也可以理解为是 应⽤程序的"操作说明书

相关推荐
陈大爷(有低保)9 分钟前
UDP Socket聊天室(Java)
java·网络协议·udp
kinlon.liu23 分钟前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
哈喽,树先生29 分钟前
1.Seata 1.5.2 seata-server搭建
spring·springcloud
王哲晓44 分钟前
Linux通过yum安装Docker
java·linux·docker
java6666688881 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存1 小时前
源码分析:LinkedList
java·开发语言
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
Jarlen1 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽1 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
Reese_Cool1 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言