(一).介绍Spring MVC
1.具体概念
Spring MVC,通常我们都是叫做Spring Web MVC,是基于Servlet API 构建的原始框架。总结来说,Spring Web MVC 是一个Web框架, 简称为Spring MVC。
MVC,即 Model View Controller,是软件工程中的一种软件架构设计模式,它把软件系统分为模型,视图和控制器三部分

view:指在应用程序中专门用来与浏览器进行交互,展示数据的资源
model:是应用程序的主体部分,用来处理程序中数据逻辑的部分
controller:可以理解为分发器,用于决定对于视图发来的请求,需要从哪一个模型来处理,以及处理完后需要跳回到哪一个视图,即用来连接视图和模型
MVC是一种架构设计模式,也是一种思想,而Spring MVC是对MVC思想的具体实现。除此之外,Spring MVC还是一个Web框架。总的来说,Spring MVC是一个实现了MVC模式的Web框架。
事实上,在前面介绍Spring快速入门的时候,已经使用过Spring MVC了,在创建Spring Boot项目的时候,我们选中了Spring Web 框架,其实就是Spring MVC。

Spring Boot 只是实现Spring MVC的其中⼀种⽅式⽽已.
Spring Boot 可以添加很多依赖, 借助这些依赖实现不同的功能. Spring Boot 通过添加Spring Web
MVC框架, 来实现web功能。不过Spring在实现MVC时, 也结合⾃⾝项⽬的特点, 做了⼀些改变, 相对⽽⾔, 下⾯这个图或许更加合适⼀些.

只不过现在基本没有View了,现在后端都是直接返回数据到浏览器上,不通过view了。
2.学习SpringMVC
在学习Spring MVC的时候,主要是学习如何通过浏览器和用户程序进行交互。主要分为三个方面①.建立连接②.请求③.响应
(二).建立连接
1.基础概念
在Spring MVC中,使用@RequestMapping注解来实现URL路由映射,也就是浏览器连接程序的作用。下面通过一个例子来看。


可以看到,在浏览器中通过访问URL,得到了后端的数据。可能有人会问了,既然@RequestMapping可以完成任务了,那么为什么还要加个@RestController ?


当我将@RestController去掉之后,重新运行,发现报错了,状态码为404,这个状态码在JavaEE初阶的时候介绍过。这个因为一个项目中会有很多类,每个类可能由很多个方法

随便点一个就很多。Spring在启动的时候,会对所有的类进行扫描,如果类加了@RestController或@Controller(@RestController=@Controller + @ResponseBody),Spring才会去看这个类里面的方法有没有加@RequestMapping这个注解,当然关于@RestController这个注解的作用还不止这一点,后面还会详细介绍。
这是@RestController的原码

2.@RequestMapping的使用
@RequestMapping既可以修饰类,也可以修饰方法。修饰类的时候称为"类注解",修饰方法的时候,称为"方法注解"。

3.@RequestMapping的请求方式
(1).既支持GET,也支持POST
@RequestMapping既支持GET请求,也支持POST请求

通过上面的抓包,可以看出是一个GET请求
如果想要是POST请求,则需要通过form表单来构造请求
也可以在后面跟数组,这样也可以
java
@RequestMapping(value = "/func1",method = {RequestMethod.GET,RequestMethod.POST})
public String func1(){
return "func1";
}
(2).只支持GET
方式1:
java
@RequestMapping(value = "/func2",method = RequestMethod.GET)
public String func2(){
return "func2";
}

可以看到,我在@RequestMapping中添加了一个method属性,然后指定方法为GET

这是method的原码,主要用途就是指定请求方式的,可以看到,是一个枚举数组,如果只跟一个值的话,不需要加大括号,如果多个值的话就需要加大括号
方式2:
java
@GetMapping("/func3")
public String func3(){
return "func3";
}
直接通过@GetMapping注解,来指定请求方式为GET
(3).只支持POST
方式1:
java
@RequestMapping(value = "/func4",method = RequestMethod.POST)
public String func4(){
return "func4";
}
在@RequestMapping中添加了一个method属性,然后指定方法为POST
方式2:
java
@PostMapping("/func5")
public String func5(){
return "func5";
}
直接通过@PostMapping注解,来指定请求方式为POST
4.下载Postman
(1).具体介绍
Postman,可以理解为是一个测试工具,当我们写完代码后,都是需要进行测试的,这时候就可以使用Postman来进行测试。使用Postman进行测试和使用浏览器进行测试,本质上是一样的效果
(2).使用规则

(3).传参介绍
①.普通传参,即通过查询字符串来传参

②.form-data传参
表单提交数据,通常用于提交图片或文件
选择Body -> form-data 选项

③.x-www-form-urlencoded
form表单

④.raw
可以上传任意格式的文本,可以上传text,json,xml,html等等

(三).请求数据
对于请求数据,当访问不同的路径的时候,就是发送的不同的请求,在发送请求的时候,可能会带一些参数,下面就来介绍如何携带参数
1.传递单个参数
java
@RequestMapping("/r1")
public String r1(String username){
return "接收参数:" + username;
}

注意:在postman中测试的时候,传递参数的名称要和后端一样,否则会出错

2.传递两个参数
java
@RequestMapping("/r2")
public String r2(String username,String password){
return "接收参数:用户名"+username+" 密码"+password;
}

3.基本类型接收和包装类型接收的区别
java
@RequestMapping("/r3")
public String r3(Integer num){ //包装类型
return "接收参数:"+num;
}
@RequestMapping("/r4")
public String r4(int num){ //基本类型
return "接收参数:"+num;
}


可以看到,当传递值的时候没有区别


当没传递值的时候,区别就出来了,没传递值的时候,基本类型直接报错,而包装类型则为null
4.传递对象
java
package org.review.blog.springbootblog.model;
public class TestUserInfo {
private String name;
private int age;
private int gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getGender() {
return gender;
}
public void setGender(int gender) {
this.gender = gender;
}
@Override
public String toString() {
return "TestUserInfo{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}
上面是我定义了一个User类,然后我们就开传递这个User类创建的对象
java
@RequestMapping("/r5")
public String r5(TestUserInfo userInfo){
return "接收参数:"+userInfo.toString();
}


上图这两种方式都可以,一种是通过get传输的,一种是通过post传输的
主要是把这个对象当成"请求参数对象"进行处理
5.绑定参数,重命名参数
先通过一个示例来看

当我搜索一个"qqq"的时候,在url的查询字符串中显示了"q=qqq",这个可能是前端进行了一些加密手段,但是对于后端来说,并不知道这个"q"是什么意思,所以当前端向后端发请求的时候,由于后端不知道"q"的具体含义是什么,所以就无法进行处理。此时,解决方法就是,后端对参数进行重命名。
java
@RequestMapping("/r6")
//将前端传过来的q 和 keyword进行绑定,如果不重命名,前端直接传keyword则会直接报错
//参数绑定的意思主要是,后端不理解q是什么意思,所以通过直到该参数的意思的变量来接收这个参数
public String r6(@RequestParam("q") String keyword){
return "接收参数:"+keyword;
}

同时,使用了@RequestParam这个注解后,表示这个参数是一个"必传参数",如果不传,则直接报错

如果想要避免这种情况,可以在后面加上一个"require"属性,并设置为false
java
@RequestMapping("/r6")
//将前端传过来的q 和 keyword进行绑定,如果不重命名,前端直接传keyword则会直接报错
//参数绑定的意思主要是,后端不理解q是什么意思,所以通过直到该参数的意思的变量来接收这个参数
public String r6(@RequestParam(value = "q",required = false) String keyword){
return "接收参数:"+keyword;
}

这样,即使不传参数,也不会报错
事实上,@RequestParam说是参数重命名,事实上就是"参数绑定",让"q"这个参数和"keyword"这个参数进行绑定
注意:@RequestParam是用来接收URL查询参数或表单数据的,无法直接解析请求体里的JSON数据,所以不能使用JSON传输
6.传递数组
java
@RequestMapping("/r7")
public String r7(String[] arr){
return "传递参数:"+Arrays.toString(arr);
}


这两种方式都可以
7.传递集合
java
@RequestMapping("/r8")
public String r8(@RequestParam List<String> list){
return "接收参数:"+list;
}
注意:这里使用@RqeustParam的原因是,默认情况下接收的是一个数组,然后让这个数组和list进行绑定


注意:不能使用JSON传输,原因和上面的一样
8.传递JSON
(1).介绍JSON
java
{
"squadName": "Super hero squad",
"homeTown": "Metro City",
"formed": 2016,
"secretBase": "Super tower",
"active": true,
"members": [{
"name": "Molecule Man",
"age": 29,
"secretIdentity": "Dan Jukes",
"powers": ["Radiation resistance", "Turning tiny", "Radiation
blast"]
}, {
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": ["Million tonne punch", "Damage resistance", "Superhuman
reflexes"]
}, {
"name": "Eternal Flame",
"age": 1000000,
"secretIdentity": "Unknown",
"powers": ["Immortality", "Heat Immunity", "Inferno",
"Teleportation", "Interdimensional travel"]
}]
}
上面的代码就是一段JSON代码。JSON语法分为这几点:①.数据在 键值对(Key/Value) 中 ②.数据用 " , " 隔开 ③.对象用 " { } "表示④.数组用 " " 表示,值可以为对象,也可以为数组,数组中可以包含多个对象
常见的JSON和对象的转换工具有三个:①.gson,是由谷歌推出的 ②.fasjson 是由阿里巴巴推出的 ③.jackon 是Spring内部就集成了的

如果没有引依赖,则需要引入依赖;如果已经引入依赖了,则通过ObjectMapper这个类中的readValue()方法,将JSON转成对象,通过ObjectMapper这个类中的writeValueAsString()方法,将对象转为JSON
由于下面主要是使用这两个方法,所以我们直接在test这个包中写测试代码
java
//对象转JSON
@Test
void ObjecttoJson(){
TestUserInfo testUserInfo=new TestUserInfo();
testUserInfo.setName("zhangsan");
testUserInfo.setAge(21);
testUserInfo.setGender(1);
ObjectMapper objectMapper=new ObjectMapper();
String str = objectMapper.writeValueAsString(testUserInfo); //进行转换
System.out.println(str);
}

这里使用@Test注解,主要目的就是为了标记,"标记"这是一个单元测试方法,这个单元测试方法是一个可以独立运行的测试方法
java
//JSON转对象
@Test
void JsontoObject(){
ObjectMapper objectMapper=new ObjectMapper();
String str="{\"age\":21,\"gender\":1,\"name\":\"zhangsan\"}";
TestUserInfo testUserInfo = objectMapper.readValue(str, TestUserInfo.class); //进行转换
System.out.println(testUserInfo);
System.out.println(testUserInfo.getName());
}

(2).传输JSON
这里的传递JSON,指的是,前端向后端传递JSON格式的数据

使用@RequestBody注解的原因是,把请求的正文JSON转换成TestUserInfo类型。因为前端只能传输JSON,不能传输对象,所以前端传的时候是通过JSON传的,然后通过@RequestBody调用Jackson的消息转换器用ObjectMapper把JSON字符串反序列化成Java对象。
测试的时候必须传json数据 按 请求体 JSON 绑定 必须用 POST/PUT 等带 Body 的方法
9.从URL中获取参数

这是我从"今日头条"上,找到了一个案例。如果说,想要获取到URL中的参数的话,该如何操作?
java
@RequestMapping("/r10/{number}")
public String r10(@PathVariable Integer number){
return "接收参数:"+number;
}

要获取URL中的参数,首先要使用**@PathVariable** 注解,表示为**"路径的变量"**注解,同时要在方法路径中,将要被获取的URL参数使用 " { } " 括起来
同时,也可以获取多个参数
java
@RequestMapping("/r11/{type}/{number}")
//也可以支持重命名,重命名了之后表示"该参数必传"
public String r11(@PathVariable("type") String type111,@PathVariable Integer number){
return "接收参数:"+type111+number;
}

10.上传文件
java
@RequestMapping("/r12")
public String r12(MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
file.transferTo(new File("E:\\test\\"+file.getOriginalFilename())); //上传文件
return "上传成功";
}

同时也是支持重命名的
11.获取Cookie,存储Session,获取Session
(1).回顾Cookie和Session
Cookie最好的例子就是去医院挂号。在去医院挂号的时候,首先护士会询问你一些基本信息,例如,姓名,身份证号,性别,年龄,病状等信息,然后就会给你一张"就诊卡","就诊卡"始终在你自己的手里。当你去看医生的时候,医生也是通过刷你的"就诊卡"就知道了你的一些基本信息,每次看病的时候,医生都会刷"就诊卡"。此时,这个就诊卡就相当于"Cookie"。当医生刷你的"就诊卡"的时候,刷的一瞬间,就会向医院的服务器里,去找关于你的数据,此时医院的服务器就是"Session"。
关于Cookie和Session,出现的原因是因为,HTTP协议是**"无状态的"** 。即客户端和服务器这次的通信,和下次通信之间是没有直接的联系的。例如,当我第一次访问CSDN的时候,我首先要进行登录,当登录完成后,我就可以看我要找的博客了。看完之后,我就退出了。当下次再访问CSDN的时候,他还要我重新登录,此时就是"无状态的"。但是,每次访问都需要重新登陆,就很麻烦,所以我就希望CSDN能够记住我,当我下次再重新访问CSDN的时候,不需要再重新登陆了,此时就出现了**"Cookie"**。

就类似于上图的效果。上述图中的 "令牌" 通常就存储在 Cookie 字段中.
注意:Cookie是存储在客户端中的,Session是存储在服务器中的。就像"就诊卡",就诊卡一直在你的手中。
Session,即"会话"。

站在服务器的角度,服务器同一时刻收到的请求是很多的,例如,同一时间,我访问了CSDN的服务器,同时,张三,李四,王五,都访问了CSDN服务器。那么服务器就需要区分清楚每个请求属于哪个用户,此时就出现了"Session"。Session是服务器为了保存用户信息而创建的一个特殊的对象。
那么站在服务器的角度,服务器是如何区分不同的Session对象的?
答:通过SessionId。当客户端第一次请求服务器的时候,服务器中并没有保存客户端的信息,此时服务器就创建了一个Session,然后SessionId也就随之生成了,然后服务器在"响应"的时候,通过Set-Cookie的方式,存储到了客户端的Cookie中。当下次客户端再次访问服务器的时候,直接通过SessionId来进行访问

事实上,Session的本本质就是一个"哈希表"没存储了一些键值对结构,Key就是SessionId,Value就是用户的相关信息

当不同的用户访问服务器的时候,就会通过sessionId进行区分

重点:
①.Cookie是客户端保存用户信息的一种机制;Session是服务器端保存用户信息的一种机制
②.Cookie和Session之间主要是通过SessionId关联起来的,SessionId是Cookie和Session之间的桥梁
③.Cookie和Session经常会在一起配合使用,但不是必须配合。可以用Cookie来保存一些数据在客户端,这些数据不一定是用户身份信息,也不一定是SessionId;Session中的sessionId也不需要非得通过Cookie/Set-Cookie传递,比如通过URL传递
(2).具体代码
Ⅰ.获取Cookie

上图是postman设置Cookie的方法
方式①:老版本,不适用注解
java
@RequestMapping("/r13")
public String r13(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
if (cookies!=null){
for(Cookie cookie:cookies){
System.out.println(cookie.getName()+" : "+cookie.getValue());
}
return "获取成功";
}
return "获取失败";
}

这里我们使用了一个新的类,HttpServletRequest,对应的还有一个类HttpServletResponse。在前面JavaEE初阶的时候,我们学习了Http协议,包括报头等信息,HttpServletRequest中的方法,可以设置HTTP协议中报头的所有数据

方式②:新版本,使用注解
java
@RequestMapping("/r14")
public String r14(@CookieValue("name") String name){
return name;
}
直接通过注解@CookieValue来获取cookie值

Ⅱ.存储Session
存储Session依旧使用HttpServletRequest类中的方法
java
@RequestMapping("/r15")
public String r15(HttpServletRequest request){
//先从前端发来的请求中找到Cookie,然后从Cookie中找到SessionId,然后通过SessionId找到Session对象
HttpSession session = request.getSession();//如果sessionid不存在,则创建对象;如果存在,那么setAttribute会替代当前值
session.setAttribute("name","zhangsan");
session.setAttribute("age",1);
return "session设置成功";
}
这个方法目前不太好测,需要结合"获取Session"来进行测试
Ⅲ.获取Session
方式①:
java
@RequestMapping("/r16")
public String r16(HttpServletRequest request){
//先从前端的请求中找到Cookie,然后从Cookie中找到SessionId,然后通过SessionId找到Session对象
HttpSession session = request.getSession(false); //这里传false的原因:如果session不存在,则直接返回null,不会再进行创建新的session
if (session!=null){
//从Session中获取用户登录信息
String name = (String) session.getAttribute("name");
return "姓名:"+name;
}else{
return "用户未登录";
}
}
当我还没存储Session的时候,先获取Session

显示的是"用户未登录"
当我存储Session之后,再进行获取Session


可以看到,可以正常获取了
重点:

此时我通过postman来获取session的时候,cookie尾号为"40CE"

我通过浏览器来获取session的时候,cookie尾号为"378E"
所以,通过不同的客户端进行访问,获取到的值是不一样的。
当我将浏览器的值改了之后

发现,显示的"用户未登录"。这就是因为,当调用"获取Session"这个接口的时候,会从cookie中找SessionId,由于并没有这个SessionId创建的session,所以就显示"用户未登录"了
方式②:
java
@RequestMapping("/r17")
public String r17(HttpSession session) {
if (session == null) {
return "用户未登录";
}
String name = (String) session.getAttribute("name"); // 简化了 HttpSession session = request.getSession();
return "姓名:"+name;
}

方式③:
java
@RequestMapping("/r18")
public String r18(@SessionAttribute("name") String name){
return "姓名:"+name;
}
直接使用@SessionAttribute注解

12.获取Header中的其他属性
就拿获取"User-Agent"来说吧
方式1:
java
@RequestMapping("/r19")
public String r19(HttpServletRequest request){
String UA = request.getHeader("User-Agent");
return "User-Agent 的内容为 :"+UA;
}


方式2:
java
@RequestMapping("/r20")
public String r20(@RequestHeader("User-Agent") String UA){
return "User-Agent 的内容为 :"+UA;
}
直接使用@RequestHeader注解

