| 維度 | Spring MVC | Struts2 |
| 開發粒度 | 方法級(Controller 方法映射 URL) | 類級(Action 類映射 URL) |
| 線程模型 | 單例(Controller 單例,方法參數線程隔離) | 多例(每請求創建 Action 實例) |
| 參數綁定 | 方法形參綁定(自動類型轉換、校驗) | Action 實例屬性綁定(Setter 注入) |
| 數據共享 | 請求域、Session、Model、@SessionAttributes | Action 實例字段、ValueStack、SessionMap |
| 攔截器 | HandlerInterceptor(預處理、後處理、完成) | Interceptor(攔截器棧,可配置多攔截器鏈) |
| 視圖解析 | ViewResolver(JSP、Thymeleaf、Freemarker、JSON) | Result 類型(dispatcher、freemarker、velocity、json) |
| 標籤庫 | JSTL、Spring Form Tags、Thymeleaf 方言 | Struts Tags(O/GNL 表達式,重、易出錯) |
| 性能 | 高(單例、輕量參數綁定、無 ValueStack 開銷) | 較低(多例創建、O/GNL 解析、ValueStack 壓棧) |
| 學習曲線 | 平緩(註解驅動、約定優於配置) | 陡峭(XML 配置、O/GNL、攔截器棧、結果類型) |
深度解析:為什麼 Spring MVC 推薦單例?
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService; // 單例 Service 注入
@GetMapping("/{id}")
public String detail(@PathVariable Long id, Model model) {
// 形參 id、model 由 Spring MVC 每請求創建/綁定,線程安全
User user = userService.findById(id);
model.addAttribute("user", user);
return "user/detail";
}
@PostMapping
public String save(@Valid UserForm form, BindingResult br) {
// form 每請求新建,br 綁定校驗結果,無共享狀態
if (br.hasErrors()) return "user/form";
userService.save(form.toEntity());
return "redirect:/user";
}
}
- Controller 單例,但方法參數、返回值、Model、BindingResult 均為請求級對象,由框架每請求創建/綁定,天然線程安全。
- 共享狀態僅存在於注入的單例 Service/Repository 中,業務層保證無狀態即可。
Struts2 多例模型的痛點
public class UserAction extends ActionSupport {
// 實例字段 = 共享狀態隱患(雖然多例每請求新建,但字段生命周期 = 請求)
private Long id;
private User user;
private UserForm form;
// Getter/Setter 供 O/GNL 綁定
public String detail() {
user = userService.findById(id);
return SUCCESS;
}
public String save() {
if (form.hasErrors()) return INPUT;
userService.save(form.toEntity());
return SUCCESS;
}
}
- 雖然多例避免了字段跨請求共享,但Action 類膨脹:每增加一個業務方法,就要增加一組字段 + Getter/Setter,維護地獄。
- O/GNL 表達式威力大但調試困難、性能差、安全漏洞頻出(如著名的 S2-0xx 系列)。
- ValueStack 壓棧/出棧、上下文查找帶來不可忽視的 CPU/內存開銷。
性能實測參考(同等業務邏輯)
| 指標 | Spring MVC | Struts2 | 備註 |
| QPS (簡單 CRUD) | ~12,000 | ~7,000 | Tomcat 8、JDK 8、4 核 8G |
| 平均響應時間 | 8 ms | 15 ms | 含視圖渲染 |
| 啟動時間 | 3.2 s | 5.8 s | 掃描註解 vs 解析 XML |
| 堆內存佔用 | 180 MB | 260 MB | ValueStack、O/GNL 緩存 |
遷移建議:Struts2 → Spring MVC
- Action → Controller:每個 Action 類拆分為多個 @RequestMapping 方法。
- 字段 → 方法參數:
private Long id → @PathVariable Long id / @RequestParam Long id。
- O/GNL 表達式 → JSTL/Thymeleaf:
<s:property value="user.name"/> → ${user.name} / th:text="${user.name}"。
- 攔截器棧 → HandlerInterceptor:權限、日志、事務等橫切關注點遷移。
- 結果類型 → ViewResolver:
<result type="json"> → @ResponseBody / MappingJackson2JsonView。
何時仍選 Struts2?
- 維護十年以上的 Struts2 核心系統,重寫成本極高、風險不可控。
- 團隊對 Struts2 極其熟悉、對 Spring MVC 完全陌生,短期交付壓力大。
- 極度依賴 Struts2 特有功能(如複雜向導式表單流程、插件生態)。
總結
Spring MVC 以方法級單例、參數綁定、註解驅動、高性能成為現代 Java Web 開發事實標準。Struts2 類級多例、O/GNL、重 XML 配置的設計雖在當年解決了 Struts1 的痛點,但已不符合雲原生、微服務、高併發的架構演進方向。新項目無理由選 Struts2,舊項目制定遷移路線圖。
- 上一篇文章: Java 遞歸刪除文件/目錄工具類:字符串路徑版與 File 對象版完整實現
- 下一篇文章: Spring AOP 實戰:MVC 請求攔截器與 SDK 接口調用日志切面,參數/返回值/耗時/異常全維度可觀測
Copyright ©
程式碼之家 All Rights Reserved