一、核心概念
Spring Boot 应用通过控制器(Controller)处理 HTTP 请求,并返回响应。响应的类型和内容取决于应用的设计目标(API 服务 or 传统 Web 应用)和配置。主要分为两大类:
数据响应 (Data Response):
- 核心: 返回结构化数据,最常见的是 JSON,用于 RESTful API。
- 机制: 使用
@RestController
或@Controller
+@ResponseBody
。Spring 的HttpMessageConverter
(如MappingJackson2HttpMessageConverter
) 负责将 Java 对象(POJO)序列化为 JSON 字符串,并写入 HTTP 响应体。 - 场景: 前后端分离架构、微服务、移动应用后端。
视图响应 (View Response):
- 核心: 返回 HTML 页面,通常由模板引擎渲染生成。
- 机制: 使用
@Controller
。控制器方法返回一个视图名称 (View Name)。Spring 的ViewResolver
(视图解析器) 根据这个名称找到对应的模板文件(如 Thymeleaf 的.html
文件、JSP 的.jsp
文件),并结合模型数据(Model
)渲染成最终的 HTML。 - 场景: 服务端渲染 (SSR) 的传统 Web 应用、需要 SEO 的页面。
关键组件
HttpMessageConverter
: 负责请求体反序列化和响应体序列化。Jackson
是处理 JSON 的默认选择。ViewResolver
: 负责将逻辑视图名称解析为实际的视图实现(如模板文件)。Model
/ModelMap
/ModelAndView
: 用于在控制器和视图之间传递数据。- 模板引擎 (Template Engine): 如 Thymeleaf, FreeMarker, Velocity, JSP 等,用于将模板和数据结合生成 HTML。
二、操作步骤(非常详细)
步骤 1:返回 JSON 响应(REST API)
目标: 创建一个返回 JSON 数据的 RESTful 端点。
添加依赖 (Maven
pom.xml
):spring-boot-starter-web
已包含jackson-databind
,通常无需额外添加。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 如果需要 XML 支持 --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
创建数据模型 (POJO):
public class User { private Long id; private String name; private String email; // Constructors, Getters, and Setters public User() {} public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } // ... getter and setter methods }
创建 REST 控制器:
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; import java.util.ArrayList; @RestController // 1. 使用 @RestController (等同于 @Controller + @ResponseBody) @RequestMapping("/api/users") public class UserController { private List<User> users = new ArrayList<>(); // 简化示例,实际用 Service/Repository // GET /api/users - 返回用户列表 (JSON 数组) @GetMapping public List<User> getAllUsers() { return users; // 2. 直接返回 List<User>,Spring 自动序列化为 JSON } // GET /api/users/1 - 返回单个用户 (JSON 对象) @GetMapping("/{id}") public User getUserById(@PathVariable Long id) { return users.stream() .filter(u -> u.getId().equals(id)) .findFirst() .orElse(null); // 注意:返回 null 可能不是最佳实践,见“最佳实践” } // POST /api/users - 创建用户,接收 JSON 请求体 @PostMapping public User createUser(@RequestBody User user) { // 3. @RequestBody 接收 JSON 并反序列化 user.setId((long) (users.size() + 1)); // 简化 ID 生成 users.add(user); return user; // 4. 返回创建的用户 (JSON),通常用 201 Created } }
@RestController
确保所有方法的返回值都作为响应体。@RequestBody
告诉 Spring 将请求体(JSON)反序列化为User
对象。- 返回的
List<User>
或User
对象会被Jackson
自动转换成 JSON。
测试:
- 启动应用。
- 访问
http://localhost:8080/api/users
,应看到类似[{"id":1,"name":"John","email":"john@example.com"}]
的 JSON 响应。
步骤 2:返回 HTML 响应(视图解析)
目标: 创建一个返回 HTML 页面的端点,使用 Thymeleaf 模板引擎。
添加依赖 (Maven
pom.xml
):<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <!-- 1. 添加 Thymeleaf 依赖 --> </dependency>
spring-boot-starter-thymeleaf
会自动配置ThymeleafViewResolver
和SpringResourceTemplateResolver
。
创建 Thymeleaf 模板:
- 在
src/main/resources/templates
目录下创建 HTML 文件,例如userList.html
。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>User List</title> </head> <body> <h1>Users</h1> <ul th:if="${users != null}"> <!-- 2. 使用 th:each 遍历名为 'users' 的模型属性 --> <li th:each="user : ${users}" th:text="${user.name + ' (' + user.email + ')'}"></li> </ul> <p th:if="${users == null}">No users found.</p> </body> </html>
th:if
: 条件判断。th:each
: 循环遍历。${...}
: 访问模型 (Model) 中的变量。
- 在
创建控制器 (返回视图):
import org.springframework.stereotype.Controller; // 3. 使用 @Controller import org.springframework.ui.Model; // 4. 导入 Model import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; @Controller // 注意:这里是 @Controller,不是 @RestController @RequestMapping("/users") public class UserViewController { // GET /users - 返回 userList.html 页面 @GetMapping public String showUserList(Model model) { // 5. 方法返回 String (视图名称) List<User> users = userService.findAll(); // 假设 userService 已注入 model.addAttribute("users", users); // 6. 将数据添加到 Model return "userList"; // 7. 返回视图名称,不带 .html 扩展名 } }
@Controller
: 标记为 MVC 控制器。Model model
: 方法参数,用于向视图传递数据。model.addAttribute("users", users)
: 将users
列表放入模型,键为"users"
。return "userList"
: 返回逻辑视图名称。ThymeleafViewResolver
会根据配置(默认前缀classpath:/templates/
,后缀.html
)将其解析为classpath:/templates/userList.html
。
(可选)使用
ModelAndView
:@GetMapping("/profile") public ModelAndView showUserProfile(@RequestParam Long id) { User user = userService.findById(id); ModelAndView mav = new ModelAndView(); mav.setViewName("userProfile"); // 设置视图名称 mav.addObject("user", user); // 添加模型数据 return mav; }
ModelAndView
将模型和视图封装在一个对象中。
测试:
- 启动应用。
- 访问
http://localhost:8080/users
,应看到渲染后的 HTML 页面。
步骤 3:自定义响应(状态码、头信息)
目标: 精确控制 HTTP 响应。
使用
ResponseEntity
:import org.springframework.http.ResponseEntity; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/users") public class UserController { @PostMapping public ResponseEntity<User> createUser(@RequestBody User user) { User savedUser = userService.save(user); // 201 Created, Location header 包含新资源 URL return ResponseEntity .created(URI.create("/api/users/" + savedUser.getId())) .body(savedUser); } @GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findById(id); if (user != null) { return ResponseEntity.ok(user); // 200 OK } else { return ResponseEntity.notFound().build(); // 404 Not Found } } @DeleteMapping("/{id}") public ResponseEntity<Void> deleteUser(@PathVariable Long id) { userService.deleteById(id); return ResponseEntity.noContent().build(); // 204 No Content } // 返回自定义头信息 @GetMapping("/version") public ResponseEntity<String> getVersion() { return ResponseEntity.ok() .header("X-App-Version", "1.0.0") // 添加自定义头 .body("v1.0.0"); } }
ResponseEntity
提供了对状态码、头信息和响应体的完全控制。
返回纯文本或自定义内容类型:
@GetMapping(value = "/status", produces = "text/plain") public String getStatus() { return "OK"; // 返回纯文本 } @GetMapping(value = "/data", produces = "application/octet-stream") public byte[] downloadData() { return someService.getDataAsBytes(); // 返回二进制数据 }
三、常见错误
@RestController
vs@Controller
混淆:- 错误: 在
@Controller
中返回对象(如User
),期望得到 JSON。 - 结果: Spring 尝试将对象的
toString()
结果(如User@123abc
)当作视图名称查找,找不到模板,报错。 - 正确: REST API 用
@RestController
;返回视图用@Controller
。
- 错误: 在
@ResponseBody
遗漏 (当使用@Controller
时):- 错误: 在
@Controller
中的方法需要返回 JSON,但忘了加@ResponseBody
。 - 结果: 同上,被当作视图名称。
- 正确: 在方法上加
@ResponseBody
,或改用@RestController
。
- 错误: 在
视图名称错误或文件位置不对:
- 错误:
return "user-list"
但模板文件是userList.html
,或文件放在了错误目录(如src/main/resources
而不是templates
)。 - 结果:
Whitelabel Error Page
或TemplateInputException
,提示找不到模板。 - 正确: 确保返回的视图名称与模板文件名(不含扩展名)匹配,且文件位于
src/main/resources/templates
(Thymeleaf/FreeMarker) 或src/main/webapp/WEB-INF
(JSP)。
- 错误:
Model
属性名与模板中引用名不一致:- 错误:
model.addAttribute("usrList", users);
但在 Thymeleaf 模板中用${users}
。 - 结果: 模板中无法访问数据。
- 正确: 保持属性名一致:
model.addAttribute("users", users);
和${users}
。
- 错误:
HttpMessageConverter
缺失或配置问题:- 错误: 依赖中没有包含
jackson-databind
,或@RequestBody
对象的字段没有 getter/setter。 - 结果: 无法反序列化请求体,抛出
HttpMessageNotReadableException
。 - 正确: 确保
spring-boot-starter-web
存在,POJO 有标准的 getter/setter。
- 错误: 依赖中没有包含
返回
null
导致 200 OK:- 错误:
@GetMapping("/{id}") public User getUser(@PathVariable Long id) { return null; }
- 结果: 返回 200 OK 状态码,但响应体为空(或
null
)。对客户端不友好。 - 正确: 使用
ResponseEntity
返回 404 Not Found。@GetMapping("/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findById(id); return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build(); }
- 错误:
四、注意事项
- 选择正确的返回方式:
- API / 前后端分离: 优先使用
@RestController
返回 JSON。 - 服务端渲染 / SEO: 使用
@Controller
返回 HTML 视图。
- API / 前后端分离: 优先使用
ViewResolver
配置: Spring Boot 对 Thymeleaf、FreeMarker 等有自动配置。如需自定义(如修改前缀/后缀),可通过application.properties
或@Bean
配置。# application.properties (Thymeleaf 默认,通常无需修改) spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html
@ResponseBody
的作用: 它可以放在类上(等效于@RestController
)或方法上,强制将返回值写入响应体。- 异常处理的影响: 全局异常处理器 (
@ControllerAdvice
) 返回的ResponseEntity
或视图会覆盖控制器的默认行为。 - Content-Type 头: Spring 会根据
produces
属性、Accept
请求头和HttpMessageConverter
的能力自动设置Content-Type
响应头。确保客户端发送正确的Content-Type
(如application/json
)。 - 字符编码: 确保请求和响应的字符编码正确(通常是 UTF-8)。Spring Boot 通常已配置好。
server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true
五、使用技巧
ResponseEntity
的便捷方法:ResponseEntity.ok(body)
: 200 OKResponseEntity.created(location).body(body)
: 201 CreatedResponseEntity.accepted().build()
: 202 AcceptedResponseEntity.noContent().build()
: 204 No ContentResponseEntity.badRequest().body(error)
: 400 Bad RequestResponseEntity.notFound().build()
: 404 Not FoundResponseEntity.internalServerError().body(error)
: 500 Internal Server Error
使用
@RestControllerAdvice
统一 JSON 错误响应:- 避免在每个控制器中处理异常,返回结构化的错误 JSON。
@JsonView
过滤 JSON 字段:- 根据场景(如列表、详情)返回不同粒度的 JSON。
public class Views { public static class Summary {} public static class Detail extends Summary {} } public class User { @JsonView(Views.Summary.class) private String name; @JsonView(Views.Detail.class) private String email; // ... } @GetMapping(value = "/summary", produces = "application/json") @JsonView(Views.Summary.class) public List<User> getUserSummary() { ... } @GetMapping(value = "/{id}", produces = "application/json") @JsonView(Views.Detail.class) public User getUserDetail(@PathVariable Long id) { ... }
@JsonIgnore
,@JsonProperty
等 Jackson 注解: 精细控制 JSON 序列化/反序列化行为。RedirectView
/RedirectAttributes
:- 用于重定向,并传递一次性属性(通常用于 POST-Redirect-GET 模式)。
@PostMapping("/users") public String createUser(User user, RedirectAttributes redirectAttrs) { userService.save(user); redirectAttrs.addFlashAttribute("message", "User created successfully!"); return "redirect:/users"; // 重定向到 GET /users }
返回
InputStreamResource
/Resource
: 用于文件下载。@GetMapping("/download") public ResponseEntity<Resource> downloadFile() throws IOException { Path path = Paths.get("path/to/file.pdf"); Resource resource = new UrlResource(path.toUri()); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"") .body(resource); }
六、最佳实践
- REST API 优先使用
@RestController
: 代码更简洁,意图更明确。 - 使用
ResponseEntity
处理非 200 响应: 清晰地表达成功、创建、无内容、客户端错误、服务器错误等状态。 - 返回结构化错误信息: 对于 API,不要只返回
500
和空体。使用@ControllerAdvice
返回包含错误码、消息、时间戳等的 JSON 对象。 - DTO (Data Transfer Object): 在 API 层使用专门的 DTO 类进行数据传输,避免直接暴露领域模型(Entity),增强安全性、灵活性和解耦。
- 视图层使用模板引擎: Thymeleaf 是 Spring Boot 的推荐选择,比 JSP 更现代、更安全(支持 HTML5 模板)。
- 分离关注点: 控制器只负责请求/响应处理、参数验证、调用 Service 层。业务逻辑在
@Service
,数据访问在@Repository
。 - 参数验证: 使用
@Valid
+ Bean Validation (@NotNull
,@Size
等) 验证@RequestBody
和@RequestParam
。 - 日志记录: 记录关键操作和错误。
- API 文档: 使用 Swagger/OpenAPI (如
springdoc-openapi-ui
) 生成 API 文档。 - 安全性: 使用 Spring Security 保护端点。
七、性能优化
JSON 序列化/反序列化优化:
- 避免大对象/深层嵌套: 防止序列化耗时和响应体过大。使用 DTO 或投影(Projection)只返回必要字段。
- Jackson 配置: 合理配置
ObjectMapper
(如禁用不需要的特性MapperFeature
,使用@JsonIgnore
减少序列化字段)。 - 考虑二进制格式: 对于内部服务间调用,可考虑使用更高效的序列化格式(如 Protobuf, Avro),但这会牺牲可读性。
视图渲染优化:
- 模板缓存: Thymeleaf、FreeMarker 等默认开启模板缓存(生产环境
spring.thymeleaf.cache=true
),避免每次请求都解析模板。 - 减少模板复杂度: 避免在模板中进行复杂计算或数据库查询。
- 异步处理: 对于需要长时间加载数据的页面,考虑使用异步请求(AJAX)分步加载内容。
- 模板缓存: Thymeleaf、FreeMarker 等默认开启模板缓存(生产环境
GZIP 压缩:
- 启用服务器 GZIP 压缩,显著减小文本响应(HTML, JSON, CSS, JS)的大小。
# application.properties server.compression.enabled=true server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
HTTP 缓存:
- 对于不常变的资源(如静态文件、某些 API 响应),利用 HTTP 缓存头 (
Cache-Control
,ETag
,Last-Modified
) 让浏览器或 CDN 缓存,减少服务器负载和网络延迟。
@GetMapping(value = "/config", produces = "application/json") public ResponseEntity<String> getConfig() { String config = configService.getStaticConfig(); return ResponseEntity.ok() .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic()) // 缓存 1 小时 .body(config); }
- 对于不常变的资源(如静态文件、某些 API 响应),利用 HTTP 缓存头 (
连接池与超时: 确保数据库连接池(HikariCP)、HTTP 客户端(RestTemplate/WebClient)配置了合理的连接数、超时时间,避免线程阻塞。
异步处理:
- 对于耗时操作(如发送邮件、处理大文件),控制器方法可返回
Callable
,CompletableFuture
,DeferredResult
或使用@Async
,释放 Servlet 容器线程,提高并发能力。
@PostMapping("/async-process") public CompletableFuture<ResponseEntity<String>> asyncProcess() { return CompletableFuture.supplyAsync(() -> { // 模拟耗时任务 try { Thread.sleep(5000); } catch (InterruptedException e) { } return ResponseEntity.ok("Processed!"); }); }
- 对于耗时操作(如发送邮件、处理大文件),控制器方法可返回
监控与分析: 使用 Spring Boot Actuator 监控 HTTP 请求的处理时间 (
/metrics/http.server.requests
),识别慢请求。