一、核心概念

Spring Boot 应用通过控制器(Controller)处理 HTTP 请求,并返回响应。响应的类型和内容取决于应用的设计目标(API 服务 or 传统 Web 应用)和配置。主要分为两大类:

  1. 数据响应 (Data Response):

    • 核心: 返回结构化数据,最常见的是 JSON,用于 RESTful API。
    • 机制: 使用 @RestController@Controller + @ResponseBody。Spring 的 HttpMessageConverter (如 MappingJackson2HttpMessageConverter) 负责将 Java 对象(POJO)序列化为 JSON 字符串,并写入 HTTP 响应体。
    • 场景: 前后端分离架构、微服务、移动应用后端。
  2. 视图响应 (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 端点。

  1. 添加依赖 (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>
    
  2. 创建数据模型 (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
    }
    
  3. 创建 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。
  4. 测试:

    • 启动应用。
    • 访问 http://localhost:8080/api/users,应看到类似 [{"id":1,"name":"John","email":"john@example.com"}] 的 JSON 响应。

步骤 2:返回 HTML 响应(视图解析)

目标: 创建一个返回 HTML 页面的端点,使用 Thymeleaf 模板引擎。

  1. 添加依赖 (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 会自动配置 ThymeleafViewResolverSpringResourceTemplateResolver
  2. 创建 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) 中的变量。
  3. 创建控制器 (返回视图):

    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
  4. (可选)使用 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 将模型和视图封装在一个对象中。
  5. 测试:

    • 启动应用。
    • 访问 http://localhost:8080/users,应看到渲染后的 HTML 页面。

步骤 3:自定义响应(状态码、头信息)

目标: 精确控制 HTTP 响应。

  1. 使用 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 提供了对状态码、头信息和响应体的完全控制。
  2. 返回纯文本或自定义内容类型:

    @GetMapping(value = "/status", produces = "text/plain")
    public String getStatus() {
        return "OK"; // 返回纯文本
    }
    
    @GetMapping(value = "/data", produces = "application/octet-stream")
    public byte[] downloadData() {
        return someService.getDataAsBytes(); // 返回二进制数据
    }
    

三、常见错误

  1. @RestController vs @Controller 混淆:

    • 错误:@Controller 中返回对象(如 User),期望得到 JSON。
    • 结果: Spring 尝试将对象的 toString() 结果(如 User@123abc)当作视图名称查找,找不到模板,报错。
    • 正确: REST API 用 @RestController;返回视图用 @Controller
  2. @ResponseBody 遗漏 (当使用 @Controller 时):

    • 错误:@Controller 中的方法需要返回 JSON,但忘了加 @ResponseBody
    • 结果: 同上,被当作视图名称。
    • 正确: 在方法上加 @ResponseBody,或改用 @RestController
  3. 视图名称错误或文件位置不对:

    • 错误: return "user-list" 但模板文件是 userList.html,或文件放在了错误目录(如 src/main/resources 而不是 templates)。
    • 结果: Whitelabel Error PageTemplateInputException,提示找不到模板。
    • 正确: 确保返回的视图名称与模板文件名(不含扩展名)匹配,且文件位于 src/main/resources/templates (Thymeleaf/FreeMarker) 或 src/main/webapp/WEB-INF (JSP)。
  4. Model 属性名与模板中引用名不一致:

    • 错误: model.addAttribute("usrList", users); 但在 Thymeleaf 模板中用 ${users}
    • 结果: 模板中无法访问数据。
    • 正确: 保持属性名一致:model.addAttribute("users", users);${users}
  5. HttpMessageConverter 缺失或配置问题:

    • 错误: 依赖中没有包含 jackson-databind,或 @RequestBody 对象的字段没有 getter/setter。
    • 结果: 无法反序列化请求体,抛出 HttpMessageNotReadableException
    • 正确: 确保 spring-boot-starter-web 存在,POJO 有标准的 getter/setter。
  6. 返回 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();
      }
      

四、注意事项

  1. 选择正确的返回方式:
    • API / 前后端分离: 优先使用 @RestController 返回 JSON。
    • 服务端渲染 / SEO: 使用 @Controller 返回 HTML 视图。
  2. ViewResolver 配置: Spring Boot 对 Thymeleaf、FreeMarker 等有自动配置。如需自定义(如修改前缀/后缀),可通过 application.properties@Bean 配置。
    # application.properties (Thymeleaf 默认,通常无需修改)
    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html
    
  3. @ResponseBody 的作用: 它可以放在类上(等效于 @RestController)或方法上,强制将返回值写入响应体。
  4. 异常处理的影响: 全局异常处理器 (@ControllerAdvice) 返回的 ResponseEntity 或视图会覆盖控制器的默认行为。
  5. Content-Type 头: Spring 会根据 produces 属性、Accept 请求头和 HttpMessageConverter 的能力自动设置 Content-Type 响应头。确保客户端发送正确的 Content-Type (如 application/json)。
  6. 字符编码: 确保请求和响应的字符编码正确(通常是 UTF-8)。Spring Boot 通常已配置好。
    server.servlet.encoding.charset=UTF-8
    server.servlet.encoding.enabled=true
    server.servlet.encoding.force=true
    

五、使用技巧

  1. ResponseEntity 的便捷方法:

    • ResponseEntity.ok(body): 200 OK
    • ResponseEntity.created(location).body(body): 201 Created
    • ResponseEntity.accepted().build(): 202 Accepted
    • ResponseEntity.noContent().build(): 204 No Content
    • ResponseEntity.badRequest().body(error): 400 Bad Request
    • ResponseEntity.notFound().build(): 404 Not Found
    • ResponseEntity.internalServerError().body(error): 500 Internal Server Error
  2. 使用 @RestControllerAdvice 统一 JSON 错误响应:

    • 避免在每个控制器中处理异常,返回结构化的错误 JSON。
  3. @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) { ... }
    
  4. @JsonIgnore, @JsonProperty 等 Jackson 注解: 精细控制 JSON 序列化/反序列化行为。

  5. 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
    }
    
  6. 返回 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);
    }
    

六、最佳实践

  1. REST API 优先使用 @RestController: 代码更简洁,意图更明确。
  2. 使用 ResponseEntity 处理非 200 响应: 清晰地表达成功、创建、无内容、客户端错误、服务器错误等状态。
  3. 返回结构化错误信息: 对于 API,不要只返回 500 和空体。使用 @ControllerAdvice 返回包含错误码、消息、时间戳等的 JSON 对象。
  4. DTO (Data Transfer Object): 在 API 层使用专门的 DTO 类进行数据传输,避免直接暴露领域模型(Entity),增强安全性、灵活性和解耦。
  5. 视图层使用模板引擎: Thymeleaf 是 Spring Boot 的推荐选择,比 JSP 更现代、更安全(支持 HTML5 模板)。
  6. 分离关注点: 控制器只负责请求/响应处理、参数验证、调用 Service 层。业务逻辑在 @Service,数据访问在 @Repository
  7. 参数验证: 使用 @Valid + Bean Validation (@NotNull, @Size 等) 验证 @RequestBody@RequestParam
  8. 日志记录: 记录关键操作和错误。
  9. API 文档: 使用 Swagger/OpenAPI (如 springdoc-openapi-ui) 生成 API 文档。
  10. 安全性: 使用 Spring Security 保护端点。

七、性能优化

  1. JSON 序列化/反序列化优化:

    • 避免大对象/深层嵌套: 防止序列化耗时和响应体过大。使用 DTO 或投影(Projection)只返回必要字段。
    • Jackson 配置: 合理配置 ObjectMapper(如禁用不需要的特性 MapperFeature,使用 @JsonIgnore 减少序列化字段)。
    • 考虑二进制格式: 对于内部服务间调用,可考虑使用更高效的序列化格式(如 Protobuf, Avro),但这会牺牲可读性。
  2. 视图渲染优化:

    • 模板缓存: Thymeleaf、FreeMarker 等默认开启模板缓存(生产环境 spring.thymeleaf.cache=true),避免每次请求都解析模板。
    • 减少模板复杂度: 避免在模板中进行复杂计算或数据库查询。
    • 异步处理: 对于需要长时间加载数据的页面,考虑使用异步请求(AJAX)分步加载内容。
  3. 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
    
  4. 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);
    }
    
  5. 连接池与超时: 确保数据库连接池(HikariCP)、HTTP 客户端(RestTemplate/WebClient)配置了合理的连接数、超时时间,避免线程阻塞。

  6. 异步处理:

    • 对于耗时操作(如发送邮件、处理大文件),控制器方法可返回 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!");
        });
    }
    
  7. 监控与分析: 使用 Spring Boot Actuator 监控 HTTP 请求的处理时间 (/metrics/http.server.requests),识别慢请求。