Tomcat原始碼中Servlet
實例的建構過程如下。
Tomcat原始碼版本為8.5.x
。
簡單說Servlet是透過Java reflection(反射)建構成。
StandardWrapper.loadServlet()
負責載入並初始化Servlet
實例,
呼叫getParent()
取得父容器(parent container)也就是StandardContext
,
然後呼叫StandardContext.getInstanceManager()
取得DefaultInstanceManger
,
然後呼叫DefaultInstanceManger.newInstance(String className)
以Class全名(fully qualified name)透過反射建構Servlet
實例。如果Class名稱找不到會丟出ServletException
。
Servlet
實例產生後接著調用initServlet()
進行初始化,
在initServlet()
中呼叫Servlet.init(ServletConfig config)
開始初始化。
StandardWrapper.loadServlet()
在產生Servlet
的實例之前會先判斷實例instance
是否已存在,若已存在則回傳先前建構的實例,也就是說ServletContext中一個serlvet mapping的url
只會有一個Servlet
,當有多個相同url的請求時所使用的Servet
是同一個。
而Container會幫每個請求產生獨立的thread並交由Servlet.serivce()
處理,這就是為什麼Servet
中不應該有成員變數的原因,因為Servlet
是非執行緒安全(non-thread-safe)且可能被多個執行緒/請求同時執行。
節錄原始碼如下。
StandardWrapper
package org.apache.catalina.core;
...
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
...
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null)) // 若Servlet已存在則返回先前已建構的Servlet
return instance;
...
Servlet servlet; // Servlet變數
try {
...
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass); // 取得Servlet的實例
} catch (ClassCastException e) {
...
throw new ServletException // 若class name找不到對應的類別最終拋出ServletException。
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
...
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
...
initServlet(servlet); // 初始化Servlet
} finally {
...
}
return servlet;
}
...
private synchronized void initServlet(Servlet servlet)
throws ServletException {
...
// Call the initialization method of this servlet
try {
if( Globals.IS_SECURITY_ENABLED) {
...
} else {
servlet.init(facade); // 呼叫Servlet.init()初始化
}
...
} catch (UnavailableException f) {
...
}
}
}
DefaultInstanceManager
package org.apache.catalina.core;
...
public class DefaultInstanceManager implements InstanceManager {
...
@Override
public Object newInstance(String className) throws IllegalAccessException,
InvocationTargetException, NamingException, InstantiationException,
ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException {
Class<?> clazz = loadClassMaybePrivileged(className, classLoader);
return newInstance(clazz.getConstructor().newInstance(), clazz); // 反射建構Servlet的實例
}
...
}
例如下面的DemoServlet
mapping的url為/demo
,則每一個對/demo
的請求所使用的Servlet都是同一個DemoServlet
。例如第一個請求返回的count = 1
,但第二個請求回應的count = 2
。
DemoServlet
package com.abc.demo;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private int count = 0; // 請求共用Servlet的成員變數。
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
count++;
response.getWriter().append("count = " + count);
}
}
沒有留言:
張貼留言