逐個用例遷移,業務邏輯 (Service) 保持不變,專注於 Web 層 Action 轉換。
public class BlogService {
private static List<Blog> blogs = new ArrayList<>();
public List<Blog> list() { return blogs; }
public Blog create(Blog b) { blogs.add(b); return b; }
public void update(Blog b) { /* ... */ }
public void delete(int id) { blogs.removeIf(b -> b.getId() == id); }
public Blog findById(int id) { return blogs.stream().filter(b -> b.getId()==id).findFirst().orElse(null); }
}
ViewBlogAction, SaveBlogEntryAction, UpdateBlogEntryAction, RemoveBlogEntryAction, ListBlogsAction。BlogForm (extends ActionForm) 承載表單資料,所有欄位 String 型別。execute() 固定簽名,手動從 HttpServletRequest 或 ActionForm 取值,BeanUtils.copyProperties 轉換型別,最後 request.setAttribute 回傳資料,回傳 ActionForward。@Namespace("/blog")
@Action(value = "blog", results = {
@Result(name = "success", location = "/blog/list.jsp"),
@Result(name = "input", location = "/blog/form.jsp")
})
public class BlogAction extends ActionSupport
implements ModelDriven<Blog>, Preparable, ServletRequestAware {
private BlogService service = new BlogService();
private HttpServletRequest request;
private Integer blogId; // 對應請求參數 id
private Blog blog; // ModelDriven 模型
@Override public void setServletRequest(HttpServletRequest req) { this.request = req; }
@Override public void prepare() {
if (blogId == null || blogId == 0) {
blog = new Blog(); // 新增模式
} else {
blog = service.findById(blogId); // 編輯/刪除模式
}
}
@Override public Blog getModel() { return blog; } // 表單資料自動填入 blog
public String save() {
service.create(blog);
return SUCCESS;
}
public String update() {
service.update(blog);
request.setAttribute("blog", blog); // 若需回顯
return SUCCESS;
}
public String remove() {
service.delete(blogId);
return SUCCESS;
}
@Action(value = "blogList", results = @Result(location = "/blog/list.jsp"))
public String execute() { // list 用例
request.setAttribute("bloglist", service.list());
return SUCCESS;
}
}
@Action(value = "blogList", results = @Result(location = "/blog/list.jsp"))
public class ListBlogsAction extends ActionSupport implements ServletRequestAware {
private BlogService service = new BlogService();
private HttpServletRequest request;
@Override public void setServletRequest(HttpServletRequest req) { this.request = req; }
public String execute() {
request.setAttribute("bloglist", service.list());
return SUCCESS;
}
}
| 機制 | Struts 1 | Struts 2 |
|---|---|---|
| 表單資料綁定 | ActionForm (String 欄位) + BeanUtils.copyProperties | ModelDriven + 類型轉換攔截器 (自動 String→Integer/Date) |
| 請求參數注入 | 手動 request.getParameter() | ParamsInterceptor 自動 setter 注入 (含類型轉換) |
| 方法分派 | DispatchAction / 固定 execute() | 任意 public String xxx() 方法,XML method 屬性指定 |
| 結果導向 | ActionForward (request.setAttribute) | Result (自動從 Action 屬性/模型取值,Pull-MVC) |
| 執行緒安全 | 單例 Action,必須執行緒安全 | 每請求新建 Action 實例,天然安全 |
<action path="/blog/save" type="SaveBlogEntryAction" name="blogForm" scope="request">
<forward name="success" path="/blog/list.do" redirect="true"/>
</action>
<package name="blog" namespace="/blog" extends="struts-default">
<action name="save" class="BlogAction" method="save">
<result type="redirectAction">blogList</result>
</action>
<action name="blogList" class="BlogAction" method="execute">
<result>/blog/list.jsp</result>
</action>
</package>
差異:
- namespace + name + .action 組成 URL。
- redirectAction Result Type 替代 redirect="true"。
- 攔截器堆疊預設 defaultStack,可單獨 Action 覆寫。
Struts 2 標籤庫 (%@ taglib prefix="s" uri="/struts-tags" %@) 與 JSTL (c:forEach, c:out) 可直接替換 Struts 1 html:form, bean:write 等標籤,頁面結構幾乎無需變動。
遷移核心在於:Action 合併、ModelDriven 自動綁定、Pull-MVC 取代 Request 屬性推送、註解/配置簡化。掌握對照表即可系統性完成大型專案遷移。