我們回到Tomcat啓動過程探究 第一部分的結尾,回到StandardService的初始化方法initInternal()。
StandardService的重點方法有四個:
- engine.init():初始化Servlet引擎,引擎只負責請求的處理不需要考慮請求連接,協議等等。一個Service存在一個對應的Engine。
- executor.init():初始化線程池,該線程池可以在組件中共享。默認實現爲StandardThreadExecutor。
- mapperListener.init():初始化監聽路由。MapperListener未重寫生命週期中的init()方法,將在start()方法中詳細討論。
- connector.init():初始化連接器,connector負責監聽、讀取請求,對請求進行制定協議的解析,匹配正確的處理容器,反饋響應。Server.xml中的默認的兩個Connector如下,關於協議這塊此處不再展開,以後若有機會給予補充:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" /><!HTTP 1.1協議>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /><!AJP 1.3協議>
在StandardServer.init()方法中,可以看到Server和Service是一對多的關係。爲此,乾脆先整理各層級容器直接的關係圖:
接下來,我們關注Engine(StandardEngine)的初始化時會做些什麼。
首先在StandardEngine構造方法裏,我發現了有意思的東西:
/**
* Create a new StandardEngine component with the default basic Valve.
*/
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
注意這一句:
pipeline.setBasic(new StandardEngineValve());
查詢資料後,知道:
Pipeline(管道)採用了責任鏈的方式處理客戶端的請求,每一個Valve(閥)表示責任鏈上的每一個處理器。
StandardPipeline重寫了startInternal()方法,Pieline和Valve也將在start()方法中詳細討論。
至此,從Bootstrap.load()開始的長途跋涉就告一段落了。不過後面是流程更復雜的Bootstrap.start()。
經過了load的查看源碼的經歷,很容易就找到了start的關鍵路徑爲:Bootstrap.start()->Catalina.start()->StandardServer.startInternal()
/**
* Start nested components ({@link Service}s) and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);//使註冊的監聽器響應事件
setState(LifecycleState.STARTING);//設置生命週期狀態,使註冊的監聽器響應事件
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
StandardServer此刻註冊的監聽器列表如下:
遍歷啓動StandardService.startInternal():
/**
* Start nested components ({@link Executor}s, {@link Connector}s and
* {@link Container}s) and implement the requirements of
* {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// Start our defined Connectors second
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);
}
}
}
}
此刻StandardService的監聽器列表爲空。
在Catalina.createStartDigester()中,給StandardEngine通過addChild添加了一個child,即StandardHost。
在容器抽象類ContainerBase中startInternal()方法有這樣一段:
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
它代表了當一個父容器start的時候,子容器會開啓新線程。並執行子容器的start()方法。
接下來我們關注StandardHost的startInternal()方法:
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
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).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
這個方法只設置了一個關閉閥門,並沒有做太多的事情。
但此刻StandardHost的監聽器列表:
我們知道接下來HostConfig會接收到Lifecycle.START_EVENT的消息。
我們看下HostConfig在接收到消息後會做什麼:
/**
* Process the START event for an associated Host.
*
* @param event The lifecycle event that has occurred
*/
@Override
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;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
首先是標識符的設置,這邊只說比較重要的標識符:
- CopyXML:若爲true,複製context.xml到$CATALINA_BASE/conf/<Engine名稱>/<Host名稱>目錄下
- DeployXML:若爲true,Digester解析context.xml並創建Context對象。
之後就是HostConfig.start()方法了:
/**
* Process a "start" event for this Host.
*/
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.warn(sm.getString("hostConfig.jmx.register", oname), e);
}
if (!host.getAppBaseFile().isDirectory()) {
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
if (host.getDeployOnStartup())
deployApps();
}
這裏比較有意思的一段是:
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
將當前監聽器註冊到MBean,由其管理。關於MBean,後面會另做一章介紹,這裏只要知道是註冊上去就可以了。
HostConfig.delpoyApps()方法:
/**
* Deploy applications for any directories or WAR files that are found
* in our "application root" directory.
*/
protected void deployApps() {
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
- deployDescriptors:從Context描述文件部署context。例如:$CATALINA_BASE/conf/Catalina/localhost下創建app.xml文件。
- deployWARs:從War包部署context。例如:把Web工程的打包的War包複製到Host指定的appBase下。
- deployDirectories:從Web路徑部署context。例如:把Web工程的所有資源文件複製到Host指定的appBase下。
這三個方法,其實是在不同的地方做同樣一件事情,以deployDirectories爲例:
- 創建新的線程,通過方法對應的方式,解析生成Context實例StandardContext。
- 給StandardContext設置監聽器ContextConfig。deployDirectory(ContextName cn, File dir):
Class<?> clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
這裏的關係實際上已在Catalina.createStartDigester()中配置。
- 將StandardContext設置爲StandardHost的child,若Host已經啓動,則會直接啓動context:
host.addChild(context);
接下來執行的是StandardContext.startInternal(),這個方法非常的冗長,這裏只選取重要的部分:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
上兩段爲創建和啓動Web應用類加載器WebappClassLoader。
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
CONFIGURE_START_EVENT事件的,通知。順便整理髮送該通知時,StandardContext的監聽列表:
ContextConfig接收到CONFIGURE_START_EVENT後,會執行configureStart()->webConfig()。
webConfig()是真正解析web.xml的方法,是和Web開發關係最緊密,也是我們最常見的配置xml。我們重點看一下這個方法,已經在重要的步驟上添加了註釋:
/**
* 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() {
/*
* Anything and everything can override the global and host defaults.
* This is implemented in two parts
* - Handle as a web fragment that gets added after everything else so
* everything else takes priority
* - Mark Servlets as overridable so SCI configuration can replace
* configuration from the defaults
*/
/*
* The rules for annotation scanning are not as clear-cut as one might
* think. Tomcat implements the following process:
* - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
* which Servlet spec version is declared in web.xml. The EG has
* confirmed this is the expected behaviour.
* - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
* web.xml is marked as metadata-complete, JARs are still processed
* for SCIs.
* - If metadata-complete=true and an absolute ordering is specified,
* JARs excluded from the ordering are also excluded from the SCI
* processing.
* - If an SCI has a @HandlesType annotation then all classes (except
* those in JARs excluded from an absolute ordering) need to be
* scanned to check if they match.
*/
WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
context.getXmlValidation(), context.getXmlBlockExternal());
Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment(webXmlParser));
WebXml webXml = createWebXml();
// Parse context level web.xml
InputSource contextWebXml = getContextWebXmlSource();//解析容器基本的web.xml
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
ServletContext sContext = context.getServletContext();
// Ordering is important here
// Step 1. Identify all the JARs packaged with the application and those
// provided by the container. If any of the application JARs have a
// web-fragment.xml it will be parsed at this point. web-fragment.xml
// files are ignored for container provided JARs.
//掃描/WEB-INF/lib下的所有JAR包,如果包含/META-INF/web-fragment.xml這類片段webXml
//則創建片段webXml
Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);
// Step 2. Order the fragments.
//對片段webXml進行排序,排序結果影響了Filter的執行順序
Set<WebXml> orderedFragments = null;
orderedFragments =
WebXml.orderWebFragments(webXml, fragments, sContext);
// Step 3. Look for ServletContainerInitializer implementations
//查找並創建ServletContainerInitializer的實現類
if (ok) {
processServletContainerInitializers();
}
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
// Steps 4 & 5.
//註解配置解析,將解析結果合併到web.xml
processClasses(webXml, orderedFragments);
}
if (!webXml.isMetadataComplete()) {
// Step 6. Merge web-fragment.xml files into the main web.xml
// file.
//合併片段webXml按照順序到web.xml
if (ok) {
ok = webXml.merge(orderedFragments);
}
// Step 7. Apply global defaults
// Have to merge defaults before JSP conversion since defaults
// provide JSP servlet definition.
//配置JSP Servlet
webXml.merge(defaults);
// Step 8. Convert explicitly mentioned jsps to servlets
if (ok) {
convertJsps(webXml);
}
// Step 9. Apply merged web.xml to Context
//使用合併後的web.xml配置當前StandardContext,包括Servlet、Filter、Listener
//並針對Servlet創建StandardWrapper,添加到StandardContext
if (ok) {
configureContext(webXml);
}
} else {
webXml.merge(defaults);
convertJsps(webXml);
configureContext(webXml);
}
if (context.getLogEffectiveWebXml()) {
log.info("web.xml:\n" + webXml.toXml());
}
// Always need to look for static resources
// Step 10. Look for static resources packaged in JARs
//添加META-INF/resources/下的靜態資源到standardContext
if (ok) {
// Spec does not define an order.
// Use ordered JARs followed by remaining JARs
Set<WebXml> resourceJars = new LinkedHashSet<>();
for (WebXml fragment : orderedFragments) {
resourceJars.add(fragment);
}
for (WebXml fragment : fragments.values()) {
if (!resourceJars.contains(fragment)) {
resourceJars.add(fragment);
}
}
processResourceJARs(resourceJars);
// See also StandardContext.resourcesStart() for
// WEB-INF/classes/META-INF/resources configuration
}
// Step 11. Apply the ServletContainerInitializer config to the
// context
if (ok) {
for (Map.Entry<ServletContainerInitializer,
Set<Class<?>>> entry :
initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(
entry.getKey(), null);
} else {
context.addServletContainerInitializer(
entry.getKey(), entry.getValue());
}
}
}
}
至此,終於真正的完成了web.xml的合併、解析,並應用於StandardContext。
我們回到StandardContext.startInternal(),看看接下來還做了什麼:
// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
啓動StandardWrapper後,StandarWrapper.startInternal()只有一個作用,即由MBean發送Notification廣播。
隨後沿着addChild這條線,會繼續調用StandarWrapper.load()->loadServlet()方法,開始初始化和創建Servlet實例。
至此, Web應用的加載過程就完畢了。