首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Struts 框架 Model 組件建構完整指南:ActionForm、Action 與 JavaBean 分層設計

     編輯:Java 相關知識

前言

Struts 框架中 Model 層負責封裝業務邏輯與資料,核心組件包含 ActionForm (表單資料)、Action (控制流程) 與 JavaBean/EJB (業務實體)。本文系統性解析三類 Bean 的職責、作用域、驗證機制與最佳實踐。

Model 三大組件架構

Model Layer
├── ActionForm Beans (表單資料傳輸)
├── Action Beans (請求處理控制)
└── Business Beans (業務邏輯與資料)
    ├── JavaBeans (POJO 領域模型)
    ├── System State Beans (系統狀態如購物車)
    └── EJBs (企業級分散式組件)

一、Bean 作用域與生命週期

作用域生命週期可見範圍Servlet API 對應適用場景
page單一 JSP 請求當前頁面Service 方法區域變數頁面暫時資料
request單一 HTTP 請求轉發鏈路內所有頁面Request 屬性請求處理結果傳遞
session用戶會話期間同用戶所有請求Session 屬性用戶登入狀態、購物車
applicationWeb 應用存活期間所有用戶、所有請求ServletContext 屬性全域配置、快取資料

跨組件共享 Bean 範例

// Servlet 中存入
MyStudy study = new MyStudy(...);
request.setAttribute("study", study);

// JSP 中取用
<jsp:useBean id="study" scope="request" class="com.mycompany.MyApp.MyStudy"/>

二、ActionForm Bean 詳細設計

核心職責

  • 封裝 HTTP 請求參數 (表單欄位)
  • 提供基礎輸入驗證 (格式、必填)
  • 保留使用者輸入值支援錯誤回顯
  • 作為 View 與 Controller 間資料傳遞契約

Struts 自動處理流程

  1. 檢查配置作用域 (request/session) 是否存在同類實例
  2. 不存在則自動建立新實例並存入對應作用域
  3. 依請求參數名稱匹配 Bean 屬性,自動呼叫 Setter 注入值
  4. 若配置 validate="true",呼叫 validate() 方法
  5. 驗證通過則傳遞給 Action.execute(),失敗則轉向 input 頁面
  6. Action 執行完畢可選擇性重置或保留 Form Bean

ActionForm 實作範本

public class UserRegistrationForm extends ActionForm {
    private String username;
    private String email;
    private String password;
    private String confirmPassword;
    private Date birthDate;
    // Getter/Setter 省略

    @Override
    public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
        ActionErrors errors = new ActionErrors();
        
        if (username == null || username.trim().isEmpty()) {
            errors.add("username", new ActionMessage("error.username.required"));
        } else if (username.length() < 3 || username.length() > 20) {
            errors.add("username", new ActionMessage("error.username.length"));
        }
        
        if (email == null || !email.matches("^[\\w.-]+@[\\w.-]+\\.\\w+$")) {
            errors.add("email", new ActionMessage("error.email.format"));
        }
        
        if (password == null || password.length() < 6) {
            errors.add("password", new ActionMessage("error.password.length"));
        }
        
        if (!password.equals(confirmPassword)) {
            errors.add("confirmPassword", new ActionMessage("error.password.mismatch"));
        }
        
        return errors.isEmpty() ? null : errors;
    }

    @Override
    public void reset(ActionMapping mapping, HttpServletRequest request) {
        // 重置預設值 (如下拉選單預設選項)
        this.username = "";
        this.email = "";
    }
}

設計原則

  • 粒度:可細粒度 (每表單一 Bean) 或粗粒度 (多表單共用),視業務複雜度決定
  • 無業務邏輯:僅做資料承載與格式驗證,業務規則委派給 Action 或 Service
  • 屬性命名:遵循 JavaBean 慣例,表單欄位 userName 對應 setUserName()
  • 不可變性:避免在 Form Bean 中持有可變集合物件,防止多請求間狀態汙染

三、Action Bean 詳細設計

核心職責

  • 接收經驗證的 ActionForm
  • 協調業務邏輯執行 (呼叫 Service/Manager/DAO)
  • 處理例外與交易邊界
  • 根據執行結果選擇轉向 (ActionForward)

執行緒安全要求

Action 為單例 (Singleton),多執行緒共用同一實例,嚴禁使用實例變數儲存請求相關狀態。

// ❌ 錯誤:實例變數會被多執行緒共享
private UserService userService; // 這個是安全的 (無狀態服務)
private String currentUsername;  // 危險!請求狀態洩漏

// ✅ 正確:請求資料全部來自參數或 ThreadLocal
public ActionForward execute(ActionMapping mapping, ActionForm form,
                             HttpServletRequest request, HttpServletResponse response) {
    UserRegistrationForm regForm = (UserRegistrationForm) form;
    String username = regForm.getUsername(); // 從 Form 取得
    // ...
}

Action 實作範本

public class UserRegistrationAction extends Action {
    private UserService userService; // 由 Spring/IOC 注入

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form,
                                 HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        
        // 1. 取消鍵檢查
        if (isCancelled(request)) {
            return mapping.findForward("cancel");
        }

        UserRegistrationForm regForm = (UserRegistrationForm) form;
        
