国庆节10月1日
--


讲清服务器把响应封装成 HttpServletResponse 之后,用代码设置响应三部分的两种方式——方式一直接操作响应对象(setStatus、setHeader、getWriter().write),方式二返回 ResponseEntity 链式设置(status、header、body),附本机实测的两种响应头对比(方式一没有 Content-Type)与"通常不用手动设置"的原因
.webp)
34 篇把响应报文的样子看清楚了(响应行、响应头、响应体,以及状态码怎么读)。这一篇(PPT 第 39~42 页)回答最后一个问题:在代码里怎么把这三部分写出来。答案有两种方式,而且——大多数时候你什么都不用写。
PPT 第 39 页是本节目录的最后一格:HTTP 协议下面四格里,「响应数据格式」是上一篇,「响应数据设置」就是这一篇。
PPT 第 40 页把33 篇里请求方向的套路,在响应方向上又讲了一遍:
Web服务器对HTTP协议的响应数据进行了封装(HttpServletResponse),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。
两句话对照着记:
| 请求方向 | 响应方向 | |
|---|---|---|
| 服务器解析/封装出来的对象 | HttpServletRequest(请求对象) | HttpServletResponse(响应对象) |
| 怎么到我们手里 | 写在 Controller 方法参数里 | 一样,写在方法参数里 |
| 里面装着什么 | 浏览器发来的全部请求信息 | 这次要发回去的响应状态码、响应头、响应体 |
| 谁负责把它变成协议原文 | 服务器(Tomcat) | 服务器(Tomcat)——我们只管往对象里塞内容 |
换句话说:响应对象就是”这次回给浏览器的报文”在 Java 里的样子,我们往里塞什么,浏览器就收到什么。

HTTP/1.1 200 OK 是响应行;往下 Server: Apache-Coyote/1.1(内嵌 Tomcat 给自己署的名)、Content-Type: text/html;charset=UTF-8、Content-Length: 21、Date: … 是响应头;空行之后 <h1>Hello HTTP ~~</h1> 是响应体。这张图正好演示了下一篇案例里会看到的现象:响应头这些行全是服务器写的,代码里只负责给出内容这张图是旧版讲义留下的截图,它的响应体(<h1>Hello HTTP ~~</h1>)和第 41 页代码里写的 <h1>Hello Response</h1> 并不是同一次请求;看这张图只要抓住”三部分怎么分”就够了,本篇的真实数据以下面方式一、方式二的实测为准。
PPT 第 41 页给了第一种写法——把响应对象当参数写在方法里,然后一个一个设置:
1import jakarta.servlet.http.HttpServletResponse;2import java.io.IOException;3
4@RequestMapping("/response")5public void response(HttpServletResponse response) throws IOException {6 // 1.设置响应状态码7 response.setStatus(401);8 // 2.设置响应头9 response.setHeader("itheima","itheima");10 // 3.设置响应体11 response.getWriter().write("<h1>Hello Response</h1>");12}逐个对照上一篇的响应三部分:
| 代码 | 设置的是响应的哪一部分 |
|---|---|
response.setStatus(401) | 响应行里的状态码(401 = 未授权,属于 4xx 客户端错误) |
response.setHeader("itheima","itheima") | 响应头(key: value,这里自定义了一条名为 itheima 的头) |
response.getWriter().write("…") | 响应体(getWriter() 拿到的就是写响应正文的字符流) |
写的时候要注意三点:
void——响应体的内容已经通过 response 对象写完了,不需要 return;throws IOException(写输出流属于 IO 操作,可能抛受检异常);jakarta.servlet.http.HttpServletResponse(和请求对象同一个包)。把这段代码跑起来,用命令抓一下真实响应:
本机实测(Spring Boot 3.2.8 / 内嵌 Tomcat 10.1.26 / JDK 17)——请求 curl -si "http://localhost:8080/response" | head -6:
1HTTP/1.1 4012itheima: itheima3Content-Length: 234Date: Tue, 29 Sep 2026 07:41:12 GMT5
6<h1>Hello Response</h1>三项设置都生效了:响应行是 401(setStatus(401) 生效,Tomcat 只写数字、没补描述文字),响应头里出现了自定义的 itheima: itheima(setHeader 生效),响应体正是写进去的那段 HTML,Content-Length: 23 也由服务器按内容长度算好了。
但有一个细节要特别注意:这条响应里没有 Content-Type(对比下面方式二的实测,那里有)。因为 getWriter().write(...) 是绕过 Spring 的返回值处理、直接往响应流里塞内容的,框架不知道你写的是 HTML 还是文本,就没有替你写这个头——浏览器只能自己猜(大概率按 HTML 猜,所以能看到标题效果,但这属于”碰巧”)。
第二种写法不碰响应对象,而是把”状态码 + 响应头 + 响应体”打包成一个返回值交给 Spring:
1import org.springframework.http.ResponseEntity;2
3@RequestMapping("/response2")4public ResponseEntity<String> response2() {5 return ResponseEntity.status(401) // 1.设置响应状态码6 .header("group", "itcast") // 2.设置响应头7 .body("<h1>Hello Response</h1>"); // 3.设置响应体8}对应关系一模一样:
| 代码 | 设置的是响应的哪一部分 |
|---|---|
ResponseEntity.status(401) | 响应行里的状态码 |
.header("group", "itcast") | 响应头(这里换成自定义头 group: itcast) |
.body("<h1>Hello Response</h1>") | 响应体(泛型 ResponseEntity<String> 里的 String 就是响应体的类型) |
三个方法链式调用一路 . 下去,最后 return 出去:
ResponseEntity<String>(尖括号里换成对象/集合也行,Spring 会自动转成 JSON);throws IOException,因为在方法里根本没往流里写东西;status() 起头(它是静态方法,只能写在最前面)、中间可以链多个 .header(...)、最后以 .body(...) 收尾。本机实测(Spring Boot 3.2.8 / 内嵌 Tomcat 10.1.26 / JDK 17)——请求 curl -si "http://localhost:8080/response2" | head -6:
1HTTP/1.1 4012group: itcast3Content-Type: text/plain;charset=UTF-84Content-Length: 235Date: Tue, 29 Sep 2026 07:41:12 GMT6
7<h1>Hello Response</h1>状态码 401、自定义响应头 group: itcast、响应体同样是那 23 字节的 HTML,全部正确;和方式一最大的差别是——框架自动补上了 Content-Type: text/plain;charset=UTF-8。因为走返回值这条路,Spring 会替我们把”这是什么内容”也一并写好(返回字符串就是 text/plain)。
方式一:HttpServletResponse | 方式二:ResponseEntity | |
|---|---|---|
| 怎么拿到/返回 | 方法参数里声明响应对象,方法返回 void | 方法返回值就是 ResponseEntity<...> |
| 设置状态码 | response.setStatus(401) | ResponseEntity.status(401) |
| 设置响应头 | response.setHeader("itheima","itheima") | .header("group","itcast") |
| 设置响应体 | response.getWriter().write("<h1>…</h1>") | .body("<h1>…</h1>") |
| 异常声明 | 要 throws IOException | 不用 |
实测中的 Content-Type | 没有(框架不替你判断内容类型) | text/plain;charset=UTF-8(自动补上) |
| 适合什么时候用 | 需要直接操作响应对象(例如往输出流里写二进制文件、做重定向) | 需要自定义状态码或响应头、又想保持”返回数据”的写法时 |
两种方式殊途同归——最后都是把三部分交给服务器,由服务器组装成上一篇那种报文发出去。日常开发里最常用的是方式二:它不破坏”方法返回数据”的风格,还能省掉 throws IOException;方式一的价值在于”我需要亲手往响应里写东西”的场景。
PPT 第 41 页在两种方式下面加了一句很重要的话:
注意:响应状态码 和 响应头如果没有特殊要求的话,通常不手动设定。服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。
这不是空话,本机实测里到处都是在自动设置的证据:
本机实测(Spring Boot 3.2.8 / 内嵌 Tomcat 10.1.26 / JDK 17)——30 篇那个 /hello 接口的代码只有一句 return "Hello " + name + "~";,什么都没设置,curl -si "http://localhost:8080/hello?name=Tom" | head -5 收到的响应却是:
1HTTP/1.1 2002Content-Type: text/plain;charset=UTF-83Content-Length: 104Date: Tue, 29 Sep 2026 07:39:29 GMT5
6Hello Tom~状态码 200、内容类型 text/plain;charset=UTF-8、长度 10(正好是 Hello Tom~ 的字节数)、日期——全是服务器自己写的。 请求一个不存在的地址时那个 404、返回集合时自动变成的 Content-Type: application/json(都见上一篇的实测),同样没有任何一行代码去设置它们。
所以日常写接口的正确姿势是:先什么都不设,让服务器按请求处理的逻辑自己决定;只有确实有特殊要求时才动手,比如:
status(...);header(...);header("Content-Type", …) 或直接换用别的返回方式。| PPT 的问题 | 答案 |
|---|---|
| HTTP响应数据需要程序员自己手动设置吗? | 不需要——Web服务器对HTTP响应数据进行了封装(HttpServletResponse),我们按上一篇的三部分往里填(或用 ResponseEntity 把三部分一次返回)即可 |
| 响应状态码、响应头需要我们手动指定吗? | 通常情况下无需手动指定,服务器会根据请求逻辑自动设置(返回字符串自动是 200 + text/plain,路径对不上自动 404,程序抛异常自动 500);只有特殊要求时才手动设置 |
| 问题 | 答案 |
|---|---|
| 响应对象叫什么、怎么来? | HttpServletResponse(jakarta.servlet.http 包);服务器把响应数据封装好,写在 Controller 方法的参数里递进来 |
| 方式一怎么写? | 方法参数声明响应对象、方法返回 void:setStatus(401) 设状态码、setHeader("k","v") 设响应头、getWriter().write("…") 设响应体;要 throws IOException |
| 方式二怎么写? | 方法返回 ResponseEntity<响应体类型>:ResponseEntity.status(401).header("group","itcast").body("<h1>…</h1>"),链式调用三部分,不用 throws IOException |
| 两种方式的实测差别? | 都能正确设置 401 + 自定义响应头;方式一的响应里没有 Content-Type(框架不替你判断内容类型),方式二自动补上了 Content-Type: text/plain;charset=UTF-8 |
| 要不要手动设置? | 通常不用——PPT 第 41 页原话是”响应状态码和响应头如果没有特殊要求的话,通常不手动设定,服务器会根据请求处理的逻辑自动设置”;实测里 return 一句字符串就得到了 200 + Content-Type + Content-Length + Date,路径不存在自动 404,代码抛异常自动 500 |
| 什么时候才需要手动设? | 有特殊要求时:要自定义状态码(401/201 等)、要加额外的响应头、要指定内容类型;否则一律”不写” |
HttpServletResponse(jakarta.servlet.http 包);Web 服务器对 HTTP 协议的响应数据进行解析封装,在调用 Controller 方法时作为方法参数传进来——和请求方向的 HttpServletRequest 是同一套套路response.setStatus(401) 设响应行里的状态码、response.setHeader("itheima","itheima") 设响应头、response.getWriter().write("<h1>Hello Response</h1>") 设响应体;方法返回类型是 void、需要 throws IOExceptionResponseEntity.status(401).header("group","itcast").body("<h1>Hello Response</h1>");方法返回类型是 ResponseEntity<String>,泛型里写响应体的类型,不用 throws IOExceptionContent-Type(getWriter().write 绕过了框架的内容判断),方式二自动补上了 Content-Type: text/plain;charset=UTF-8HTTP/1.1 401 / itheima: itheima / Content-Length: 23 / Date: …,响应体 <h1>Hello Response</h1>(23 字节,Content-Length 由服务器按内容算好)HTTP/1.1 401 / group: itcast / Content-Type: text/plain;charset=UTF-8 / Content-Length: 23 / Date: …,响应体同上return "Hello " + name + "~"; 一句代码就得到 200 + Content-Type: text/plain;charset=UTF-8 + Content-Length: 10 + DateContent-Type 自动变成 application/json(分块传输时还会自动用 Transfer-Encoding: chunked 代替 Content-Length)HttpServletResponse);状态码和响应头需要手动指定吗 → 通常情况下无需手动指定,服务器会根据请求逻辑自动设置 2-1 用”直接操作响应对象”的方式做一个未授权响应
需求:写一个接口 /response,访问它时浏览器应该收到:状态码 401(表示未授权,属于 4xx)、一条自定义响应头(名字叫 itheima、值也是 itheima,用来标记这是我们的服务)、响应体是一段一级标题文字 Hello Response。
要求:三个设置各占一行并写中文注释;做完在文件末尾回答:这个方法的返回类型为什么可以不要返回值?方法上为什么必须声明一个受检异常?
(练习文件 test_35_方式一设置响应.java 里给了写作区。)
一级 · 思路:三条要求正好对应响应报文的三个部分——响应行、响应头、响应体,一个一个设;设完就不需要再返回内容了
二级 · 方法:参数里声明 HttpServletResponse(jakarta.servlet.http 包);三个方法是 setStatus(int)、setHeader(String,String)、getWriter().write(String);写输出流要处理 IOException
三级 · 骨架:@RequestMapping("____") public ____ response(HttpServletResponse response) throws ____ { response.____(401); response.____("____", "____"); response.____().____("<h1>____</h1>"); }
1package com.itheima;2
3import jakarta.servlet.http.HttpServletResponse;4import org.springframework.web.bind.annotation.RequestMapping;5import org.springframework.web.bind.annotation.RestController;6
7import java.io.IOException;8
9@RestController10public class ResponseController {11
12 @RequestMapping("/response")13 public void response(HttpServletResponse response) throws IOException {14 // 1.设置响应状态码(401 = 未授权,属于 4xx 客户端错误)15 response.setStatus(401);16 // 2.设置响应头(自定义一条 itheima: itheima)17 response.setHeader("itheima", "itheima");18 // 3.设置响应体(往响应的字符输出流里写 HTML)19 response.getWriter().write("<h1>Hello Response</h1>");20 }21}两个问题:
① 因为响应体的内容已经在方法体里通过响应对象写完了(getWriter().write(...) 直接把内容写进了响应),方法不需要再用返回值告诉框架”要回什么”,所以返回类型是 void;本机实测这样写出来的响应是 HTTP/1.1 401 + itheima: itheima + Content-Length: 23 + 响应体 <h1>Hello Response</h1>(注意这条响应里没有 Content-Type,框架没替你判断内容类型)。
② 因为 getWriter() 返回的是输出流,往输出流里写数据属于 IO 操作,可能抛 IOException(受检异常),必须声明或处理;所以方法签名上要写 throws IOException(也可以 try/catch,但课程里直接声明更简洁)。
2-2 把同一个需求改用”返回值”的方式重写
需求:和 2-1 完全一样的一个新接口 /response2(401 + 自定义响应头 group: itcast + 响应体 <h1>Hello Response</h1>),但这次要求方法里不接收响应对象,而是把”状态码、响应头、响应体”三样一次打包返回出去。
做完在文件末尾回答:这次的方法返回类型怎么写?为什么不写 throws IOException 也不会报错?实测这条响应的响应头里比 2-1 多了一条什么?
(练习文件 test_35_方式二设置响应.java 里给了写作区。)
一级 · 思路:三样东西打包成一个返回值——框架里现成的类就是干这个的,它的三个方法分别对应状态码、响应头、响应体,而且支持链式调用一路点下去
二级 · 方法:org.springframework.http.ResponseEntity,用 ResponseEntity.status(401).header("group","itcast").body("<h1>Hello Response</h1>");返回类型写成 ResponseEntity<String>(尖括号里是响应体类型)
三级 · 骨架:public ____<____> response2() { return ____.____(401).____("group", "itcast").____("<h1>____</h1>"); }
1package com.itheima;2
3import org.springframework.http.ResponseEntity;4import org.springframework.web.bind.annotation.RequestMapping;5import org.springframework.web.bind.annotation.RestController;6
7@RestController8public class ResponseController {9
10 @RequestMapping("/response2")11 public ResponseEntity<String> response2() {12 return ResponseEntity.status(401) // 1.设置响应状态码13 .header("group", "itcast") // 2.设置响应头14 .body("<h1>Hello Response</h1>"); // 3.设置响应体15 }16}三个问题:
① 返回类型写 ResponseEntity<String>——ResponseEntity 就是”HTTP 响应”的载体,尖括号里的 String 是响应体的类型(换成对象或集合也可以,Spring 会自动转成 JSON)。
② 因为方法里完全没有往输出流里写东西——内容只是作为返回值交出去,由 Spring 去写响应;没有 IO 操作自然不会有受检的 IOException,所以不用声明。
③ 多了一条 Content-Type: text/plain;charset=UTF-8。本机实测这条 /response2 的响应是 HTTP/1.1 401 + group: itcast + Content-Type: text/plain;charset=UTF-8 + Content-Length: 23 + 响应体,而方式一(getWriter().write)那条没有 Content-Type——因为走返回值这条路,Spring 会替我们判断内容类型。
2-3 写一个”什么都不设置”的接口,然后解释是谁在替你干活
需求:写一个接口 /hello,访问时带一个 name 参数,直接返回一句 Hello 参数值 ~ 的问候语。方法里不许出现任何设置状态码、设置响应头、写输出流的代码。
做完把这段代码跑起来访问一次,把收到的完整响应(响应行 + 响应头 + 响应体)抄到文件末尾的注释里,并回答:
Content-Type 的值是什么?为什么是它?test_35_默认响应与自动设置.java 里给了写作区。)一级 · 思路:这一题的重点不是”写代码”,而是观察到”不写”的结果——代码只负责给内容,三部分的其余部分交给服务器;第 3 问想想上一篇里”404 是谁的责任”
二级 · 方法:方法用 @RequestMapping 映射,参数 String name 会自动从请求参数绑定,return 一个字符串;抓响应用 curl -si 或浏览器 F12
三级 · 骨架:@RequestMapping("/____") public String hello(String name){ return "Hello " + ____ + " ~"; }
1package com.itheima;2
3import org.springframework.web.bind.annotation.RequestMapping;4import org.springframework.web.bind.annotation.RestController;5
6@RestController7public class HelloController {8
9 @RequestMapping("/hello")10 public String hello(String name) {11 return "Hello " + name + " ~";12 }13}三问三答:
return)。本机实测(Spring Boot 3.2.8 / 内嵌 Tomcat 10.1.26 / JDK 17)访问 http://localhost:8080/hello?name=Tom 收到的完整响应:
1HTTP/1.1 2002Content-Type: text/plain;charset=UTF-83Content-Length: 104Date: Tue, 29 Sep 2026 07:39:29 GMT5
6Hello Tom~Content-Type 的值是 text/plain;charset=UTF-8——因为方法返回的是 String,Spring 按”纯文本 + UTF-8”处理;如果返回的是对象或集合,它会自动变成 application/json。这正是 PPT 第 41 页那句”响应状态码和响应头如果没有特殊要求的话,通常不手动设定,服务器会根据请求处理的逻辑,自动设置”。curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/nope" 的结果就是 404:这个路径没有任何请求处理方法与之匹配,服务器据此判定”资源不存在”,自动给出 4xx 客户端错误——同样一行状态码设置代码都不用写。3-1 同一个”自定义响应”接口用两种方式各写一遍,抓包对比 把方式一和方式二放在一起做一遍,这套流程也正好是以后遇到”响应不对劲”时的排查动作。
/response:接收响应对象,设置状态码 401、一条自定义响应头、响应体是一段 HTML 标题;curl -i(或浏览器 F12 → Network)抓下响应,把响应行 + 全部响应头 + 响应体抄到练习文件里,逐行标出各属于哪一部分;/response2:不接收响应对象,改用”返回值”的方式把同样的三部分返回;第三个方法映射到 /hello,只 return 一句问候语,什么都不设置;Content-Type、为什么?哪一次的状态码不是你设置的、它从哪来?test_35_综合_两种方式对比.java 里按这 6 步给了写作区。)涉及知识点
| 知识点 | 在这里的应用 |
|---|---|
| 响应封装 | HttpServletResponse 作为方法参数被服务器递进来 |
| 方式一 | setStatus / setHeader / getWriter().write,返回 void + throws IOException |
| 方式二 | ResponseEntity.status(...).header(...).body(...),返回 ResponseEntity<String> |
| 自动设置 | 第 3、5 步:什么都不写也有 200+响应头,路径不存在自动 404 |
| 三部分对照 | 抓包后把响应行/响应头/响应体逐行对上代码 |
一级 · 思路:三个接口就是三种”设多少”的对照——全手动、只给内容交给框架打包、彻底不管;抓包时重点看响应头那几行的差别和状态码是谁给的
二级 · 方法:方式一用参数里的 HttpServletResponse(setStatus/setHeader/getWriter().write);方式二 return ResponseEntity.status(401).header("group","itcast").body("<h1>…</h1>");;第三个接口直接 return 字符串;抓包用 curl -i
三级 · 骨架:public void response(____ response) throws IOException { … } / public ____<String> response2(){ return ____.status(401).____("group","itcast").____("<h1>____</h1>"); } / public String hello(){ return "____"; }
3-1 1~3. 三个方法:
1package com.itheima;2
3import jakarta.servlet.http.HttpServletResponse;4import org.springframework.http.ResponseEntity;5import org.springframework.web.bind.annotation.RequestMapping;6import org.springframework.web.bind.annotation.RestController;7
8import java.io.IOException;9
10@RestController11public class ResponseLabController {12
13 // 方式一:直接操作响应对象14 @RequestMapping("/response")15 public void response(HttpServletResponse response) throws IOException {16 response.setStatus(401); // 响应行:状态码17 response.setHeader("itheima", "itheima"); // 响应头18 response.getWriter().write("<h1>Hello Response</h1>"); // 响应体19 }20
21 // 方式二:三部分打包成返回值22 @RequestMapping("/response2")23 public ResponseEntity<String> response2() {24 return ResponseEntity.status(401)25 .header("group", "itcast")26 .body("<h1>Hello Response</h1>");27 }28
29 // 什么都不设置30 @RequestMapping("/hello")31 public String hello() {32 return "Hello HTTP ~~";33 }34}/response(方式一):HTTP/1.1 401 + itheima: itheima + Content-Length: 23 + Date —— 没有 Content-Type。原因是 getWriter().write(...) 直接往响应流里写,绕过了 Spring 对返回值的内容类型判断,框架不知道你写的是 HTML 还是纯文本,就没有替你写这个头;/response2(方式二):HTTP/1.1 401 + group: itcast + Content-Type: text/plain;charset=UTF-8 + Content-Length: 23 + Date —— 状态码和自定义头是我们设的,Content-Type 是框架自动补的;/hello(什么都没设):HTTP/1.1 200 + Content-Type: text/plain;charset=UTF-8 + Content-Length: 10 + Date —— 状态码 200 和这几条响应头全都是服务器自动写的,代码里一行都没写。三次对比下来,Content-Type 那一条的差别就是”谁给的内容”决定的:走返回值 → 框架知道类型 → 补上;直接写流 → 框架不知道 → 不补。curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/nope" 得到 404,属于 4xx 客户端错误、责任在客户端(先检查请求路径与 @RequestMapping 的值是否一致)。这也是”服务器自动设置状态码”的又一个例子。HttpServletResponse);② 响应状态码、响应头通常情况下无需手动指定,服务器会根据请求逻辑自动设置。从实测得到的结论可以合成一句:能自动就自动——只有当需求里明确要求”这个接口必须返回 401 / 必须带上某个响应头”时才手动设置,那时用方式二的 ResponseEntity 最省事。如果你喜欢,那么欢迎来到我的世界!
了解更多暂未播放



"所有的相遇都是久别重逢。"—— 未知


