在 Struts2 開發中,Action 類往往需要大量的屬性與 getter/setter,導致代碼冗餘。同時,某些業務方法(如編輯頁面回顯)需要在執行前進行數據預加載。ModelDriven 與 Preparable 兩大接口正是為了解決這兩類問題而設計。
public interface Preparable {
void prepare() throws Exception; // 所有 action 方法執行前調用
}
實現 Preparable 後,Struts2 會根據調用的 action 方法名,嘗試調用對應的 prepareXxx() 方法。優先級:
prepareXxx()(針對具體方法)prepare()(通用預處理)若無對應方法則跳過,不拋異常。
public interface ModelDriven<T> {
T getModel(); // 返回模型對象實例
}
getModel() 獲取模型,將請求參數直接注入模型對象。Struts2 默認攔截器棧順序:paramsPrepareParamsStack 中,modelDriven 攔截器在 prepare 之前。但若使用自定義棧或錯誤配置,可能導致 prepareXxx() 中訪問模型屬性為 null。最佳實踐:在基類 prepare() 中實例化模型,確保 getModel() 始終返回非 null。
// 基礎 CRUD 抽象類
public abstract class CRUDAction<T> extends ActionSupport implements ModelDriven<T>, Preparable {
// 留空通用 prepare,由子類實現具體 prepareXxx
@Override public void prepare() throws Exception { }
// 強制子類實現針對 input 方法的預處理
public abstract void prepareInput() throws Exception;
}
// 具體業務 Action
public class EmployeeAction extends CRUDAction<Employee> {
private Employee employee = new Employee(); // 模型實例
@Override public Employee getModel() { return employee; }
@Override public void prepareInput() throws Exception {
// 編輯頁回顯:根據 id 查詢數據填充 employee
if (employee.getId() != null) {
employee = employeeService.get(employee.getId());
}
}
public String input() { return INPUT; } // 返回編輯頁
}
/employee_input.action 時的執行鏈CRUDAction.prepare()(空實現)EmployeeAction.prepareInput()(查詢回顯數據)EmployeeAction.input()(業務邏輯)EmployeeAction.getModel()(參數注入前獲取模型)關鍵點:prepareInput() 中查詢到的 employee 實例將被後續參數注入覆蓋表單提交的字段,實現「查詢回顯 + 表單覆蓋」的無縫銜接。
場景:編輯用戶,表單僅包含 username、email,不包含 password、createTime。若直接更新,未提交字段會被設置為 null。
解決方案:在 prepareUpdate() 中先查詢完整對象,再由參數攔截器覆蓋表單字段:
public void prepareUpdate() throws Exception {
if (model.getId() != null) {
User existing = userService.get(model.getId());
// 保留不應被修改的字段
model.setPassword(existing.getPassword());
model.setCreateTime(existing.getCreateTime());
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| prepareXxx 中模型屬性為 null | getModel() 返回 null 或模型未初始化 | 在基類 prepare() 或聲明處實例化模型 |
| 雙重參數注入導致覆蓋 | 使用 paramsPrepareParamsStack 會注入兩次 | 預處理中查詢數據,第二次注入自動覆蓋表單字段 |
| ModelDriven 與表單字段名衝突 | 模型屬性名與非模型字段重名 | 使用前綴或分離模型與表單 DTO |