JSP 架構與執行流程
JSP 運行需要支援 Servlet/JSP 規格的 Web 容器 (如 Tomcat、Jetty、WildFly)。理解架構與請求流程有助於效能調優、除錯與架構決策。
核心組件
| 組件 | 角色 | 代表實作 |
| Web Server | 處理靜態資源、SSL 終結、負載平衡、反向代理 | Nginx, Apache HTTPD, IIS |
| Servlet Container (JSP Engine) | 管理 Servlet/JSP 生命週期、請求分派、會話、安全 | Tomcat, Jetty, Undertow |
| JSP Translator/Compiler | 將 .jsp 翻譯為 .java 再編譯為 .class | Jasper (Tomcat), JspC |
| Class Loader | 載入 JSP 生成的 Servlet 類別、應用類別、庫 | WebappClassLoader |
| Session Manager | 建立/儲存/復原 HttpSession (記憶體/檔案/資料庫/Redis) | StandardManager, PersistentManager |
JSP 請求處理完整流程 (7 步驟)
- 瀏覽器發送 HTTP 請求:
GET /app/page.jsp HTTP/1.1
- Web Server 接收:靜態資源直接回應;動態請求 (如
*.jsp) 轉發給 Servlet Container (AJP/HTTP)
- Container 映射 Servlet:
JspServlet (內建) 處理 *.jsp 路徑
- 檢查/執行翻譯編譯:
- 對應
.class 存在且 .jsp 未修改 → 直接載入
- 否則:解析 JSP → 生成 Java → 編譯 → 載入類別
- 建立/取得 Servlet 實例:單例模式,首次建立呼叫
jspInit()
- 建立隱含物件並呼叫 _jspService(req, resp):
PageContext、request、response、session、application、config、out、page、exception
- 執行模板文字輸出、Scriptlet、Expression、動作元素
- 回應提交給客戶端:Container 將回應標頭、主體透過 Web Server 送回瀏覽器
流程圖 (文字版)
[Browser]
│ HTTP Request
▼
[Web Server: Nginx/Apache] ──靜態資源──► [Browser]
│ 動態請求 (*.jsp)
▼ (Proxy Pass / AJP)
[Tomcat: Coyote Connector]
│
▼
[Mapper: JspServlet (/*.jsp)]
│
▼
[JSP Compiler: Jasper] ──首次/修改──► [.jsp] → [.java] → [.class]
│ 已編譯/未修改 ──► [Class Loader]
▼
[JSP Servlet Instance] (Singleton)
│
├── jspInit() (首次)
│
▼
[_jspService(req, resp)] ◄─── 每次請求
│
├── PageContext, Request, Response, Session, Application, Out, ...
│
▼
[Template Text + Scriptlet + Expression + Actions] → out.write/print
│
▼
[Response Committed] → Coyote → Web Server → Browser
關鍵效能點
| 環節 | 優化方向 |
| 靜態資源 | Nginx 直接服務,啟用快取、GZIP、HTTP/2 |
| JSP 編譯 | 生產環境預編譯 (jspc),關閉開發模式 (development=false) |
| Session 儲存 | 高併發改用 Redis (Spring Session),避免記憶體/檔案瓶頸 |
| 資料庫連線 | 連線池 (HikariCP),JNDI DataSource |
| 模板渲染 | 複雜頁面改用 Thymeleaf/FreeMarker,或前後端分離 (REST + Vue/React) |
| 類別載入 | 避免熱部署類別洩漏,生產環境完整重啟 |
Tomcat 目錄結構與 JSP 相關
tomcat/
├─ bin/ 啟動/關閉腳本
├─ conf/
│ ├─ server.xml 連接器、引擎、主機、Context 設定
│ ├─ web.xml 全域 Servlet 映射 (含 JspServlet)
│ └─ context.xml 全域 Context 參數
├─ lib/ 共享庫 (servlet-api, jsp-api, jdbc drivers)
├─ webapps/ 應用部署目錄
│ └─ ROOT/ 預設應用
│ ├─ WEB-INF/
│ │ ├─ web.xml 應用部署描述
│ │ ├─ classes/ 應用類別
│ │ ├─ lib/ 應用專用 JAR
│ │ └─ *.tld 標籤庫描述
│ ├─ *.jsp JSP 頁面
│ └─ static/ 靜態資源
├─ work/
│ └─ Catalina/localhost/_/org/apache/jsp/ JSP 編譯產生的 .java/.class
└─ logs/ 存取日誌、催化日誌
部署模式
| 模式 | 說明 | 優點 | 缺點 |
| 獨立 Tomcat | 單一 JVM 運行 Container + App | 簡單、資源共享 | 單點故障、擴展受限 |
| Nginx + Tomcat 叢集 | Nginx 負載平衡分發至多 Tomcat | 高可用、水平擴展 | Session 需外部化 (Redis) |
| Docker/K8s 容器化 | 映像打包 App + Tomcat,K8s 編排 | 彈性擴縮、滾動更新、隔離 | 運維複雜度較高 |
| 前後端分離 | JSP 退役,改 REST API + 前端框架 | 關注點分離、體驗佳、技術棧靈活 | SEO 需 SSR、雙端開發成本 |
常見問題與診斷
- 修改 JSP 不生效:檢查
work 目錄是否殘留舊編譯檔、Tomcat development 參數、瀏覽器/Proxy 快取
- 中文亂碼:
URIEncoding="UTF-8"、pageEncoding、request.setCharacterEncoding、資料庫連線編碼
- 類別衝突:
WEB-INF/lib 版本與 tomcat/lib 衝突 (如 servlet-api, jstl)
- 記憶體洩漏:ThreadLocal 未清理、JDBC 驅動未註銷、靜態集合持有請求物件 → 使用
Matthews / VisualVM 分析
- JSP 編譯錯誤:查看
work/.../org/apache/jsp/ 下生成的 .java 定位行號
現代化演進建議
- 新專案採用 Spring Boot + Thymeleaf/FreeMarker:內嵌容器、自動配置、模板引擎功能更強
- 微服務架構:JSP 單體應用拆分為 REST API 服務 + 前端獨立部署
- Serverless:函數計算 (AWS Lambda, Azure Functions) 運行 Java 函數,無容器管理
- GraalVM Native Image:Java 應用編譯為原生執行檔,啟動極快、記憶體低