Spring 3 引入全面 REST 支援,與 JAX-RS (Jersey 參考實現) 形成兩大主流選擇。本文從引導程序、資源/控制器模型、請求映射、參數綁定、內容協商、異常處理六大維度系統對比,助你根據場景選型。
| 維度 | JAX-RS (Jersey) | Spring MVC |
|---|---|---|
| 核心目標 | Web Services (API) | Web Applications + Web Services |
| 編程模型 | Resource (根/子資源鏈) | Controller (扁平映射) |
| Spring 整合 | SpringServlet 代理 | DispatcherServlet 原生 |
| 部署單元 | WAR (Servlet 容器) | WAR (Servlet 容器) |
兩者均使用 `ContextLoaderListener` 載入業務/持久層 Spring 容器:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:META-INF/spring/module-config.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
@Path("/accounts/")
@Component @Scope("prototype") // 每請求實例
public class AccountResource {
@Context UriInfo uriInfo;
@Autowired AccountRepository repo;
@GET @Path("{username}")
public Account getAccount(@PathParam("username") String username) { ... }
@Path("{username}/portfolios/")
public PortfolioResource getPortfolioResource(@PathParam("username") String username) {
return new PortfolioResource(repo, username, uriInfo); // 子資源手動實例化
}
}

@Controller @RequestMapping("/accounts")
public class AccountController {
@Autowired AccountRepository repo;
@RequestMapping(value="/{username}", method=GET)
public @ResponseBody Account getAccount(@PathVariable String username) { ... }
}
@Controller @RequestMapping("/accounts/{username}/portfolios")
public class PortfolioController { // 獨立控制器,無鏈式關係
@Autowired AccountRepository repo;
...
}
| 特性 | JAX-RS @Path | Spring MVC @RequestMapping |
|---|---|---|
| 路徑變量 | `{username}` | `{username}` |
| 正則約束 | `{username:.*}` | `{username:.*}` |
| 查詢參數存在性 | 不支援 | `params="foo"` / `params="!foo"` |
| 查詢參數值 | 不支援 | `params="foo=123"` |
| Header 存在性 | 不支援 | `headers="Foo-Header"` |
| Header 值匹配 | 不支援 | `headers="content-type=text/*"` |
| 媒體類型消費/產生 | `@Consumes`/`@Produces` | `consumes`/`produces` 屬性 |
Spring MVC 請求匹配條件更豐富,適合複雜 Web 應用路由。
| 來源 | JAX-RS | Spring MVC |
|---|---|---|
| 查詢參數 | `@QueryParam` | `@RequestParam` |
| 表單參數 | `@FormParam` | `@RequestParam` (統一) |
| Cookie | `@CookieParam` | `@CookieValue` |
| Header | `@HeaderParam` | `@RequestHeader` |
| 矩陣參數 | `@MatrixParam` (獨有) | 不支援 |
| 路徑變量 | `@PathParam` | `@PathVariable` |
Spring MVC 亮點:參數名與註解值同名時可省略註解值 (`@RequestParam String q`),需編譯時保留參數名 (`-parameters` / debug symbols)。
| 操作 | JAX-RS | Spring MVC |
|---|---|---|
| 讀取請求體 (JSON/XML) | 參數直接綁定,`MessageBodyReader` (JAXB/Jackson) | `@RequestBody` 參數,`HttpMessageConverter` (JAXB/Jackson/XMLBeans/Castor/JiBX/XStream) |
| 寫入響應體 | 返回值自動序列化,`MessageBodyWriter` | `@ResponseBody` 返回值,同樣 `HttpMessageConverter` |
| 配置簡化 | 需手動註冊 Provider | ` |

| 場景 | JAX-RS | Spring MVC |
|---|---|---|
| 設定狀態碼 | `Response.status(204).build()` 鏈式 API | `@ResponseStatus(HttpStatus.NO_CONTENT)` 方法級 |
| Location Header | `Response.created(uri).build()` + `UriBuilder` | 注入 `HttpServletResponse` 手動 `setHeader` |
| 異常處理 | 拋 `WebApplicationException` / 子類 (如 `NotFoundException`),每方法需 try-catch | `@ExceptionHandler` + `@ResponseStatus` 類級集中處理 |
無絕對優劣,只有場景契合度。理解兩種模型本質差異,才能在架構演進中做正確決策。