Spring Boot啟動(dòng)過程(六)之內(nèi)嵌Tomcat中StandardHost、StandardContext和StandardWrapper的啟動(dòng)教程詳解
StandardEngine[Tomcat].StandardHost[localhost]的啟動(dòng)與StandardEngine不在同一個(gè)線程中,它的start:
// 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])));
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStartFailed"));
}
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
這個(gè)start流程中,initInternal方法是ContainerBase的代碼,還是那個(gè)初始化startStopExecutor的,線程名例如Thread[localhost-startStop-1,5,main],這次是用來初始化host的子容器的,然后是StandardHost中的startInternal方法,主要是注冊(cè)了一個(gè)errorValue,如果現(xiàn)有的pipeline中沒有errorvalue,則反射創(chuàng)建org.apache.catalina.valves.ErrorReportValve實(shí)例,并加入pipeline中,容器pipeline加入Value時(shí)會(huì)發(fā)布一個(gè)Container.ADD_VALVE_EVENT事件,與engine一樣,之后進(jìn)入ContainerBase的startInternal,但是這次Realm是null不需要啟動(dòng),然后findChildren出StandardEngine[Tomcat]. StandardHost [localhost].StandardContext[],然后同樣新開個(gè)線程new StartChild,start同樣是上面的代碼,需要特別說明的是,這次before_init的事件有監(jiān)聽的了,F(xiàn)ixContextListener,DisablePersistSessionListener,MemoryLeakTrackingListener;FixContextListener監(jiān)聽的處理,會(huì)加入一個(gè)用于不做用戶身份認(rèn)證的安全檢查的Value:
Context context = (Context) event.getLifecycle();
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
context.setConfigured(true);
}
// LoginConfig is required to process @ServletSecurity
// annotations
if (context.getLoginConfig() == null) {
context.setLoginConfig(
new LoginConfig("NONE", null, null, null));
context.getPipeline().addValve(new NonLoginAuthenticator());
}
DisablePersistSessionListener監(jiān)聽只處理start事件,所以這里只判斷了一下發(fā)現(xiàn)不是就出去了,其實(shí)這里可以思考下,有沒有更好的辦法,讓監(jiān)聽不只是廣播方式,能不能用訂閱方式,先不細(xì)想了,接著看代碼,MemoryLeakTrackingListener只監(jiān)聽了after_start事件,這步同樣什么都沒做。
于是來到了StandardContext的initInternal,它的super.initInternal又是一個(gè)startStopExecutor,ContainerBase的super.initInternal就不再說了,發(fā)送j2ee.object.created消息:
Notification notification = new Notification("j2ee.object.created",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
Notification是EventObject的子類,代表由MBean發(fā)出的通知,MBean server發(fā)出的通知會(huì)包含發(fā)出的MBean的引用,如果MBean注冊(cè)了監(jiān)聽,可以通過object name或引用獲取消息發(fā)出者,官方建議使用object name;sendNotification方法:
/**
* Sends a notification.
*
* If an {@code Executor} was specified in the constructor, it will be given one
* task per selected listener to deliver the notification to that listener.
*
* @param notification The notification to send.
*/
public void sendNotification(Notification notification) {
if (notification == null) {
return;
}
boolean enabled;
for (ListenerInfo li : listenerList) {
try {
enabled = li.filter == null ||
li.filter.isNotificationEnabled(notification);
} catch (Exception e) {
if (logger.debugOn()) {
logger.debug("sendNotification", e);
}
continue;
}
if (enabled) {
executor.execute(new SendNotifJob(notification, li));
}
}
}
發(fā)完消息就轉(zhuǎn)變狀態(tài)為初始化完成,因?yàn)楸O(jiān)聽器是注冊(cè)在context容器上的,于是after_init事件又觸發(fā)了那三個(gè)監(jiān)聽器,這一階段監(jiān)聽器什么都沒處理走了下過場(chǎng)而已;before_start同走過場(chǎng);然后StandardContext的startInternal方法,發(fā)布了個(gè)j2ee.state.starting消息object name為Tomcat:j2eeType=WebModule,name=//localhost/,J2EEApplication=none, J2EEServer=none;setConfigured(false)還沒有正確的配置;設(shè)置WebResourceRoot,WebResourceRoot提供整個(gè)應(yīng)用資源處理類的各種方法,內(nèi)嵌用的實(shí)現(xiàn)類是StandardRoot,set的過程中加了寫鎖:
try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
StandardRoot的屬性allResources:
private final List<List<WebResourceSet>> allResources =
new ArrayList<>();
{
allResources.add(preResources);
allResources.add(mainResources);
allResources.add(classResources);
allResources.add(jarResources);
allResources.add(postResources);
}
http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/WebResourceRoot.html有相關(guān)說明,我就不翻譯了。
set之后就是啟動(dòng)resourcesStart,initInternal執(zhí)行的是StandardRoot的initInternal方法,super.initInternal中依然是那兩行代碼,register(cache, getObjectNameKeyProperties() + ",name=Cache")會(huì)發(fā)送MBeanServerNotification. REGISTRATION_NOTIFICATION通知,生成ObjectName這里cacheJmxName是Tomcat:type=WebResourceRoot,host=localhost,context=/,name=Cache;registerURLStreamHandlerFactory里面的代碼是TomcatURLStreamHandlerFactory.register()這行代碼的注釋說這是為了支持war包內(nèi)的jar資源的。之后是循環(huán)上面的allResources,init里面加入的webResourceSet,但是由于全都是空的,所以等于沒執(zhí)行,就不說了,回頭再仔細(xì)看看什么情況下回不為空,還是內(nèi)嵌的就是空的。createMainResourceSet主要是設(shè)置個(gè)主目錄,例如/tmp/tomcat-docbase.3031819619941848514.80,然后是各種資源該放在哪個(gè)子目錄的一些設(shè)置代碼;這次資源有一個(gè)了,所以可以有一個(gè)start了,DirResourceSet的;super.initInternal()的super是AbstractFileResourceSet:
//-------------------------------------------------------- Lifecycle methods
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Is this an exploded web application?
if (getWebAppMount().equals("")) {
// Look for a manifest
File mf = file("META-INF/MANIFEST.MF", true);
if (mf != null && mf.isFile()) {
try (FileInputStream fis = new FileInputStream(mf)) {
setManifest(new Manifest(fis));
} catch (IOException e) {
log.warn(sm.getString("dirResourceSet.manifestFail", mf.getAbsolutePath()), e);
}
}
}
}
super.initInternal主要是對(duì)base目錄進(jìn)行了一些規(guī)范化處理,規(guī)范的方法主要是UnixFileSystem中的canonicalize其中還使用ExpiringCache對(duì)路徑做了緩存,另外還有在normalize方法中對(duì)路徑中類似"\.."的部分做了處理。WebAppMount是Web應(yīng)用發(fā)布資源的位置,必須以‘/'開頭,這里應(yīng)該是通過它來判斷不是war包部署的模式,然后由于manifest沒找到,所以方法返回初始化完成,這個(gè)資源一路狀態(tài)變化就啟動(dòng)完了。
回到StandardRoot,接下來是processWebInfLib方法,代碼很直觀,不解釋了:
private void processWebInfLib() {
WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
for (WebResource possibleJar : possibleJars) {
if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
createWebResourceSet(ResourceSetType.CLASSES_JAR,
"/WEB-INF/classes", possibleJar.getURL(), "/");
}
}
}
接下來也不解釋:
// Need to start the newly found resources
for (WebResourceSet classResource : classResources) {
classResource.start();
}
cache.enforceObjectMaxSizeLimit是計(jì)算緩存限制的,詳細(xì)的可以參考http://tomcat.apache.org/tomcat-8.0-doc/config/resources.html,至此StandardRoot的啟動(dòng)完成就只剩下改狀態(tài)了。
回到StandardContext,因?yàn)閏lassloader已經(jīng)有了不需要new了;接著創(chuàng)建Rfc6265CookieProcessor類型的cookieProcessor實(shí)例,關(guān)于Rfc6265標(biāo)準(zhǔn)參考http://www.rfc-editor.org/rfc/rfc6265.txt;character set mapper因?yàn)橐呀?jīng)初始化好了只判斷了下;工作目錄處理,先根據(jù)host和engine名生成路徑如:work/Tomcat/localhost/ROOT,結(jié)合前面的base創(chuàng)建目錄例如/tmp/tomcat.3726907762383543267.80/work/Tomcat/localhost/ROOT,然后初始化StandardContext中的ApplicationContext類型可繼承的全局變量context構(gòu)造用參數(shù)是this(context = new ApplicationContext(this)),返回new ApplicationContextFacade(this);將上面的全路徑設(shè)置給ServletContext.TEMPDIR屬性,并將這個(gè)屬性設(shè)置為只讀:
/**
* Set an attribute as read only.
*/
void setAttributeReadOnly(String name) {
if (attributes.containsKey(name))
readOnlyAttributes.put(name, name);
}
之后是對(duì)擴(kuò)展進(jìn)行驗(yàn)證,這里說一下,StandardContext中不管是這里的獲取資源還是之后的讀取classloader都是加了讀鎖的:
// Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error(sm.getString("standardContext.extensionValidationError"), ioe);
dependencyCheck = false;
}
catalina.useNaming用于是否開啟命名服務(wù)支持,開啟了就會(huì)注冊(cè)NamingContextListener監(jiān)聽器:
if (!dependencyCheck) {
// do not make application available if depency check fails
ok = false;
}
// Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
}
if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
}
ClassLoader oldCCL = bindThread()里有個(gè)ThreadBindingListener,不過因?yàn)閣ebApplicationClassLoader是null,所以等于沒執(zhí)行,返回的是null,里面的邏輯還不少,命名服務(wù)也沒開ContextBindings.bindThread于是也沒執(zhí)行。
old的沒有,但是loader還是有的,到了loader的start了,主要要說的是WebappLoader的startInternal方法,classloader創(chuàng)建:
classLoader = createClassLoader(); classLoader.setResources(context.getResources()); classLoader.setDelegate(this.delegate);
buildClassPath的主要功能是遍歷各個(gè)層次的classloader并將其中classpath的jar拼成一個(gè)字符串,例如
:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar...,是以':'作為分隔是因?yàn)槲业拈_發(fā)環(huán)境是linux,在windows中應(yīng)該是';':
while (loader != null) {
if (!buildClassPath(classpath, loader)) {
break;
}
loader = loader.getParent();
}
if (delegate) {
// Delegation was enabled, go back and add the webapp paths
loader = getClassLoader();
if (loader != null) {
buildClassPath(classpath, loader);
}
}
delegate之前提過了,是會(huì)向基loader類委托的;setClassPath的最后一句:servletContext.setAttribute(Globals.CLASS_PATH_ATTR, this.classpath)。
setPermissions方法,由于我這第一個(gè)判斷就返回了,而且看上去代碼也很直觀,我就不說了:
private void setPermissions() {
if (!Globals.IS_SECURITY_ENABLED)
return;
if (context == null)
return;
// Tell the class loader the root of the context
ServletContext servletContext = context.getServletContext();
// Assigning permissions for the work directory
File workDir =
(File) servletContext.getAttribute(ServletContext.TEMPDIR);
if (workDir != null) {
try {
String workDirPath = workDir.getCanonicalPath();
classLoader.addPermission
(new FilePermission(workDirPath, "read,write"));
classLoader.addPermission
(new FilePermission(workDirPath + File.separator + "-",
"read,write,delete"));
} catch (IOException e) {
// Ignore
}
}
for (URL url : context.getResources().getBaseUrls()) {
classLoader.addPermission(url);
}
}
((Lifecycle) classLoader).start(),這個(gè)classloader是TomcatEmbeddedWebappClassLoader走的是WebappClassLoaderBase中的start方法,這里因?yàn)槭莾?nèi)嵌的版本(我沒確認(rèn),猜測(cè))所以也并沒有加載到東西,所以也不細(xì)說了:
public void start() throws LifecycleException {
state = LifecycleState.STARTING_PREP;
WebResource classes = resources.getResource("/WEB-INF/classes");
if (classes.isDirectory() && classes.canRead()) {
localRepositories.add(classes.getURL());
}
WebResource[] jars = resources.listResources("/WEB-INF/lib");
for (WebResource jar : jars) {
if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
localRepositories.add(jar.getURL());
jarModificationTimes.put(
jar.getName(), Long.valueOf(jar.getLastModified()));
}
}
state = LifecycleState.STARTED;
}
然后生成ObjectName例如:Tomcat:context=/,host=localhost,type=TomcatEmbeddedWebappClassLoader,然后注冊(cè)MBean:getMBeanServer().registerMBean( mbean, oname);WebappLoader的start就沒什么了,started之后就是設(shè)置了幾個(gè)屬性:
// since the loader just started, the webapp classloader is now
// created.
setClassLoaderProperty("clearReferencesRmiTargets",
getClearReferencesRmiTargets());
setClassLoaderProperty("clearReferencesStopThreads",
getClearReferencesStopThreads());
setClassLoaderProperty("clearReferencesStopTimerThreads",
getClearReferencesStopTimerThreads());
setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
getClearReferencesHttpClientKeepAliveThread());
這里的unbindThread因?yàn)榍懊娴腷ind幾乎沒做什么,所以什么也沒做;接著的bindThread主要講線程與classloader做了綁定: Thread.currentThread().setContextClassLoader (webApplicationClassLoader),至于threadBindingListener.bind()由于threadBindingListener用了個(gè)空實(shí)現(xiàn),所以這里什么也沒做。
接下來用讀鎖取到Realm并start它;接下來發(fā)布configure_start事件,F(xiàn)ixContextListener中執(zhí)行了context.setConfigured(true)。
終于到了StandardWrapper(StandardEngine[Tomcat].StandardHost[localhost].StandardContext[].StandardWrapper[default])的start了,initInternal直接就是ContainerBase的初始化startStopExecutor,startInternal方法是發(fā)了個(gè)j2ee.state.starting的消息,ObjectName是Tomcat:j2eeType=Servlet, WebModule=//localhost/, name=default, J2EEApplication=none, J2EEServer=none,然后又到ContainerBase的startInternal,然而由于它沒有子容器了,所以這里并沒有StartChild的任務(wù)產(chǎn)生;于是開始執(zhí)行它的Value,先start它的pipeline,startInternal方法依然是StandardPipeline的,按順序start,由于到這的時(shí)候一個(gè)都沒有,所以執(zhí)行的是basic的,StandardWrapperValve的initInternal中只有一句注釋:Don't register this Valve in JMX;startInternal的最后是threadStart,但由于backgroundProcessorDelay是-1所以并沒有啟動(dòng)背景線程;setAvailable(0L)設(shè)置可用,它的說明 The date and time at which this servlet will become available (in milliseconds since the epoch), or zero if the servlet is available;然后發(fā)送一個(gè)消息j2ee.state.running,ObjectName是Tomcat:j2eeType=Servlet,WebModule=//localhost/,name=default,J2EEApplication=none,J2EEServer=none;
StandardWrapper就啟動(dòng)完了,回到StandardContext,start它的pipeline;與StandardWrapper的pipeline不同,它之前被注冊(cè)過NonLoginAuthenticator,它的startInternal方法定義在AuthenticatorBase,方法中設(shè)置了jaspicAppContextID(例如:Tomcat/localhost ),然后獲取上級(jí)容器也就是host的pipeline中的所有Value,并找到其中SingleSignOn類型的Value,明顯是用于單點(diǎn)登錄的,我這里沒有,于是又去找了上一級(jí)容器engine當(dāng)然還是沒有,于是就往下走了;實(shí)例化了一個(gè)StandardSessionIdGenerator,設(shè)置安全隨機(jī)數(shù)生成算法我這里是SHA1PRNG,生成器類名為null,生成器provider也是null,然后就是下一個(gè)Value對(duì)象StandardContextValve的start,只不過它的start是標(biāo)準(zhǔn)的什么額外事都沒干,于是回到了StandardContext中。下面一段主要是執(zhí)行了TomcatEmbeddedContext中的setManager方法:
@Override
public void setManager(Manager manager) {
if (manager instanceof ManagerBase) {
((ManagerBase) manager).setSessionIdGenerator(new LazySessionIdGenerator());
}
super.setManager(manager);
}
這里判斷是true,LazySessionIdGenerator整個(gè)的代碼:
class LazySessionIdGenerator extends StandardSessionIdGenerator {
@Override
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
}
}
TomcatEmbeddedContext的super.setManager(manager)的super是StandardContext,在寫鎖中執(zhí)行的,spring中多數(shù)的set都是交換的方式,先set個(gè)old保存下來,然后判斷新值和old是否相同,不相同用新的并將新值綁定容器,相同直接返回;getServletContext().setAttribute(Globals.RESOURCES_ATTR, getResources())沒什么好解釋的;setNamingResources(new NamingResourcesImpl());然后init這個(gè)namingResources,NamingResourcesImpl的initInternal,在設(shè)置當(dāng)前已知命名資源前設(shè)置resourceRequireExplicitRegistration用于避免時(shí)序問題,重復(fù)注冊(cè)是正常的,后面一段我不想解釋:
for (ContextResource cr : resources.values()) {
try {
MBeanUtils.createMBean(cr);
} catch (Exception e) {
log.warn(sm.getString(
"namingResources.mbeanCreateFail", cr.getName()), e);
}
}
for (ContextEnvironment ce : envs.values()) {
try {
MBeanUtils.createMBean(ce);
} catch (Exception e) {
log.warn(sm.getString(
"namingResources.mbeanCreateFail", ce.getName()), e);
}
}
for (ContextResourceLink crl : resourceLinks.values()) {
try {
MBeanUtils.createMBean(crl);
} catch (Exception e) {
log.warn(sm.getString(
"namingResources.mbeanCreateFail", crl.getName()), e);
}
}
init之后是start,start中只發(fā)布了個(gè)configure_start事件。
setInstanceManager(new DefaultInstanceManager(context, injectionMap, this, this.getClass().getClassLoader())),InstanceManager主要是用于創(chuàng)建和回收實(shí)例,然后綁定:
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
還有:
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
合并參數(shù)mergeParameters由于我這里是空的,所以什么也沒做;然后遍歷initializers并onStartup:

先是進(jìn)入到TomcatStarter的onStartup,這里又是:
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
先是執(zhí)行:
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
selfInitialize(servletContext);
}
};
}
EmbeddedWebApplicationContext中的selfInitialize ,prepareEmbeddedWebApplicationContext正常情況下先打一條日志Initializing Spring embedded WebApplicationContext然后servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this)然后將this綁定servletContext,如果啟動(dòng)Info級(jí)別日志,會(huì)打印類似這樣的日志:Root WebApplicationContext: initialization completed in 3150193 ms;然后new ExistingWebApplicationScopes,這玩意的注釋說它允許與非嵌入式相同的方式注冊(cè)作用域到ApplicationContextInitializer,先執(zhí)行了一個(gè)靜態(tài)代碼塊:
static {
Set<String> scopes = new LinkedHashSet<String>();
scopes.add(WebApplicationContext.SCOPE_REQUEST);//request
scopes.add(WebApplicationContext.SCOPE_SESSION);//session
scopes.add(WebApplicationContext.SCOPE_GLOBAL_SESSION);//global session
SCOPES = Collections.unmodifiableSet(scopes);
}
但是似乎在我這add白做了,因?yàn)闃?gòu)造函數(shù)中從bean工廠并沒取到Scope實(shí)例:
this.beanFactory = beanFactory;
for (String scopeName : SCOPES) {
Scope scope = beanFactory.getRegisteredScope(scopeName);
if (scope != null) {
this.scopes.put(scopeName, scope);
}
}
真正注冊(cè)作用域是在下一句WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, getServletContext()):
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
registerResolvableDependency將類型與對(duì)應(yīng)的裝配對(duì)象注冊(cè)進(jìn)bean工廠。existingScopes.restore里的代碼:
public void restore() {
for (Map.Entry<String, Scope> entry : this.scopes.entrySet()) {
if (logger.isInfoEnabled()) {
logger.info("Restoring user defined scope " + entry.getKey());
}
this.beanFactory.registerScope(entry.getKey(), entry.getValue());
}
}
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, getServletContext())把相應(yīng)的變量key與值注冊(cè)給bean工廠,如servletContext、contextParameters和contextAttributes;從bean工廠中獲取所有org.springframework.boot.web.servlet.ServletContextInitializer類型的bean,如filterRegistrationBean和dispatcherServletRegistration然后add給ServletContextInitializerBeans實(shí)例的initializers;addAdaptableBeans方法先從bean工廠中獲取javax.servlet.MultipartConfigElement類型的對(duì)象,然而javax.servlet.Servlet沒在bean工廠里找到,所以add什么也沒做;javax.servlet.Filter找到characterEncodingFilter、hiddenHttpMethodFilter、httpPutFormContentFilter、requestContextFilter;ServletListenerRegistrationBean.getSupportedTypes()取的是ServletListenerRegistrationBean的SUPPORTED_TYPES,不過全都沒找到,所以什么也沒做:
static {
Set<Class<?>> types = new HashSet<Class<?>>();
types.add(ServletContextAttributeListener.class);
types.add(ServletRequestListener.class);
types.add(ServletRequestAttributeListener.class);
types.add(HttpSessionAttributeListener.class);
types.add(HttpSessionListener.class);
types.add(ServletContextListener.class);
SUPPORTED_TYPES = Collections.unmodifiableSet(types);
}
然后是對(duì)找到的進(jìn)行排序:
List<ServletContextInitializer> sortedInitializers = new ArrayList<ServletContextInitializer>();
for (Map.Entry<?, List<ServletContextInitializer>> entry : this.initializers
.entrySet()) {
AnnotationAwareOrderComparator.sort(entry.getValue());
sortedInitializers.addAll(entry.getValue());
}
this.sortedList = Collections.unmodifiableList(sortedInitializers);
public static void sort(Object[] array) {
if (array.length > 1) {
Arrays.sort(array, INSTANCE);
}
}
private int doCompare(Object o1, Object o2, OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
}
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider);
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
然后對(duì)這些初始化器進(jìn)行beans.onStartup(servletContext);filterRegistrationBean執(zhí)行的AbstractFilterRegistrationBean的,主要執(zhí)行了這兩句:
FilterRegistration.Dynamic added = servletContext.addFilter(name, filter);
...
configure(added);
name:characterEncodingFilter,filter:OrderedCharacterEncodingFilter,它的配置中這里設(shè)定了過濾器轉(zhuǎn)發(fā)模式有FORWARD、INCLUDE、REQUEST、ASYNC,攔截路徑:"/*";然后是hiddenHttpMethodFilter和OrderedHiddenHttpMethodFilter,httpPutFormContentFilter和OrderedHttpPutFormContentFilter,requestContextFilter和OrderedRequestContextFilter,cipherFilter和CipherFilter(我這自定義的)。ServletRegistrationBean的:dispatcherServlet和DispatcherServlet,asyncSupported是true,url映射是‘/',設(shè)置StandardWrapper的loadOnStartup、 multipartConfigElement。
到了下一個(gè)初始化器SessionConfiguringInitializer:
public void onStartup(ServletContext servletContext) throws ServletException {
if (this.session.getTrackingModes() != null) {
servletContext.setSessionTrackingModes(this.session.getTrackingModes());
}
configureSessionCookie(servletContext.getSessionCookieConfig());
}
將session中的cookie信息補(bǔ)充進(jìn)ApplicationSessionCookieConfig的實(shí)例中,例如:
config.setName(cookie.getName());
config.setDomain(cookie.getDomain());
config.setPath(cookie.getPath());
config.setComment(cookie.getComment());
config.setHttpOnly(cookie.getHttpOnly());
config.setSecure(cookie.getSecure());
config.setMaxAge(cookie.getMaxAge());
實(shí)際中我這里一個(gè)都沒執(zhí)行,因?yàn)槲疫@的session中cookie信息都是null。
下一個(gè)初始化器InitParameterConfiguringServletContextInitializer由于參數(shù)沒有,所以進(jìn)去就出來了。
回到listenerStart,listenerStart:org.apache.tomcat.websocket.server.WsContextListener,用前面的DefaultInstanceManager的newInstance創(chuàng)建,然后加到lifecycleListeners中,然后傳給applicationLifecycleListenersObjects,然后是newServletContextListenerAllowed=false:當(dāng)listener發(fā)生調(diào)用后不允許添加,發(fā)布beforeContextInitialized事件,然后WsContextListener的contextInitialized:
ServletContext sc = sce.getServletContext();
if(sc.getAttribute("javax.websocket.server.ServerContainer") == null) {
WsSci.init(sce.getServletContext(), false);
}
init中先是初始化WsServerContainer:
static {
GET_BYTES = "GET ".getBytes(StandardCharsets.ISO_8859_1);
ROOT_URI_BYTES = "/".getBytes(StandardCharsets.ISO_8859_1);
HTTP_VERSION_BYTES = " HTTP/1.1\r\n".getBytes(StandardCharsets.ISO_8859_1);
}
static {
AUTHENTICATED_HTTP_SESSION_CLOSED = new CloseReason(CloseCodes.VIOLATED_POLICY, "This connection was established under an authenticated HTTP session that has ended.");
}
WsServerContainer(ServletContext servletContext) {
this.enforceNoAddAfterHandshake = Constants.STRICT_SPEC_COMPLIANCE; //Boolean.getBoolean("org.apache.tomcat.websocket.STRICT_SPEC_COMPLIANCE")
this.addAllowed = true;
this.authenticatedSessions = new ConcurrentHashMap();
this.endpointsRegistered = false;
this.servletContext = servletContext;
//我這里添加了org.apache.tomcat.websocket.server和本地語言en_US(我代碼是在英文版ubuntu上跑的)
this.setInstanceManager((InstanceManager)servletContext.getAttribute(InstanceManager.class.getName()));
String value = servletContext.getInitParameter("org.apache.tomcat.websocket.binaryBufferSize");
if(value != null) {
this.setDefaultMaxBinaryMessageBufferSize(Integer.parseInt(value));
}
value = servletContext.getInitParameter("org.apache.tomcat.websocket.textBufferSize");
if(value != null) {
this.setDefaultMaxTextMessageBufferSize(Integer.parseInt(value));
}
//Java WebSocket 規(guī)范 1.0 并不允許第一個(gè)服務(wù)端點(diǎn)開始 WebSocket 握手之后進(jìn)行程序性部署。默認(rèn)情況下,Tomcat 繼續(xù)允許額外的程序性部署。
value = servletContext.getInitParameter("org.apache.tomcat.websocket.noAddAfterHandshake");
if(value != null) {
this.setEnforceNoAddAfterHandshake(Boolean.parseBoolean(value));
}
Dynamic fr = servletContext.addFilter("Tomcat WebSocket (JSR356) Filter", new WsFilter());
fr.setAsyncSupported(true);
EnumSet types = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
fr.addMappingForUrlPatterns(types, true, new String[]{"/*"});
}

init創(chuàng)建了 WsServerContainer之后,將它設(shè)置給servletContext的javax.websocket.server.ServerContainer屬性,然后servletContext.addListener(new WsSessionListener(sc))加進(jìn)前面的applicationLifecycleListenersObjects中,init結(jié)束,回到StandardContext發(fā)布afterContextInitialized事件,我這到這里listenerStart結(jié)束。
checkConstraintsForUncoveredMethods(findConstraints())因?yàn)槲疫@里find出來的并沒有,所以pass;start StandardManager startInternal先是super(ManagerBase),一進(jìn)方法先是將兩個(gè)雙端隊(duì)列sessionCreationTiming和sessionExpirationTiming根據(jù)常量TIMING_STATS_CACHE_SIZE用null填滿,設(shè)置jvmRoute(jvmRoute用于區(qū)分多tomcat節(jié)點(diǎn),根據(jù)jvmRoute的值來確定當(dāng)前會(huì)話屬于哪個(gè)節(jié)點(diǎn) ),從engine上取得,之前設(shè)置過,getEngine:
public Engine getEngine() {
Engine e = null;
for (Container c = getContext(); e == null && c != null ; c = c.getParent()) {
if (c instanceof Engine) {
e = (Engine)c;
}
}
return e;
}
set給sessionIdGenerator,將之前初始化過的一些sessionIdGenerator值set給新new的SessionIdGeneratorBase,然后start之前的sessionIdGenerator,這個(gè)start沒做什么特別的,于是回到StandardManager,加載文件(例:/tmp/tomcat.7550276477249965168.80/work/Tomcat/localhost/ROOT/SESSIONS.ser),用于session持久化的,這時(shí)候找不到的。
filterStart對(duì)filterConfigs同步鎖,filterConfigs.put(name, filterConfig):






loadOnStartup(findChildren()),其實(shí)都一起start過了就不用了:

該啟動(dòng)StandardContext的后天線程了super.threadStart(),當(dāng)然因?yàn)閎ackgroundProcessorDelay所以也沒啟,unbindThread說是解綁,其實(shí)只是把classloader還原了,別的沒做什么,對(duì)應(yīng)著之前的bind。
設(shè)置StandardContext的startTime=System.currentTimeMillis(),發(fā)j2ee.state.running的通知,ObjectName是Tomcat:J2EEApplication=none, J2EEServer=none, j2eeType=WebModule, name=//localhost/;getResources().gc()因?yàn)閃ebResources引用了一些jar,有些平臺(tái)可能會(huì)對(duì)jar加鎖,這里先清理,但實(shí)際上這里的實(shí)現(xiàn)是空的。
DisablePersistSessionListener由于并沒有配置session持久化,所以會(huì)觸發(fā)這個(gè)監(jiān)聽器,實(shí)際只執(zhí)行了((StandardManager) manager).setPathname(null)。MemoryLeakTrackingListener只走了個(gè)過場(chǎng)。
發(fā)布after_start事件,這回終于執(zhí)行了MemoryLeakTrackingListener:
if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
if (event.getSource() instanceof Context) {
Context context = ((Context) event.getSource());
childClassLoaders.put(context.getLoader().getClassLoader(),
context.getServletContext().getContextPath());
}
}
子容器就啟動(dòng)完成了。
咱最近用的github:https://github.com/saaavsaaa
以上所述是小編給大家介紹的Spring Boot啟動(dòng)過程(六)之內(nèi)嵌Tomcat中StandardHost、StandardContext和StandardWrapper的啟動(dòng)教程詳解,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Java DWR內(nèi)存泄漏問題解決方案
- 基于JQuery和DWR實(shí)現(xiàn)異步數(shù)據(jù)傳遞
- java并發(fā)編程專題(七)----(JUC)ReadWriteLock的用法
- Java多線程 ReentrantReadWriteLock原理及實(shí)例詳解
- PipedWriter和PipedReader源碼分析_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java 中的 BufferedWriter 介紹_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- DWR中各種java方法的調(diào)用
- DWR異常情況處理常見方法解析
相關(guān)文章
使用springboot實(shí)現(xiàn)上傳文件時(shí)校驗(yàn)文件是否有病毒
在SpringBoot中實(shí)現(xiàn)文件上傳時(shí)的病毒校驗(yàn),可以使用ClamAV、Metascan或VirusTotal等工具,這些工具通過掃描上傳的文件,可以有效地檢測(cè)和阻止惡意軟件的傳播,安裝和配置ClamAV服務(wù)的步驟如下:下載并安裝ClamAV二進(jìn)制文件,配置clamd.conf文件2025-01-01
Spring項(xiàng)目里將SQL語句寫在.sql文件中的方法
這篇文章主要介紹了Spring項(xiàng)目里如何將SQL語句寫在.sql文件中的方法,文中給出了詳細(xì)的介紹和示例代碼,相信對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,有需要的朋友們下面來一起看看吧。2017-01-01
關(guān)于在IDEA中SpringBoot項(xiàng)目中activiti工作流的使用詳解
這篇文章主要介紹了關(guān)于在IDEA中SpringBoot項(xiàng)目中activiti工作流的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Java將中文轉(zhuǎn)化為拼音的簡(jiǎn)單代碼示例
在我們使用手機(jī)通訊錄或各種APP的搜索功能時(shí),既可以根據(jù)中文搜索,也可以根據(jù)拼音搜索,這種時(shí)候就使用到了中文轉(zhuǎn)拼音的功能了,下面這篇文章主要給大家介紹了關(guān)于Java將中文轉(zhuǎn)化為拼音的簡(jiǎn)單代碼示例,需要的朋友可以參考下2024-03-03
Java整合mybatis實(shí)現(xiàn)過濾數(shù)據(jù)
這篇文章主要介紹了Java整合mybatis實(shí)現(xiàn)過濾數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01
Java編譯錯(cuò)誤問題:需要class,interface或enum
這篇文章主要介紹了Java編譯錯(cuò)誤問題:需要class,interface或enum,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
解決springboot接入springfox-swagger2遇到的一些問題
這篇文章主要介紹了解決springboot接入springfox-swagger2遇到的一些問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07

