RESTful 核心模式:同一 URL 根據 Accept/擴展名/參數返回 JSON/XML/HTML
Spring MVC 的 ContentNegotiatingViewResolver 讓單一 Controller 方法根據客戶端需求自動選擇視圖表現形式。本文詳細配置擴展名、Accept Header、請求參數三種協商策略,整合 Jackson JSON、JAXB XML、JSP 三種視圖,並給出 Spring 4/5 與 Boot 2.x 完整配置代碼。
一、三種內容協商策略
| 策略 | 客戶端請求示例 | 適用場景 |
| 路徑擴展名 | GET /user/1.json /user/1.xml /user/1 | 瀏覽器直訪、API 文檔、緩存友好 |
| Accept Header | Accept: application/json / application/xml | 標準 REST 客戶端、Ajax、移動端 |
| 請求參數 | GET /user/1?format=json | 無法設置 Header 的場景、快速測試 |
二、XML 配置完整版
<!-- 1. 內容協商視圖解析器:優先級最高 (order=1) -->
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1"/>
<!-- 啟用擴展名協商 -->
<property name="favorPathExtension" value="true"/>
<!-- 禁用參數協商 -->
<property name="favorParameter" value="false"/>
<!-- 忽略 Accept Header(按需開啟) -->
<property name="ignoreAcceptHeader" value="true"/>
<!-- 擴展名 → MediaType 映射 -->
<property name="mediaTypes">
<map>
<entry key="json" value="application/json"/>
<entry key="xml" value="application/xml"/>
<entry key="html" value="text/html"/>
</map>
</property>
<!-- 預設視圖:JSON (Jackson) + XML (JAXB) -->
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
<bean class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg>
<bean class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list><value>com.cn.my.entity.Course</value></list>
</property>
</bean>
</constructor-arg>
</bean>
</list>
</property>
</bean>
<!-- 2. JSP 視圖解析器:兜底 (order=2) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="order" value="2"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
</bean>
三、Controller 實現
@Controller
@RequestMapping("/mul")
public class MultiViewController {
@RequestMapping("/simple/{courseId}")
public String simple(@PathVariable String courseId, ModelMap model) {
Course c = new Course();
c.setId(courseId);
c.setName("李四");
c.setContent("測試內容");
model.put("course", c);
return "course"; // 邏輯視圖名
}
}
- 返回
String 邏輯視圖名,配合 ModelMap 傳遞數據。
- 無
@ResponseBody,視圖解析器接管渲染。
Course 需加 @XmlRootElement 支持 JAXB 序列化。
四、測試驗證
| 請求 URL | 響應 Content-Type | 響應體 |
| /mul/simple/123.json | application/json | {"id":"123","name":"李四","content":"測試內容"} |
| /mul/simple/123.xml | application/xml | <course><id>123</id>...</course> |
| /mul/simple/123 | text/html | course.jsp 渲染頁面 |
五、Spring Boot 2.x 簡化配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true)
.favorParameter(false)
.ignoreAcceptHeader(true)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
@Bean
public ViewResolver contentNegotiatingViewResolver(ContentNegotiationManager manager) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(manager);
resolver.setDefaultViews(Arrays.asList(
new MappingJackson2JsonView(),
new MarshallingView(jaxb2Marshaller())
));
resolver.setOrder(1);
return resolver;
}
@Bean
public ViewResolver jspViewResolver() {
InternalResourceViewResolver r = new InternalResourceViewResolver();
r.setPrefix("/WEB-INF/jsp/"); r.setSuffix(".jsp"); r.setOrder(2);
return r;
}
@Bean public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller m = new Jaxb2Marshaller();
m.setClassesToBeBound(Course.class); return m;
}
}
六、核心原理解析
ContentNegotiatingViewResolver 實現 ViewResolver,但不直接渲染,而是委派給 defaultViews 或其他 ViewResolver。
- 解析流程:
a. 根據 favorPathExtension/Parameter/AcceptHeader 確定請求的 MediaType。
b. 遍歷 defaultViews,找到 view.getContentType() 匹配的 View。
c. 若無匹配,委派給 order 更大的 ViewResolver(如 JSP)。
MappingJackson2JsonView 使用 ObjectMapper 序列化 Model 數據。
MarshallingView 使用 Marshaller (JAXB/XStream) 序列化。
七、最佳實踐建議
- 生產環境同時啟用擴展名 + Accept Header,
favorPathExtension=true, ignoreAcceptHeader=false。
- API 版本控制:
/v1/user/1.json 或 Header Accept: application/vnd.myapp.v1+json。
- 錯誤處理:配置
DefaultHandlerExceptionResolver 返回統一 JSON 錯誤格式。
- 大文件下載:用
AbstractView 子類直接寫 HttpServletResponse,避免內存佔用。
- 上一篇文章: Java 入門第一課:JDK 安裝、環境變量、HelloWorld、跨平台原理與版本演進
- 下一篇文章: 計算 Ln(N!) 的高效算法:避免階乘溢出的對數求和法與 Stirling 公式近似
Copyright ©
程式碼之家 All Rights Reserved