• Spring - 2 ( 16000 字 Spring 入门级教程)


    一:Spring Web MVC⼊⻔

    Spring Web MVC 是⼀个 Web 框架,简称之为: Spring MVC,要想真正的理解什么是 Spring MVC,首先要搞清楚什么是 MVC

    1.1 MVC 定义

    MVC 是 Model View Controller 的缩写,它是软件工程中的⼀种软件架构设计模式,它把软件系统分为模型、视图和控制器三个基本部分

    在这里插入图片描述

    • View(视图) 指在应用程序中专门用来与浏览器进行交互,展示数据的资源.
    • Model(模型) 是应用程序的主体部分,用来处理程序中数据逻辑的部分.
    • Controller(控制器)用来连接视图和模型

    1.2 什么是Spring MVC

    MVC 是⼀种架构设计模式, 也⼀种思想, 而 Spring MVC 是对 MVC 思想的具体实现,Spring MVC 是⼀个实现了 MVC 模式的 Web 框架, Spring MVC 主要关注两个点:

    1. MVC
    2. Web框架

    其实, Spring MVC 我们在前面已经用过了, 在创建 Spring Boot 项目时,我们勾选的 Spring Web 框架其实就是 Spring MVC 框架:

    在这里插入图片描述

    Spring Boot 只是实现 Spring MVC 的其中⼀种方式,Spring Boot 可以添加很多依赖, 借助这些依赖实现不同的功能. Spring Boot 通过添加 Spring WebMVC 框架, 来实现web功能.

    Spring在实现MVC时,也结合自身项目的特点,做了⼀些改变,相对而言,下⾯这个图或许更加合适⼀些。

    在这里插入图片描述

    1.3 学习Spring MVC

    学习 Spring MVC, 重点也就是学习如何通过浏览器和用户程序进行交互.

    主要分以下三个方面:

    1. 建立连接:将用户(浏览器)和 Java 程序连接起来
    2. 请求: 在程序中要想办法获取到用户请求时带的一些参数, 所以请求这块主要是获取参数的功能.
    3. 响应: 执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应.

    1.3.1 项目准备

    Spring MVC 项目创建和 Spring Boot 创建项目相同,在创建的时候选择 Spring Web 就相当于创建了Spring MVC 的项⽬.

    创建项目时, 勾选上 Spring Web 模块即可,如下图所示:
    在这里插入图片描述

    1.4 建立连接

    在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射 ,也就是浏览器连接程序的作用

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

    我们先来看看代码怎么写:

    创建⼀个 UserController 类,实现用户通过浏览器和程序的交互,具体实现代码如下:

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    @RestController
    public class UserController {
        // 路由器规则注册
        @RequestMapping("/sayHi")
        public String sayHi(){
            return "hello,Spring MVC";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意:方法名和路径名称无需⼀致:

    接下来访问: http://127.0.0.1:8080/sayHi, 就可以看到程序返回的数据了

    在这里插入图片描述

    1.4.1 @RequestMapping 注解

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

    1.4.2 @RestController

    既然 @RequestMapping 已经可以达到我们的目的了, 我们为什么还要加 @RestController 呢?

    我们把 @RestController 去掉, 再来访问⼀次:
    在这里插入图片描述
    可以看到, 程序报了404, 找不到该页面

    一个项目中, 会有很多类, 每个类可能有很多的方法, Spring 程序怎么知道要执行哪个方法呢?

    Spring 会对所有的类进行扫描, 如果类加了注解 @RestController, Spring 才会去看这个类里面的方法有没有加 @RequestMapping 这个注解, 当然他的作用不止这⼀点, 咱们后面再详细讲

    1.4.3 @RequestMapping 使⽤

    @RequestMapping 即可修饰类,也可以修饰方法 ,当修饰类和方法时,访问的地址是类路径 + 方法路径.

    • @RequestMapping 标识⼀个类:设置映射请求的请求路径的初始信息
    • @RequestMapping 标识⼀个方法:设置映射请求请求路径的具体信息
    1. 代码例子:
    @RequestMapping("/user")
    @RestController
    public class UserController {
        @RequestMapping("/sayHi")
        public String sayHi(){
            return "hello,Spring MVC";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    访问地址:http://127.0.0.1:8080/user/sayHi (类路径 + 方法路径)

    在这里插入图片描述

    1. 注意

    @RequestMapping 的 URL 路径最前⾯加不加 / (斜杠)都可以, Spring程序启动时, 会进行判断, 如果前面没有加 / , Spring会拼接上⼀个 /(通常情况下, 我们加上 / )

    @RequestMapping("user")
    @RestController
    public class UserController {
        @RequestMapping("sayHi")
        public String sayHi(){
            return "hello,Spring MVC";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    依然可以正确响应.

    在这里插入图片描述

    1. @RequestMapping 的 URL 路径也可以是多层路径, 最终访问时, 依然是类路径 + 方法路径
    @RequestMapping("/user/m1")
    @RestController
    public class UserController {
        @RequestMapping("/say/hi")
        public String sayHi(){
            return "hello,Spring MVC";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    访问路径: http://127.0.0.1:8080/user/m1/say/hi

    在这里插入图片描述

    1.4.4 @RequestMapping 是 GET 还是 POST 请求

    @RequestMapping 既支持 Get 请求, 又支持 Post 请求. 也支持其他的请求方式,那我们该如何指定 GET 或者 POST 类型呢?

    1.4.4.1 指定 GET/POST 方法类型

    我们可以显示的指定 @RequestMapping 来接收 POST 的情况,如下所示:

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    @RestController
    public class UserController {
        @RequestMapping(value = "/getRequest",method= RequestMethod.POST)
        public String sayHi(){
            return "get request...";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    具体来说:

    • value = "/getRequest":这指定了请求的URL路径为/getRequest。也就是说,当有请求发送到/getRequest路径时,该方法将被调用来处理这个请求。
    • method = RequestMethod.POST:这指定了请求的 HTTP 方法为 POST。也就是说,这个方法只会处理发送到/getRequest路径的 POST 请求。

    1.4.5 Postman介绍

    从上⾯的案例中, 也发现了⼀个新的问题, 就是我们测试后端方法时, 还需要去写前端代码. 这对我们来说, 是⼀件麻烦又痛苦的事情.

    随着互联网的发展, 也随着项目难度的增加,后端开发工程师, 不要求也不需要掌握前端技能了.那后端开发工程师, 如何测试自己的程序呢? – 使用专业的接口测试工具 Postman

    1.4.6 创建请求

    1. 第一步:

    在这里插入图片描述

    1. 第二步:

    在这里插入图片描述

    1. 界面介绍

    在这里插入图片描述

    1.4.7 传参

    1. 普通传参, 也就是通过查询字符串来传参

    查询字符串就是请求的参数

    在这里插入图片描述

    1. form-data

    表单提交的数据, 在 form 标签中加上 enctyped=“multipart/form-data” , 通常用于提交图片 / 文件。

    在这里插入图片描述
    3. x-www-form-urlencoded

    form表单, 对应 Content-Type: application/x-www-from-urlencoded

    在这里插入图片描述
    4. raw

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

    在这里插入图片描述

    1.5请求

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

    1.5.1 传递单个参数

    接收单个参数, 在 Spring MVC 中直接用方法中的参数就可以,比如以下代码:

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    @RequestMapping("/param")
    public class ParamController {
        @RequestMapping("/m1")
        public String method1(String name){
            return "接收到参数name:"+ name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    咱们使⽤浏览器发送请求并传参:http://127.0.0.1:8080/param/m1?name=spring

    在这里插入图片描述
    可以看到, 后端程序正确拿到了name参数的值,Spring MVC 会根据方法的参数名, 找到对应的参数, 赋值给方法
    在这里插入图片描述
    但如果参数不⼀致, 是获取不到参数的,比如请求 URL: http://127.0.0.1:8080/param/m1?name1=spring

    响应结果:
    在这里插入图片描述
    注意事项:使用基本类型来接收参数时, 参数必须传(除boolean类型), 否则会报500错误,类型不匹配时, 会报400错误.

    代码例子:

    @RequestMapping("/m1/int")
    public Object method1GetInt(int age){
            return "接收到参数age:" + age;
            }
    
    • 1
    • 2
    • 3
    • 4
    1. 正常传递参数:http://127.0.0.1:8080/param/m1/int?age=18

    在这里插入图片描述

    通过 Fiddler 观察请求和响应, HTTP 响应状态码为 200, Content-Type 为 text/html

    在这里插入图片描述

    1. 不传递age参数:http://127.0.0.1:8080/param/m1/int

    在这里插入图片描述
    通过 Fiddler 观察请求和响应, HTTP 响应状态码为 500

    在这里插入图片描述

    尝试观察程序的错误日志并解决

    在这里插入图片描述

    1. 传递参数类型不匹配:http://127.0.0.1:8080/param/m1/int?age=abc

    在这里插入图片描述
    通过 Fiddler 观察请求和响应, HTTP 响应状态码为 400

    在这里插入图片描述

    对于包装类型, 如果不传对应参数,Spring 接收到的数据则为null,所以企业开发中,对于参数可能为空的数据,建议使用包装类型

    1.5.2 传递多个参数

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

    代码示例:

    @RequestMapping("/m2")
    public Object method2(String name, String password) {
            return "接收到参数name:" + name + ", password:" + password;
            }
    
    • 1
    • 2
    • 3
    • 4

    使⽤浏览器发送请求并传参: http://127.0.0.1:8080/param/m2?name=zhangsan&password=123456

    在这里插入图片描述

    可以看到, 后端程序正确拿到了 name 和 password 参数的值

    在这里插入图片描述
    当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置是不影响后端获取参数的结果.

    访问: http://127.0.0.1:8080/param/m2?password=123456&name=zhangsan 同样可以拿到正确的结果

    在这里插入图片描述

    1.5.3 传递对象

    如果参数比较多时, 方法声明就需要有很多形参. 并且后续每次新增⼀个参数, 也需要修改方法声明,我们不妨把这些参数封装为⼀个对象.

    Spring MVC 也可以自动实现对象参数的赋值,比如 Person 对象:

    **public class Person {
        private int id;
        private String name;
        private String password;
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getPassword() {
            return password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }**
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    传递对象代码实现:

    @RequestMapping("/m3")
    public Object method3(Person p){
            return p.toString();
            }
    
    • 1
    • 2
    • 3
    • 4

    使⽤浏览器发送请求并传参: http://127.0.0.1:8080/param/m3?id=5&name=zhangsan&password=123456

    在这里插入图片描述

    可以看到, 后端程序正确拿到了Person对象⾥各个属性的值

    在这里插入图片描述
    Spring 会根据参数名称自动绑定到对象的各个属性上, 如果某个属性未传递, 则赋值为null,基本类型则赋值为默认初识值

    1.5.4 后端参数重命名

    某些特殊的情况下,前端传递的参数 key 和我们后端接收的 key 可以不⼀致,比如前端传递了⼀个 time 给后端,而后端是使⽤ createtime 字段来接收的,这样就会出现参数接收不到的情况,我们就可以使用 @RequestParam 来重命名前后端的参数值,以此避免这种情况

    @RequestMapping("/m4")
    public Object method_4(@RequestParam("time") String createtime) {
            return "接收到参数createtime:" + createtime;
    }
    
    • 1
    • 2
    • 3
    • 4

    @RequestParam(“time”) String createtime 表示 createtime 参数会接收来自 HTTP 请求中名为 time 的参数的值,相当于将 createtime 重命名为了 time

    使用浏览器发送请求并传参: http://127.0.0.1:8080/param/m4?time=2023-09-12

    在这里插入图片描述
    可以看到, Spring 可以正确的把浏览器传递的参数 time 绑定到了后端参数 caretetime 参数上

    此时, 如果浏览器使用 createtime 进行参数传递呢?

    访问URL: http://127.0.0.1:8080/param/m4?createtime=2023-09-12

    在这里插入图片描述
    错误日志信息为:
    在这里插入图片描述

    可以得出结论:

    1. 使⽤ @RequestParam 进行参数重命名时, 请求参数只能和 @RequestParam 声明的名称⼀致, 才能进行参数绑定和赋值.
    2. 使⽤ @RequestParam 进行参数重命名时, 参数就变成了必传参数.

    非必传参数设置:

    如果我们的实际业务前端的参数是⼀非必传的参数, 针对上述问题, 如何解决呢?

    先来了解下参数必传的原因, @RequestParam 注解实现如下:

    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    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"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以看到 required 的默认值为true, 表示含义就是: 该注解修饰的参数默认为必传,既然如此, 我们可以通过设置 @RequestParam 中的 required=false 来避免不传递时报错,

    具体实现如下:

    @RequestMapping("/m4")
    public Object method4(@RequestParam(value = "time", required = false) Stiring createtime) {
            return "接收到参数createtime:" + createtime;
            }
    
    • 1
    • 2
    • 3
    • 4

    1.5.5 传递数组

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

    @RequestMapping("/m5")
    public String method5(String[] arrayParam) {
            return Arrays.toString(arrayParam);
            }
    
    • 1
    • 2
    • 3
    • 4

    使用浏览器发送请求并传参:

    数组参数:请求参数名与形参数组名称相同且请求参数为多个, 后端定义数组类型形参即可接收参数

    • http://127.0.0.1:8080/param/m5?arrayParam=zhangsan&arrayParam=lisi&arrayParam=wangwu
    • http://127.0.0.1:8080/param/m6?listParam=zhangsan%2clisi%2cwangwu(%2c 代表逗号)
    • http://127.0.0.1:8080/param/m5?arrayParam=zhangsan,lisi,wangwu

    这三种方式都可以

    浏览器响应结果:

    在这里插入图片描述

    1.5.6 传递集合

    集合参数:和数组类似, 同⼀个请求参数名有为多个, 且需要使⽤ @RequestParam 绑定参数关系

    1. 浏览器传参:
    • 方式⼀:http://127.0.0.1:8080/param/m6listParam=zhangsan&listParam=lisi&listParam=wangwu
    • 方式⼆: http://127.0.0.1:8080/param/m6?listParam=zhangsan%2clisi%2cwangwu

    代码如下:

    **@RequestMapping("/m6")
    public String method6(@RequestParam List<String> listParam){
            return "size:"+listParam.size() + ",listParam:"+listParam;
    }
    
    • 1
    • 2
    • 3
    • 4
    1. Postman 传参测试:

    在这里插入图片描述

    1.5.7 传递JSON数据

    JSON就有自己的格式和语法, 使用文本表示⼀个对象或数组的信息, JSON 是⼀个字符串,其格式非常类似于 JavaScript 对象字面量的格式

    JSON的语法:

    1. 数据在 键值对(Key/Value) 中
    2. 数据由逗号 , 分隔
    3. 对象⽤ {} 表示
    4. 数组⽤ [] 表示
    5. 值可以为对象, 也可以为数组, 数组中可以包含多个对象

    我们来看⼀段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 strength"]
        },
        {
          "name": "Eternal Flame",
          "age": 1000000,
          "secretIdentity": "Unknown",
          "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation"]
        }
      ]
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    也可以压缩表⽰:

    {"squadName":"Super hero squad","homeTown":"Metro City","formed":2016,"secretBaseSuper tower","active":true,"members":[{"name":"M Man","age":29,"secretIdentity":"Dan Jukes","powers":["Radiation resistance","Turning tiny","Radiation blast"]},{"name":"Madameppercut","age":39,"secretIdentity":"Jane Wilson","powers":["Million ton punch","Damage resistance","Superhuman reflexes"]},{"name":"Eternal Flame","age":1000000,"secretIdentity":"Unknown","powers":["Immortality","Heat Immunity","Inferno","Teleportation","Interdimensional travel"]}]}
    
    • 1

    JSON的两种结构

    1. 对象: 大括号 {} 保存的对象是⼀个无序的键值对集合. ⼀个对象以左括号 { 开始, 右括号 }结束。每个"键"后跟⼀个冒号 : 键值对使用逗号 , 分隔
    2. 数组: 中括号 [] 保存的数组是值(value)的有序集合. ⼀个数组以左中括号 [ 开始, 右中括号 ] 结束,值之间使⽤逗号 , 分隔。

    在这里插入图片描述
    以下都是合法的JSON数据:

    {"name":"admin","age":18}
    ["hello", 3.1415, "json"]
    [{"name":"admin","age":18},{"name":"root","age":16},{"name":"张三","age":20}]
    
    • 1
    • 2
    • 3

    JSON优点:

    1. 简单易用: 语法简单,易于理解和编写,可以快速地进行数据交换
    2. 跨平台支持: JSON可以被多种编程语言解析和生成, 可以在不同的平台和语言之间进行数据交换和传输
    3. 轻量级: 相较于XML格式, JSON数据格式更加轻量级, 传输数据时占用带宽较小, 可以提高数据传输速度
    4. 易于扩展: JSON的数据结构灵活,支持嵌套对象和数组等复杂的数据结构,便于扩展和使用
    5. 安全性: JSON数据格式是⼀种纯文本格式,不包含可执行代码, 不会执行恶意代码,因此具有较高的安全性

    基于以上特点, JSON 在 Web 应用程序中被广泛使用

    我们接收JSON对象, 需要使用 @RequestBody 注解(请求正文),这个注解作用在请求正文的数据绑定,请求参数必须在写在请求正⽂中

    @RequestMapping(value = "/m7")
    public Object method7(@RequestBody Person person) {
            return person.toString();
    }
    
    • 1
    • 2
    • 3
    • 4

    RequestBody 注解应用在方法的参数 Person person 上,表示这个方法期望接收一个HTTP请求的主体,并将其转换为一个名为 person 的 Person 对象。

    使用 Postman 来发送 json 请求参数:

    在这里插入图片描述
    可以看到, 后端正确接收了,通过 Fiddler 观察⼀下请求参数:

    在这里插入图片描述

    1.5.8 获取URL中参数 @PathVariable

    path variable: 路径变量,和字面表达的意思⼀样, 这个注解主要作用在请求 URL 路径上的数据绑定,默认传递参数写在 URL 上,SpringMVC 就可以获取到

    @RequestMapping("/m8/{id}/{name}")
    public String method8(@PathVariable Integer id, @PathVariable("name") String userName )
            return "解析参数id:"+id+",name:"+userName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • @PathVariable Integer id:这表示从URL路径中获取名为"id"的变量值,并将其转换为Integer类型,并将其传递给方法的id参数。
    • @PathVariable(“name”) String userName:这表示从URL路径中获取名为"name"的变量值,并将其作为字符串传递给方法的userName参数。

    使用浏览器发送请求:http://127.0.0.1:8080/param/m8/5/zhangsan

    或者使⽤ Postman 发送请求:
    在这里插入图片描述

    可以看到, 后端正确获取到了URL中的参数,参数对应关系如下:
    在这里插入图片描述

    注意:

    • 如果方法参数名称和需要绑定的 URL 中的变量名称⼀致时, 可以简写, 不用给 @PathVariable 的属性赋值,如上述例子中的 id 变量
    • 如果方法参数名称和需要绑定的 URL 中的变量名称不⼀致时, 需要 @PathVariable 的属性 value 赋值,如上述例子中的 userName 变量.

    1.5.9 上传文件 @RequestPart

    @RequestMapping("/m9")
    public String getfile(@RequestPart("file") MultipartFile file) throws IOException
    //获取⽂件名称
            String fileName = file.getOriginalFilename();
    //⽂件上传到指定路径
            file.transferTo(new File("D:/temp/" + file.getOriginalFilename()));
            return "接收到⽂件名称为: "+fileName;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • MultipartFile file:这是方法的参数,表示接收到的上传文件。

    MultipartFile是Spring框架中用于表示上传的类,它包含了文件的内容、名称、大小等信息。在这例子中,使用MultipartFile类型的参数file来接收上传的文件。

    • file.getOriginalFilename():这是MultipartFile类的方法,用于获取上传文件的原始文件名。
    • file.transferTo(new File(“D:/temp/” + file.getOriginalFilename())):这是MultipartFile类的方法,用于将上传的文件保存到指定路径。

    在这个例子中,文件将被保存到"D:/temp/"目录下,并使用原始文件名作为文件名。

    使用 Postman 发送请求:
    在这里插入图片描述
    观察 D:/temp 路径下,文件是否上传成功:
    在这里插入图片描述

    1.5.10 获取 Cookie

    HTTP 协议自身是属于 “无状态” 协议,即默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系,但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的.

    在这里插入图片描述
    上述图中的 “令牌” 通常就存储在 Cookie 字段中,此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作.

    1.5.10.1 理解Session

    Session, 会话,也就是对话的意思
    在这里插入图片描述
    在计算机领域, 会话是⼀个客户与服务器之间的不中断的请求响应. 对客户的每个请求,服务器能够识别出请求来自于同⼀个客户.

    当⼀个未知的客户⼾向 Web 应用程序发送第⼀个请求时就开始了⼀个会话.当客户明确结束会话或服务器在⼀个时限内没有接受到客户的任何请求时,会话就结束了.

    服务器同⼀时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是从属于哪个用户, 也就是属于哪个会话, 所以就需要在服务器这边记录每个会话以及与用户的信息的对应关系,Session 是服务器为了保存用户信息而创建的⼀个特殊的对象.

    在这里插入图片描述
    Session的本质就是⼀个 “哈希表”, 存储了⼀些键值对结构. Key 就是SessionID, Value 就是用户信息

    在这里插入图片描述
    在这里插入图片描述

    1. 当用户登陆的时候, 服务器在 Session 中新增⼀个新记录, 并把 sessionId 返回给客⼾端. (通过HTTP 响应中的 Set-Cookie 字段返回).
    2. 客户端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的 Cookie 字段带上).
    3. 服务器收到请求之后, 根据请求中的 sessionId 在 Session 信息中获取到对应的用户信息, 再进行后续操作.找不到则重新创建 Session, 并把 SessionID 返回

    在这里插入图片描述
    注意:Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.

    1.5.10.2 Cookie 和 Session 的区别
    • Cookie 是客户端保存用户信息的⼀种机制. Session 是服务器端保存用户信息的⼀种机制.
    • Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session之间的桥梁
    • Cookie 和 Session 经常会在⼀起配合使用. 但是不是必须配合.
    • 完全可以用 Cookie 来保存⼀些数据在客户端, 这些数据不⼀定是用户身份信息, 也不⼀定是 SessionId
    • Session 中的 sessionId 也不需要非得通过 Cookie/Set-Cookie 传递, 还可以通过 URL 传递.
    1.5.10.3 获取Cookie

    传统获取Cookie:

    @RequestMapping("/m10")
    public String method10(HttpServletRequest request,HttpServletResponse response){
            // 获取所有 cookie 信息
            Cookie[] cookies = request.getCookies();
            //打印Cookie信息
            StringBuilder builder = new StringBuilder();
            if (cookies!=null){
                for (Cookie ck:cookies) {
                    builder.append(ck.getName()+":"+ck.getValue());
                }
            }
            return "Cookie信息:"+builder;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 此时没有设置 Cookie, 通过浏览器访问: http://127.0.0.1:8080/param/m10 ,得到 Cookie 为null

    在这里插入图片描述

    1. 我们设置⼀下Cookie的值:

    在这里插入图片描述

    1. 设置后再次访问:

    在这里插入图片描述
    从这个例子中, 也可以看出 Cookie 是可以伪造的, 也就是不安全的, 所以使用 Cookie 时, 后端需要进行 Cookie 校验

    简洁获取Cookie

    @RequestMapping("/getCookie")
    public String cookie(@CookieValue("bite") String bite) {
            return "bite:" + bite;
    }
    
    • 1
    • 2
    • 3
    • 4

    @CookieValue(“bite”)表示从 HTTP 请求的 Cookie 中获取名为 “bite” 的 Cookie 的值,并将其转换为字符串类型(因为方法参数类型是String)传递给方法。

    运行结果: http://127.0.0.1:8080/param/getCookie
    在这里插入图片描述

    1.5.11 获取Session

    Session 也是基于HttpServletRequest 来存储和获取的,Session 是服务器端的机制, 我们需要先存储, 才能再获取

    1. Session存储
    @RequestMapping("/setSess")
    public String setsess(HttpServletRequest request) {
            // 获取Session对象
            HttpSession session = request.getSession();
                if (session != null) {
                 session.setAttribute("username", "java");
                }
            return "session 存储成功";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个代码中看不到 SessionId 这样的概念. getSession 操作内部提取到请求中的 Cookie ⾥的 SessionId , 然后根据 SessionId 获取到对应的 Session 对象, Session 对象用HttpSession来描述

    在这里插入图片描述

    1. 获取 Session

    获取 Session 有两种方式:

    HttpSession getSession(boolean create);
    HttpSession getSession();
    
    • 1
    • 2
    • HttpSession getSession(boolean create) : 参数如果为 true, 则当不存在会话时新建会话;参数如果为 false, 则当不存在会话时返回 null
    • HttpSession getSession(): 和 getSession(true) 含义⼀样, 默认值为true.
    • void setAttribute(String name, Object value): 使用指定的名称绑定⼀个对象到该 session 会话
    1.5.11.1 Session读取

    读取 Session 可以使用 HttpServletRequest

    @RequestMapping("/getSess")
    public String sess(HttpServletRequest request) {
    // 如果 session 不存在, 不会⾃动创建
            HttpSession session = request.getSession(false);
            String username = null;
                if (session != null && session.getAttribute("username") != null) {
                     username = (String) session.getAttribute("username");
                }
            return "username:" + username;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行: http://127.0.0.1:8080/param/setSess
    在这里插入图片描述
    通过 Fiddler 观察 Http 请求和响应情况:

    在这里插入图片描述
    可以看到, Http响应中, 通过 Set-Cookie 告知客户端, 把 SessionID 存储在 Cookie 中,通过浏览器, 可以观察到运行结果:

    在这里插入图片描述

    获取Session: http://127.0.0.1:8080/param/getSess

    在这里插入图片描述

    通过 Fiddler 观察 Http 请求和响应

    在这里插入图片描述
    可以看到, Http请求时, 把SessionId通过Cookie传递到了服务器.

    1. 简洁获取 Session(1)
    @RequestMapping("/getSess2")
    public String sess2(@SessionAttribute(value = "username",required = false) String
            return "username:"+username;
    }
    
    • 1
    • 2
    • 3
    • 4

    @SessionAttribute(value = “username”, required = false)表示从当前会话中获取名为 “username” 的属性,并将其作为参数传递给方法。

    • value = “username”:这是指定的Session属性的名称,即要从当前Session中获取的属性名。

    • required = false:这是一个标志,指示属性是否是必需的。如果为false,即使Session中没有该属性,Spring框架也不会抛出错误。如果为true,那么Session中没有该属性时会抛出异常。

    运⾏结果: http://127.0.0.1:8080/param/getSess2
    在这里插入图片描述

    1. 简洁获取 Session(2)

    通过 Spring MVC 内置对象 HttpSession 来获取

    @RequestMapping("/getSess3")
    public String sess3(HttpSession session){
            String username=(String)session.getAttribute("username");
            return"username:"+username;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    HttpSession session = request.getSession() 中 Session 不存在的话, 会自动进行创建

    运行结果: http://127.0.0.1:8080/param/getSess3

    在这里插入图片描述

    1.5.12 获取Header

    1. 传统获取 header:获取Header也是从 HttpServletRequest 中获取
    @RequestMapping("/param10")
    public String param10(HttpServletRequest request, HttpServletResponse response)
            String userAgent = request.getHeader("User-Agent");
            return name + ":"+userAgent;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    运⾏结果: http://127.0.0.1:8080/param/getHeader

    在这里插入图片描述
    通过 Fiddler 观察, 获取的 User-Agent 是否正确

    在这里插入图片描述

    1. 简洁获取 Header:
    @RequestMapping("/header")
    public String header(@RequestHeader("User-Agent") String userAgent) {
            return "userAgent:"+userAgent;
    }
    
    • 1
    • 2
    • 3
    • 4

    @RequestHeader(“User-Agent”) 表示从 HTTP 请求的请求头中获取名为 User-Agent 的值,并将其转换为字符串类型(因为方法参数类型是String)传递给方法。

    • User-Agent:这是指定的请求头的名称,即要从请求中获取的请求头名称
    • String userAgent:这是方法的参数,表示接收到的请求头的值将存储在`userAgent变量中。

    运行结果: http://127.0.0.1:8080/param/getHeader2

    在这里插入图片描述

  • 相关阅读:
    Java面向对象编程(七)
    CentOS安装Docker
    爬虫 — Js 逆向案例三凡科网登录
    RabbitMQ消息可靠性(二)-- 消费者消息确认
    VRTK4⭐一.VRTK4和VRTK的区别 , 及VRTK4简介
    HarmonyOS开发:【基于命令行(开发环境)】
    第5章 Kafka,构建TB级异步消息系统
    图像识别,骨架剪枝,去分叉 ,去毛刺
    【数据结构】B树
    C#实现软键盘的制作
  • 原文地址:https://blog.csdn.net/weixin_73232539/article/details/138057216