        try {
            // 2. 呼叫業務服務 (交易邊界在 Service 層)
            UserVO user = new UserVO();
            user.setUsername(regForm.getUsername());
            user.setEmail(regForm.getEmail());
            user.setPassword(regForm.getPassword());
            
            userService.registerUser(user);
            
            // 3. 成功訊息
            saveMessage(request, "message.registration.success");
            
            // 4. 轉向成功頁
            return mapping.findForward("success");
            
        } catch (BusinessException e) {
            // 5. 業務異常處理
            saveErrors(request, e.getActionErrors());
            return mapping.getInputForward(); // 回輸入頁
        }
    }
}

Action 實用方法

方法用途
getLocale(request)取得用戶 Locale (支援國際化)
getResources()取得 MessageResources (訊息國際化)
isCancelled(request)檢查用戶是否按取消鍵
saveErrors(request, errors)儲存錯誤訊息供 JSP 顯示
saveMessages(request, messages)儲存成功/資訊訊息

四、業務層 Bean 設計 (JavaBean / EJB)

系統狀態 Bean

// Session 作用域:購物車
public class ShoppingCart implements Serializable {
    private Map items = new HashMap<>();
    
    public void addItem(Product product, int quantity) { ... }
    public void removeItem(Product product) { ... }
    public BigDecimal getTotalPrice() { ... }
    // 無業務規則,純資料容器
}

// Application 作用域:產品目錄快取
public class ProductCatalog {
    private Map products = new ConcurrentHashMap<>();
    public Product getProduct(String id) { ... }
    public List search(String keyword) { ... }
}

業務邏輯 Bean (Service Layer)

// 無狀態、可重用、不依賴 Web 環境
public class UserService {
    private UserDAO userDAO;
    private EmailService emailService;
    
    @Transactional
    public void registerUser(UserVO user) throws BusinessException {
        // 1. 業務規則驗證
        if (userDAO.exists(user.getUsername())) {
            throw new BusinessException("error.username.exists");
        }
        
        // 2. 執行核心邏輯
        user.setCreatedDate(new Date());
        user.setStatus(UserStatus.ACTIVE);
        userDAO.insert(user);
        
        // 3. 發送歡迎郵件 (非同步)
        emailService.sendWelcomeEmailAsync(user.getEmail());
    }
}

設計鐵律:業務層零 Web 依賴

  • ❌ import javax.servlet.*、HttpServletRequest、HttpSession
  • ✅ 純 Java 類別,參數為 VO/DTO/Primitive
  • ✅ 可在單元測試、批次程式、Web Service 重用
  • ✅ Action 負責從 Request 提取資料轉為 VO 傳給 Service

五、資料庫連線池整合

啟動 Servlet 初始化連線池

public class DataSourceInitServlet extends HttpServlet {
    private ConnectionPool pool;

    @Override
    public void init() throws ServletException {
        String url = getInitParameter("jdbc.url");
        String user = getInitParameter("jdbc.user");
        String pass = getInitParameter("jdbc.password");
        int maxConn = Integer.parseInt(getInitParameter("jdbc.maxconn"));
        
        pool = new ConnectionPool(url, user, pass, maxConn);
        getServletContext().setAttribute("connectionPool", pool);
    }

    @Override
    public void destroy() {
        if (pool != null) pool.releaseAll();
    }
}

web.xml 配置

<servlet>
    <servlet-name>DataSourceInit</servlet-name>
    <servlet-class>com.myapp.DataSourceInitServlet</servlet-class>
    <init-param><param-name>jdbc.url</param-name><param-value>jdbc:mysql://localhost/mydb</param-value></init-param>
    <init-param><param-name>jdbc.user</param-name><param-value>root</param-value></init-param>
    <init-param><param-name>jdbc.password</param-name><param-value>secret</param-value></init-param>
    <init-param><param-name>jdbc.maxconn</param-name><param-value>20</param-value></init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

Action 中使用

Connection conn = null;
try {
    ConnectionPool pool = (ConnectionPool) getServletContext().getAttribute("connectionPool");
    conn = pool.getConnection();
    userDAO.setConnection(conn);
    userService.registerUser(user);
} finally {
    if (conn != null) pool.returnConnection(conn); // 歸還而非關閉
}

六、struts-config.xml 完整配置範例

<struts-config>
    <form-beans>
        <form-bean name="userRegistrationForm" 
                   type="com.myapp.form.UserRegistrationForm"/>
    </form-beans>

    <action-mappings>
        <action path="/register"
                type="com.myapp.action.UserRegistrationAction"
                name="userRegistrationForm"
                scope="request"
                validate="true"
                input="/register.jsp">
            <forward name="success" path="/welcome.jsp"/>
            <forward name="cancel" path="/index.jsp"/>
        </action>
    </action-mappings>

    <message-resources parameter="ApplicationResources"/>
</struts-config>

總結:Model 分層最佳實踐

層級組件職責作用域關鍵原則
表現層ActionForm資料承載、格式驗證、錯誤回顯request/session無業務邏輯、不可變集合
控制層Action流程協調、例外處理、轉向決定Singleton執行緒安全、無實例狀態、薄控制器
業務層Service/Manager核心業務規則、交易管理Singleton/Prototype零 Web 依賴、可獨立測試、POJO
資料層DAO/Entity持久化操作、資料映射Prototype介面導向、連線池管理、異常轉換
狀態層State Beans會話/應用層級資料快取session/application序列化、同步控制、生命週期管理
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved