点进来你就是我的人了
博主主页:戳一戳,欢迎大佬指点!欢迎志同道合的朋友一起加油喔
目录
1. 什么是 Spring MVC
1.1 Spring、Spring MVC、Spring Boot
1.2 MVC 定义
1.3 MVC 和 Spring MVC 的关系
2. 怎么学 Spring MVC?
2.1将浏览器和服务器连接起来
2.1.1 @RequestMapping, @GetMapping, @PostMapping 三个注解的区别
2.2 在程序中获取前端传递过来的参数
2.2.1 传递单个参数
2.2.2 传递多个参数
2.2.3 获取对象
2.2.4 获取表单参数
2.2.5 获取 ajax 传递的普通对象
2.2.6 获取 JSON 对象【@RequestBody】
2.2.7 上传文件 【@RequestPart 】(获取前端的发来的文件)
将前端发来的文件保存在不同环境下
注意事项
2.2.8 获取 Cookie 【@CookieValue】
2.2.9 获取 Session 【@SessionAttribute】
2.2.10 获取 Header【@RequestHeader】
2.2.11 后端参数重命名【@ReuqestParam】
2.2.12 获取基础URL里的参数【@PathVariable】
2.2.13 设置参数必传【required】
2.3将结果返回给用户
2.3.1返回一个静态html文件
2.3.2 返回 text/html
2.3.3 返回 JSON 对象
2.3.4 请求转发和请求重定向【面试题】
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,它也是存在于 Spring 框架之中的,通俗的说,它其实就是 Spring 框架中的 Web 模块。 所以说,之前 Servlet 拥有的 " request 和 response ",我们依然能够在此框架中使用。
Spring,Spring Boot,Spring MVC 三者的关系,Spring 是最核心,其他两者都是基于 Spring 拓展出来的东西。
Spring MVC 是随着 Spring 的诞生而存在的一个框架。Spring 和 Spring MVC 诞生的历史是比较久远,在它们之后才有了 Spring Boot. 之前我们说 Spring Boot 是 Spring 的脚手架,也就是说 Spring Boot 就是 Spring 框架的框架,它的存在,就是为了程序员更快速地使用 Spring 框架。
现在绝大部分的 Java 项目都是基于 Spring (或 Spring Boot) 的,而 Spring 的核心就是 Spring MVC. 实际上,我们基于 Spring Boot 框架添加一个 【 Spring Web 依赖】 ,此时项目就变成了一个 Spring MVC 项目。所以,在当前主流的网络开发环境下,一个 Spring Boot 项目,我们也可以说是一个 Spring MVC 项目。
MVC 是 Model View Controller 的缩写,它是一种设计模式,这种设计模式把软件系统分为模型、视图和控制器
三个基本部分。
>>> 如何理解上图三者之间的关系
假设你正在一个在线图书商店中,你想找一本关于宇宙的书。于是,你在搜索框中输入"宇宙",然后点击"搜索"按钮。这个动作将你的请求发送给了 Controller(控制器)。
Controller 在这个场景中就像是商店的客服。它会接收你的请求,确认你的搜索词(在这里是"宇宙")是否符合要求,没有包含任何不恰当的字符等。确认无误后,Controller 就会将这个搜索请求交给 Model(模型)。
Model 就像是书店的书库管理员,知道每本书的详细信息和它们的存储位置。在收到 Controller 的请求后,Model 会在数据库中查找所有关于"宇宙"的书籍,然后将这些信息返回给 Controller。
Controller 收到这些书籍信息后,将其传递给 View(视图)。
View 就像是书店的橱窗设计师,负责按照一定的布局和样式显示书籍。View 将 Controller 提供的书籍信息渲染到页面上,使其成为一个你可以看到的、美观的书籍列表。
最后,这个包含了关于"宇宙"的书籍列表的视图就会返回给你,呈现在你的屏幕上。你就可以从这个列表中挑选你感兴趣的书籍了。
总的来说,在 MVC 架构中,Controller 接收并处理你的请求,通过 Model 获取需要的数据,然后将数据交给 View 进行美观的渲染,并最终将生成的视图返回给你。这就是 MVC 架构的工作方式。
当然上图所示的 MVC 是最初的一个设计, 由于 Spring MVC 是和 Spring 一块诞生的, Spring 诞生的时候, 那时候还没有前后端分离的概念, 所以那时候的程序员是啥都要去做的, 前后端是融在一块的. 这个模型在当时来说是没有问题的, 但是放在今天来说已经不适用了. 不过 Spring MVC 它也在演化, 也在更新迭代, 只是名字还叫做 MVC, 对于 Java 程序猿来说, 只做后端的事情, Spring MVC 依然是一个 Web 框架, 只要是 Web 框架, 就可以实现 HTTP 响应, 只不过现在不需要再做数据渲染的事了. 只管把数据返回给前端就行了, 前端自己来处理.
MVC 是⼀种思想,⽽ Spring MVC 是对 MVC 思想的具体实现。
总结来说: Spring MVC 是⼀个实现了 MVC 模式,并继承了 Servlet API 的 Web 框架。既然是 Web 框架,那么当⽤户在浏览器中输⼊了 url 之后,我们的 Spring MVC 项⽬就可以感知到⽤户的请求。
学习 Spring MVC 我们只需要掌握以下 3 个功能:
对于 Spring MVC 来说,掌握了以上 3 个功能就相当于掌握了 Spring MVC。
我们连接用户和 Java 程序之前, 先要创建一个 Spring MVC 项目, 而 SpringMVC 项目就是基于 Spring MVC 项目本身是基于 Spring Boot 项目的 (前面的博客有详细讲解 - 创建 Spring Boot 项目)
此处我们只需要在创建的时候多添加一个 Spring Web 框架即可:
Spring MVC 项目创建好了之后, 就可以进行编写代码了:
【代码示例】
@Controller // 加载并注册类
@ResponseBody // 当前类返回的是非静态页面
@RequestMapping("/web") // 使用 "/web" 可以访问到当前类 (可省略)
public class WebController {
@RequestMapping("/hello") // 使用 "/web" + "/hello" 可以访问到当前方法
public String hello() {
return "你好 世界";
}
}
当我们写出这样一个代码的时候, 浏览器通过 http://127.0.0.1:8080/web/hello 的时候就可以映射到我们程序中的 hello() 方法.
- @Controller :加上@Controller 注解是为了让 spring MVC 项目加载类, 加载类的时候, 才可以调用对应的 hello() 方法. 如果不加 @Controller 注解, 就不能正确访问到本地程序
- @RequestMapping : 该注解既可以修饰方法, 也可以修饰类. 当它指修饰方法的时候, 只需要通过方法的路由就可以映射到对应的方法; 当修饰类和方法时, 就需要通过类的路由 + 方法的路由一起才能映射到对应的方法.
- @ResponseBody : 该注解表示当前类返回的是非静态页面, 如果不加该注解, 访问方法时就会报错.
【注意事项】
- @Controller 注解不能被 @Sevice, @Component 等其他类注解替代, 如果换成其他类注解, 访问时就会报错. 这样设计的原因或许就是防止滥用.
- @RequestMapping 注解可以只给方法加,但不能只给类加.
- 当我们返回的是静态页面的时候, 可以不加 @RequestBody 注解.
- @RequestBody 注解 + @Controller 注解 = @RestController 注解, 可以替换.
前边代码使用的 @RequestMapping 注解, 可以正确访问到本地程序的方法, 且在浏览器通过 url 映射到本地程序本身就是 Get 请求, 那我们来看看 POST 请求是否也能正常访问到程序.
通过 postman 构造了一个 POST 请求访问时, 可以看见输出我们想要的结果了 , 由此证明 2.6.13 版本后的 @RequestMapping 注解是既支持 GET 请求又支持 POST 请求的.
当你的程序想要只支持 GET 请求或者只支持 POST 请求的时候, 就可以使用 @GetMapping 和 @PostMapping 注解, 这俩注解和 @RequestMapping 是类似的用法, 使用相应的请求类型, 是能够正确映射到本地程序的.
【处理 Get 请求】
// 写法一(都适用)
@RequestMapping("/hello")
// 写法二
@RequestMapping(value="/hello", method=RequestMethod.GET)
// 写法三
@GetMapping("/hello")
【处理 Post 请求】
// 写法一
@RequestMapping(value = "/hello",method = RequestMethod.POST)
// 写法二
@PostMapping("/hello")
参数请求的类型无非就三种:
第一种获取方式(了解):
URL 传参 : http://127.0.0.1:8080/web/hello?name=zhangsan
1. 通过 Servlet 中的 request 方式获取, Spring MVC 是基于 Servlet API 的, 它内置了 HttpServletRequest, HttpServletResponse 对象, 只不过在 Spring MVC 中默认隐藏了这两个参数.
2.这是在学习 Servlet 的时候用的方法, 它还是比较麻烦的, 并且它拿到的参数永远是 String 类型的, 如果传递过来的参数是 int 类型的, 就需要强转,如果前端没传这个参数, 强转就会有空指针的问题.所以这种方式既麻烦, 又不安全.
第二种获取方式:
URL 传参: http://127.0.0.1:8080/web/get1?age=11
http://127.0.0.1:8080/web/hello?name="书生"&age=18
浏览器访问结果:
- 前端传递多个参数, 后端可以不按顺序接收参数, 只要变量名对上了就可以. 获取参数的结果和参数名有关, 和顺序无关.
- 如果前端传递过来的参数不止两三个, 假设有一百个怎么办. 这时候就可以封装成对象来接受了.
使用 postman 传递参数.
1. 当前端传递成百上千个参数的时候, 我们就不再使用接收多个变量的方式了, 而是直接封装成对象来接收, 此时 Spring MVC 就会去这个实体类里解析它所有的属性, 然后调用属性的 setter 方法, 将传递过来的值根据 key 值设置进去.
2. 当某一天, 前端又突然加了几个参数时, 我们的方法是不需要做出改变的, 只需要给实体类 User 中扩充属性即可.(参数名要保证一致!!!)
3. 参数名是大小写敏感的, 如果大小写不一样, 依然获取不到参数. 建议全部使用小写, 不要使用小驼峰.
浏览器访问: http://127.0.0.1:8080/login.html 输入姓名, 密码提交后, 显示运行结果:
打开 fiddler 抓包查看 Content-Type:
>>> 当后端返回 map 类型的时候, Spring MVC 会自动识别键值对格式并帮我们转成 JSON 格式返回给前端.
后端代码: (@RequestBody 神奇注解)
前端通过 postman 构造参数发送请求:
>>> 此时再抓包查看请求中的 Content-Type:
此时请求的数据类型已经不再是 form-urlencoded, 而是变成了真正的 JSON 格式 的类型了. body 中的数据格式也随之而变了.
1. 当前端传递过来 JSON 格式的数据时, 后端如果还使用前面的 "接收多个参数" 的形式来接收, 又或者是通过 "接收普通对象" 的方式来接收, 此时就接收不到数据了, 此时通过 postman 发送请求时, 响应里的键对应的 value 值就都为 null 了.
2. 接收 JSON 格式的数据时, 后端需要使用 @RequestBody 注解来实现, @RequestBody 注解就可以从 body 中拿到 JSON 格式的数据. 并且接收 JSON 对象时, 只能是以对象的形式来接收, 不能写成多个参数的形式.
3. @requestBody 注解只能接收 JSON 对象, 不能用于接收普通对象, 不能混着用, 需要配套使用.
后端代码:
前端通过 postman 构造:
抓包结果:
我们利用抓包的时候,可以看到图片是乱码,其实也就是二进制的数据,这些二进制的数据被放在正文中,并以 boundary 作为边界,放入边界之内。此外,我们刚刚上传图片时附带的 " myfile" 参数,也都与图片一样,放在了边界之内。
- 上传文件的时候, 我们使用 @RequestPart 注解来实现, 注解中的参数对应 postman 中的 key.
- 虽然我们这一次存储文件,执行起来没有错误,但我们应该思考一个问题:这一次存储图片的时候,放在了本地的 Windows 系统下的一个固定的文件夹,也就是说,我们这一次是将图片的路径写死了,而且是写在了开发环境下吗,实际上,我们以后将项目部署在 Linux 服务器上的时候,用户上传的文件应该放在云服务器上才对
针对上面的第 ② 点,前后端交互不用变,但我们应该重新考虑一下业务逻辑。
a.因为在公司中会涉及不同的测试,开发环境,运行环境等等,然而当环境改变的时候,为了使我们更加方便,所以我们引入相应的配置,最终的时候,只需要修改一行配置,就可以完成切换。如下,我们设置三个yml配置文件。
我们这里就用yml文件来进行演示,需要注意的是,前面的application是固定的,我们只需要更改后面的名称即可。
(1)主配置文件application.yml
中来配置配置文件的运行平台
# 设置配置文件的运行平台
spring:
profiles:
active: xxx
# 如运行application-dev配置文件 即active: dev
(2)在不同的环境下配置不同的参数,如在开发配置文件中配置图片保存路径:
# 文件保存路径
img:
path: d:\\aa\\
b.而在这种情况下,我们要保存图片到任意路径的话,需要操作以下几步:
(1)配置并获取保存的路径
(2)利用(UUID)来保持图片名称的唯一性
(在此步要获取到名片的格式,并且将UUID与图片的格式拼接在一起)
(3)储存图片
【代码示例】
@Slf4j
@Controller // 加载并注册类
@ResponseBody // 当前类返回的是非静态页面
@RequestMapping("/web") // 使用 "/web" 可以访问到当前类 (可省略)
public class WebController2 {
//获取配置文件的保存路径
@Value("${img.path}")
private String imgpath; //imgpath通过注解获取到了文件保存的路径
//上传文件
@RequestMapping("/upload") // 使用 "/web" + "/hello" 可以访问到当前方法
public boolean upload(String name, @RequestPart("myfile")MultipartFile file) throws IOException {
boolean result = false;
//得到原图片的名称
String filename = file.getOriginalFilename();
//得到图片后缀(png)
filename = filename.substring(filename.lastIndexOf("."));
//生成不重名的文件名
filename = UUID.randomUUID().toString() + filename;
try {
//将图片保存
file.transferTo(new File( imgpath+ filename));
result = true;
} catch (IOException e) {
log.error("图片上传失败!" + e.getMessage());
}
return result;
}
}
再次通过postman构造请求上传照片:
我上传了四次图片,可以发现最终生成的文件名不一样,这里抓包结果就不看了,和之前的差不多。
① 我们通过配置文件这种方式,可以选择不同系统、不同环境下的存储路径。所以,我们就不能忘记使用配置文件,我们应该通过 " @Value " 注解方式,将文件路径引入到代码中。在这里,我选择了将文件保存至本地中,也就是 " -dev " 开发环境。
② 这里的 " UUID " 表示的是全世界唯一的 ID,或者说是全局 ID,高级语言一般都有此功能,Java 同样为我们实现了这个功能,它能生成一段随机字符串,供我们使用。我们不但可以利用 UUID 作为目录的名字,也可以让其作为文件的名字,如果一个用户需要一个文件夹,就可以选择前者,如果将所有用户的信息放在一个文件夹,就可以选择后者。
③如果你想允许上传更大的文件,你可以在 Spring Boot 配置文件中增加上传文件大小的限制。在 application.properties
或者 application.yml
中,你可以添加或者更改以下设置:(这将允许上传最大为 10MB 的文件。你可以根据需要调整这个大小限制。)
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
④ 业务逻辑代码中有个很关键的点,为什么一定要拿到用户上传的文件后缀格式呢?
答:用户上传的文件不仅仅是 " xxx.png “,” xxx.jpg ",还有可能是 " xxx.mp4 “,” xxx.gif " …如果我们需要 " UUID " 生成一个独一无二的名字,就需要获取文件格式。因为根据用户上传的特定格式,后端必须定义相同的文件格式,只是名字发生变化而已。所以,我们就可以通过 " 截取字符串 " 的经典方式来实现,如下所示:
String filename = file.getOriginalFilename(); //得到原图片的名称
filename = filename.substring(filename.lastIndexOf(".")); //得到图片后缀(png)
filename = UUID.randomUUID().toString() + filename; //生成不重名的文件名
【传统方式】Servlet 获取 Cookie
通过开发者工具构造 Cookie :
【Spring MVC 获取cookie 的方式】@CookieValue
获取一个 Cookie:
获取多个 Cookie:
1. Spring MVC 中可以使用 @CookieValue 注解的方式实现更简单的获取 Cookie, 而 Servlet 时代的方式只有一个 getCookies() 方法, 如果你想要获取的 Cookie 有很多, 还是可以用原来的方式.
2. 在开发者工具里伪造的 Cookie 的默认级别是浏览器级别, 是存在内存中的, 关闭浏览器后再次访问就访问不到了.
在获取 session 之前, 先存储 session: (获取前存储session的方式 spring mvc 和 servlet 一样)
以前获取 Session 的方式:
【Spring MVC 获取session 的方式】@SessionAttribute
浏览器输入url验证session是否获取成功:
获取 Session 时, 需要先存储 session , 否则就会报错, 因为@SessionAttribute 注解中的默认的 required(是否必须) 是为 true 的. 也可以手动将其改为 false, 这样就算没有存储 session ,去获取 Session 也不会报错, 只会显示 name: null.
传统的方式就不啰嗦了, 直接演示 Spring MVC 的写法:
浏览器访问: http://127.0.0.1:8080/web/get_head
有时候, 前端传过来的参数, 不符合后端的命名规则时, 这时候就要用到后端参数重命名了.
例如前端传递过来一个时间变量 t, 后端就会认为是 TM 什么魔法字符, 对于后端人员来说, 就需要使用 time 来接收, 否则代码 review 过不了. >>> 怎么解决?
1. 和前端人员协商 (难度大)
对于大公司来说, 前端人员和后端人员是分开干活的, 说先你找不找得到他是一个问题, 其次你找到他了, 他听不听你的又是一个问题. 又或者他有他的一套方法论, 他传一个 t , 不传一个 time, 我一个接口能节省这么多带宽, 如果有成千上万个人的话, 他可能会从节省网络带宽, 从性能等方面来说服你, 这时候各有各的说法, 问题就很难得到解决.
2. 使用 @ReuqestParam 注解来进行后端参数重命名
当前端不好配合, 我们拿他没招时, 就可以使用后端参数重命名来解决.
浏览器访问: http://127.0.0.1:8080/web/rename?t=2022-12-6%2014:48:21
【注意】当我们使用了后端参数重命名时, 前端如果此时又觉得你的说法是对的, 将 t 改回 time 时, 那么此时再次访问, 就访问不到了. 所以一旦使用了后端参数重命名, 前端传递的参数就要和 @RequestParam() 注解中的变量保持一致. 当然也可以像前面那样将 required 改成 false.这样即使变量名对不上, 也不会报错.
此处讲到的 URL 中传参和普通的 URL 传参还不太一样
>>>为什么会有这样的 URL 传参呢, 其实就是为了提高在搜索引擎中的权重.
就好比我们在搜索引擎中搜索世界杯, 假设两家公司的规模都是差不多大的, 为什么搜出来的结果, 排名一个在前, 一个在后? 下面这两种写法就会引起这种区别:
排在前面的公司的 URL : http://127.0.0.1:8080/世界杯/abc?name=cdef
排在后面的公司的 URL : http://127.0.0.1:8080/web/hello?param=世界杯
第一个 URL 中世界杯这个关键词出现在主 URL 中, 而第二个 URL 中世界杯关键词出现在 URL 后面的参数中, 而主 URL 的搜索引擎权重就会比参数中的搜索引擎权重高.
Spring MVC 接收 URL 中参数的代码:
使用postman构造get请求:
1. 获取 URL 中传递过来的特殊参数时, 需要两步, 首先 @RequestMapping 注解中需要使用花括号在路由后面跟上相应的参数(@RequestMapping(“/xxx/{参数1}{参数2}”)), 其次就是在方法上使用 @PathVariable 注解.
2. 此处的 @PathVariable 注解也可以根据需求进行参数重命名.
3. 特殊的 URL参数中不能出现带 '/' 的参数, 因为这些特殊的 URL 参数是通过 / 来区分层级的, 如果出现类似 'name/age' 这样一个完整的参数时, 那么程序访问时就会报错.
其实前面讲到过的设置 required 参数就是来控制参数是否必传.
如果我们的实际业务前端的参数是⼀个非必传的参数,我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错, 默认是 required=true.
前面的三部曲,我们已经进行了两步,下面将是最后的环节,即从服务器返回给客户端(用户)
在 static 目录下新建一个 xxx.html 文件:
Controller 代码:
加上 @RestController 注解或者 @Controller + @ResponseBody, 返回String 等基本类型.
当我们的返回类型为集合或者对象的时候, Spring MVC 会自动帮我们转成 JSON 格式的对象, 返回给前端.
通过postman发送请求:
【举例说明】请求转发和请求重定向
假设我们有一个在线购物网站,用户可以在网站上查看、选择商品,并将它们添加到购物车。
- 请求转发的例子:
假设一个用户在购物车页面上点击"结账"按钮,这将发起一个请求到"/checkout" URL。然后服务器可能会决定,因为这个用户还没有登录,所以不能直接进入结账页面。因此,它将请求转发到登录页面。
在这个例子中,用户只知道他们点击了"结账"按钮并尝试访问"/checkout",但实际上他们看到的是登录页面的内容。同时,浏览器地址栏仍然显示的是"/checkout" URL。
- 请求重定向的例子:
假设用户在登录页面输入了他们的用户名和密码,然后点击了"登录"按钮。这将发起一个请求到"/login" URL,并在请求中包含了他们输入的用户名和密码。
服务器验证了这些信息后,如果登录成功,它会发送一个重定向响应,让浏览器重定向到用户的个人主页(例如,"/user-home")。浏览器收到这个响应后,会发送一个新的请求到"/user-home" URL,显示用户的个人主页。在这个过程中,浏览器地址栏会更新为新的"/user-home" URL。
【区别】forward VS redirect
1. 请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。
2. 请求重定向地址发生变化,请求转发地址不发生变化。
3. 请求重定向与直接访问新地址效果⼀致,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问。
请求重定向(redirect):浏览器发送请求后,服务器会返回一个特殊的响应,告诉浏览器这个请求需要重新定位到另一个URL。这个过程中,浏览器会再发送一个新的请求到新的URL,因此,用户的地址栏会显示新的URL。
请求转发(forward):转发发生在服务器端,用户发送一个请求到服务器,服务器接收请求后,将其转发到另一个资源(比如另一个Servlet,或JSP页面)。这个过程中,浏览器并不知道服务器内部发生了什么,因此用户的地址栏仍显示原始的URL。