一、按响应内容类型分类
1. 纯文本 / 字符串(String)
用于返回简单文本、HTML 片段或模板名称。
示例:
@GetMapping("/text")
public String text() {
return "Hello, Spring Boot!";
}
行为说明:
- 如果 没有使用
@ResponseBody,Spring 会将其视为 视图名称(View Name) ,尝试跳转到模板(如 Thymeleaf 的text.html)。 - 如果 加上
@ResponseBody或类上有@RestController,则直接以text/plain返回字符串内容。
✅ 推荐写法(REST API):
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello() {
return "Hello World"; // 直接返回文本
}
}
2. JSON 对象(POJO / Map / List)
最常用的 RESTful API 响应格式。
@GetMapping("/user")
public User getUser() {
return new User(1L, "Alice");
}
@GetMapping("/data")
public Map<String, Object> getData() {
Map<String, Object> map = new HashMap<>();
map.put("time", Instant.now());
map.put("status", "ok");
return map;
}
行为说明:
- 在
@RestController或方法有@ResponseBody时,Spring 使用 Jackson(默认)将对象序列化为 JSON; - Content-Type 自动设为
application/json; - 支持
List<T>、Map、自定义 POJO、ResponseEntity<T>等。
3. ResponseEntity:带状态码和响应头的灵活响应
用于精确控制 HTTP 状态码、Header、Body。
@GetMapping("/not-found")
public ResponseEntity<String> notFound() {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.header("X-Custom", "Error")
.body("Resource not found");
}
@PostMapping("/create")
public ResponseEntity<User> createUser(@RequestBody User user) {
User saved = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
优势:
- 可设置任意 HTTP 状态码(200、201、404、500 等);
- 可添加自定义响应头(如
Cache-Control,ETag); - 适用于需要精细控制响应的场景(如 RESTful 规范)。
4. 视图(View):返回 HTML 页面(服务端渲染)
适用于传统 Web 应用(如 Thymeleaf、Freemarker)。
示例(Thymeleaf):
@Controller // 注意:这里是 @Controller,不是 @RestController
public class PageController {
@GetMapping("/home")
public String home(Model model) {
model.addAttribute("message", "Welcome!");
return "home"; // 对应 templates/home.html
}
}
行为说明:
- 返回值是 逻辑视图名;
- Spring 会结合视图解析器(如 ThymeleafViewResolver)渲染 HTML;
- Content-Type 为
text/html; - 需要引入模板引擎依赖(如
spring-boot-starter-thymeleaf)。
5. 文件下载 / 二进制流(Resource / InputStreamResource)
用于返回图片、PDF、Excel 等文件。
示例:
@GetMapping("/download/report.pdf")
public ResponseEntity<Resource> downloadReport() throws IOException {
Path path = Paths.get("files/report.pdf");
Resource resource = new UrlResource(path.toUri());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=report.pdf")
.body(resource);
}
关键点:
- 使用
Resource(如FileSystemResource,ClassPathResource); - 设置
Content-Type和Content-Disposition: attachment实现下载; - 大文件建议配合
StreamingResponseBody(见下文)。
6. 流式响应(StreamingResponseBody)
适用于大文件下载或实时数据流(如 SSE 初步支持)。
示例:
@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> streamData() {
StreamingResponseBody stream = outputStream -> {
for (int i = 0; i < 10; i++) {
outputStream.write(("Line " + i + "\n").getBytes());
Thread.sleep(500);
}
};
return ResponseEntity.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(stream);
}
⚠️ 注意:此方式不会缓冲整个响应,适合内存敏感场景。
7. 异步响应(DeferredResult / CompletableFuture)
用于提高吞吐量,处理耗时任务(如调用远程服务)。
示例(CompletableFuture):
@GetMapping("/async")
public CompletableFuture<String> asyncCall() {
return CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作
try { Thread.sleep(2000); } catch (Exception e) {}
return "Async result";
});
}
优势:
- 不阻塞 Servlet 线程;
- 适合 I/O 密集型任务;
- 返回类型会被 Spring 异步处理并最终写入响应。
客户端视角 vs 服务端视角
核心机制:HTTP 连接保持打开,但服务端线程已释放
- 客户端:发起 HTTP 请求后,TCP 连接建立,它会一直等待服务器返回完整的 HTTP 响应(状态码 + headers + body)。
- 服务端(Spring Boot) :
- 控制器方法虽然"立即返回"了
DeferredResult对象,- 但 Spring 检测到这是异步类型,不会立即提交 HTTP 响应;
- 而是调用
request.startAsync(),进入 Servlet 异步模式;- 此时 HTTP 连接仍然保持打开 ,但 Tomcat 的工作线程已被释放;
- 真正的响应内容会在未来某个时刻(比如 2 秒后)由后台线程触发写入。
👉 所以:客户端感知不到"服务端线程是否被释放",它只关心"什么时候收到完整响应"。而这个时间点,就是异步任务完成的时候。
假设你用浏览器或 curl 访问/async接口:
curl http://localhost:8080/async
你执行命令后,光标会卡住(没有立即返回结果);
2 秒后(假设异步任务耗时 2 秒),终端突然打印出:
Hello from DeferredResult!这说明:HTTP 响应是在 2 秒后才完成的,尽管服务端控制器"立刻返回"了。
💡 客户端看到的延迟 = 异步任务的实际执行时间,和同步接口表现完全一样 。
区别只在服务端资源利用效率更高。
📡 网络层面发生了什么?
- 客户端 → 服务端:发送 HTTP 请求(TCP 数据包);
- 服务端接收请求,分配 Tomcat 线程处理;
- 控制器返回
DeferredResult,Spring 启动异步上下文(startAsync());- Tomcat 线程归还线程池 ,但 TCP 连接未关闭(处于"半开"等待状态);
- 后台线程完成计算,调用
setResult(...);- Spring 通过异步上下文获取
HttpServletResponse,写入状态码、headers、body;- Tomcat 将响应数据通过同一个 TCP 连接发回客户端;
- 客户端收到完整响应,连接关闭(或 keep-alive)。
🌐 整个过程对客户端透明:它不知道服务端用了多少线程、是否异步,只知道"我发了个请求,等了 X 秒,收到了响应"。
(动手试试)
@GetMapping("/test") public DeferredResult<String> test() { DeferredResult<String> dr = new DeferredResult<>(); new Thread(() -> { try { Thread.sleep(3000); dr.setResult("Done after 3s"); } catch (InterruptedException e) { dr.setErrorResult("Error"); } }).start(); return dr; // 立即返回,但客户端要等 3 秒 }用 curl 测试
time curl http://localhost:8080/test Done after 3s real 0m3.012s ← 总耗时约 3 秒 user 0m0.005s sys 0m0.007s→ 证明客户端确实等了 3 秒才收到响应。
8. 无响应体(void / ResponseEntity)
用于仅返回状态码的接口(如 DELETE 成功)。
示例:
@DeleteMapping("/user/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT) // 204
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}
// 或
@DeleteMapping("/user2/{id}")
public ResponseEntity<Void> deleteUser2(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build(); // 204
}
总结表格:Spring Boot 响应类型一览
| 返回类型 | 适用场景 | 是否需 @ResponseBody |
Content-Type | 备注 |
|---|---|---|---|---|
String |
文本 / 视图名 | @RestController 下自动 |
text/plain 或 text/html |
注意区分视图与文本 |
POJO / Map / List |
JSON API | 是 | application/json |
最常用 |
ResponseEntity<T> |
精细控制响应 | 是 | 可自定义 | 推荐用于标准 REST API |
String(配合 @Controller) |
HTML 页面 | 否 | text/html |
需模板引擎 |
Resource |
文件下载 | 是 | 如 application/pdf |
小文件 |
StreamingResponseBody |
大文件/流 | 是 | 自定义 | 避免内存溢出 |
CompletableFuture<T> |
异步处理 | 是 | 同 T 类型 | 提升并发 |
void |
无内容响应 | 否 | 无 | 常用于 DELETE |
在 Spring Boot 中 返回 JSP 页面
✅ 一、前提条件
- 必须使用 WAR 包部署(不能用内嵌 Tomcat 直接运行 JSP);
- 不能使用
spring-boot-starter-tomcat的内嵌方式直接运行 JSP(JSP 需要编译,而内嵌 Tomcat 默认不支持); - 推荐:打成 WAR 包,部署到外部 Tomcat 容器。
📌 注意:Spring Boot 3.x 已移除对 JSP 的部分支持,建议使用 Spring Boot 2.7.x 或更早版本。
✅ 二、完整配置步骤(以 Spring Boot 2.7 + 外部 Tomcat 为例)
1. 添加依赖(pom.xml)
<dependencies>
<!-- Web starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JSP 支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope> <!-- 打包时排除,由外部 Tomcat 提供 -->
</dependency>
<!-- JSTL 标签库(可选但推荐) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- 如果使用 Java 17+,注意 javax.* 已迁移到 jakarta.*(见下方说明) -->
</dependencies>
⚠️ 重要 :
tomcat-embed-jasper的 scope 必须是provided,否则内嵌 Tomcat 会尝试加载,但无法正确编译 JSP。
2. 配置打包方式为 WAR
<packaging>war</packaging>
并继承 SpringBootServletInitializer:
@SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
3. 配置 JSP 视图解析器(可选,Spring Boot 通常自动配置)
在 application.properties 中指定 JSP 路径:
# JSP 文件存放位置(必须在 src/main/webapp/ 下)
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
📁 目录结构要求:
src/
└── main/
├── java/
└── webapp/
└── WEB-INF/
└── jsp/
└── hello.jsp ← 你的 JSP 页面
4. 编写 Controller(使用 @Controller,不要用 @RestController)
@Controller
public class HomeController {
@GetMapping("/hello")
public String hello(Model model) {
model.addAttribute("message", "Hello from Spring Boot + JSP!");
return "hello"; // 对应 /WEB-INF/jsp/hello.jsp
}
}
5. 创建 JSP 页面
src/main/webapp/WEB-INF/jsp/hello.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Hello JSP</title>
</head>
<body>
<h1>${message}</h1>
<p>Current time: <%= new java.util.Date() %></p>
</body>
</html>
6. 打包并部署到外部 Tomcat
# 打包
mvn clean package
# 将 target/*.war 拷贝到 Tomcat 的 webapps 目录
cp target/myapp.war $TOMCAT_HOME/webapps/
# 启动 Tomcat
$TOMCAT_HOME/bin/startup.sh
访问:http://localhost:8080/myapp/hello
⚠️ 常见问题与限制
| 问题 | 说明 |
|---|---|
| 内嵌 Tomcat 无法运行 JSP | Spring Boot 内嵌 Tomcat 不包含 Jasper 编译器(即使加了依赖也不行),必须用外部 Tomcat |
| Spring Boot 3 不兼容 | Spring Boot 3 使用 Jakarta EE 9+,包名从 javax.* 变为 jakarta.*,而 Tomcat 10+ 才支持。若用 SB3,需升级到 Tomcat 10+ 并改用 jakarta.servlet |
| IDE 直接运行失败 | 在 IDEA/Eclipse 中直接运行 main 方法会报 404,因为 JSP 未被编译 |
替代方案建议(强烈推荐)
由于 JSP 已过时,官方推荐使用现代模板引擎:
| 模板引擎 | 优点 |
|---|---|
| Thymeleaf | Spring Boot 默认支持,天然集成,支持 HTML 原型 |
| Freemarker | 轻量、高性能,语法灵活 |
| Mustache | 简洁,前后端通用 |
例如,使用 Thymeleaf 只需:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
然后将页面放在 src/main/resources/templates/ 下即可,无需 WAR 包,支持内嵌运行。
💡 一句话结论 :
"能不用 JSP 就不用,除非你别无选择。"