[Spring] Spring Web MVC基础理论

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

1. 什么是Spring Web MVC

Spring Web MVC是基于Servlet API构建的原始Web框架,从⼀开始就包在Spring框架中。它的正式名称“Spring Web MVC”来自其源模块的名称(Spring-webmvc),但它通常被称为"Spring MVC".
总结来说,Spring Web MVC是一个Web框架.
想要理解什么是Spring MVC我们首先先要理解什么是MVC

1.1 什么是MVC

MVC是Model View Controller的缩写,是软件工程中共的一种软件架构的设计模式.把软件系统分为模型,控制器,视图三个部分.
在这里插入图片描述

  • view(视图): 指的是在应用中专门用来与浏览器交互,展示数据的资源.
  • model(模型): 只应用程序的主题部分,用来处理程序中数据逻辑的部分.
  • controller(控制器): 可以理解为一个分发器,用来决定对于视图发来的请求,需要哪一个模型来处理,以及处理之后需要跳回哪个视图.即用来连接视图和模型.

比如我们去饭店吃饭:
顾客进店之后,服务员来接待客户点餐,客户点完餐之后,把客户菜单交给前厅,前厅根据客户菜单给后厨下达命令.后厨负责做饭,做完之后,再根据菜单告诉服务员,这是X号餐桌客人的饭.
在这个过程中:

  • 服务员就是view(视图):负责接待顾客,给顾客点餐,给顾客端饭.
  • 前厅就是controller(控制器):用来给后厨下达做菜的命令.
  • 后厨就是model(模型):根据前厅发来的要求来做菜.

1.2 什么是Spring MVC

MVC是一种架构模式,也是一种思想.而Spring MVC是对MVC思想的具体实现.除此之外,Spring MVC还是一个Web框架.
总结:Spring MVC是一个实现了MVC软件设计模式的Web框架.
其实Spring MVC在前面我们就使用过了.在我们创建Spring Boot项目的时候,选择Spring Web的时候,其实就是Spring MVC框架.也就是在创建的Spring Boot项目中添加了Spring Web MVC 的相关依赖,使得该项目具有了网络通信的功能.
在这里插入图片描述

  • 那么这时候问题又来了,Spring Boot和Spring MVC究竟有什么关系?
    Spring Boot只是实现Spring MVC的一种方式而已.Spring Boot中可以添加很多依赖,我们在Spring Boot项目中添加了Spring MVC的框架,那么这个Spring Boot项目就可以实现Web的功能.

不过Spring MVC在实现MVC模式的时候,也结合了自身的一些特点,下面这个图更加适合描述Spring MVC.
在这里插入图片描述
通过浏览器来向后端发送请求的时候,没有经过view,而是直接把请求传递给了controller,之后controller选择合适的模型,传递给model,model处理数据之后,把响应返回给controller,之后controller再把响应返回给view,之后view把响应返回给浏览器.

就比如我们去公司面试:我们(浏览器)想要面试的时候,我们可以直接找到公司某部门的负责人(controller),说我要面试(请求),之后部门负责人会找到面试你的那个人(model),面试之后,加入你通过了面试,面试你的那个人会把面试结果传递给部门负责人,之后部门负责人把消息通知给HR(view),之后HR会给你发offer.

2. Spring MVC深入学习

学习Spring MVC,重点也就是学习用户通过浏览器与服务端交互的过程.
主要分为一下三个点:

  • 建立连接:将用户(浏览器)和Java程序连接起来,也就是Java程序打开了大门,允许外界访问.此时访问的地址能够调用我们的Spring程序.
  • 传递参数:用户请求的时候会带一些参数.这些参数会传递到后端,在后端程序中要想办法获取到参数.
  • 返回结果:执行了业务逻辑之后,需要把执行的结果返回给用户,也就是响应.

比如去银行存款:

  1. 建立连接:去柜台
  2. 传递参数:拿着身份证,银行卡去存款
  3. 返回结果:银行返回一张存折.

掌握了上面的三个功能就相当于掌握了Spring MVC.

2.1 建立连接

在Spring MVC中使用@RequestMapping来实现URL的路由映射,也就是通过这个注解来使得Spring项目与浏览器建立连接.代码如下:

package com.example.demo;
import org.springframework.web.bind.annotation.*;
@RestController
public class DemoController {
    @RequestMapping("/hello")//可以理解为资源路径
    public String hello() {
        return "Hello World";
    }
}

接下来访问http://127.0.0.1:8080/hello就可以看到返回的程序了.
在这里插入图片描述

2.2.1 @RequsetMapping注解介绍

@RequestMapping是Spring Web MVC应用程序最常被用到的注解之一.它用来注册接口的路由映射.表示的是,服务器在接收到请求的时候,路径为/hello的请求就会调用hello这个方法的代码.

何为路由映射?
当用户访问⼀个URL时,将用户的请求对应到程序中某个类的某个方法的过程就叫路由映射.

  • 问题:既然@RequestMapping已经达到了我们的目的,我们为什么还要加@RestController呢?
    @RestController在资源访问的过程中起着相当重要的作用,在Spring项目接收到一个请求之后,Spring会对所有的类进行扫描,如果家里注解@RestController,Spring才会去看这个类里面有没有加@RequestMapping这个注解,才可以通过浏览器中输入的URL对应到这个类中注册的路由映射.
    如果我们把@RestController去掉,就访问不到了.
package com.example.demo;
import org.springframework.web.bind.annotation.*;
@RequestMapping("/demo")
public class DemoController {
    @RequestMapping("/hello")//可以理解为资源路径
    public String hello() {
        return "Hello World";
    }
}

在这里插入图片描述

2.2.2 @RequsetMapping的使用

不仅仅方法前面可以加上@RequestMapping注解,类的前面也可以加该注解,即@RequestMapping不仅仅可以修饰方法,还可以修饰类.当修饰类和方法的时候,访问的地址是类路径+方法路径

package com.example.demo;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/demo")
public class DemoController {
    @RequestMapping("/hello")//可以理解为资源路径
    public String hello() {
        return "Hello World";
    }
}

那么在访问hello这个方法的时候,路径就会变为/demo/hello.
在这里插入图片描述
注: 注解中的/hello"demo"虽然不加/也可以正确响应,但是为了编程的规范,还是建议加上.

2.2.3 @RequsetMapping支持哪些方法类型的请求

首先给出结论,@RequsetMapping支持所有方法类型的请求.下面我们来通过Postman构造请求来实验一下.
在这里插入图片描述
GET方法支持

POST方法支持
剩下的方法都是同样的道理,显示的结果都是Hello World,这里不再一一展示.

  • 那么如何使@RequsetMapping只接受指定的几种方法的请求呢?
    我们就需要再注解中加上另外的一个参数,method键值对
@RequestMapping(value = "/hello1",method = RequestMethod.GET)
public String hello1() {
    return "Hello World 1";
}

在这里插入图片描述
在这里插入图片描述
我们看到/hello1对应的路由映射只支持GET方法.
现在我们去看看method键值对截取的部分源码

public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};//method返回的是一个RequestMethod类型的数组

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

在源码中,我们可以看到,method返回的是一个RequestMethod类型的数组.那么这个RequestMethod中都有什么,我们再去查看截取的部分源码.

public enum RequestMethod {
    GET,
    HEAD,
    POST,
    PUT,
    PATCH,
    DELETE,
    OPTIONS,
    TRACE;
}

这里我们可以看到RequestMethod是一个枚举类型,这里面存放的都是请求中的方法.
由于method那里接收的是一个关于RequestMethod枚举类型的数组,所以我们在注解后的键值对的值上传入的也是枚举类型的数组,比如我们想支持GET和POST两种方法:

@RequestMapping(value = "/hello1",method = {RequestMethod.GET, RequestMethod.POST})
public String hello1() {
    return "Hello World 1";
}

当然当元素只有一个的时候,大括号是可以省略的.就比如上面那个只有GET方法的例子.

  • 如果指定的方法只有一种,我们也可以采用其他注解来解决
    比如只支持GET方法,我们就可以使用@GetMapping注解来解决.
@GetMapping("/hello3")
public String hello3() {
    return "Hello World 3";
}

在比如只支持POST方法,可以使用@PostMapping来解决

@PostMapping("/hello4")
public String hello4() {
    return "Hello World 4";
}

2.3 请求

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

2.3.1 传递单个参数

在前面,我们的方法都是没有参数存在的,如果给我们的方法加上参数之后会怎么样呢?

@RequestMapping("/name1")
public String name1(String name) {
    System.out.println("接收到了" + name);
    return "接收到了" + name;
}

我们在请求的URL中加上查询字符串,即参数:http://127.0.0.1:8080/demo/name1?name=zhangsan(?后面的是参数)
在URL中加上的参数传入到后端之后,Spring MVC会根据方法的参数名,找到对应的参数,赋值给方法.之后拿到传入的参数在方法中进行一系列操作之后返回给前端.比如我们将这个请求通过Postman发送给Spring项目.
在这里插入图片描述
我们发现成功返回了响应,并且返回了正确的响应.
如果参数不一致,则获取不到参数.
在这里插入图片描述

  • 注意事项:参数类型是包装类型和基本类型的区别
    • 当参数类型是包装类型和基本类型的时候,传入的参数Spring进行隐式转换之后发现参数类型不一致均会报400错误.
    @RequestMapping("/age1")
    public String age1(int age) {
        System.out.println("接收到了" + age);
        return "接收到了" + age;
    }
    @RequestMapping("/age2")
    public String age2(Integer age) {
        return "接收到了" + age;
    }
    
    在这里插入图片描述
    在这里插入图片描述
    • 但是如果我们不传递任何参数的时候,这时候基本类型和包装类型就会有所区别,基本类型会直接抛出500错误,而包装类型会输出默认的空值null.
      在这里插入图片描述
      在这里插入图片描述
      所以,我们在企业开发中,对于参数可能为空的数据,我们建议使用包装类型.

2.3.2 传递多个参数

和接收单个参数⼀样,直接使用方法的参数接收即可.使用多个形参.

@RequestMapping("/person1")
public String person1(String name,Integer age) {
    return "接收到了name" + name + "接收到了age" + age;
}

在这里插入图片描述
注:

  1. 也可以通过构造form表单来发送请求,这时候在URL中就没有了参数的存在,参数跑到了请求中的正文内容.
  2. 如果在项目运行的过程中,我们要对方法的参数进行修改,我们不建议在原来的方法上直接进行修改,而是另起一个方法重新写.

2.3.3 传递对象

如果需要传递的参数比较多的时候,我们不妨把这些参数封装成一个对象.

@RequestMapping("/person3")
public String person3(Person person) {
    return person.getName()+person.getAge()+person.getSex();
}
package com.example.demo;

public class Person {
    public String name;
    public int age;
    public String sex;

    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 String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}

之后我们使用Postman进行请求发送.http://127.0.0.1:8080/demo/person3?name=zhangsan&age=20&sex=男
在这里插入图片描述
注意:

  1. 如果某个基本类型的参数未传递,如果这个基本类型参数是一个类中的成员变量,如果这个参数未传递,那么这个参数会有默认的初始值,这一点是和上面直接把基本参数类型的参数写在方法的参数列表中是不一样的.比如我没有传入age参数.
    在这里插入图片描述
    我们可以看到age的初始值被默认赋值为了0.
  2. 如果我们针对参数是对象的使用form表单进行参数传递,在GET和POST两种方法中,只有POST方法会返回正确的结果,而GET方法会返回默认的空值.
    在这里插入图片描述
    在这里插入图片描述

2.3.4 后端参数重命名

在一些特殊的情况下,前端传递的参数key和我们后端接收的key可能不⼀致,比如我们传递了一个Name给后端,但是后端需要接收的参数是name,这时候就需要用到@RequestParam(翻译:请求参数)来对后端的参数进行重命名.

@RequestMapping("/person2")
public String person2(@RequestParam("Name") String name,Integer age) {
    return "接收到name" + name + "接收到age" + age;
}

上面这段代码,其中Name就是前端要传递的参数,而name是后端使用的参数,此时Spring可以正确的把请求传递的参数Name绑定到后端参数name参数上.
我们使用http://127.0.0.1:8080/demo/person2?Name=zhangsan&age=20来进行请求传递
在这里插入图片描述
如果我们把Name改成name,就无法进行正确的参数传递了.

在这里插入图片描述
[注意事项]
在使用@RequestParam对参数进行重命名的时候,参数就变成了必传参数.
在这里插入图片描述
那么造成上面这种情况的原因是什么呢,又该如何让他变成一个非必传参数呢?现在我们来查看@RequestParam的源码:

public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

我们可以看到,required那一栏默认的值是true,表示的含义就是,该注解修饰的参数是必传参数.既然如此,我们可以通过设置@RequestParam的required参数=false来实现让这个参数成为非必传参数.

@RequestMapping("/person2")
public String person2(@RequestParam(value = "Name",required = false) String name,Integer age) {
    return "接收到name" + name + "接收到age" + age;
}

在这里插入图片描述
此时Name为传递的时候,会有默认的初始值null来返回.

2.3.5 传递数组

Spring MVC可以自动绑定数组参数的赋值.

@RequestMapping("/param1")
public String param1(String[] arrayParam) {
    return Arrays.toString(arrayParam);
}

使用Postman进行传参:

  • 请求参数名与形参数组名称相同且请求参数为多个,后端的方法形式参数中即可自动接收传输过来的参数.
    在这里插入图片描述
  • 把所要传递的参数合并在一起传递,中间用逗号隔开.
    在这里插入图片描述
    可以看到以上两种方法均返回了正确的响应.
  • 我们如果使用from表单进行发送的话,这时候请求GET和POST就只有POST返回的是正确的结果,而GET返回的是null.
    在这里插入图片描述

2.3.6 传递集合

集合参数: 和数组传递参数的方法类似,可以是相同的参数名多个参数,也可以把参数写到一起,但是在后端那里,需要加上@RequestParam来绑定参数关系.
默认的情况下,请求中的参数名相同的多个值,封装的时候是一个数组,而如果要封装集合的话,就需要在参数前面加上@RequestParam来绑定参数关系,表示传过来的是一个数组,需要转换成集合.

@RequestMapping("/param2")
public String param2(@RequestParam("ArrayParam") List<String> arrayParam) {
    return Arrays.toString(arrayParam.toArray());
}

在这里插入图片描述

如果不加注解后面的参数的话,在前端传递参数的时候默认就和后端的方法参数是一样的.
在这里插入图片描述

2.3.7 传递json数据

  • 什么是json
    JSON就是⼀种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串.主要负责在不同的语言中数据传递和交换.
  • json的语法
    json是一个字符串,其格式非常类似于python中的字典和JavaScript对象字面量的格式.
    1. 数据存储在键值对(key/value)中,key和value之间用:分割.
    2. 键值对之间由,分割.
    3. 对象用{ }表示
    4. 数组用[ ]表示
    5. 值可以为对象,数组,字符串等等.
  • json的两种结构
    1. 对象: 大括号{}保存的对象是⼀个无序的键值对集合.⼀个对象以左括号{ 开始,右括号}结束。每个"键"后跟⼀个冒号: ,键值对使用逗号,分隔
    2. 数组: 中括号[]保存的数组是值(value)的有序集合.⼀个数组以左中括号[开始,右中括号]结束,值之间使用逗号, 分隔,数组中可以存放多个对象,对象和对象之间用,分割.

下面我们展示一段json字符串:

{
	 "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"]
	 }]
}

也可以压缩表示为:

