本教學承接上一篇「SSH 整合」,將表現層從 Struts 2 替換為 Spring MVC,核心變更僅在 web.xml (Filter → DispatcherServlet)、applicationContext.xml (排除 Controller 掃描)、新增 springmvc-servlet.xml (掃描 Controller、視圖解析器)、Action 改為 @Controller + @RequestMapping。底層 Spring IOC、Hibernate、事務、AOP、快取、連線池完全復用。這完美展示了 Spring 生態「核心容器不變、Web 框架可插拔」的設計哲學。
| 項目 | Struts 2 版 | Spring MVC 版 |
|---|---|---|
| 表現層包名 | ssh.action | ssh.controller |
| 核心配置 | struts.xml | springmvc-servlet.xml |
| web.xml 入口 | StrutsPrepareAndExecuteFilter | DispatcherServlet |
| URL 映射 | *.action | *.do (可自訂) |
| Action 註解 | @Controller (Struts 管理) | @Controller + @RequestMapping (Spring MVC 管理) |
| JSON 回應 | Struts json-plugin + result type="json" | @ResponseBody + Gson/Jackson |
<web-app ...> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app> 關鍵:ContextLoaderListener 載入「根容器」 (Service/Dao/事務/資料源);DispatcherServlet 載入「Web 容器」 (Controller/視圖解析器/攔截器),兩者父子關係,Web 容器可引用根容器 Bean。
<context:component-scan base-package="ssh"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> 其餘 C3P0、SessionFactory、事務、AOP 配置 完全同 SSH 版,此處省略。
<beans ...> <context:component-scan base-package="ssh.controller"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 視圖解析器:JSP 模式 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 若需 REST/JSON 純 API,可改用 <mvc:annotation-driven/> 配合 Jackson --> </beans> 注意:include-filter 覆蓋父容器的 exclude-filter,確保 Controller 只被 Web 容器掃描一次,避免重複實例化。
@Controller @RequestMapping("/user") public class UserController { @Resource private UserService userService; @Resource private AopTest aopTest; @RequestMapping(value="/addUser") @ResponseBody public void addUser(HttpServletRequest request, HttpServletResponse response) { /* 參數接收、userService.add、Gson 回應 success/fail */ } @RequestMapping(value="/queryUser") @ResponseBody public void queryAllUser(HttpServletRequest request, HttpServletResponse response) { aopTest.test1(); aopTest.test2(); // 驗證 AOP 切面 /* userService.queryAllUser、Gson 回應 List */ } } | Struts 2 | Spring MVC | 說明 |
|---|---|---|
| struts.xml <action name="addUser" class="..." method="addUser"> | @RequestMapping("/user/addUser") | URL 映射集中在代碼,無需 XML |
| result type="json" + root="json" | @ResponseBody + 直接寫 response | Spring MVC 原生支援,無需插件 |
| ServletActionContext.getRequest() | 方法參數直接注入 HttpServletRequest | 更簡潔、可測試 |
$.ajax({ url: 'user/addUser.do', type: 'post', dataType: 'json', data: { account: ..., name: ..., address: ... }, success: function(data) { /* ... */ } }); $.ajax({ url: 'user/queryUser.do', type: 'post', dataType: 'json', success: function(data) { /* 渲染列表 */ } }); index.jsp。insert SQL,前端彈窗「添加成功」,列表即時新增。before~ / test1 running~ / after~ / before~ / test2 running~ / after~。| 遷移點 | Struts 2 做法 | Spring MVC 做法 | 注意事項 |
|---|---|---|---|
| 入口組件 | Filter | Servlet (DispatcherServlet) | Servlet 需 load-on-startup=1 確保啟動優先級 |
| Bean 掃描隔離 | 無 (Struts 自己管 Action) | 父子容器隔離 (exclude/include Controller) | 否則 Controller 被雙重實例化,事務/AOP 失效 |
| URL 映射 | XML 集中配置 | 註解分散配置 | 建議統一加模組前綴 @RequestMapping("/module") |
| JSON 回應 | Struts json-plugin | @ResponseBody + Jackson/Gson | Spring Boot 直接 @RestController 更簡潔 |
| 檔案上傳 | Struts interceptor | MultipartResolver Bean | 需在 springmvc-servlet.xml 配置 |
| 攔截器 | Struts interceptor stack | HandlerInterceptor + mvc:interceptors | 生命週期 preHandle/postHandle/afterCompletion |
本教學證明了 Spring 核心容器 (IOC/AOP/事務/ORM) 與 Web 框架 (Struts 2 / Spring MVC) 的解耦性。若專案仍在維護傳統 SSH/SHM 架構,建議漸進式遷移:
① 引入 springmvc-servlet.xml 並行運行 ② 新功能用 Spring MVC 開發 ③ 逐步將 Struts Action 重寫為 Controller ④ 移除 Struts 依賴。
若為新專案,直接使用 Spring Boot:@SpringBootApplication + application.yml + @RestController + spring-boot-starter-data-jpa/mybatis,上述所有配置均自動化,專注業務邏輯即可。