Spring Boot 响应给客户端的常见返回类型

一、按响应内容类型分类

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-TypeContent-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 秒后才完成的,尽管服务端控制器"立刻返回"了。

💡 客户端看到的延迟 = 异步任务的实际执行时间,和同步接口表现完全一样

区别只在服务端资源利用效率更高。


📡 网络层面发生了什么?

  1. 客户端 → 服务端:发送 HTTP 请求(TCP 数据包);
  2. 服务端接收请求,分配 Tomcat 线程处理;
  3. 控制器返回 DeferredResult,Spring 启动异步上下文(startAsync());
  4. Tomcat 线程归还线程池 ,但 TCP 连接未关闭(处于"半开"等待状态);
  5. 后台线程完成计算,调用 setResult(...)
  6. Spring 通过异步上下文获取 HttpServletResponse写入状态码、headers、body
  7. Tomcat 将响应数据通过同一个 TCP 连接发回客户端;
  8. 客户端收到完整响应,连接关闭(或 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/plaintext/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 页面


✅ 一、前提条件

  1. 必须使用 WAR 包部署(不能用内嵌 Tomcat 直接运行 JSP);
  2. 不能使用 spring-boot-starter-tomcat 的内嵌方式直接运行 JSP(JSP 需要编译,而内嵌 Tomcat 默认不支持);
  3. 推荐:打成 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 就不用,除非你别无选择。"

相关推荐
韩立学长2 小时前
【开题答辩实录分享】以《植物爱好者交流平台的设计与实现》为例进行答辩实录分享
spring boot·后端·mysql
Wzx1980122 小时前
go基础语法练习
开发语言·后端·golang
老友@2 小时前
一次由 PageHelper 分页污染引发的 Bug 排查实录
java·数据库·bug·mybatis·pagehelper·分页污染
AI分享猿2 小时前
小白学规则编写:雷池 WAF 配置教程,用 Nginx 护住 WordPress 博客
java·网络·nginx
sp422 小时前
漫谈 Java 轻量级的模板技术:从字符串替换到复杂模板
java·后端
2301_795167202 小时前
玩转Rust高级应用. ToOwned trait 提供的是一种更“泛化”的Clone 的功能,Clone一般是从&T类型变量创造一个新的T类型变量
开发语言·后端·rust
952363 小时前
数据结构-链表
java·数据结构·学习
喵手3 小时前
Java线程通信:多线程程序中的高效协作!
java
草莓熊Lotso3 小时前
C++ 方向 Web 自动化测试实战:以博客系统为例,从用例到报告全流程解析
前端·网络·c++·人工智能·后端·python·功能测试