{"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的书写,如https://www.json.cn/.

  • json字符串和Java对象的互转
    Spring MVC框架集成了json的转换工具,我们可以直接拿来使用.我们可以使用ObjectMapper中的一系列方法来对json和Java对象两者之间进行转换.
package com.example.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class json {
    private static ObjectMapper objectMapper = new ObjectMapper();
    public static void main(String[] args) throws JsonProcessingException {
        Person person = new Person();
        person.setName("zhangsan");
        person.setAge(18);
        person.setSex("男");
        String json = objectMapper.writeValueAsString(person);
        System.out.println(json);
        Person person2 = objectMapper.readValue(json, Person.class);//传入一个json字符串和一个类的类对象
        System.out.println(person2.toString());
    }
}

package com.example.demo;
public class Person {
    public String name;
    public int age;
    public String sex;

    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 String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

在这里插入图片描述

  • 如何传递json对象
    接收json对象,需要使用@RequestBody注解,这个注解翻译过来就是请求正文的意思,意思是这个注解起到的作用是从请求中的正文部分获取数据.请求的参数必须写在正文中.而我们的json字符串就写在请求的正文中.
    后端是这样实现的:
@RequestMapping("/param3")
public String param3(@RequestBody Person person) {
    return person.toString();
}

接下来我们使用Postman构造请求,把json字符串写入请求的正文中:

{
    "name":"zhangsan",
    "age":18,
    "sex":"male"
}

响应结果如下:
在这里插入图片描述
如果去掉注解@RequestBody,那么后端就无法正确接收json数据,前端返回的响应也不正确.
在这里插入图片描述
由于后端没有接收到关于person对象传递的任何信息,所以都默认都赋值为了初始值.

2.3.8 获取URL中的参数@PathVariable

PathVariable翻译过来之后,是"路径可变"的意思,意思是我们要通过URL获取路径作为传递过来的参数,而这个路径是不固定的.
这个注解主要的作用就是在请求URL路径上的数据绑定.之前我们通过http请求传递参数都是在?后面的查询字符串中.而现在我们要通过URL中的资源路径来传递参数.
后端代码实现如下:
@RequestMapping的参数中在加上一些路径的标识,每个参数外面使用{ }括起来.

@RequestMapping("/param4/{name}/{age}")
public String param4(@PathVariable String name,@PathVariable Integer age){
    return name+age;
}

我们通过Postman来构造请求http://127.0.0.1:8080/demo/param4/zhangsan/18
在这里插入图片描述
我们看到前端返回了正确的响应.
[注意事项]
如果方法参数名称和需要绑定的URL中的变量名称不⼀致时,需要@PathVariable的属性value赋值.

@RequestMapping("/param4/{Name}/{Age}")
public String param4(@PathVariable("Name") String name,@PathVariable("Age") Integer age){
    return name+age;
}

在这里插入图片描述
前端还是会返回正确的响应.

2.3.9 上传文件@RequestPart

后端代码实现:

@RequestMapping("/param5")
public String param5(@RequestPart("file") MultipartFile file) throws IOException {
    file.transferTo(new File("D:/personal/" + file.getOriginalFilename()));
    //上传文件,前面写的是存储文件的路径,后面加上原文件的名字
    return "已经获取到" + file.getOriginalFilename();
}

使用Postman向指定位置发送文件.
在这里插入图片描述
指定位置接收到了文件.
在这里插入图片描述

2.3.10 获取Cookie/Session

  • 回顾Cookie和Session
    Cookie中的内容和URL中的query string内容类似,都是键值对内容,它们都是程序员自定义的.每个键值对之间用";“隔开,键和值之间用”="隔开.
    在这里插入图片描述

    • Cookie是浏览器本地存储的一种机制,Cookie本质上可以在客户端的硬盘上持久化保存的.是浏览器给网页的一种能够持久化存储数据的机制.
    • 有的网站,需要在客户端这边存储一些必要的信息,希望可以持久化存储,于是浏览器就给网页提供了Cookie,是浏览器对于硬盘的操作做了一些特殊的封装,相当于提供了一个或者一组特殊的文件,并且内容只能是键值对.
    • 那么Cookie是具体如何保存的呢?
      浏览器会针对不同的域名,每个网站都有自己的Cookie文件保存在硬盘中.
    • Cookie从哪里来?
      Cookie中的数据,来自于服务器(服务器返回给浏览器的数据),访问网站的时候,网站的服务器会返回http响应,在http响应中,会包含Set-Cookie这样的header,它就会把一些键值对保存到浏览器的Cookie中.Cookie保存到浏览器之后,后续浏览器访问该网站的时候,就会在请求的header中,把之前保存的键值对都带入进去,在返回给服务器.
    • 那么问题又来了,为什么还要返回给服务器?
      这是因为Cookie可以使客户端存储一些必要的"配置信息",从而让服务器对于用户提供的服务更加"个性化".

举例说明:剪头发
A去了一家理发店之后,理发师就问他:“你想怎么剪?” 但是这时候A就会反问理发师:“你能怎么剪”?于是理发师就说:“可以剪平头,毛寸…”,于是A就说:"给我剪个平头."在A下次去了之后,就可以直接告诉理发师,剪个平头.同理,B也经历了上述过程,他最终选择了毛寸.

和上面剪头发是相同的道理,客户端也不止有一个,每个客户端都会有自己的偏好,此时就需要让每个客户端保存这样的数据,之后就可以通过Cookie随时把这样的信息返回给服务器.例如:浏览器的夜间模式和白日模式,一次设置好了之后,下次再打开服务器的时候,浏览器的颜色模式不会改变.

  • Cookie自动登录
    Cookie中虽然有很多的键值对都是程序员自定义的,但是往往会有一个特殊的键值对,用来标识用户的身份信息.
    在这里插入图片描述
    • 首先在获取登录页面与返回登录登录页面的html的过程中不包含任何的Cookie.
    • 在用户输入用户名和密码之后,这时候用户名和密码就会交给服务器,验证它们的正确性,在确认正确之后,就会创建会话(session),(会话可以理解为一个类,其中类中具体包含什么,要看业务逻辑,但是其中一定有sessionId,也就是令牌)并把sessionId返回给浏览器,这个sessionId存在于响应报文header中的Set-Cookie中,我们也可以把他叫做"令牌",令牌中存储的是一个字符串,类似于"身份标识",不会存储太多的信息,在浏览器收到sessionId之后,就会把Id存储在硬盘中,即创建了Cookie.
    • 在之后客户端要访问该域名下的其他页面的时候,就可以把sessionId交给服务器,服务器获取到sessionId之后,就可以根据这个值,知道用户的详细信息.也就是直接通过之前创建的sessionId的Cookie就可以访问到,无需再次登录.

举例说明:去医院看病

  1. 到了医院先挂号.挂号时候需要提供⾝份证,同时得到了⼀张"就诊卡",这个就诊卡存储着关于患者身份信息的sessionId,就相当于患者的"令牌".
  2. 后续去各个科室进行检查,诊断,开药等操作,都不必再出示⾝份证了,只要凭就诊卡即可识别出当前患者的⾝份.在就诊室的刷卡机上刷一下就诊卡,医生就会知道你的所有信息.
  3. 看完病了之后,不想要就诊卡了,就可以注销这个卡.此时患者的⾝份和就诊卡的关联就销毁了.(类似于⽹站的注销操作)
  4. ⼜来看病,可以办⼀张新的就诊卡,此时就得到了⼀个新的"令牌"
  • 使用Spring获取Cookie
    • 传统获取Cookie
      @RequestMapping("/param6")
      public String param6(HttpServletRequest request, HttpServletResponse response) throws IOException  {
          Cookie[] cookies = request.getCookies();
          for (Cookie cookie : cookies) {
              System.out.print(cookie.getName()+":"+cookie.getValue());
          }
          return "已经获取到Cookie";
      }
      
      之后我们通过浏览器伪造Cookie,来向后端传递请求.http://127.0.0.1:8080/demo/param6
      在这里插入图片描述
      我们看到了后端已经成功拿到了Cookie中的数据.
      在这里插入图片描述
      Spring MVC是Spring基于Servlet实现的.其中上面一段代码中的HttpServletRequestHttpServletResponse是Servlet提供的两个类.这两个类在每一个接口中均默认存在,需要的时候写出来就可以.
      HttpServletRequest 对象代表客户端的请求.请求中的所有信息均可以通过这个类拿到.
      HttpServletResponse对象代表服务器的响应.响应中的所有信息均可以通过这个类拿到.
    • 获取Cookie中的某一个键值对
      方法一: 使用equals()方法
      @RequestMapping("/param7")
      public String param7(HttpServletRequest request) {
          Cookie[] cookies = request.getCookies();
          for (Cookie cookie : cookies) {
              if ("bite".equals(cookie.getName())) {
                  return cookie.getValue();
              }
          }
          return "为获取到指定的Cookie";
      }
      
      通过浏览器构造Cookie,我们看到了前端返回了正确的响应.
      在这里插入图片描述
      后端打印了相应的日志:
      在这里插入图片描述
      方法二:通过@CookieValue注解获取.
          @RequestMapping("/param8")
          public String param8(@CookieValue("bite") String bite) {
              System.out.println("获取到了" + bite);
              return bite;
          }
      
      前端与后端响应均正确:
      在这里插入图片描述
      在这里插入图片描述
  • Session的存储和获取
    Session是服务器端的机制,它需要先存储,才能获取到.
    • Session的存储
      @RequestMapping("/param9")
      public String param9(HttpServletRequest request){
          //获取Session对象,如果不存在Session对象,getSession之后不加或参数为true会自动创建
          HttpSession session = request.getSession();
          if (session != null) {//确保Session成功创建
              session.setAttribute("bite", "888");
          }
          return "Session存储成功";
      }
      
      方法解释:
      getSession(boolean create): 如果参数为true的时候,就会在Session不存在的时候,自动创建Session,如果参数为false,就不会创建.
      getSession():和上一种方法参数为true的时候效果相同.
      setAttribute(String s,String o):设置Session中的参数.
    • Session的读取
      读取Session依然使用HttpServletRequest
      @RequestMapping("/param10")
      public String param10(HttpServletRequest request){
          HttpSession session = request.getSession(false);
          if (session != null){
              String s = (String) session.getAttribute("bite");
              System.out.println("获取到了Session");
              return s;
          }
          return "未获取到Session";
      }
      
    • 运行
      [注意事项] 在重启服务器之后,上一次存在内存中的Session数据会被清空,需要重新设置Session之后才可以获取到.
      在这里插入图片描述
      我们可以看到,存储了Session之后,浏览器把SessionID存储在了Cookie中.
      在这里插入图片描述
      之后我们可以使用浏览器存储的令牌获取Session.
      在这里插入图片描述
    • 简洁获取Session
      上面获取Session的方法比较传统,我们下面展示两种简洁的方法.
      方法一:使用@SessionAttribute注解
      @RequestMapping("/param11")
      public String param11(@SessionAttribute(value = "bite",required = false)
                            String bite){
          return bite;
      }
      
      运行结果如下:在这里插入图片描述
      @SessionAttribute的后面两个注解表示的意思和前面@RequestParam注解后面的两个参数的作用非常像.第一个作用是参数绑定的作用,第二个参数如果为true或者不写,表示这个参数是必传参数,如果为false,就是非必传参数,如果Session传递未成功,就返回null.
      方法二:直接使用HttpSession作为参数
      @RequestMapping("/param12")
      public String param12(HttpSession session){
          String string = (String) session.getAttribute("bite");
          System.out.println("成功获取到了Session");
          return string;
      }
      
      HttpSession作为参数的时候,效果和getSession()方法一样,在没有Session的时候,会自动创建Session.
      运行结果如下:
      在这里插入图片描述

2.3.11 获取Header

  • 传统获取方法
    传统的方法依然是从HttpServletRequst中获取.
@RequestMapping("/param13")
public String param13(HttpServletRequest request){
    return request.getHeader("User-Agent");
}

我们使用getHeader方法来获取Header.在后面的参数是Header中的"key".
运行测试:
在这里插入图片描述
下面是我们通过抓包软件抓取的网络通行信息.我们发现Header中的User-Agent一栏与浏览器上的一致.
在这里插入图片描述

  • 简洁获取方法
    通过@RequestHeader注解来获取,在注解后面加上需要获取Header中的"key".
@RequestMapping("/param14")
public String param14(@RequestHeader("sec-ch-ua-platform") String string){
    return string;
}

运行结果:
在这里插入图片描述
与抓包工具中的结果一致:
在这里插入图片描述

2.4 响应

在我们前面代码的例子中,每次浏览器都会返回响应的响应,而前面的响应都是数据响应,响应还可以是页面,状态码,Header等.

2.4.1 返回静态页面

首先我们需要穿件一个前端页面index.html.创建的文件放在static目录下.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>我是index文件</h1>
</body>
</html>

[注意] 在写完前端文件之后,不要通过idea右上角的浏览器小图标的方式打开,我们需要通过后端返回页面的方式打开.
在这里插入图片描述
下面我们展示一种后端代码的写法:

@RequestMapping("/demo2")
@RestController
public class DemoController2 {
    @RequestMapping("/param1")
    public Object param1(){
        return "/index.html";
    }
}

在这里插入图片描述
我们看到,浏览器并没有返回对应的html页面,而是直接返回了一串字符串数据.那么怎么解决呢.我们需要把@RestController注解变成@Controller注解.

@RequestMapping("/demo2")
@Controller
public class DemoController2 {
    @RequestMapping("/param1")
    public Object param1(){
        return "/index.html";
    }
}

我们看到这次浏览器返回了对应的html页面.
在这里插入图片描述

  • 那么@RestController@Controller有什么区别呢?
    @RestController一般用来返回数据,这些数据的响应报头中的Content-Type都是text/html格式的,@Controller一般用来返回视图.这个视图一般是用前端的代码写好的文件.就是前面我们在MVC设计模式中提到过的视图(view).
    @RestController == @Controller + @ResponseBody.其中@Controller表示的是我们前面我们MVC模式中的控制器.@ResponseBody表示的是响应正文,定义返回的数据为非视图模式.
    @RestController的源码如下,我们也可以看到这个注解上面也标有@Controller + @ResponseBody两个注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

2.4.2 返回数据@ResponseBody

上面我们提到了@ResponseBody注解表示的是返回数据,也就是以text/html的格式返回
@ResponseBody即是类注解又是方法注解.给类加上该注解之后,就表示类中所有的方法返回的都是数据,给方法加上之后,表示的是只有这个方法返回的是数据.

同样,如果类上有@RestController注解的时候,就证明给所有的方法都加了@ResponseBody注解,所有的方法都以数据的方式返回.

如果一个类中即有页面要返回,也有数据要返回,就在需要返回数据的方法上面加上@ResponseBody.类的控制器使用@Controller.

@RequestMapping("/demo2")
@Controller
public class DemoController2 {
    @RequestMapping("/param1")
    public Object param1(){
        return "/index.html";
    }
    @RequestMapping("param2")
    @ResponseBody
    public Object param2(){
        return "String";
    }
}

浏览器返回的视图与数据如下:
在这里插入图片描述
在这里插入图片描述

2.4.3 返回html代码片段

    @RequestMapping("/param3")
    @ResponseBody
    public String param3(){
        return "<h1>我是html~</h1>";
    }

在这里插入图片描述

2.4.4 返回json

后端返回json的方法是使用对象返回.可以使用HashMap返回,也可以另外创建一个对象,按属性返回.
方法一:创建HashMap

@RequestMapping("/param4")
@ResponseBody
public HashMap<String,Integer> param4(){
    HashMap<String, Integer> map = new HashMap<>();
    map.put("aa",1);
    map.put("bb",2);
    map.put("cc",3);
    return map;
}

浏览器返回响应,是json格式的数据:
在这里插入图片描述
方法二:通过类中的属性

@RequestMapping("/param5")
@ResponseBody
public Person param5(){
    Person person = new Person();
    person.setName("zhangsan");
    person.setAge(19);
    person.setSex("male");
    return person;
}

浏览器返回响应,依然是json格式的数据,返回的是对象中属性的值:
在这里插入图片描述

2.4.5 设置状态码

SpringMVC也为程序员提供了自定义状态码的功能,可以让程序员手动指定状态码.
使用HttpServletResponse+setStatus方法访问到响应中的状态码.

@RequestMapping("/param6")
@ResponseBody
public String param6(HttpServletResponse response){
    response.setStatus(400);
    return "设置状态码为400";
}

浏览器返回响应,需要注意的是,我们自定义的错误状态码返回的不一定是浏览器报错的一大坨信息,状态码并不影响页面的显示.
在这里插入图片描述
我们通过抓包工具发现,响应的状态码被设置为了400.图标也变为了红色.
在这里插入图片描述
在这里插入图片描述

2.4.6 设置header

Http响应报头也会向客户端传递一些附加信息,如:Content-Type,Local等.这些信息就是通过@RequestMapping来实现的.先来看一看@RequestMapping的源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective({ControllerMappingReflectiveProcessor.class})
public @interface RequestMapping {
    String name() default "";

    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    RequestMethod[] method() default {};

    String[] params() default {};

    String[] headers() default {};

    String[] consumes() default {};

    String[] produces() default {};
}

  1. value(),path():指定映射的URL.
  2. method():指定请求的方法.如GET,POST等.
  3. consumes():指定处理的请求的提交内容类型(Content-Type),例如:application/json, text/html等.
  4. produces():指定返回的内容类型,也就是浏览器上响应之后显示的内容类型.和上面的consumes()一样.
  • 设置Content-Type
    我们通过设置produces的值,设置响应报头的Content-Type.
//返回的响应是json格式
@RequestMapping(value = "/param7",produces = "application/json")
@ResponseBody
public String param7(){
    return "{\"success\":true}";
}

在这里插入图片描述
我们看到浏览器返回的响应数据是json格式的数据.

如果不在@RequestMapping后面加上produces参数的话,返回响应的时候,Spring还是会以默认的text.html格式返回.

  • 设置其他的Header
    设置其他Header,需要使用Spring中提供的HttpServletResponse提供的方法来设置.
@RequestMapping("/param8")
@ResponseBody
public String param8(HttpServletResponse response){
    response.setHeader("MyHeader","value");
    return "设置Header成功";
}

我们通过Fiddler抓包可以看到,报头中自定义的header已经被加入进去了.
在这里插入图片描述

如果header的名称已经存在,在设置它的Value的时候会覆盖掉原来的值.

  • 79
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 41
    评论
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值