Web應用伺服器 相關知識梳理(二)Tomcat的系統架構
Tomcat非常模組化,其總體架構本人歸納為以下:
話說 有這樣的一家人 被稱為Tomcat:
(一)Server
父親被稱為Server,其權利可謂至高無上,控制家中每對孩子;作為每對夫妻的生存環境,掌控整個Tomcat生命週期;其標準實現類StandardServer中的一個重要方法addService:
/** * Add a new Service to the set of defined Services. * * @param service The Service to be added */ @Override public void addService(Service service) { service.setServer(this); synchronized (servicesLock) { //Server使用一個數組來管理Service,每新增一個Service就把原來的Service拷貝到一個新的陣列中,再把新的Service放入Service陣列中。 Service results[] = new Service[services.length + 1]; System.arraycopy(services, 0, results, 0, services.length); results[services.length] = service; services = results; if (getState().isAvailable()) { try { service.start(); } catch (LifecycleException e) { // Ignore } } // Report this property change to interested listeners support.firePropertyChange("service", null, service); } }
(二)Service
Service就是連線每對小夫妻婚姻的結婚證,每個結婚證都被父親Server保管,其標準實現類StandardService中的兩個重要方法addConnector及setContainer:
/** * Add a new Connector to the set of defined Connectors, and associate it * with this Service's Container. * * @param connector The Connector to be added */ @Override public void addConnector(Connector connector) { synchronized (connectorsLock) { //類似動態陣列 不是List集合 connector.setService(this); Connector results[] = new Connector[connectors.length + 1]; System.arraycopy(connectors, 0, results, 0, connectors.length); results[connectors.length] = connector; connectors = results; if (getState().isAvailable()) { try { connector.start(); } catch (LifecycleException e) { log.error(sm.getString("standardService.connector.startFailed",connector), e); } } // Report this property change to interested listeners support.firePropertyChange("connector", null, connector); } }
@Override public void setContainer(Engine engine) { Engine oldEngine = this.engine; // 如果已經關聯 則去掉舊關聯關係 if (oldEngine != null) { oldEngine.setService(null); } //如果未關聯 便進行關聯 this.engine = engine; if (this.engine != null) { this.engine.setService(this); } if (getState().isAvailable()) { //若未關聯 則將engine啟動 if (this.engine != null) { try { this.engine.start(); } catch (LifecycleException e) { log.warn(sm.getString("standardService.engine.startFailed"), e); } } // Restart MapperListener to pick up new engine. try { mapperListener.stop(); } catch (LifecycleException e) { log.warn(sm.getString("standardService.mapperListener.stopFailed"), e); } try { mapperListener.start(); } catch (LifecycleException e) { log.warn(sm.getString("standardService.mapperListener.startFailed"), e); } if (oldEngine != null) { try { oldEngine.stop(); } catch (LifecycleException e) { log.warn(sm.getString("standardService.engine.stopFailed"), e); } } } // Report this property change to interested listeners support.firePropertyChange("container", oldEngine, this.engine); }
(三)Connector
Connector作為每對小夫妻中的男人,主要負責外部交流;對瀏覽器發過來的TCP連線請求通過Request及Response物件進行和Container交流。
那Request及Response物件在一次請求中是如何變化的吶???
其中在Connector及Container之間Request、Response類的關係在程式碼上的展示如下:
---------------上下Request及Response兩類關係中均使用到門面設計模式,詳情另見-----------
那麼在一次請求中如何根據這個URL到達正確的Container容器吶???
這種對映工作在Tomcat 5 以前是通過org.apache.tomcat.util.http.Mapper類來完成,這個類儲存了Container中所有子容器的資訊,org.apache.catalina.connector.Request類在進入Container容器之前,Mapper類將會根據此次請求的hostname及contestpath將Host和Context設定到該Request的mappingData屬性中,所以Request在進入Container容器之前已經確定要訪問哪個子容器;在Tomcat 5以後該Mapper類的功能被移到Request類中。
通過將MapperListener類作為一個監聽者加到整個Container容器中每個子容器上,來確保任何一個子容器變化,相應的儲存容器關係的MapperListener的mapper屬性同時被修改,如下MapperListener類中原始碼:
@Override
public void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
Engine engine = service.getContainer();
if (engine == null) {
return;
}
findDefaultHost();
addListeners(engine);
Container[] conHosts = engine.findChildren();
for (Container conHost : conHosts) {
Host host = (Host) conHost;
if (!LifecycleState.NEW.equals(host.getState())) {
// Registering the host will register the context and wrappers
registerHost(host);
}
}
}
/**
* Add this mapper to the container and all child containers
*
* @param container
*/
private void addListeners(Container container) {
container.addContainerListener(this);
container.addLifecycleListener(this);
for (Container child : container.findChildren()) {
addListeners(child);
}
}
private void findDefaultHost() {
Engine engine = service.getContainer();
String defaultHost = engine.getDefaultHost();
boolean found = false;
if (defaultHost != null && defaultHost.length() >0) {
Container[] containers = engine.findChildren();
for (Container container : containers) {
Host host = (Host) container;
if (defaultHost.equalsIgnoreCase(host.getName())) {
found = true;
break;
}
String[] aliases = host.findAliases();
for (String alias : aliases) {
if (defaultHost.equalsIgnoreCase(alias)) {
found = true;
break;
}
}
}
}
if(found) {
mapper.setDefaultHostName(defaultHost);
} else {
log.warn(sm.getString("mapperListener.unknownDefaultHost",defaultHost, service));
}
}
/**
* Register host.
*/
private void registerHost(Host host) {
String[] aliases = host.findAliases();
mapper.addHost(host.getName(), aliases, host);
for (Container container : host.findChildren()) {
if (container.getState().isAvailable()) {
registerContext((Context) container);
}
}
if(log.isDebugEnabled()) {
log.debug(sm.getString("mapperListener.registerHost",host.getName(), domain, service));
}
}
(四)Servlet容器Container
Container作為每對小夫妻中的女人,主要負責內部事務處理;該容器父介面邏輯上由4個父子關係的子容器元件構成:Engine、Host、Context,而Context又包含Wrapper。Container容器作為邏輯核心元件,其實並不存在;其Engine引擎作為top容器,無父容器,如以下server.xml檔案中可以看出:
<Server port="" shutdown="SHUTDOWN">
<Service name="Catalina">
<Executor ...... />
<Connector port="" ... />
<Connector port="" ... />
<Engine name="Catalina" ... >
<Host name="localhost" appBase="webapps" ... >
<Valve className="" .../>
<Context docBase="" path="" ... />
</Host>
</Engine>
</Service>
</Server>
appBase :所指向的目錄應該是準備用於存放這一組Web應用程式的目錄,而不是具體某個 Web 應用程式的目錄本身(即使該虛擬主機只由一個 Web 應用程式組成);預設是webapps。
path:是虛擬目錄,訪問的時候用127.0.0.1:8080/welcome/*.html訪問網頁,welcome前面要加/;
docBase:是網頁實際存放位置的根目錄,對映為path虛擬目錄。
(五)Engine引擎
每個Service只能包含一個Engine引擎,其作用主要是決定從Connector聯結器過來的請求應該交由哪一個Host處理,一個Engine代表一套完整的Servlet引擎;其標準實現類StandardEngine的addChild方法:
/**
* Add a child Container, only if the proposed child is an implementation
* of Host.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
//其新增的子容器型別也只能是Host型別
if (!(child instanceof Host))
throw new IllegalArgumentException(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
(六)Host
一個Host在Engine中代表一臺虛擬主機,虛擬主機的作用就是執行多個應用,並負責安裝和展開這些應用及對這些應用的區分。同時Host並不是必需的,但是要執行war程式,就必須要使用Host;因為在war中必有web.xml,該檔案的解析必需使用Host。其標準實現類StandardHost的addChild方法同理Engine:
/**
* Add a child Container, only if the proposed child is an implementation
* of Context.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
child.addLifecycleListener(new MemoryLeakTrackingListener());
if (!(child instanceof Context))
throw new IllegalArgumentException(sm.getString("standardHost.notContext"));
super.addChild(child);
}
(七)Context
Context容器具備Servlet基本的執行環境,所以真正管理Servlet的容器是Context;一個Context對應一個Web工程,簡單的Tomcat可以沒有Engine及Host,只要有Context容器就能執行Servlet。
新增一個Web應用時:
/**
* @param host The host in which the context will be deployed
* @param contextPath The context mapping to use, "" for root context. 訪問路徑
* @param docBase Base directory for the context, for static files. 存放的物理路徑
* Must exist, relative to the server home
* @param config Custom context configurator helper
* @return the deployed context
* @see #addWebapp(String, String)
*/
public Context addWebapp(Host host, String contextPath, String docBase,LifecycleListener config) {
silence(host, contextPath);
// 新增一個Web應用時便會建立一個StandardContext實現類物件
Context ctx = createContext(host, contextPath);
ctx.setPath(contextPath);
ctx.setDocBase(docBase);
ctx.addLifecycleListener(getDefaultWebXmlListener());
ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
ctx.addLifecycleListener(config);
if (config instanceof ContextConfig) {
// prevent it from looking ( if it finds one - it'll have dup error )
((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
}
if (host == null) {
getHost().addChild(ctx);
} else {
host.addChild(ctx);
}
return ctx;
}
private Context createContext(Host host, String url) {
String contextClass = StandardContext.class.getName();
if (host == null) {
host = this.getHost();
}
if (host instanceof StandardHost) {
contextClass = ((StandardHost) host).getContextClass();
}
try {
return (Context) Class.forName(contextClass).getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException
| ClassNotFoundException e) {
throw new IllegalArgumentException(
"Can't instantiate context-class " + contextClass
+ " for host " + host + " and url "+ url, e);
}
}
之後進行Web應用的初始化,主要就是解析web.xml檔案;將解析後的相應屬性儲存在WebXml物件,並將該物件內容設定到Context容器中。
在server.xml檔案中Context容器配置時當reloadable設為true時,war被修改後Tomcat會自動載入這個應用:
原始碼詳解:
reload方法按照先呼叫stop方法再呼叫start方法,完成一次Context的一次重新載入。
backgroundProcess方法是在StandardContext的父類ContainerBase類中的內部類ContainerBackgroundProcessor中被週期呼叫,這個內部類類執行在一個後臺執行緒中:
/**
* Private thread class to invoke the backgroundProcess method
* of this container and its children after a fixed delay.
*/
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
Throwable t = null;
String unexpectedDeathMessage = sm.getString("containerBase.backgroundProcess.unexpectedThreadDeath",
Thread.currentThread().getName());
try {
while (!threadDone) {
try {
Thread.sleep(backgroundProcessorDelay * 1000L);
} catch (InterruptedException e) {
// Ignore
}
if (!threadDone) {
processChildren(ContainerBase.this);
}
}
} catch (RuntimeException|Error e) {
t = e;
throw e;
} finally {
if (!threadDone) {
log.error(unexpectedDeathMessage, t);
}
}
}
protected void processChildren(Container container) {
ClassLoader originalClassLoader = null;
try {
if (container instanceof Context) {
Loader loader = ((Context) container).getLoader();
// Loader will be null for FailedContext instances
if (loader == null) {
return;
}
// Ensure background processing for Contexts and Wrappers
// is performed under the web app's class loader
originalClassLoader = ((Context) container).bind(false, null);
}
container.backgroundProcess();
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i]);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
if (container instanceof Context) {
((Context) container).unbind(false, originalClassLoader);
}
}
}
}
(八)Wrapper
Wrapper負責管理一個Servlet,包括Servlet的裝載、初始化、執行及銷燬;最底層的容器,無子容器,及無addChild方法;其實現類StandardWrapper中的一個重要方法loadServlet:
/**
* Load and initialize an instance of this servlet, if there is not already 載入並初始化Servlet例項
* at least one initialized instance. This can be used, for example, to
* load servlets that are marked in the deployment descriptor to be loaded
* at server startup time.
* @return the loaded Servlet instance
* @throws ServletException for a Servlet load error
*/
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException(sm.getString("standardWrapper.notClass", getName()));
}
// 獲取InstanceManager例項
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
// 載入:獲取servletClass,然後交給InstanceManager去建立一個基於servletClass.class的Servlet例項
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException(sm.getString("standardWrapper.notServlet", servletClass),e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException(sm.getString("standardWrapper.instantiate", servletClass),e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation = servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement = new MultipartConfigElement(annotation);
}
}
// Special handling for ContainerServlet instances
// Note: The InstanceManager checks if the application is permitted
// to load ContainerServlets
if (servlet instanceof ContainerServlet) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
// 初始化Servlet例項
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
private synchronized void initServlet(Servlet servlet)throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this servlet
try {
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",servlet,classType,args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(servlet);
}
}
} else {
//核心:呼叫Servlet的init方法,並將StandardWrapper物件的門面類物件StandardWrapperFacade作為ServletConfig引數傳入
servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
unavailable(f);
throw f;
} catch (ServletException f) {
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
getServletContext().log("StandardWrapper.Throwable", f );
// If the servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException(sm.getString("standardWrapper.initException",getName()),f);
}
}
在該Servlet載入之前,先確定是否屬於JSP,如果是,則會載入並初始化一個JspServlet例項:
與Servlet主動關聯的類有三個:ServletConfig、ServletRequest、ServletResponse;ServletConfig在Servlet初始化的時候作為引數進入Servlet;其他兩個都是當請求到達時呼叫Servlet傳遞過來的,那ServletContext吶,與ServletConfig有何異同???
Servlet的執行是典型的“握手型的互動式”執行模式,可以理解為:模組之間的資料交換要準備一個交易場景,這個場景一直跟隨這個交易過程直到交易結束;而交易場景的初始化需要一些指定引數,都存放在配置類ServletConfig中;交易場景由ServletContext來表示,同時可以通過ServletConfig來獲取;ServletRequest、ServletResponse通常作為運輸工具來傳遞要互動的資料。
(九)元件的生命線——“Lifecycle”
Tomcat中元件的生命週期都是通過Lifecycle介面控制,元件只要繼承這個介面並實現其中的方法則可以統一控制其子元件,所以最高級別的元件Server就可以一層一層地控制所有元件的生命週期,如StandardServer中:
或者 StandardService中: