目录
2.1.1 @RequestMapping, @GetMapping, @PostMapping 三个注解的区别
MVC 的全称为 Model View Controller , 它是一种软件架构模式, 将 web 应用程序分为模型、视图、控制器三部分.
关系图:
>>> 如何理解上图三者之间的关系
假设用户需要买鞋, 发送一个 HTTP 请求, 请求会先来到 Controller 这里, Controller 就是接受前端向后端发送的请求的 , 它是第一站, 就相当于乘坐地铁要先过安检一样. Controller 首先会校验前端的参数是否是安全的, 如果安全, 就继续将请求分发给 Model, 广义上来说, Model 分为业务模型和数据模型, 请求来到 Model 这里时, 就会组合数据, 查询与鞋子相关的所有物品, 再把这些物品相关的数据返回给 Controller, 此时 Controller 拿到的只是一些数据, 用户看不懂, 所以需要再分发给 View , 然后再结合前端页面进行渲染, 最后把有动态数据的页面通过响应返回给用户.此时用户得到的就是一个包含了鞋子的页面了. 此时用户的请求就得到了完整的响应了. <<<
当然上图所示的 MVC 是最初的一个设计, 由于 Spring MVC 是和 Spring 一块诞生的, Spring 诞生的时候, 那时候还没有前后端分离的概念, 所以那时候的程序员是啥都要去做的, 前后端是融在一块的. 这个模型在当时来说是没有问题的, 但是放在今天来说已经不适用了. 不过 Spring MVC 它也在演化, 也在更新迭代, 只是名字还叫做 MVC, 对于 Java 程序猿来说, 只做后端的事情, Spring MVC 依然是一个 Web 框架, 只要是 Web 框架, 就可以实现 HTTP 响应, 只不过现在不需要再做数据渲染的事了. 只管把数据返回给前端就行了, 前端自己来处理.
Spring MVC 和 MVC 的关系就和 IoC 和 DI 的关系一样, MVC 是一种思想, 而 Spring MVC 是一种具体的实现, 简单来说就是 Spring MVC 实现了 MVC.
学习 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 "hello Spring MVC~";
- }
- }
当我们写出这样一个代码的时候, 浏览器通过 http://127.0.0.1:8080/web/hello 的时候就可以映射到我们程序中的 hello() 方法.
- @Controller :加上@Controller 注解是为了让 spring MVC 项目加载类, 加载类的时候, 才可以调用对应的 hello() 方法. 如果不加 @Controller 注解, 就不能正确访问到本地程序
- @RequestMapping : 该注解既可以修饰方法, 也可以修饰类. 当它指修饰方法的时候, 只需要通过方法的路由就可以映射到对应的方法; 当修饰类和方法时, 就需要通过类的路由 + 方法的路由一起才能映射到对应的方法.
- @ResponseBody : 该注解表示当前类返回的是非静态页面, 如果不加该注解, 访问方法时就会报错.
【注意事项】
1. @Controller 注解不能被 @Sevice, @Component 等其他类注解替代, 如果换成其他类注解, 访问时就会报错. 这样设计的原因或许就是防止滥用.
2. @RequestMapping 注解可以只给方法加,但不能只给类加.
3. 当我们返回的是静态页面的时候, 可以不加 @RequestBody 注解.
4. @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
这种方式来获取前端传递过来的参数, 看起来就简单很多了, 这才是 Spring MVC 推荐获取参数的方法.
1. 这种写法获取 int 类型的参数时, 建议使用包装类, 如果前端不传递参数, 就会报错.
2. 前端传递过来的参数名字一定要和方法中的变量名要相同, 否则就获取不到.
http://127.0.0.1:8080/web/get1/name=zhangsan&age=11
浏览器访问结果:
1. 前端传递多个参数, 后端可以不按顺序接收参数, 只要变量名对上了就可以. 获取参数的结果和参数名有关, 和顺序无关.
如果前端传递过来的参数不止两三个, 假设有一百个怎么办. 这时候就可以封装成对象来接受了.
使用 postman 传递参数.
1. 当前端传递成百上千个参数的时候, 我们就不再使用接收多个变量的方式了, 而是直接封装成对象来接收, 此时 Spring MVC 就会去这个实体类里解析它所有的属性, 然后调用属性的 setter 方法, 将传递过来的值根据 key 值设置进去.
2. 当某一天, 前端又突然加了几个参数时, 我们的方法是不需要做出改变的, 只需要给实体类 User 中扩充属性即可.(参数名要保证一致!!!)
3. 参数名是大小写敏感的, 如果大小写不一样, 依然获取不到参数. 建议全部使用小写, 不要使用小驼峰.
前端代码:
后端代码:
浏览器访问: http://127.0.0.1:8080/login.html
输入姓名, 密码提交后, 显示运行结果:
form 表单传递参数时, 如果参数很多, 也可以使用获取对象的方式来接收, 代码的写法和前面是一样的.
前端通过 ajax 传参:
后端代码:
>>> 当后端返回 map 类型的时候, Spring MVC 会自动识别键值对格式并帮我们转成 JSON 格式返回给前端.
打开 fiddler 抓包查看 Content-Type:
>>> 前端传递过来的参数不是 JSON 对象, 只是简单的对象, 抓包查看 Content-Type:
后端代码: (@RequestBody 神奇注解)
前端通过 postman 构造参数:
执行结果:
>>> 此时再抓包查看请求中的 Content-Type:
此时请求的数据类型已经不再是 form-urlencoded, 而是变成了真正的 JSON 格式 的类型了. body 中的数据格式也随之而变了.
1. 当前端传递过来 JSON 格式的数据时, 后端如果还使用前面的 "接收多个参数" 的形式来接收, 又或者是通过 "接收普通对象" 的方式来接收, 此时就接收不到数据了, 此时通过 postman 发送请求时, 响应里的键对应的 value 值就都为 null 了.
2. 接收 JSON 格式的数据时, 后端需要使用 @RequestBody 注解来实现, @RequestBody 注解就可以从 body 中拿到 JSON 格式的数据. 并且接收 JSON 对象时, 只能是以对象的形式来接收, 不能写成多个参数的形式.
3. @requestBody 注解只能接收 JSON 对象, 不能用于接收普通对象, 不能混着用, 需要配套使用.
后端代码:
前端通过 postman 构造:
1. 上传文件的时候, 我们使用 @RequestPart 注解来实现, 注解中的参数对应 postman 中的 key.
2. 保存文件的时候, 此处最好写绝对路径, 一般都是写绝对路径, 写相对路径的时候, 我这里是代码不会报错, 但是在路径下没有找到对应的图片.
【传统方式】Servlet 获取 Cookie
通过开发者工具构造 Cookie :
浏览器访问: http://127.0.0.1:8080/web/getck
【Spring MVC 中的方式】@CookieValue
获取一个 Cookie:
获取多个 Cookie:
1. Spring MVC 中可以使用 @CookieValue 注解的方式实现更简单的获取 Cookie, 而 Servlet 时代的方式只有一个 getCookies() 方法, 如果你想要获取的 Cookie 有很多, 还是可以用原来的方式.
2. 在开发者工具里伪造的 Cookie 的默认级别是浏览器级别, 是存在内存中的, 关闭浏览器后再次访问就访问不到了.
在获取 session 之前, 先存储 session:
以前获取 Session 的方式:
【Spring MVC 的方式】@SessionAttribute
获取 Session 时, 需要先设置 session , 否则就会报错, 因为@SessionAttribute 注解中的默认的 required(是否必须) 是为 true 的. 也可以手动将其改为 false, 这样先获取 getSession , 就不会报错, 只会显示 name: null.
【玩个花活】
由于 Session 是依赖于 Cookie 的, 如果我们篡改了 SessionId, 浏览器是否还能访问到 ?
此时, 我们在设置 session 的情况下再去获取 session, 就会显示 null 了.
【理解 Cookie 和 Session 机制】
1. 当客户端访问服务器时, 浏览器会默认将当前网站的所有 Cookie 发送给后端, 原因就是因为 HTTP 是一个无状态的协议, 它不知道你是张三, 还是李四.
2. 另外 sessionId 通过 Cookie 存储在客户端也是有原因的, 如果直接在 URL 中传输, 就很容易被篡改, 就像上面我伪造 sessionId 之后, 再次获取 session 就获取不到了. 而通过 Cookie 存储 sessionId, 想要篡改的成本就会很高, sessionId 会有过期时间, 其次, 用户重新登录该网站时, sessionId 会不断改变.
了解更多 Cookie ,Session 机制 : https://blog.csdn.net/xaiobit_hl/article/details/127487411
传统的方式就不啰嗦了, 直接演示 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 中参数的代码:
1. 获取 URL 中传递过来的特殊参数时, 需要两步, 首先 @RequestMapping 注解中需要使用花括号在路由后面跟上相应的参数, 其次就是需要使用 @PathVariable 注解.
2. 此处的 @PathVariable 注解也可以根据需求进行参数重命名.
3. 特殊的 URL参数中不能出现带 '/' 的参数, 因为这些特殊的 URL 参数是通过 / 来区分层级的, 如果出现类似 'name/age' 这样一个完整的参数时, 那么程序访问时就会报错.
其实前面讲到过的设置 required 参数就是来控制参数是否必传.
如果我们的实际业务前端的参数是⼀个非必传的参数,我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错, 默认是 required=true.
在 static 目录下新建一个 xxx.html 文件:
index.html:
Controller 代码:
加上 @RestController 注解或者 @Controller + @ResponseBody, 返回String 等基本类型.
当我们的返回类型为集合或者对象的时候, Spring MVC 会自动帮我们转成 JSON 格式的对象, 返回给前端.
浏览器访问 URL: http://127.0.0.1:8080/index
使用 fiddler 抓包查看 Content-Type.
【举例说明】请求转发和请求重定向
假如 a 页面不用了, 换了新页面, 对于老用户, 还记住了原来的 a 页面, 于是就会对内帮助老用户请求转发到新页面, 对外(新用户), 就会显示访问失败,告诉浏览器你请求的页面不在我这, 你去其他网站请求.
【区别】forward VS redirect
1. 请求重定向(redirect)将请求重新定位到资源;请求转发(forward)服务器端转发。2. 请求重定向地址发生变化,请求转发地址不发生变化。3. 请求重定向与直接访问新地址效果⼀致,不存在原来的外部资源不能访问;请求转发服务器端转发有可能造成原外部资源不能访问。
本篇文章就到这里了, 谢谢观看!!