深入淺出 web 容器 - tomcat 原始碼分析
TRANSCRIPT
Web 容器與 Servlet
容器(Container)…裝水的嗎?
別鬧了…容器是個 Java 應用程式,介於 Servlet 與Web 伺服器之間,我管很多事,你不用認識 Web 伺服器,你只要認得我!
Web 容器與 Servlet
管很多事?哪些事啊?
你沒想過 HTTP 那些麻煩的東西是怎麼變成 Java 物件的嗎?你以為 Servlet 中的
Request 與 Response 是怎麼來的?
if(servlets.get(servletName) == null) {
servlet = (Servlet) myClass.newInstance();
servlets.put(servletName, servlet);
servlet.init(new Config(myClass));
}
else {
servlet = servlets.get(servletName);
}
servlet.service(
(ServletRequest) requestFacade,
(ServletResponse) responseFacade);
public void destroy() {
for(Servlet servlet : servlets.values()) {
servlet.destroy();
}
}
至少你要知道init()、service()與destroy()…
RequestFacade requestFacade =
new RequestFacade(request);
ResponseFacade responseFacade =
new ResponseFacade(response);
Web 容器與 Servlet
你管的就是這些?
當然更多!不只ServletRequest、ServletResponse、ServletConfig、Servlet這些…
對了!這個範例改寫自這邊…
http://onjava.com/pub/a/onjava/2003/05/14/j
ava_webserver.html
public void run() {
while (running) {
// Allocate a new worker thread
MasterSlaveWorkerThread workerThread = createWorkerThread();
// Accept the next incoming connection from the server socket
...
Socket socket = acceptSocket();
workerThread.assign(socket);
}
..
}
沒原始碼沒真相...XD
採用Thread Pool...XD
從 HTTP 請求到 service()
接下來快轉一下…來到了Http11Processor…
我應該說過我負責建立Request與Response物件吧…這個類別剖析HTTP
並設定Request、Response
從 HTTP 請求到 service()
在Http11Processor的process()中,呼叫 adapter.service()
adapter.service(request, response);
再來的話這邊的request、response會被org.apache.catalina.connector套件中的Request、Response包裹起來...
WHY?
從 HTTP 請求到 service() package org.apache.catalina.connector;
public class Request implements HttpServletRequest {
…
}
package org.apache.catalina.connector;
public class Response implements HttpServletResponse {
…
}
從 HTTP 請求到 service() 再快轉一下…Request與Response送到容器ContainerBase,最後來到StandardWrapper…
你要注意一下loadServlet()方法…
回憶…
•Servlet第一次被請求時會被載入…
•建立ServletConfig…
•執行init()…
•呼叫service()…
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
if (classLoader != null) {
classClass = classLoader.loadClass(actualClass);
} else {
classClass = Class.forName(actualClass);
}
}
...
// Instantiate and initialize an instance of the servlet class
try {
servlet = (Servlet) classClass.newInstance();
}
...
try {
…
servlet.init(facade);
…
servlet.service(req, res);
}
facade 參考至實作 ServletConfig 的實例
protected StandardWrapperFacade facade =
new StandardWrapperFacade(this);
在service()的前後
• GenericServlet類別
–還實作了ServletConfig介面,將容器呼叫init()方法時所傳入的ServletConfig實例封裝起來
– service()方法直接標示為abstract而沒有
任何的實作
• HTTP相關服務流程定義在HttpServlet的service()方法
在service()的前後
protected void service(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod(); // 取得請求的方法
if (method.equals(METHOD_GET)) { // HTTP GET
// 略...
doGet(req, resp);
// 略 ...
} else if (method.equals(METHOD_HEAD)) { // HTTP HEAD
// 略 ...
doHead(req, resp);
} else if (method.equals(METHOD_POST)) { // HTTP POST
// 略 ...
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) { // HTTP PUT
// 略 ...
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
在service()的前後
這實現了Template Method模式。。。
現在好像談過了幾個模式?Worker
Thread、Interceptor Filter、Template
Method…
在service()的前後 if (connector.getEmptySessionPath()
&& isRequestedSessionIdFromCookie()) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
public Session createSession(String sessionId) {
Session session = createEmptySession()
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(this.maxInactiveInterval);
if (sessionId == null) {
sessionId = generateSessionId();
session.setId(sessionId);
sessionCounter++;
return (session);
}
在service()的前後
你應該看一下org.apache.catalina.
session.StandardSession,了解Session怎麼儲存…好吧!接下來換個口味好了…
Servlet 談好久了…要來談一下 JSP 嗎? …XD
從 JSP 到 Servlet
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<title>SimpleJSP</title>
</head>
<body>
<h1><%= new java.util.Date() %></h1>
</body>
</html>
從 JSP 到 Servlet package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class index_jsp
extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
// 略...
public void _jspInit() {
// 略...
}
public void _jspDestroy() {
}
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws java.io.IOException, ServletException {
// 略...
}
}
從 JSP 到 Servlet
public abstract class HttpJspBase extends HttpServlet
implements HttpJspPage {
// 略...
public final void init(ServletConfig config)
throws ServletException {
super.init(config);
jspInit();
_jspInit();
}
// 略...
public final void destroy() {
jspDestroy();
_jspDestroy();
}
public final void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
_jspService(request, response);
}
// 略...
}
從 JSP 到 Servlet
•在<%!與%>之間宣告的程式碼,都將轉譯為Servlet中的類別成員或方法
• <%與%>之間所包括的內容,將被轉譯為Servlet原始碼_jspService()方法中的內容
• <%=與%>運算式元素中的運算式,會直接轉譯為out物件print()輸出時的指定內容
從 JSP 到 Servlet
• 隱含物件,其實就是_jspService()中的
區域參考名稱…
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
原則上是…因為都是轉成Servlet…只不過角色
職責不同…這是另一個故事了…
這麼說Servlet作的到的,JSP
都作的到 …
自訂標籤處理
• 當JSP網頁中包括Simple Tag自訂標籤 ,在轉譯之後... 1. 建立自訂標籤處理器實例。
2. 呼叫標籤處理器的setJspContext()方法設定PageContext實例。
3. 如果是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的setParent()方法,並傳入外層標籤處理器的實例。
4. 設定標籤處理器屬性(例如這邊是呼叫IfTag的setTest()方法來設定)。
5. 呼叫標籤處理器的setJspBody()方法設定JspFragment實例。
6. 呼叫標籤處理器的doTag()方法。
7. 銷毀標籤處理器實例。
cc.openhome.WhenTag _jspx_th_f_005fwhen_005f0 =
new cc.openhome.WhenTag();
...
_jspx_th_f_005fwhen_005f0.setJspContext(_jspx_page_context);
_jspx_th_f_005fwhen_005f0.setParent(_jspx_parent);
…
_jspx_th_f_005fwhen_005f0.setTest(…);
_jspx_th_f_005fwhen_005f0.setJspBody(new Helper(…));
_jspx_th_f_005fwhen_005f0.doTag();
…
<f:choose>
<f:when test="${user.valid}">
<h1>${user.name}登入成功</h1>
</f:when>
..
</f:choose>
這代表用Simple Tag實作標籤時,你的標籤處理器不能太肥大…
自訂標籤處理
• 當JSP中遇到TagSupport自訂標籤時
1. 嘗試從標籤池(Tag Pool)找到可用的標籤物件,如果找到就直接使用,如果沒找到就建立新的標籤物件。
2. 呼叫標籤處理器的setPageContext()方法設定PageContext實例。
3. 如果是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的setParent()方法,並傳入外層標籤處理器的實例。
4. 設定標籤處理器屬性(例如這邊是呼叫IfTag的setTest()方法來設定)。
5. 呼叫標籤處理器的doStartTag()方法,並依不同的傳回值決定是否執行本體或呼叫doAfterBody()、doEndTag()方法。
6. 將標籤處理器實例置入標籤池中以便再度使用。
org.apache.taglibs.standard.tag.rt.core.WhenTag
_jspx_th_c_005fwhen_005f0 =
(org.apache.taglibs.standard.tag.rt.core.WhenTag)
_005fjspx_005ftagPool_005fc_005fwhen_0026_005ftest.get(
org.apache.taglibs.standard.tag.rt.core.WhenTag.class);
...
_005fjspx_005ftagPool_005fc_005fwhen_0026_005ftest.reuse(
_jspx_th_c_005fwhen_005f0);
這代表用TagSupport實作標籤時,必須注意是否要重置標籤處理器的狀態。。
Pool的工作是由org.apache.jasper.
runtime.TagHandlerPool完成…
自訂標籤處理
自訂標籤處理 public Tag get(Class handlerClass) throws JspException {
Tag handler = null;
synchronized( this ) {
if (current >= 0) {
handler = handlers[current--];
return handler;
}
}
try {
Tag instance = (Tag) handlerClass.newInstance();
…
}
public void reuse(Tag handler) {
synchronized( this ) {
if (current < (handlers.length - 1)) {
handlers[++current] = handler;
return;
}
}
…
}
自訂標籤處理
是啦!不過…Tag File 會被我轉成 Simple
Tag 的實作…XD
我記得還有個 Tag File 的東西…
package org.apache.jsp.tag.web;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class Errors_tag
extends javax.servlet.jsp.tagext.SimpleTagSupport
implements org.apache.jasper.runtime.JspSourceDependent {