响应处理与视图解析
一、返回值类型
返回视图名
java
@Controller
public class ViewController {
// 返回 String → 视图名
@GetMapping("/user/list")
public String list(Model model) {
model.addAttribute("users", userService.findAll());
return "user-list"; // 视图名
}
// 返回 ModelAndView
@GetMapping("/user/{id}")
public ModelAndView detail(@PathVariable Long id) {
ModelAndView mav = new ModelAndView("user-detail");
mav.addObject("user", userService.findById(id));
return mav;
}
}返回 JSON
java
@RestController
public class JsonController {
// 返回对象 → JSON
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
// 返回 Map → JSON
@GetMapping("/user/{id}/info")
public Map<String, Object> getUserInfo(@PathVariable Long id) {
return Map.of("name", "张三", "age", 18);
}
// 返回 List → JSON 数组
@GetMapping("/users")
public List<User> list() {
return userService.findAll();
}
}返回 ResponseEntity
java
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
// 带自定义 Header
@GetMapping("/user/{id}")
public ResponseEntity<User> getUserWithHeader(@PathVariable Long id) {
User user = userService.findById(id);
HttpHeaders headers = new HttpHeaders();
headers.add("X-Custom-Header", "value");
headers.add("ETag", "\"12345\"");
return new ResponseEntity<>(user, headers, HttpStatus.OK);
}二、视图解析器
InternalResourceViewResolver(JSP)
java
@Configuration
public class ViewConfig implements WebMvcConfigurer {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setOrder(1); // 解析器优先级
return resolver;
}
}ThymeleafViewResolver
java
@Configuration
public class ThymeleafConfig {
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML");
return resolver;
}
}多视图解析器
java
@Configuration
public class MultiViewConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// 方式1:默认配置
// registry.jsp("/WEB-INF/views/", ".jsp");
// 方式2:多个视图解析器
registry.enableContentNegotiation(
new MappingJackson2JsonView(), // JSON 视图
new BeanNameViewResolver() // 按 Bean 名称解析
);
}
}三、ModelAndView
创建方式
java
@GetMapping("/user/{id}")
public ModelAndView getUser(@PathVariable Long id) {
User user = userService.findById(id);
// 方式1:构造函数
ModelAndView mav = new ModelAndView("user-detail");
mav.addObject("user", user);
// 方式2:构造+链式
return new ModelAndView("user-detail", "user", user);
// 方式3:Model 构造
ModelAndView mav = new ModelAndView("user-detail",
new HashMap<String, Object>() {{ put("user", user); }});
// 方式4:视图对象
View view = new RedirectView("/users");
return new ModelAndView(view);
}ModelAndView 常用方法
java
ModelAndView mav = new ModelAndView("user-detail");
// 设置模型数据
mav.addObject("user", user); // 单个
mav.addObject("title", "用户详情"); // 单个
mav.addAllObjects(map); // 批量
// 设置视图
mav.setViewName("other-view"); // 修改视图名
mav.setView(view); // 设置视图对象
// 获取数据
mav.getModel(); // 返回 Map
mav.getModelObject(); // 返回 Object
mav.getViewName(); // 返回视图名四、转发与重定向
转发(Forward)
java
@GetMapping("/forward")
public String forward() {
// 转发到其他 Controller
// URL 不变,一次请求
return "forward:/other/endpoint";
}
@GetMapping("/old-url")
public String redirectOld() {
// 转发到视图
return "forward:/WEB-INF/views/old.jsp";
}重定向(Redirect)
java
@GetMapping("/redirect")
public String redirect() {
// 重定向,浏览器发起新请求
// URL 改变
return "redirect:/new-url";
}
@PostMapping("/user/save")
public String save(User user) {
userService.save(user);
// PRG 模式:Post-Redirect-Get
return "redirect:/users/" + user.getId();
}flash 属性
java
// 重定向时传递数据(存储在 session 中,重定向后自动清除)
@PostMapping("/user/save")
public String save(User user, RedirectAttributes attributes) {
userService.save(user);
attributes.addFlashAttribute("message", "保存成功!");
return "redirect:/users";
}
@GetMapping("/users")
public String list(Model model) {
// model 中有 flash 属性
// ${message} 可在 Thymeleaf 中使用
return "user-list";
}
// 添加多个 flash 属性
attributes.addFlashAttribute("success", true);
attributes.addFlashAttribute("userId", user.getId());转发 vs 重定向
| 对比 | 转发 (Forward) | 重定向 (Redirect) |
|---|---|---|
| 请求次数 | 1次 | 2次 |
| URL 变化 | 不变 | 改变 |
| 共享 request | 是 | 否 |
| 共享 session | 是 | 是 |
| 跳转位置 | 服务端 | 浏览器 |
| 适用 | 内部跳转 | 跨控制器跳转 |
五、视图渲染
JSP EL 表达式
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>用户详情</h1>
<p>姓名:${user.name}</p>
<p>年龄:${user.age}</p>
<%-- 循环 --%>
<c:forEach var="item" items="${userList}">
<li>${item.name}</li>
</c:forEach>
<%-- 条件 --%>
<c:if test="${not empty user}">
<p>用户存在</p>
</c:if>
</body>
</html>Thymeleaf 模板
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${title}">默认标题</title>
</head>
<body>
<h1>用户详情</h1>
<p th:text="${user.name}">姓名</p>
<p th:text="${user.age}">年龄</p>
<!-- 循环 -->
<ul>
<li th:each="item : ${userList}" th:text="${item.name}">姓名</li>
</ul>
<!-- 条件 -->
<p th:if="${not empty user}">用户存在</p>
<!-- 内联 JS -->
<script th:inline="javascript">
var user = /*[[${user}]]*/ {};
</script>
</body>
</html>Thymeleaf 常用语法
html
<!-- 文本 -->
<span th:text="${name}">文本</span>
<span th:utext="${htmlContent}">HTML内容</span>
<!-- 循环 -->
<tr th:each="user, stat : ${users}">
<td th:text="${stat.count}">序号</td>
<td th:text="${user.name}">姓名</td>
</tr>
<!-- 条件 -->
<div th:if="${user.role == 'admin'}">管理员</div>
<div th:unless="${user.role == 'admin'}">普通用户</div>
<!-- Switch -->
<div th:switch="${user.role}">
<span th:case="'admin'">管理员</span>
<span th:case="'user'">用户</span>
<span th:case="*">其他</span>
</div>
<!-- URL -->
<a th:href="@{/user/{id}(id=${user.id})}">详情</a>
<a th:href="@{/users(role=${role})}">筛选</a>
<!-- 表单 -->
<form th:action="@{/user/save}" th:object="${user}" method="post">
<input th:field="*{name}" />
</form>
<!-- 局部替换 -->
<div th:insert="~{fragment :: content}">替换内容</div>
<!-- 三元运算符 -->
<span th:text="${user.status ? '启用' : '禁用'}">状态</span>
<!-- 默认值 -->
<span th:text="${user.name} ?: '匿名'">默认值</span>六、JSON 视图
@RestController 返回 JSON
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}配置 Jackson
yaml
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
serialization:
write-dates-as-timestamps: false
pretty-print: true
deserialization:
fail-on-unknown-properties: false自定义 JSON 视图
java
// 方式1:@JsonView
public class User {
public interface BasicView {}
public interface DetailView extends BasicView {}
@JsonView(BasicView.class)
private Long id;
@JsonView(BasicView.class)
private String name;
@JsonView(DetailView.class)
private String email;
}
@RestController
public class UserController {
@GetMapping("/{id}")
@JsonView(User.BasicView.class)
public User getBasic(@PathVariable Long id) { ... }
@GetMapping("/{id}/detail")
@JsonView(User.DetailView.class)
public User getDetail(@PathVariable Long id) { ... }
}返回 Map 而非对象
java
@GetMapping("/user/{id}")
public Map<String, Object> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return Map.of(
"id", user.getId(),
"name", user.getName(),
"timestamp", System.currentTimeMillis()
);
}七、Content Negotiation
客户端 Accept 头
Accept: application/json → 返回 JSON
Accept: text/html → 返回 HTML
Accept: application/xml → 返回 XML路径后缀
yaml
spring:
mvc:
contentnegotiation:
favor-parameter: true # ?format=jsonGET /users?format=json → JSON
GET /users?format=xml → XML配置多种视图
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorParameter(true)
.parameterName("format")
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("html", MediaType.TEXT_HTML);
}
}八、错误处理
@ExceptionHandler
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map<String, Object> handleNotFound(UserNotFoundException e) {
return Map.of(
"code", 404,
"message", e.getMessage()
);
}
@ExceptionHandler(Exception.class)
public Map<String, Object> handleGeneral(Exception e) {
return Map.of(
"code", 500,
"message", "服务器内部错误"
);
}
}@ResponseStatus
java
// 异常类标注
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
// Controller 方法标注
@GetMapping("/user/{id}")
@ResponseStatus(HttpStatus.OK)
public User getUser(@PathVariable Long id) {
// ...
}九、面试高频问题
Q1: Controller 返回值类型有哪些?
String:视图名ModelAndView:视图+模型Object:JSON(@ResponseBody)ResponseEntity:完整响应(状态码+Header+Body)void:通过 HttpServletResponse 直接响应
Q2: forward 和 redirect 区别?
- forward:服务端转发,URL 不变,一次请求
- redirect:浏览器重定向,URL 改变,两次请求
Q3: @RestController 和 @Controller 区别?
@Controller:返回视图@RestController=@Controller+@ResponseBody,所有方法返回 JSON
Q4: 如何返回 XML?
- 添加 JAXB 依赖
- 对象添加
@XmlRootElement注解 - 请求时
Accept: application/xml
Q5: Thymeleaf 如何获取 Session 属性?
html
<span th:text="${session.user.name}">用户</span>十、下一章预告
下一章我们将学习 Spring MVC 拦截器与过滤器:
- HandlerInterceptor 拦截器
- Filter 过滤器
- 登录拦截
- CORS 跨域处理