Tomcat原始碼解析:Catalina原始碼解析
1.Catalina
對於Tomcat來說,Catalina是其核心元件,所有基於JSP/Servlet的Java Web應用均需要依託Servlet容器執行並對外提供服務。
4.0版本後,Tomcat完全重新設計了其Servlet容器的架構,新版本的Servlet容器被命名為Catalina。
Catalina包含了前面講到的所有容器元件。它通過鬆耦合的方式繼承Coyote,以完成按照請求協議進行資料讀寫。同時還包括我們的啟動入口、Shell程式等
1)Tomcat分層示意圖
2)Digester
Catalina使用Digester解析XML配置檔案並建立應用伺服器
Digester是一款將XML轉換為java物件的事件驅動型工具,是對SAX的高層次封裝。通過流讀取XML檔案,當識別出XML節點後便執行特定的動作,或者建立java物件,或者執行物件的某個方法。
Digester路徑為:tomcat-util-scan.jar包下 org.apache.tomcat.util.digester包路徑下
3)Server
以下是有關Server的結構
2.tomcat8.0.52 有關於Catalina原始碼解析
1)%TOMCAT_HOME%/bin/startup.bat
作為tomcat的啟動檔案,可以看到,其直接呼叫了catalina.bat檔案
set _EXECJAVA=%_RUNJAVA% set MAINCLASS=org.apache.catalina.startup.Bootstrap set ACTION=start set SECURITY_POLICY_FILE= set DEBUG_OPTS= set JPDA=
可以看到主類為Bootstrap,執行方法為start
2)Bootstrap
反射的絕佳用例
/**
* Start the Catalina daemon.
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
其主要作用就是啟動一個catalinaDaemon,在其init()方法中可以看到
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
...
catalinaDaemon = startupInstance;
3)Catalina.start()
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
...
/**
* Start a new server instance.
*/
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
...
/**
* Create and configure the Digester we will be using for startup.
*/
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>();
ArrayList<String> attrs = new ArrayList<>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
...
在初始化的時候根據server.xml配置來載入Server、Service等物件
3.Digester
Catalina使用Digester來解析XML(server.xml)配置檔案並建立應用伺服器。
Digester是一款用於將XML轉換為java物件的事件驅動型工具。是對SAX的高層次封裝。
現在Digester已經移到了Apache Commons專案,我們可以通過以下來獲取對Digester的使用
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-digester3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
<version>3.2</version>
</dependency>
具體的使用可參考:Apache Commons Digester 一 (基礎內容、核心API) 這篇文章
4.Web應用載入
Web應用載入屬於Server啟動的核心處理過程。
Catalina對web應用的載入主要由StandardHost、HostConfig、StandardContext、ContextConfig、StandardWrapper這五個類來完成。時序圖如下:
5.StandardServer
Catalina在解析server.xml時呼叫Catalina.createStartDigester()方法,具體內容如下:
// 1.關於Server的解析,建立
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
// 2.關於Service的解析建立
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
// 呼叫其Server.addService方法,將該service新增到Server中
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
預設會載入StandardServer類。
並且會建立Service實現類,並新增到Server中。
1)StandardServer.addService()如下所示
public void addService(Service service) {
service.setServer(this);
synchronized (servicesLock) {
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
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// Report this property change to interested listeners
support.firePropertyChange("service", null, service);
}
}
2)StandardService.start()
預設實現在LifecycleBase類中,每個元件都使用到了這個方法,各自實現其startInternal()方法即可
6.StandardService
Catalina在解析server.xml時呼叫Catalina.createStartDigester()方法,有關於Service具體內容如下:
// 1.建立Service
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
// 2.建立Listener 並新增到Service
digester.addObjectCreate("Server/Service/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 3.建立Executor,並新增到Service
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor");
digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor");
// 4.建立Connector並新增到Service
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
1)StandardService.startInternal()
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// 1.啟動engine
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 2.啟動executor
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// 3.啟動connector
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
7.StandardEngine
同理,我們通過分析以上的方式來分析StandardEngine
8.StandardHost
StandardHost載入web應用(即StandardContext)的入口有兩個:
* Catalina在構造Server例項時,如果在server.xml中Host元素存在Context子元素,那麼Context元素就會作為Host容器的子容器新增到Host例項中
* HostConfig自動掃描部署目錄,建立Context例項並啟動。這是大多數Web應用的載入方式
1)StandardHost.startInternal()啟動虛擬主機
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
9.HostConfig
HostConfig類註釋如下:
/**
* Startup event listener for a <b>Host</b> that configures the properties
* of that Host, and the associated defined contexts.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class HostConfig implements LifecycleListener {
主要用於監聽Host的事件,實現了lifecycleEvent方法,具體內容如下:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// 定時掃描Web應用的變更,並進行重新載入
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
// 該事件在Host啟動時觸發,host啟動後掃描web應用包進行部署
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
既然說是Host的監聽器,那麼我們就從原始碼的角度來分析一下HostConfig是如何監控的
1)尋找lifecycleEvent方法的被呼叫場景
發現只有LifecycleBase.fireLifecycleEvent有呼叫
2)尋找LifecycleBase.fireLifecycleEvent方法被呼叫場景
分析:既然是Host的監聽器,那就應該是StandardHost的某個方法呼叫的,我們來看下StandardHost的類結構圖
通過上面兩個圖結合來看,可以確定在以下兩個方法中
而backgroundProcess方法中監聽的是Lifecycle.PERIODIC_EVENT事件,所以我們最後就確定LifecycleBase.setStateInternal方法
LifecycleBase.setState方法有關於呼叫LifecycleBase.setStateInternal()
3)尋找LifecycleBase.setState方法被呼叫場景
我們直接去其子類ContainerBase中看setState被呼叫的場景
發現有兩個方法startInternal、stopInternal呼叫了setState方法,這兩個方法就很熟悉了,startInternal是抽象類LifecycleBase的抽象方法,需要子類實現的
startInternal方法在StandardHost中也有實現,內容如下:
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
// 呼叫ContainerBase.startInternal()
super.startInternal();
}
4)那麼HostConfig的監聽器是什麼時候關聯到StandardHost的呢?
Catalina.createStartDigester()方法是解析server.xml時被呼叫的,裡面有一句
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
// addRuleSet方法
public void addRuleSet(RuleSet ruleSet) {
String oldNamespaceURI = getRuleNamespaceURI();
String newNamespaceURI = ruleSet.getNamespaceURI();
if (log.isDebugEnabled()) {
if (newNamespaceURI == null) {
log.debug("addRuleSet() with no namespace URI");
} else {
log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
}
}
setRuleNamespaceURI(newNamespaceURI);
// 主要是這句,會呼叫HostRuleSet.addRuleInstances方法
ruleSet.addRuleInstances(this);
setRuleNamespaceURI(oldNamespaceURI);
}
// 我們來看下這個HostRuleSet.addRuleInstances方法
public void addRuleInstances(Digester digester) {
// 1.建立StandardHost
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
// 2.建立並新增HostConfig
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
//Cluster configuration start
digester.addObjectCreate(prefix + "Host/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Cluster");
digester.addSetNext(prefix + "Host/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Listener");
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
digester.addObjectCreate(prefix + "Host/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Valve");
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
}
所以:HostConfig是在使用Digester解析server.xml時就關聯到StandardHost的
至此,我們就看到一條完整的呼叫鏈:
StandardHost.startInternal() -> ContainerBase.startInternal() -> LifecycleBase.setState() -> LifecycleBase.setStateInternal() -> LifecycleBase.fireLifecycleEvent() -> HostConfig.lifecycleEvent()
10.StandardContext
StandardContext包含了具體的web應用初始化及啟動工作,該部分工作由元件Context完成
Tomcat提供的ServletContext實現類為ApplicationContext,該類僅為Tomcat伺服器使用;Web應用使用的是其門面類ApplicationContextFacade。
1)StandardContext的啟動過程(被StandardHost呼叫start方法)
實際呼叫為LifecycleBase.start(),真正StandardContext的實現方法為startInternal(),
具體內容有點長,可具體參考原始碼,部落格參考:https://blog.csdn.net/w1992wishes/article/details/79499867
重點過程包括:
* 初始化當前Context使用的WebResourceRoot並啟動,WebResourceRoot維護了web應用所有的資源集合(Class檔案、jar包以及其他資源),主要用於類載入和按照路徑查詢資原始檔
// Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
* 建立web應用類載入器(WebappLoader),並啟動
* 建立會話管理器
* 建立例項管理器(InstanceManager),用於建立物件例項,如Servlet、Filter等
* 例項化應用監聽器,分為事件監聽器(ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener)和生命週期監聽器(HttPSessionListener、ServletContextListener)
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
* 對於loadOnStartup>=0的Wrapper,呼叫wrapper.load(),該方法負責例項化Servlet,並呼叫Servlet.init進行初始化
注意:這個只是針對於啟動就載入的Servlet
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
11.ContextConfig
上述StandardContext的啟動過程,並不包含web.xml中Servlet、請求對映、Filter等相關配置。
這部分的工作是由ContextConfig負責的。
至於ContextConfig是如何關聯StandardContext的,可以參考上面 那麼HostConfig的監聽器是什麼時候關聯到StandardHost的呢?
1)ContextConfig.lifecycleEvent(用於監聽各種事件)
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// 建立Wrapper(重要階段)
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
// 在Context啟動之前觸發,用於更新Context的docBase屬性和解決Web目錄鎖的問題
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
// Context初始化階段
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
2)詳細介紹CONFIGURE_START_EVENT事件觸發的configureStart()方法
ContextConfig.configureStart()方法根據配置建立Wrapper(Servlet)、Filter、ServletContextListener,具體步驟如下:
protected synchronized void configureStart() {
...
// 重要方法
webConfig();
...
}
// webConfig()
/**
* Scan the web.xml files that apply to the web application and merge them
* using the rules defined in the spec. For the global web.xml files,
* where there is duplicate configuration, the most specific level wins. ie
* an application's web.xml takes precedence over the host level or global
* web.xml file.
*/
protected void webConfig() {
...
// Step 8. Convert explicitly mentioned jsps to servlets
if (ok) {
convertJsps(webXml);
}
// Step 9. Apply merged web.xml to Context
if (ok) {
configureContext(webXml);
}
}
// configureContext()解析web.xml並載入Servlet/Filter
private void configureContext(WebXml webxml) {
// 載入Servlet
for (ServletDef servlet : webxml.getServlets().values()) {
// 建立Wrapper,具體在3)中繼續分析
Wrapper wrapper = context.createWrapper();
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map<String,String> params = servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
...
wrapper.setOverridable(servlet.isOverridable());
context.addChild(wrapper);
}
// 載入servletMapping
for (Entry<String, String> entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
...
}
總結:完成2)步驟的時候,ServletWrapper與Context的關係就建立起來了
3)論Wrapper與Servlet的關係
// StandardContext.createWrapper()
public Wrapper createWrapper() {
Wrapper wrapper = null;
if (wrapperClass != null) {
try {
wrapper = (Wrapper) wrapperClass.newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("createWrapper", t);
return (null);
}
} else {
wrapper = new StandardWrapper();
}
...
}
12.StandardWrapper
StandardWrapper具體維護了Servlet例項
StandardWrapper與Servlet的關係,下面來看下StandardWrapper原始碼:
1)StandardWrapper是Servlet的包裝類,下面是一些主要成員變數
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
/**
* The (single) possibly uninitialized instance of this servlet.
*/
protected volatile Servlet instance = null;
/**
* Flag that indicates if this instance has been initialized
*/
protected volatile boolean instanceInitialized = false;
/**
* The load-on-startup order value (negative value means load on
* first call) for this servlet.
*/
protected int loadOnStartup = -1;
/**
* Mappings associated with the wrapper.
*/
protected final ArrayList<String> mappings = new ArrayList<>();
/**
* The initialization parameters for this servlet, keyed by
* parameter name.
*/
protected HashMap<String, String> parameters = new HashMap<>();
/**
* The security role references for this servlet, keyed by role name
* used in the servlet. The corresponding value is the role name of
* the web application itself.
*/
protected HashMap<String, String> references = new HashMap<>();
2)StandardWrapper.load()
如果該servlet配置load-on-startup>=0,則需要呼叫其load方法,完成Servlet的載入(如果沒有配置,則等到使用者首次呼叫Servlet的時候,才會載入)
原始碼如下:
public synchronized void load() throws ServletException {
// 載入Servlet,建立instance例項,並呼叫Servlet.init方法
instance = loadServlet();
if (!instanceInitialized) {
initServlet(instance);
}
// jsp處理
if (isJspServlet) {
StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name=");
oname.append(getName());
oname.append(getJ2EEKeyProperties());
try {
jspMonitorON = new ObjectName(oname.toString());
Registry.getRegistry(null, null)
.registerComponent(instance, jspMonitorON, null);
} catch( Exception ex ) {
log.info("Error registering JSP monitoring with jmx " +
instance);
}
}
}
// loadServlet()
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;
...
Servlet servlet;
try {
...
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
// 1.建立對應的servletClass物件
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
...
}
...
// 2.呼叫servlet.init方法
initServlet(servlet);
// 3.觸發load監聽
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
...
}
return servlet;
}