[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
    • 1.1 什么是MVC
    • 1.2 什么是Spring MVC
  • 2. Spring MVC深入学习
    • 2.1 建立连接
      • 2.2.1 @RequsetMapping注解介绍
      • 2.2.2 @RequsetMapping的使用
      • 2.2.3 @RequsetMapping支持哪些方法类型的请求
    • 2.3 请求
      • 2.3.1 传递单个参数
      • 2.3.2 传递多个参数
      • 2.3.3 传递对象
      • 2.3.4 后端参数重命名
      • 2.3.5 传递数组
      • 2.3.6 传递集合
      • 2.3.7 传递json数据
      • 2.3.8 获取URL中的参数@PathVariable
      • 2.3.9 上传文件@RequestPart
      • 2.3.10 获取Cookie/Session
      • 2.3.11 获取Header
    • 2.4 响应
      • 2.4.1 返回静态页面
      • 2.4.2 返回数据@ResponseBody
      • 2.4.3 返回html代码片段
      • 2.4.4 返回json
      • 2.4.5 设置状态码

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;}@Overridepublic 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一般用来返回数据,@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注解表示的是返回数据.
@ResponseBody即是类注解又是方法注解.给类加上该注解之后,就表示类中所有的方法返回的都是数据,给方法加上之后,表示的是只有这个方法返回的是数据.

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

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

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

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

2.4.3 返回html代码片段

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

这里需要注意的是,返回html也需要加上@ResponseBody注解.
在这里插入图片描述

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.图标也变为了红色.
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/44494.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QT 多线程 QThread

继承QThread的线程 继承 QThread 是创建线程的一个普通方法。其中创建的线程只有 run() 方法在线程里的。其他类内定义的方法都在主线程内。 通过上面的图我们可以看到&#xff0c;主线程内有很多方法在主线程内&#xff0c;但是子线程&#xff0c;只有 run() 方法是在子线…

SvANet:微小医学目标分割网络,增强早期疾病检测

SvANet&#xff1a;微小医学目标分割网络&#xff0c;增强早期疾病检测 提出背景前人工作医学对象分割微小医学对象分割注意力机制 SvANet 结构图SvANet 解法拆解解法逻辑链 论文&#xff1a;SvANet: A Scale-variant Attention-based Network for Small Medical Object Segmen…

【JAVA poi-tl-ext 富文本转word】

富文本转word 环境使用poi-tl-ext的原因富文本转word代码 环境 jdk 1.8 <dependency><groupId>io.github.draco1023</groupId><artifactId>poi-tl-ext</artifactId><version>0.4.16</version> </dependency>poi-tl-ext已经包…

可灵重大升级!新增Web端上线、首尾帧控制、单次生成视频时长增加至10s!

快手视频生成大模型“可灵”&#xff08;Kling&#xff09;&#xff0c;作为全球首个真正用户可用的视频生成大模型&#xff0c;自面世以来&#xff0c;凭借其无与伦比的视频生成效果&#xff0c;在全球范围内赢得了用户的热烈追捧与高度评价。截至目前&#xff0c;申请体验其内…

修正版头像上传组件

修正版头像上传组件 文章说明核心源码展示运行效果展示源码下载 文章说明 在头像剪切上传一文中&#xff0c;我采用div做裁剪效果&#xff0c;感觉会有一些小问题&#xff0c;在昨天基于canvas绘制的功能中改进了一版&#xff0c;让代码变得更简洁&#xff0c;而且通用性相对高…

【WebGIS】从设计层面设计系统

本项目在通过现代信息技术手段&#xff0c;对古村古镇进行多方位、多角度的数字化记录、展示与传播&#xff0c;实现文化遗产的数字化保护、活化利用与共享。项目内容主要包括&#xff1a;1&#xff09;古村古镇数据库的建立&#xff1a;通过多种渠道收集古村古镇的各类信息&am…

如何从 PDF 中删除背景

您是否曾经收到过充满分散注意力背景的扫描 PDF 文档&#xff1f;也许是带有繁忙水印的旧收据或背景光线不均匀的扫描文档。虽然这些背景可能看起来没什么大不了的&#xff0c;但它们会使您的工作空间变得混乱&#xff0c;并使您难以专注于重要信息。轻松删除这些不需要的元素并…

短视频SEO矩阵系统:源码开发与部署全攻略

在数字化时代&#xff0c;短视频已成为人们获取信息、娱乐休闲的重要方式。随着短视频平台的兴起&#xff0c;如何让自己的内容在众多视频中脱颖而出&#xff0c;成为每个创作者和内容运营者关注的焦点。本文将为您深入解析短视频SEO矩阵系统的源码开发与部署&#xff0c;助您在…

MT6825磁编码IC在智能双旋机器人中的应用

MT6825磁编码IC在智能双旋机器人中的应用&#xff0c;无疑为这一领域的创新和发展注入了新的活力。作为一款高性能的磁性位置传感器&#xff0c;MT6825以其独特的优势&#xff0c;在智能双旋机器人的运动控制、定位精度以及系统稳定性等方面发挥了关键作用。 www.abitions.com …

Midjourney v6.5 可能会在“7月底”发布,并改进了真实感和皮肤纹理

Midjourney v6.5即将发布&#xff0c;这一更新将大幅提升图像的真实感和皮肤纹理&#xff0c;为用户带来更逼真的视觉体验。首席执行官David Holz在电话会议中宣布&#xff0c;新版本将提高图像清晰度&#xff0c;特别是在手部和皮肤细节上&#xff0c;同时改进Web应用程序和个…

ABAP调用BAPI时COMMIT WORK AND WAIT未按照预期同步提交问题分析

背景&#xff1a; 在做ABAP开发时&#xff0c;经常会有连续调用BAPI的需求&#xff0c;比如先创建销售订单&#xff0c;再依据销售订单创建交货单&#xff0c;再对交货单进行过账等类似的一连串调用&#xff0c;这种类似的场景往往需要前一步操作的数据完全写入数据库才能进行…

编译打包自己的云手机(redroid)镜像

前言 香橙派上跑云手机可以看之前的文章&#xff1a; 香橙派5plus上跑云手机方案一 redroid(带硬件加速)香橙派5plus上跑云手机方案二 waydroid 还有一个cuttlefish方案没说&#xff0c;后面再研究&#xff0c;cuttlefish的优势在于可以自定义内核且selinux是开启的&#xf…

Aop切面编程(2)--代理模式

1、代理模式的理解&#xff1a;不修改A对象的代码的基础上&#xff0c;对A代码块进行拓展。通过创建ProxyA代理对象&#xff0c;拓展A对象并调用A对象的核心功能&#xff1b; 即&#xff1a;不修改对象的源码基础上&#xff0c;创建代理对象&#xff0c;进行功能的附加和增强&…

端到端拥塞控制的本质

昨天整理了一篇 bbr 的微分方程组建模(参见 bbr 建模)&#xff0c;算是 bbr 算法终极意义上的一个总结&#xff0c;最后也顺带了对 aimd 的描述&#xff0c;算是我最近比较满意的一篇分享了。那么接下来的问题&#xff0c;脱离出具体算法&#xff0c;上升到宏观层面&#xff0c…

uniapp微信小程序 TypeError: $refs[ref].push is not a function

我的写法 this.$refs.addPopup.open();报错 打印出来是这样的 解决 参考未整理 原因 在当前页面使用的v-for循环 并且循环体内也有组件使用了ref&#xff08;而我没有把每个ref做区别命名&#xff09; 这样就导致了我有很多同名的ref&#xff0c;然后就报错了 解决办法&a…

AI人工智能作词,为音乐注入未来之力

在当今的音乐世界中&#xff0c;创新的力量不断推动着边界的拓展&#xff0c;而人工智能作词正以其独特的魅力&#xff0c;成为引领音乐走向未来的强大动力。 “妙笔生词智能写歌词软件&#xff08;veve522&#xff09;”无疑是这股浪潮中的璀璨明星。它利用先进的人工智能技术…

input上传--upload

1.HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>上传文件</title><link rel"…

数据结构——线性表(C语言实现)

写在前面&#xff1a; 在前面C语言的结构体学习中&#xff0c;我提及了链表的操作&#xff0c; 学习数据结构我认为还是需要对C语言的数组、函数、指针、结构体有一定的了解&#xff0c;不然对于结构体的代码可能很难理解&#xff0c;特别是一些书籍上面用的还是伪代码&#xf…

OpenGL笔记一之基础窗体搭建以及事件响应

OpenGL笔记一之基础窗体搭建以及事件响应 总结自bilibili赵新政老师的教程 code review! 文章目录 OpenGL笔记一之基础窗体搭建以及事件响应1.运行2.目录结构3.main.cpp4.CMakeList.txt 1.运行 2.目录结构 01_GLFW_WINDOW/ ├── CMakeLists.txt ├── glad.c ├── main…

Linux基于centos7指令初学3

date指令 作用&#xff1a; date指令可以查看时间 这个指令可以进行格式化 格式&#xff1a;date %想要的内容 Y&#xff1a;年份 m&#xff1a;月份 d&#xff1a;日 H&#xff1a;时 M&#xff1a;分 S&#xff1a;秒 时间分界线可以由…