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

JSP 設計模式核心:MVC 架構在傳統 Java Web 中的落地實踐與分層規範

     編輯:關於JSP

前言:JSP 即視圖,而非控制器

早期 JSP 開發常陷入「Model 1」誤區:單個 JSP 同時承擔請求接收、業務邏輯、資料庫操作、HTML 渲染,導致代碼耦合度極高、難以測試、前後端協作混亂。Sun 官方早於 JSP 0.92 規範即推薦 **Model 2 (MVC)** 架構:Servlet 作為 Controller、JavaBean/EJB 作為 Model、JSP 作為 View。本文系統闡述 MVC 在傳統 JSP 專案中的分層設計、包結構規範、數據流轉與工程化實踐。

MVC 三層職責邊界

層角色技術實現核心職責禁止事項
View (V) 展示邏輯 JSP + JSTL/EL + 自定義標籤 + CSS/JS 數據渲染、表單提交、頁面跳轉、國際化、權限性顯示 包含 JDBC 代碼、業務計算、事務控制、複雜邏輯判斷
Controller (C) 請求調度 Servlet / Struts Action / Spring MVC Controller 參數接收驗證、調用 Service、異常捕獲、選擇視圖、設置響應狀態 直接操作資料庫、包含業務規則、生成 HTML 片段
Model (M) 業務與數據 Service (POJO/EJB) + DAO + Domain Object (VO/PO/DTO) 業務規則執行、事務邊界、數據持久化、外部系統集成、緩存策略 依賴 Servlet API、持有請求/會話狀態、直接輸出響應

推薦包結構與命名規範

com.company.project
├── web                    # Web 層 (Controller + View)
│   ├── controller         # Servlet/Action/Controller
│   │   ├── user
│   │   │   ├── UserListController.java
│   │   │   ├── UserEditController.java
│   │   │   └── UserDeleteController.java
│   │   └── ...
│   ├── taglib             # 自定義標籤
│   └── filter             # 過濾器 (編碼、登入檢查、日誌)
├── service                # 業務邏輯層
│   ├── user
│   │   ├── UserService.java          # 介面
│   │   └── impl
│   │       └── UserServiceImpl.java  # 實現,@Transactional
│   └── ...
├── dao                    # 數據訪問層
│   ├── user
│   │   ├── UserDao.java
│   │   └── impl
│   │       └── UserDaoImpl.java
│   └── ...
├── domain                 # 領域模型 (跨層傳遞)
│   ├── vo                 # View Object (視圖專用)
│   │   └── UserVO.java
│   ├── dto                # Data Transfer Object (服務間傳遞)
│   │   └── UserDTO.java
│   ├── po                 # Persistent Object (映射表)
│   │   └── UserPO.java
│   └── entity             # 核心實體 (含業務行為)
│       └── User.java
├── util                   # 工具類
└── config                 # 配置類

實戰案例:留言板系統分層實現

領域模型

// domain/po/GuestbookPO.java
public class GuestbookPO {
    private Long id;
    private String title;
    private String body;
    private Long authorId;
    private Long targetId;
    private Date createTime;
    private Boolean isRead;
    // Getter/Setter
}

// domain/vo/GuestbookVO.java (視圖專用,含格式化、衍生字段)
public class GuestbookVO {
    private Long id;
    private String title;
    private String summary; // 正文摘要
    private String authorName; // 關聯查詢獲得
    private String timeAgo; // "3分鐘前"
    // Getter/Setter
}

DAO 層:純粹的 CRUD

public interface GuestbookDao {
    GuestbookPO findById(Long id);
    List<GuestbookPO> findPage(String condition, String orderBy, int offset, int limit);
    int count(String condition);
    void insert(GuestbookPO po);
    void update(GuestbookPO po);
    void deleteById(Long id);
}

// 實現使用 JDBC Template / MyBatis / 手寫 JDBC,無任何業務判斷

Service 層:業務核心與事務邊界

public interface GuestbookService {
    GuestbookVO getDetail(Long id);
    PageResult<GuestbookVO> queryPage(GuestbookQuery query);
    void publish(GuestbookDTO dto); // 發布留言
    void markRead(Long id); // 標記已讀
    void delete(Long id, Long operatorId); // 刪除(權限檢查)
}

public class GuestbookServiceImpl implements GuestbookService {
    @Autowired GuestbookDao dao;
    @Autowired UserDao userDao;

    @Transactional
    public void publish(GuestbookDTO dto) {
        // 1. 業務校驗
        if (StringUtils.isBlank(dto.getTitle())) throw new BizException("標題不能為空");
        if (dto.getBody().length() > 2000) throw new BizException("內容過長");
        
        // 2. 構建持久化對象
        GuestbookPO po = new GuestbookPO();
        po.setTitle(dto.getTitle());
        po.setBody(dto.getBody());
        po.setAuthorId(dto.getAuthorId());
        po.setTargetId(dto.getTargetId());
        po.setCreateTime(new Date());
        po.setIsRead(false);
        
        // 3. 持久化
        dao.insert(po);
        
        // 4. 衍生業務:通知目標用戶、積分獎勵等
        notificationService.send(dto.getTargetId(), "新留言:" + dto.getTitle());
        pointService.add(dto.getAuthorId(), 5, "發布留言");
    }

    public PageResult<GuestbookVO> queryPage(GuestbookQuery query) {
        int total = dao.count(query.buildCondition());
        List<GuestbookPO> pos = dao.findPage(query.buildCondition(), query.buildOrder(), 
                                             query.getOffset(), query.getPageSize());
        // PO -> VO 轉換 (可用 MapStruct)
        List<GuestbookVO> vos = pos.stream().map(this::convertToVO).collect(Collectors.toList());
        return PageResult.of(vos, total, query.getPageNum(), query.getPageSize());
    }
    
    private GuestbookVO convertToVO(GuestbookPO po) {
        GuestbookVO vo = new GuestbookVO();
        vo.setId(po.getId());
        vo.setTitle(po.getTitle());
        vo.setSummary(StringUtils.abbreviate(po.getBody(), 50));
        vo.setAuthorName(userDao.findNameById(po.getAuthorId()));
        vo.setTimeAgo(DateUtils.formatAgo(po.getCreateTime()));
        return vo;
    }
}

Controller 層:薄薄的調度者

@WebServlet("/guestbook/*")
public class GuestbookController extends HttpServlet {
    @Autowired GuestbookService service; // 需配合 Spring WebApplicationContext

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String action = req.getPathInfo(); // /list, /detail, /delete
        try {
            switch (action) {
                case "/list": list(req, resp); break;
                case "/detail": detail(req, resp); break;
                case "/delete": delete(req, resp); break;
                default: resp.sendError(404);
            }
        } catch (BizException e) {
            req.setAttribute("error", e.getMessage());
            forward("/error.jsp", req, resp);
        } catch (Exception e) {
            log.error("系統異常", e);
            forward("/500.jsp", req, resp);
        }
    }

    private void list(HttpServletRequest req, HttpServletResponse resp) {
        GuestbookQuery query = new GuestbookQuery();
        query.setPageNum(ParseUtils.parseInt(req.getParameter("page"), 1));
        query.setPageSize(20);
        // 其他查詢條件綁定...
        
        PageResult<GuestbookVO> result = service.queryPage(query);
        req.setAttribute("pageResult", result);
        forward("/guestbook/list.jsp", req, resp);
    }
    
    private void forward(String path, HttpServletRequest req, HttpServletResponse resp) {
        req.getRequestDispatcher(path).forward(req, resp);
    }
}

View 層:純渲染

<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<c:forEach items="${pageResult.records}" var="msg">
    <div class="message">
        <h3>${fn:escapeXml(msg.title)}</h3>
        <p>${fn:escapeXml(msg.summary)}</p>
        <small>作者:${msg.authorName} | ${msg.timeAgo}</small>
    </div>
</c:forEach>

<!-- 分頁導航 -->
<c:url value="/guestbook/list" var="baseUrl">
    <c:param name="page" value="${pageResult.pageNum - 1}"/>
</c:url>
<a href="${baseUrl}">上一頁</a> ...

關鍵設計原則總結

  1. 單一職責:每層只做屬於自己的事,DAO 不含業務,Service 不含 HTTP,JSP 不含 Java 邏輯。
  2. 依賴倒置:Controller 依賴 Service 介面,Service 依賴 Dao 介面,實現類可替換、可 Mock 測試。
  3. 數據傳輸對象隔離:PO/VO/DTO 分離,避免持久化模型污染視圖,防止懶加載異常、過度暴露敏感字段。
  4. 事務在 Service:@Transactional 標註在 Service 方法,DAO 不管理事務,Controller 不管理事務。
  5. 異常分層處理:DAO 拋 SQLException → Service 包裝為 BizException / SysException → Controller 統一映射到錯誤頁或 JSON 錯誤碼。
  6. 會話狀態極小化:僅存用戶身份標識、CSRF Token、閃存消息,業務數據走請求參數或資料庫。

從 Struts 1 到 Spring MVC 的演進

早期 Struts 1 的 Action、ActionForm、ActionForward 即 MVC 的具體實現。Spring MVC 以 DispatcherServlet 為前端控制器,@Controller + @RequestMapping 簡化配置,HandlerAdapter 適配多種處理器,ViewResolver 解耦視圖技術(JSP/Thymeleaf/FreeMarker/JSON),是現代化改造的首選路徑。

總結

JSP 設計模式的核心即 **MVC 分層**。嚴格遵循「Controller 調度、Service 業務、DAO 持久、JSP 渲染」的職責邊界,配合領域模型分離(PO/VO/DTO)、事務邊界下沉、異常統一處理、依賴注入管理,能構建出結構清晰、可測試、可維護、易擴展的傳統 Java Web 應用。即使最終遷移至前後端分離架構,良好的後端分層依然是微服務拆分、API 設計的堅實基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved