JSP 原理深度解析:編譯流程、生成 Servlet 結構與部署路徑
JSP 編譯產物位置
Eclipse 不同版本輸出路徑不同,通用搜尋法:workspace 目錄下搜尋 JSP 檔名。
| 類型 | 典型路徑 |
| .class 檔案 |
WEB-INF/classes 或 .metadata/.plugins/org.eclipse.wst.server.core/tmp*/work/Catalina/localhost/專案名/org/apache/jsp/ |
| .java 檔案 |
work/ 目錄下同層級 |
JSP 編譯生成的 Servlet 原始碼結構
以 NewFile.jsp 為例,生成類別 org.apache.jsp.NewFile_jsp:
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class NewFile_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
private static Map<String, Long> _jspx_dependants;
private volatile ExpressionFactory _el_expressionfactory;
private volatile InstanceManager _jsp_instancemanager;
// 依賴管理
public Map<String, Long> getDependants() { return _jspx_dependants; }
// EL 表達式工廠(雙重檢查鎖單例)
public ExpressionFactory _jsp_getExpressionFactory() {
if (_el_expressionfactory == null) {
synchronized (this) {
if (_el_expressionfactory == null) {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(
getServletConfig().getServletContext()).getExpressionFactory();
}
}
}
return _el_expressionfactory;
}
// 實例管理器
public InstanceManager _jsp_getInstanceManager() {
if (_jsp_instancemanager == null) {
synchronized (this) {
if (_jsp_instancemanager == null) {
_jsp_instancemanager = InstanceManagerFactory.getInstanceManager(getServletConfig());
}
}
}
return _jsp_instancemanager;
}
public void _jspInit() { }
public void _jspDestroy() { }
// 核心服務方法
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
PageContext pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);
JspWriter out = pageContext.getOut();
HttpSession session = pageContext.getSession();
ServletContext application = pageContext.getServletContext();
ServletConfig config = pageContext.getServletConfig();
Object page = this;
try {
response.setContentType("text/html; charset=ISO-8859-1");
// 靜態模板文字 → out.write()
out.write("\r\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"https://www.w3.org/TR/html4/loose.dtd\">\r\n");
out.write("<html>\r\n<head>\r\n");
out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\r\n");
out.write("<title>Insert title here</title>\r\n</head>\r\n<body>\r\n");
// JSP 指令碼片段 → 直接生成 Java 代碼
for (int i = 0; i < 10; i++) {
out.write("\r\n\t<p>hello world!</p>\r\n ");
}
out.write("\r\n</body>\r\n</html>");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)) {
if (out != null && out.getBufferSize() != 0) {
try {
if (response.isCommitted()) out.flush();
else out.clearBuffer();
} catch (IOException ignored) {}
}
if (pageContext != null) pageContext.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(pageContext);
}
}
}
關鍵結構解析
| 元素 | 說明 |
繼承 HttpJspBase |
本質是 Servlet,繼承鏈:HttpJspBase → HttpServlet → GenericServlet → Servlet |
實作 JspSourceDependent |
支援 JSP 依賴檢查,來源檔變更自動重編譯 |
_jspService() |
對應 service(),處理每個請求 |
PageContext |
頁面上下文,封裝 9 大隱含物件 |
JspWriter out |
輸出流,支援緩衝、自動 flush |
靜態文字 → out.write() |
模板內容直接生成字串寫入 |
<% ... %> 指令碼 |
直接內嵌至 _jspService() 方法體 |
| 異常處理 |
SkipPageException 支援 <jsp:forward>、return 提前結束 |
| 資源釋放 |
finally 區塊歸還 PageContext 至工廠 |
JSP 隱含物件對應關係
| JSP 隱含物件 | 生成代碼中的變數 | 類型 |
| request |
request |
HttpServletRequest |
| response |
response |
HttpServletResponse |
| session |
session |
HttpSession |
| application |
application |
ServletContext |
| config |
config |
ServletConfig |
| pageContext |
pageContext |
PageContext |
| out |
out / _jspx_out |
JspWriter |
| page |
page |
Object (this) |
| exception |
僅錯誤頁面可用 |
Throwable |
JSP 生命週期
- 翻譯階段:.jsp → .java → .class(首次請求或預編譯)
- 載入與實例化:容器載入 Servlet 類別,建立實例
- 初始化:
_jspInit()(可透過 <%@ page isThreadSafe="false" %> 控制單執行緒)
- 請求處理:每請求呼叫
_jspService()
- 銷毀:
_jspDestroy()
開發建議
- 避免在 JSP 中寫複雜業務邏輯,改用 MVC 架構(Controller + Service + JSP/View)
- 啟用預編譯(
jsp-precompile),生產環境避免首次請求編譯延遲
- 注意執行緒安全:預設多執行緒共享實例,不要在宣告區
<%! ... %> 定義非執行緒安全欄位
- 使用 EL 表達式(
${...})與 JSTL 標籤庫替代 Java 指令碼片段