Spring MVC

发布于:2025-07-25 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

1. MVC架构图

1.1 角色分工(类比餐厅)

1.2 流程拆解(点菜全过程)

1.3 为啥要用 MVC?(好处)

1.4 举个实际代码例子(Java Web)

2. Spring MVC

3. @RequestMapping

3.1 @RequestMapping注解介绍

3.2 @RequestMapping的使用

3.3 @RequestMapping是GET还是POST请求?

4.Postman传参介绍

4.1 普通传参

4.2 form-data

4.3 x-www-form-urlencoded

4.4 Raw

5.Postman发送请求

5.1 传递单个/多个参数

5.2 传递对象

5.3 后端参数中重命名(后端参数映射)

 5.4 传递数组

5.5 传递集合

5.6 传递Json数据

6.获取URL中参数@PathVariable

7.上传文件@RequestPart

8.获取Cookie/Session

8.1 理解Session

8.1 设置Cookie

8.2 获取Cookie

8.3 总结Cookie

8.4 设置Session

8.5 获取Session

8.6 总结Session

8.7 请求对象 vs 会话对象 vs 响应对象

1. 核心作用

2. 生命周期与作用域

3. 核心 API 与典型场景

4. 依赖关系

一句话总结

9.获取Header

10.响应

10.1 返回静态页面

10.2 返回数据@ResponseBody

10.3 返回 HTML 代码片段

10.4 返回JSON

10.5 设置状态码

10.6 设置Header

10.6.1 设置Content-Type

10.6.2 设置其它Header

11.综合性练习

11.1加法计算器

11.1.1 约定前后端交互接口

 11.1.2 后端代码

11.1.3 前端代码

11.1.4运行测试

11.2 用户登录

11.2.1 约定前后端交互接口

 11.2.2 后端代码

11.2.3 前端代码

11.2.4 运行测试

11.3 留言板

11.3.1 约定前后端交互接口

11.3.2 后端代码

11.3.3 前端代码

11.3.4 运行测试


1. MVC架构图

Spring Web MVC 是基于 Servlet API构建的原始 Web 框架,从一开始就包含在Spring框架中。它的正式名称“SpringWebMVC”来自其源模块的名称(Spring-webmvc),但它通常被称为"SpringMVC".

Spring Web MVC 是⼀个 Web 框架.

1.1 角色分工(类比餐厅)

MVC 角色 类比餐厅里的谁? 负责干啥?
View(视图) 「菜单 + 餐桌」 给用户看的 “界面”(菜单让你点菜、餐桌让你放盘子)
Controller(控制器) 「服务员」 接收用户需求,安排后厨做菜、给你上菜
Model(模型) 「后厨 + 食材」 处理具体业务(切菜、炒菜),操作数据(食材)
浏览器(用户) 「你(顾客)」 发起请求(点菜、催菜)

1.2 流程拆解(点菜全过程)

① 你(浏览器)发起请求 → 喊 “服务员!我要点菜!”

  • 对应图里的 「1. 请求」: 你在浏览器里点击按钮、输入网址,就像招手喊服务员,告诉系统 “我要干啥”(比如 “我要一份宫保鸡丁”)。

② 服务员(Controller)接需求 → “好的,我去安排后厨做!”

  • 对应图里的 「2. 选择模型,进行处理」: 服务员(Controller)收到你的需求后,会判断 “该让后厨(Model)做啥菜”,然后把任务派给后厨(比如 “给 3 号桌做宫保鸡丁”)。

③ 后厨(Model)做菜 → “宫保鸡丁做好啦!”

  • 对应图里的 「3. 返回处理结果」: 后厨(Model)收到任务后,处理具体业务(切鸡丁、炒配料),把 “宫保鸡丁”(处理后的数据 / 结果)交给服务员(Controller)。

④ 服务员(Controller)选 “上菜方式” → “用啥盘子装?端哪张桌?”

  • 对应图里的 「4. 响应(选择视图,展示模型)」: 服务员(Controller)拿到做好的菜(Model 的结果),会决定 “用啥视图(盘子、摆盘)给你上菜”,比如选个精致的盘子,把宫保鸡丁端到你桌前。

⑤ 你(浏览器)看到菜 → “开吃!”

  • 对应图里的 「5. 返回给浏览器」: 服务员把装好盘的菜(View 视图)端到你面前(浏览器显示页面),你就能看到最终结果(宫保鸡丁),开始享用(操作页面)。

1.3 为啥要用 MVC?(好处)

  • 分工明确:服务员只管传需求、后厨只管做菜、菜单只管展示 → 改一处不影响其他环节(比如换菜单样式,不用改后厨做菜流程)。

  • 复用性高:比如 “宫保鸡丁” 可以用不同盘子(View)端给不同桌(用户),但后厨(Model)做菜逻辑不变。

  • 好维护:出问题能快速定位(菜不好吃→找后厨,盘子难看→换视图,服务员反应慢→优化控制器)。

1.4 举个实际代码例子(Java Web)

假设做一个 “用户登录” 功能,对应 MVC 流程:

  • View(视图):登录页面(login.html),有用户名、密码输入框 → 对应 “菜单”。

  • Controller(控制器):LoginController.java,接收用户提交的账号密码 → 对应 “服务员”。

  • Model(模型):UserService.java,判断账号密码是否正确(查数据库、验证逻辑) → 对应 “后厨”。

流程:

  1. 你在浏览器(View)输入账号密码 → 点 “登录”(发起请求)。

  2. LoginController(Controller)收到请求 → 调用 UserService(Model)验证账号密码。

  3. UserService(Model)查数据库,返回 “验证结果”(对 / 错)。

  4. LoginController(Controller)根据结果,选 “登录成功页” 或 “错误提示页”(View)。

  5. 浏览器显示最终页面(View),你看到结果。

2. Spring MVC

Web框架,当用户在浏览器中输入了url之后,Spring MVC项目就可以感知到用户的请求并给予响应

重点学习如何通过浏览器和用户程序进行交互

主要分为以下三个方面:

  1. 建立连接:将用户(浏览器)和Java程序连接起来,也就是访问一个地址能够调用到我们的Spring程序

  2. 请求:用户请求的时候会带一些参数,在程序中要想办法获取到参数,所以请求这块主要是获取参数的功能

  3. 响应:执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应

3. @RequestMapping

3.1 @RequestMapping注解介绍

注册接口的路由映射,表示服务器收到请求时.路径为/sayHi的请求就会调用sayHi这个方法的代码

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

@Restcontroller在此处的作用:一个项目中会有很多的类,每个类下可能会有很多的方法,Spring会对所有的类进行扫描.如果类加了注解@Restcontroller才会去看这个类里面的方法有没有加@RequestMapping这个注解

@RestController用来声明标记这是一个"外卖窗口",重点检查里面的窗口

@RequestMapping指定"窗口"接收的订单类型

方法的返回值就是给顾客的餐食

package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @RequestMapping("/sayHi")
    public String sayHi(){
        return "hello,Spring";
    }

}

3.2 @RequestMapping的使用

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

标识一个类:设置映射请求的请求路径的初始信息

标识一个方法:设置映射请求请求路径的具体信息

package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/sayHi")
    public String sayHi() {
        return "Hello World";
    }
}

 

类路径+方法路径:类上的 @RequestMapping("/user") 是 前缀,方法上的@RequestMapping("/sayHi") 是 子路径,最终完整路径是 /user/sayHi。 

3.3 @RequestMapping是GET还是POST请求?

均支持

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/sayHi" method="post">
    <input type="submit" value="提交">
</form>

</body>
</html>

指定Get/Post方法类型

@RestController
public class UserController {
    @RequestMapping(value = "/sayHello",method = RequestMethod.Get)
    public String sayHello() {
        return "Hello World";
    }
}

//另一种写法
@GetMapping("/sayHaha")
    public String sayHaha(){
    return "Haha";
    }

此时前端代码为post方法,当点击提交按钮后就会显示失败 

4.Postman传参介绍

4.1 普通传参

通过查询字符串来传参,查询字符串就是请求的参数

@RequestMapping("/v1")
public String v1(String name,int age){
    return "v1接收到参数"+name + ","+age;
}

4.2 form-data

(完整表示为: multipart/form-data)

表单提交的数据,在 form标签中加上enctyped="multipart/form-data",

通常用于提交图片/文件.对应Content-Type:multipart/form-data

4.3 x-www-form-urlencoded

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

4.4 Raw

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

5.Postman发送请求

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

传递参数,咱们主要是使用浏览器和Postman来模拟.

Postman/浏览器模拟传参,后端接收参数

5.1 传递单个/多个参数

当age的类型为int时,当不传递该参数时发起请求就会出现500的错误,当类型为Integer时不传递age默认就是null

注:key:value本质上都是字符串类型

@RequestMapping("/v1")
//传参时顺序不分先后,key:value对应即可
public String v1(String name,Integer age){
    return "v1接收到参数"+name + ","+age;
}

//底层代码
@RequestMapping("/v2")
public String v2(HttpServletRequest request){
    String name = request.getParameter("name");
    //把传入的 String 类型参数转换为 Integer 类型的对象
    Integer age = Integer.valueOf(request.getParameter("age"));
    return "接收到参数,name:"+name+",age:"+age;
}

注:使用基本类型来接收参数时,参数必须传(除boolean类型),否则会报500错误 类型不匹配时,会报400错误. 

5.2 传递对象

当传递多个参数时可以封装成对象

@RequestMapping("/v3")
public String v3(User user){
    return "User" +user;
}
public class User {
    private String name;
    private int age;
    private Integer gender;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                '}';
    }
}
import lombok.Data;

@Data

public class User {
    private String name;
    private int age;
    private Integer gender;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                '}';
    }
}

此处对象中的的age为成员变量默认值为0 

 

5.3 后端参数中重命名(后端参数映射)

某些特殊的情况下,前端传递的参数key和我们后端接收的key可以不一致,比如前端传递了一个

name给后端,而后端是使用UserName字段来接收的,这样就会出现参数接收不到的情况,如果出现

这种情况,我们就可以使用@RequestParam来重命名前后端的参数值.

此处的前端变量名为name(注解中的为前端变量名)后端名为userName

@RequestMapping("/v4")
public Object v4(@RequestParam("name") String userName){
    return "接收到参数name:" + name;
}

@RequestMapping("/v4")
public Object v4(@RequestParam String UserName){ // 前后端变量名一致
    return "接收到参数UserName:" + UserName;
}

 

//参数绑定默认是必传参数 required为true
@RequestMapping("/v6")
public String v6(@RequestParam(value="name",required = false) String userName){
    return "接收到参数name:" + userName;
}

 

为什么前后端变量名要重命名,这里的注解不还是要知道前后端的变量名是什么,为什么不用统一的变量名?

  1. 前端可以使用符合自己业务逻辑的参数名(如 UserName

  2. 后端可以使用符合自己代码风格的参数名(如 name

  3. 通过 @RequestParam 注解进行映射,实现了解耦

 5.4 传递数组

@RequestMapping("/v7")
public String v7(String[] arr){
    return Arrays.toString(arr);
    //List.of(arr)
}
//此时不传值就为null
@RequestMapping("/v8")
public String v8(@RequestParam (value = "arr",required = false) String[] arr){
    return Arrays.toString(arr);
}

5.5 传递集合

List是接口,需要用具体的类实现

集合类必须加注解,即便实现了接口类也必须加

 

//错误写法
public String v9(List<String> list)
//错误写法 
public String v9(ArrayList<String> list)

@RequestMapping("/v9")
//正确写法一
public String v9(@RequestParam ArrayList<String> list){
    return "size:"+list.size() + ",listParam:"+list;
}
//正确写法二
@RequestMapping("/v10")
public String v10(@RequestParam List<String> listParam){
    return "size:"+listParam.size() + ",listParam:"+listParam;
}

为什么必须加 @RequestParam

  • 不加注解时: Spring 认为 list 是请求体或路径变量,但实际是查询参数 → 无法匹配。

  • @RequestParam 后: Spring 明确知道要从查询参数中找 list,并自动收集多个同名参数到 ArrayList

5.6 传递Json数据

Json语法

  1. 数据在 键值对(Key/Value) 中

  2. 数据由逗号, 分隔

  3. 对象用{} 表示

  4. 数组用 [] 表示

  5. 值可以为对象,也可以为数组,数组中可以包含多个对象

JSON的两种结构

  1. 对象:打括号{} 保存的对象是⼀个无序的 键值对 集合.⼀个对象以左括号{ 开始,右括号} 结束。每个"键"后跟⼀个冒号: ,键值对使用逗号 , 分隔

  2. 数组:中括号[] 保存的数组是值(value)的有序集合.⼀个数组以左中括号 [ 开始,右中括 号 ] 结束,值之间使用逗号, 分隔。


接收JSON对象,需要使用 @RequestBody 注解

RequestBody:请求正文,意思是这个注解作用在请求正文的数据绑定,请求参数必须在写在请求正文中

@RequestMapping("/v11")
public String v11(@RequestBody User user){
    return "user:" + user;
}

 

6.获取URL中参数@PathVariable

Path variable为路径变量,该注解主要作用在请求URL路径上的数据绑定,默认传递参数写在URL上,SpringMVC就可以获取到

此时的路径是写死的,必须进行填值

@RequestMapping("/v12/{id}/{name}")
public String v12(@PathVariable Integer id, @PathVariable("name") String userName){
    return "id:"+id+",name:"+userName;
}

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

如果方法参数名称和需要绑定的URL中的变量名称不一致时,需要@PathVariable的属性value赋值,如上述例子中的userName

7.上传文件@RequestPart

@RequestMapping("/v13")
public String v13(MultipartFile file){
    System.out.println(file.getOriginalFilename());
    return "文件名称:"+file.getOriginalFilename();
}

//重命名
@RequestMapping("/v13")
public String v13(@RequestPart("fileName") MultipartFile file){
    System.out.println(file.getOriginalFilename());
    return "文件名称:"+file.getOriginalFilename();
}

 

在文件路径中,D:/JavaCode/D:/JavaCode 的区别主要在于 是否以路径分隔符(/)结尾 

@RequestMapping("/v14")
public String v14(@RequestPart("fileName") MultipartFile file) throws IOException {
    // 1. 获取上传文件的原始名称(包含扩展名)
    String fileName = file.getOriginalFilename();
    
    // 2. 将文件保存到指定路径(D:/JavaCode/目录下)
    file.transferTo(new File("D:/JavaCode/" + file.getOriginalFilename()));
    
    // 3. 打印文件名(用于调试)
    System.out.println(file.getOriginalFilename());
    
    // 4. 返回包含文件名的响应
    return "文件名称:"+fileName;
}

8.获取Cookie/Session

HTTP自身是属于"无状态协议"

无状态:默认情况下HTTP协议的客户端和服务器之间的这次通信,和下次通信没有直接的联系

在实际开发中,我们很多时候是需要知道请求之间的关联关系的;例如登陆网站后第二次访问服务器的时候服务器就能知道该求情是否已经登录过了

 

上述图中的"令牌"通常就存储在Cookie字段中

此时在服务器这边就需要记录"令牌"信息,以及令牌对应的用户信息,这个就是Session机制所做的工作

8.1 理解Session

在计算机领域,会话是一个客户与服务器之间的不中断的请求响应,对客户的每个请求,服务器能够识别出请求来自于同一个客户,当一个未知的客户向Web应用程序发送第一个请求时就开始了一个会话,当客户明确结束会话或服务器在一个时限内没有接受客户的任何请求时,会话就结束了

服务器同一时刻收到的请求是很多的,服务器需要清楚的区分每个请求是属于哪个用户,也就是属于哪个会话,就需要在服务器这边记录每个会话以及与用户的信息的对应关系。

Session是服务器为了保存用户信息而创建的一个特殊的对象.

Session的本质就是一个"哈希表",存储了一些键值对结构.Key就是SessionID,Value就是用户信息(用

户信息可以根据需求灵活设计).

 

Sessionld是由服务器生成的一个"唯一性字符串",从Session机制的角度来看,这个唯一性字符串称为"Sessionld".但是站在整个登录流程中看待,也可以把这个唯一性字符串称为"token".

上述例子中的令牌ID,就可以看做是SessionId,只不过令牌除了ID之外,还会带一些其他信息,比如时间,签名等.

  1. 当用户登陆的时候,服务器在Session中新增一个新记录,并把sessionId通过HTTP响应中的Set-Cookie字段返回给客户端

  2. 客户端后续再给服务器发送请求的时候,需要在请求中带上sessionId,通过HTTP请求中的Cookie字段带上

  3. 服务器收到请求之后,根据请求中的sessionId在Session信息中获取到对应的用户信息,再进行后续操作,找不到则重新创建Session,并把SessionId返回

 

Cookie和Session的区别

  • Cookie是客户端保存用户信息的一种机制,Session是服务端保存用户信息的一种机制

  • Cookie和Session之间主要是通过SessionId关联起来的,SessionId是Cookie和Session之间的桥梁

  • Cookie和Session经常会在一起配合使用,但不是必须配合 完全可以用Cookie来保存一些数据在客户端,这些数据不一定是用户身份信息,也不一定是SessionId Session中的sessionld也不需要非得通过Cookie/Set-Cookie传递,比如通过URL传递

8.1 设置Cookie

  • 若控制器类或方法使用了@ResponseBody注解,则直接返回字符串 "success" 作为响应体

  • 创建一个名为key、值为value的 Cookie 对象

  • 通过HttpServletResponse将 Cookie 添加到响应头

  • 客户端(浏览器)接收到响应后会存储该 Cookie

@ResponseBody
@RequestMapping("setCookie")
public String setCookie(HttpServletResponse response){
    Cookie cookies = new Cookie("key","value");
    response.addCookie(cookies);
    return "success";
}

8.2 获取Cookie

@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            System.out.println(cookie.getName() + ":" + cookie.getValue());
        }
    }
    return "获取Cookie成功";
}

@RequestMapping("/getCookie1")
public String getCookie1(HttpServletRequest request)
{
    // 获取所有 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;
}

 

需要提前手动添加/设置Cookie才能获取到

  • @CookieValue("Jack"):从请求的 Cookie 中查找名称为 Jack 的 Cookie

  • 将该 Cookie 的值赋给方法参数 name

  • 功能:返回包含 Cookie 值的响应字符串(例如:name:cookie值

 

@RequestMapping("/getCookie2")
public String getCookie3(@CookieValue("Jack") String name){
    return "name:"+name;
}

8.3 总结Cookie

设置Cookie是通过HttpServletResponse将 Cookie 添加到响应头

获取Cookie是通过HttpServletRequest将 Cookie 获取到请求头

  1. 服务器通过Set-Cookie响应头发送 Cookie 给客户端,客户端存储 Cookie(内存或硬盘)。

  2. 客户端在后续请求中通过Cookie请求头将 Cookie 回传给服务器。

操作 方向 使用的 HTTP 头 Java API
设置 Cookie 服务器 → 客户端 Set-Cookie(响应头) response.addCookie(cookie)
获取 Cookie 客户端 → 服务器 Cookie(请求头) request.getCookies()

 

 

8.4 设置Session

Session是服务器的机制,我们需要先存储才能再获取

Session也是基于HttpServerRequest来存储和获取的

@RequestMapping("/setSession")
public String setSession(HttpServletRequest request){
    HttpSession session = request.getSession();
    session.setAttribute("userName","Jack");
    session.setAttribute("age",18);
    return "设置session成功";
}

 

request.getSession() 的工作机制

  • 首次访问: 服务器会创建一个新的 HttpSession 对象,并生成一个唯一的 Session ID(类似 “图书馆分配借阅卡号”)。 Session ID 会通过响应头的 Set-Cookie 字段发送给客户端,客户端将其存储在浏览器的 Cookie 中。

  • 后续访问: 客户端每次请求时会自动携带该 Cookie(类似 “读者每次借书都出示借阅卡”),服务器通过 Session ID 找到对应的 Session 对象。

  • 默认为true,可以显式设置falserequest.getSession(false)则表示不会创建新的对象

setAttribute() 存储的数据类型

  • 支持任意类型: setAttribute() 的第二个参数是 Object 类型,因此可以存储任意 Java 对象(如 StringInteger、自定义对象等)。

  • 类型转换注意: 从 Session 中取值时需要手动强转(例如获取 age 时需转为 Integer)。

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

 

8.5 获取Session

//方式一
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request) {
    HttpSession session = request.getSession();
    String username = (String) session.getAttribute("userName");
    Object age = session.getAttribute("age");
    return "从session中获取信息,userName:" + username + ", age:" + age;
}
//方式二
@RequestMapping("/getSession2")
public String getSession2(HttpSession session) {
    String username = (String) session.getAttribute("userName");
    Integer age = (Integer) session.getAttribute("age");
    // 设置默认值,避免 null
    if (age == null) age = 0; 
    return "从session中获取信息,userName:" + username + ", age:" + age;
}
//方式三
@RequestMapping("/getSession3")
public String getSession3(
    @SessionAttribute(value = "userName", required = false) String userName,
    @SessionAttribute(value = "age", required = false) Integer age
) {
    // 设置默认值,避免 null
    if (age == null) age = 0;
    return "从session中获取信息,userName:" + userName + ", age:" + age;
}

 

 

HttpSession getSession(boolean create); 
HttpSession getSession(); 

 

  • HttpSession getSession(boolean create):参数如果为true,则当不存在会话时新建会话;参数如果 为false,则当不存在会话时返回null

  • HttpSessiongetSession():和getSession(true)含义一样,默认值为true.

  • void setAttribute(String name,Object value):使用指定的名称绑定⼀个对象到该session会话

8.6 总结Session

  • Session 内容:服务器端存储,客户端无法直接查看具体数据,只能通过 JSESSIONID 关联。

  • 作用:靠 JSESSIONID 实现 “客户端 - 服务器” 的会话关联,既安全又能维持用户状态 。

 

操作 方向 使用的 HTTP 头 Java API 关键用法
设置 Session 数据 服务器内部操作(基于 Session 关联) 依托 Set-Cookie(响应头传递 JSESSIONID ) HttpSession session = request.getSession();
session.setAttribute("key", "value");
获取 Session 数据 服务器内部根据关联 Session 读取 依托 Cookie(请求头携带 JSESSIONID ) HttpSession session = request.getSession(false);
if(session != null) {
Object value = session.getAttribute("key");
}
Session 关联标识传递(JSESSIONID) 服务器 ↔ 客户端(建立 / 维持关联) Set-Cookie(响应头,设置 JSESSIONID )、
Cookie(请求头,携带 JSESSIONID )
服务器设置:由 request.getSession() 触发,自动通过 Set-Cookie 响应头下发 JSESSIONID ;
客户端携带:浏览器自动把 JSESSIONID 放入请求头 Cookie ,服务器通过 request.getCookies() 间接拿到 JSESSIONID 关联 Session

 

8.7 请求对象 vs 会话对象 vs 响应对象

以下是 Java Web 开发中请求对象(HttpServletRequest)、会话对象(HttpSession)、响应对象(HttpServletResponse) 的核心区别总结:

1. 核心作用

对象 核心作用
HttpServletRequest 封装 客户端→服务器 的请求数据(如 URL 参数、请求头、Cookie、请求体),是请求数据的 “输入载体”。
HttpSession 存储 用户会话周期 的状态数据(如登录信息、购物车),跨请求共享,依赖 Cookie(JSESSIONID)或 URL 重写维持会话。
HttpServletResponse 封装 服务器→客户端 的响应数据(如响应头、Cookie、响应体),是响应数据的 “输出载体”。

2. 生命周期与作用域

对象 创建时机 销毁时机 作用域
HttpServletRequest 每次请求到达服务器时由容器创建 请求处理完毕后销毁 单次请求(仅本次请求有效)
HttpSession 首次调用 request.getSession() 时创建 超时(默认 30 分钟)、手动调用 invalidate() 或浏览器关闭(会话 Cookie 失效) 会话周期(跨请求、跨页面)
HttpServletResponse 每次响应时由容器创建 响应发送给客户端后销毁 单次响应(仅本次响应有效)

3. 核心 API 与典型场景

对象 关键 API 典型场景
HttpServletRequest getParameter("name"):获取 URL 参数
getHeader("User-Agent"):获取请求头
getCookies():获取客户端 Cookie
getInputStream()/getReader():获取请求体
- 登录接口获取表单参数
- 解析请求头中的 Token
- 文件上传时读取请求体
HttpSession setAttribute("key", value):存数据
getAttribute("key"):取数据
getId():获取 Session ID
invalidate():销毁 Session
- 存储用户登录状态(如 userId)
- 记录购物车数据
- 实现验证码校验
HttpServletResponse setHeader("Content-Type", "application/json"):设置响应头
addCookie(cookie):添加 Cookie
getWriter().write("data"):输出响应体
sendRedirect("/login"):重定向
- 设置响应格式(JSON/HTML)
- 登录成功后写入 Session ID 到 Cookie
- 文件下载时设置 Content-Disposition

4. 依赖关系

  • Session 依赖 Request: HttpSession 需通过 request.getSession() 获取,依赖请求中的 Cookie(JSESSIONID)或 URL 参数关联会话。

  • Response 处理 Session 结果: 服务器通过 response.addCookie() 将 JSESSIONID 写入客户端 Cookie,后续请求才能携带 JSESSIONID 关联到对应 Session。

一句话总结

  • 请求对象:聚焦 “客户端输入”,是单次请求的数据载体。

  • 会话对象:聚焦 “用户状态”,跨请求存储会话数据。

  • 响应对象:聚焦 “服务器输出”,构建并返回响应结果。

三者共同构成 Java Web 请求 - 响应循环的核心组件。

9.获取Header

@RequestMapping("getHeader")
public String getHeader(HttpServletRequest request){
    String userAgent = request.getHeader("User-Agent");
    return "userAgent:" + userAgent;
}
@RequestMapping("getHeader2")
public String getHeader2(@RequestHeader("User-Agent")  String userAgent){
    return "userAgent:" + userAgent;
}

10.响应

在我们前面的代码例子中,都已经设置了响应数据,Http响应结果可以是数据,也可以是静态页面,也可以针对响应设置状态码,Header信息等.

10.1 返回静态页面

 

@Controller
public class IndexController {
    @RequestMapping("/index")
    public String index() {
        return "/index.html";
    }
}

@RestController@Controller 有着什么样的关联和区别呢?

公式含义:@RestController = @Controller + @ResponseBody

  • @Controller: 标记这是一个 Spring MVC 控制器,告诉 Spring “这个类里有处理 HTTP 请求的方法”。

  • @ResponseBody: 让控制器方法的返回值直接转为 JSON/XML 等格式,写入 HTTP 响应体(Response Body),而不是跳转视图。

  • 组合后: @RestController 同时具备两个注解的功能 → 既标记是控制器,又让所有方法返回数据(而非视图)。

不加 @ResponseBody 会怎样?

如果只用 @Controller,方法返回值默认是 视图名称(比如 return "index" 会跳转 index.html)。

加上 @ResponseBody(或用 @RestController),返回值会被 序列化为 JSON(或其他格式),直接给前端当数据

@RestController/@Controller + @ResponseBody返回数据

@Controller返回视图

10.2 返回数据@ResponseBody

@ResponseBody
@RequestMapping("/index1")
public String index1() {
    return "/index.html";
}

@ResponseBody 既是类注解,又是方法注解 如果作用在类上,表示该类的所有方法,返回的都是数据,如果作用在方法上,表示该方法返回的是数据. 

@Controller
public class IndexController {
    @RequestMapping("/index")
    public Object index(){
        return "/index.html";
    }
    @RequestMapping("/returnData")
    public String returnData(){
        return "该⽅法返回数据";
    }
}

程序会认为需要返回的是视图,根据内容去查找⽂件,但是查询不到,路径不存在,报404

10.3 返回 HTML 代码片段

@RequestMapping("/returnHtml")
@ResponseBody
public String returnHtml() {
    return "<h1>Hello,HTML~</h1>";
}

 

响应中的Content-Type常见取值有以下几种:

  • text/html :body数据格式是HTML

  • text/css :body数据格式是CSS

  • application/javascript :body数据格式是JavaScript

  • application/json :body数据格式是JSON

如果请求的是js文件,SpringMVC会自动设置Content-Type为 application/javascript

如果请求的是css文件,SpringMVC会自动设置Content-Type为 text/css

@RequestMapping("/index2") 
public Object index2(){  
    return "/a.js"; 
} 
@RequestMapping("/index3") 
public Object index3(){  
    return "/b.css"; 
} 

10.4 返回JSON

@RequestMapping("/returnJson")
@ResponseBody
public HashMap<String,String> returnJson(){
    HashMap<String,String> map = new HashMap<>();
    map.put("Java","Java Value");
    map.put("Spring","Spring Value");
    return map;
}

通过Fiddler观察响应结果, Content-Type 为application/json

10.5 设置状态码

SpringMVC会根据我们方法的返回结果自动设置响应状态码,程序员也可以手动指定状态码 通过SpringMVC的内置对象HttpServletResponse提供的方法来进行设置

 

@RequestMapping("/setStatus")
@ResponseBody
public String setStatus(HttpServletResponse response) {
    response.setStatus(200);
    return "设置状态码成功";
}

10.6 设置Header

Http响应报头也会向客户端传递⼀些附加信息,比如服务程序的名称,请求的资源已移动到新地址等,如: Content-Type,Local等. 这些信息通过 @RequestMapping 注解的属性来实现

10.6.1 设置Content-Type

反斜杠 \ 是转义字符,用来处理字符串里的特殊字符(比如双引号 "

@ResponseBody
@RequestMapping(value = "/setHeader",produces = "application/json")
public String setHeader(){
    return "{\"success\":true}";
}

@ResponseBody
@RequestMapping(value = "/setHeader1",produces = "application/json")
public Map<String, Boolean> setHeader1(){
    HashMap<String,Boolean> map = new HashMap<>();
    map.put("sucess",true);
    return map;
}

 

10.6.2 设置其它Header

设置其他Header的话,需要使用SpringMVC的内置对象HttpServletResponse提供的方法来进行设置

@ResponseBody
@RequestMapping("setHeader2")
public String setHeader2(HttpServletResponse response){
    response.setStatus(500);
    response.setContentType("text/html;charset=utf-8");
    response.setCharacterEncoding("utf-8");
    response.setHeader("Content-Type","text/html;charset=utf-8");
    return "设置Header成功";
}

 

11.综合性练习

11.1加法计算器

需求:输入两个整数,点击"相加按钮",显示计算结果

11.1.1 约定前后端交互接口

  • 约定"前后端交互接口"是进行Web开发中的关键环节

  • 接口又叫API,是指应用程序对外提供的服务的描述,用于交换信息和执行任务,

  • 简单来说就是允许客户端给服务器发送哪些HTTP请求,并且每种请求预期获取什么样的HTTP响应

  • 接口,其实也就是我们前面网络模块讲的的"应用层协议".把约定的内容写在文档上,就是”接口文档”,接

  • 口文档也可以理解为是应用程序的”操作说明书”

需求分析:

加法计算器功能,对两个整数进行相加,需要客户端提供参与计算的两个数,服务端返回这两个整数计算

的结果

接口定义:

请求路径:calc/sum

请求方式:GET/POST

接口描述:计算两个整数相加

请求参数:

参数名

类型

是否必须

备注

num1

Integer

参与计算的第一个数

num2

Integer

参与计算的第二个数

响应数据:

示例:num1=5 num2=3

Content-Type:text/html
响应内容:计算机计算结果:8

 11.1.2 后端代码

package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/calc")
public class CalcController {
    @RequestMapping("/sum")
    public String sum(@RequestParam("num1") Integer num1,@RequestParam("num2") Integer num2){
        Integer sum = num1 + num2;
        return "计算机计算结果" + sum;
    }
}

11.1.3 前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
     <form action="calc/sum" method="post">
        <h1>计算器</h1>
        数字1:<input name="num1" type="text"><br>
        数字2:<input name="num2" type="text"><br>
        <input type="submit" value=" 点击相加 ">
    </form>
</body>

</html>

11.1.4运行测试

前端输入num1和num2,将两个参数传到后端进行相加计算后返回sum

11.2 用户登录

11.2.1 约定前后端交互接口

需求分析:

对于后端开发人员而言,不涉及前端页面的展示,只需要提供两个功能

1.登录页面:通过账号和密码,校验输入的账号密码是否正确,并告知前端

2.首页:告知前端当前登录用户,如果当前已有用户登录,返回登录的账号,如果没有,返回空

接口定义:

  1. 校验接口
     

    /user/login 
    请求方式:POST 
    接口描述:校验账号密码是否正确 

请求参数:

参数名

类型

是否必须

备注

userName

String

校验的账号

password

String

校验的密码

响应数据:
 

Content-Type: text/html 
响应内容:
true //账号密码验证成功
false//账号密码验证失败 
  1. 2查询登陆用户接口

    请求路径:/user/getLoginUser 
    请求方式:GET 
    接口描述:查询当前登录的用户 

请求参数:无

响应数据:

Content-Type: text/html 
响应内容: admin 

 11.2.2 后端代码

package com.example.demo;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class LoginController {
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public Boolean login(String userName, String password, HttpServletRequest request) {
        if(!StringUtils.hasLength(userName) && !StringUtils.hasLength(password)){
            return false;
        }
        //校验前端传来的账号密码是否正确,客户端向服务器发请求为request
        if("admin".equals(userName) && "123456".equals(password)){
            //设置session
            HttpSession session = request.getSession(true);
            session.setAttribute("userName", userName);
            return true;
        }
        return false;
    }
    @RequestMapping("getLoginUser")
    public Object getLoginUser(HttpSession session){
        if(session.getAttribute("userName") != null){
            return session.getAttribute("userName");
        }
        return null;
    }
}

11.2.3 前端代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>登录页面</title>
</head>

<body>
  <h1>用户登录</h1>
  用户名:<input name="userName" type="text" id="userName"><br>
  密码:<input name="password" type="password" id="password"><br>
  <input type="button" value="登录" onclick="login()">
  
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script>
    function login() {
      $.ajax({
        type: "post",
        url: "/user/login",
        data: {
          "userName": $("#userName").val(),
          "password": $("#password").val()
        },
        success: function (result) {
          if (result) {
            location.href = "/index.html"
          } else {
            alert("账号或密码有误.");
          }
        }
      });

    }

  </script>
</body>

</html>
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>用户登录首页</title>
</head>

<body>
    登录人: <span id="loginUser"></span>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        $.ajax({
            type: "get",
            url: "/user/getLoginUser",
            success: function (result) {
                $("#loginUser").text(result);
            }
        });
    </script>
</body>

</html>

11.2.4 运行测试

11.3 留言板

11.3.1 约定前后端交互接口

需求:

  1. 输入留言信息,点击提交,后端把数据存储起来

  2. 页面展示输入的表白墙信息

需求分析

后端需要提供两个服务

  1. 提交留言:用户输入留言信息之后,后端需要把留言信息保存起来

  2. 展示留言:页面展示时,需要从后端获取到所有的留言信息

接口定义

  1. 获取全部留言 全部留言信息,我们用List来表示,可以用Json来描述这个List数据

请求

GET /message/getList 

 响应:Json格式

[  
    {  
        "from": "⿊猫",  
        "to": "⽩猫",  
        "message": "喵" 
    },{  
        "from": "⿊狗",  
        "to": "⽩狗",  
        "message": "汪"  },  
        //... ] 

浏览器给服务器发送一个GET/message/getList这样的请求,就能返回当前一共有哪些留言

记录.结果以json的格式返回过来.

  1. 2发表新留言

请求:body也为JSON格式

POST /message/publish 
{  
    "from": "⿊猫",  
    "to": "⽩猫",  
    "message": "喵" 
} 

响应JSON格式

{  
    ok: 1 
} 

我们期望浏览器给服务器发送一个POST/message/publish这样的请求,就能把当前的留言提

交给服务器。

11.3.2 后端代码

package com.example.demo;

import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/message")
public class MessageController {
    //获取全部留言信息
    private List<MessageInfo> messageInfos = new ArrayList<>();

    @RequestMapping("/getList")
    public List<MessageInfo> getList(){
        return messageInfos;
    }

    @RequestMapping(value = "/publish",produces = "application/json")
    public String publish(@RequestBody MessageInfo messageInfo){
        System.out.println(messageInfo);
        if(StringUtils.hasLength(messageInfo.getFrom())
                &&StringUtils.hasLength(messageInfo.getTo())
                &&StringUtils.hasLength(messageInfo.getMessage())){
            messageInfos.add(messageInfo);
            System.out.println(messageInfo);
            return "{\"ok\":1}";
        }
        return "{ \"ok\":0}";
    }
}

11.3.3 前端代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
    <style>
        .container {
            width: 350px;
            height: 300px;
            margin: 0 auto;  /*水平方向居中*/
            /* border: 1px black solid; */
            text-align: center;
        }

        .grey {
            color: grey;
        }

        .container .row {
            width: 350px;
            height: 40px;

            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .container .row input {
            width: 260px;
            height: 30px;
        }

        #submit {
            width: 350px;
            height: 40px;
            background-color: orange;
            color: white;
            border: none;
            margin: 10px;
            border-radius: 5px;
            font-size: 20px;
        }
    </style>
</head>

<body>
<div class="container">
    <h1>留言板</h1>
    <p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
    <div class="row">
        <span>谁:</span> <input type="text" name="" id="from">
    </div>
    <div class="row">
        <span>对谁:</span> <input type="text" name="" id="to">
    </div>
    <div class="row">
        <span>说什么:</span> <input type="text" name="" id="say">
    </div>
    <input type="button" value="提交" id="submit" onclick="submit()">
    <!-- <div>A 对 B 说: hello</div> -->
</div>

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>

    function submit(){
        let from = $("#from").val();
        let to = $("#to").val();
        let say = $("#say").val();

        if(from=="" || to == "" || say == ""){
            alert("请检查输入内容");
            return;
        }

        let html = "<div>"+ from +" 对 "+ to +" 说: "+say+"</div>";

        $(".container").append(html);
        $(":text").val("");

    }

</script>
</body>

</html>

11.3.4 运行测试


网站公告

今日签到

点亮在社区的每一天
去签到