Spring Boot启动过程(六)之内嵌Tomcat中StandardHost、StandardContext和StandardWrapper的启动教程详解
standardengine[tomcat].standardhost[localhost]的启动与standardengine不在同一个线程中,它的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; } }
这个start流程中,initinternal方法是containerbase的代码,还是那个初始化startstopexecutor的,线程名例如thread[localhost-startstop-1,5,main],这次是用来初始化host的子容器的,然后是standardhost中的startinternal方法,主要是注册了一个errorvalue,如果现有的pipeline中没有errorvalue,则反射创建org.apache.catalina.valves.errorreportvalve实例,并加入pipeline中,容器pipeline加入value时会发布一个container.add_valve_event事件,与engine一样,之后进入containerbase的startinternal,但是这次realm是null不需要启动,然后findchildren出standardengine[tomcat]. standardhost [localhost].standardcontext[],然后同样新开个线程new startchild,start同样是上面的代码,需要特别说明的是,这次before_init的事件有监听的了,fixcontextlistener,disablepersistsessionlistener,memoryleaktrackinglistener;fixcontextlistener监听的处理,会加入一个用于不做用户身份认证的安全检查的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监听只处理start事件,所以这里只判断了一下发现不是就出去了,其实这里可以思考下,有没有更好的办法,让监听不只是广播方式,能不能用订阅方式,先不细想了,接着看代码,memoryleaktrackinglistener只监听了after_start事件,这步同样什么都没做。
于是来到了standardcontext的initinternal,它的super.initinternal又是一个startstopexecutor,containerbase的super.initinternal就不再说了,发送j2ee.object.created消息:
notification notification = new notification("j2ee.object.created", this.getobjectname(), sequencenumber.getandincrement()); broadcaster.sendnotification(notification);
notification是eventobject的子类,代表由mbean发出的通知,mbean server发出的通知会包含发出的mbean的引用,如果mbean注册了监听,可以通过object name或引用获取消息发出者,官方建议使用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)); } } }
发完消息就转变状态为初始化完成,因为监听器是注册在context容器上的,于是after_init事件又触发了那三个监听器,这一阶段监听器什么都没处理走了下过场而已;before_start同走过场;然后standardcontext的startinternal方法,发布了个j2ee.state.starting消息object name为tomcat:j2eetype=webmodule,name=//localhost/,j2eeapplication=none, j2eeserver=none;setconfigured(false)还没有正确的配置;设置webresourceroot,webresourceroot提供整个应用资源处理类的各种方法,内嵌用的实现类是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有相关说明,我就不翻译了。
set之后就是启动resourcesstart,initinternal执行的是standardroot的initinternal方法,super.initinternal中依然是那两行代码,register(cache, getobjectnamekeyproperties() + ",name=cache")会发送mbeanservernotification. registration_notification通知,生成objectname这里cachejmxname是tomcat:type=webresourceroot,host=localhost,context=/,name=cache;registerurlstreamhandlerfactory里面的代码是tomcaturlstreamhandlerfactory.register()这行代码的注释说这是为了支持war包内的jar资源的。之后是循环上面的allresources,init里面加入的webresourceset,但是由于全都是空的,所以等于没执行,就不说了,回头再仔细看看什么情况下回不为空,还是内嵌的就是空的。createmainresourceset主要是设置个主目录,例如/tmp/tomcat-docbase.3031819619941848514.80,然后是各种资源该放在哪个子目录的一些设置代码;这次资源有一个了,所以可以有一个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主要是对base目录进行了一些规范化处理,规范的方法主要是unixfilesystem中的canonicalize其中还使用expiringcache对路径做了缓存,另外还有在normalize方法中对路径中类似"\.."的部分做了处理。webappmount是web应用发布资源的位置,必须以‘/'开头,这里应该是通过它来判断不是war包部署的模式,然后由于manifest没找到,所以方法返回初始化完成,这个资源一路状态变化就启动完了。
回到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是计算缓存限制的,详细的可以参考,至此standardroot的启动完成就只剩下改状态了。
回到standardcontext,因为classloader已经有了不需要new了;接着创建rfc6265cookieprocessor类型的cookieprocessor实例,关于rfc6265标准参考;character set mapper因为已经初始化好了只判断了下;工作目录处理,先根据host和engine名生成路径如:work/tomcat/localhost/root,结合前面的base创建目录例如/tmp/tomcat.3726907762383543267.80/work/tomcat/localhost/root,然后初始化standardcontext中的applicationcontext类型可继承的全局变量context构造用参数是this(context = new applicationcontext(this)),返回new applicationcontextfacade(this);将上面的全路径设置给servletcontext.tempdir属性,并将这个属性设置为只读:
/** * set an attribute as read only. */ void setattributereadonly(string name) { if (attributes.containskey(name)) readonlyattributes.put(name, name); }
之后是对扩展进行验证,这里说一下,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用于是否开启命名服务支持,开启了就会注册namingcontextlistener监听器:
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()里有个threadbindinglistener,不过因为webapplicationclassloader是null,所以等于没执行,返回的是null,里面的逻辑还不少,命名服务也没开contextbindings.bindthread于是也没执行。
old的没有,但是loader还是有的,到了loader的start了,主要要说的是webapploader的startinternal方法,classloader创建:
classloader = createclassloader(); classloader.setresources(context.getresources()); classloader.setdelegate(this.delegate);
buildclasspath的主要功能是遍历各个层次的classloader并将其中classpath的jar拼成一个字符串,例如
:/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...,是以':'作为分隔是因为我的开发环境是linux,在windows中应该是';':
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之前提过了,是会向基loader类委托的;setclasspath的最后一句:servletcontext.setattribute(globals.class_path_attr, this.classpath)。
setpermissions方法,由于我这第一个判断就返回了,而且看上去代码也很直观,我就不说了:
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(),这个classloader是tomcatembeddedwebappclassloader走的是webappclassloaderbase中的start方法,这里因为是内嵌的版本(我没确认,猜测)所以也并没有加载到东西,所以也不细说了:
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,然后注册mbean:getmbeanserver().registermbean( mbean, oname);webapploader的start就没什么了,started之后就是设置了几个属性:
// since the loader just started, the webapp classloader is now // created. setclassloaderproperty("clearreferencesrmitargets", getclearreferencesrmitargets()); setclassloaderproperty("clearreferencesstopthreads", getclearreferencesstopthreads()); setclassloaderproperty("clearreferencesstoptimerthreads", getclearreferencesstoptimerthreads()); setclassloaderproperty("clearreferenceshttpclientkeepalivethread", getclearreferenceshttpclientkeepalivethread());
这里的unbindthread因为前面的bind几乎没做什么,所以什么也没做;接着的bindthread主要讲线程与classloader做了绑定: thread.currentthread().setcontextclassloader (webapplicationclassloader),至于threadbindinglistener.bind()由于threadbindinglistener用了个空实现,所以这里什么也没做。
接下来用读锁取到realm并start它;接下来发布configure_start事件,fixcontextlistener中执行了context.setconfigured(true)。
终于到了standardwrapper(standardengine[tomcat].standardhost[localhost].standardcontext[].standardwrapper[default])的start了,initinternal直接就是containerbase的初始化startstopexecutor,startinternal方法是发了个j2ee.state.starting的消息,objectname是tomcat:j2eetype=servlet, webmodule=//localhost/, name=default, j2eeapplication=none, j2eeserver=none,然后又到containerbase的startinternal,然而由于它没有子容器了,所以这里并没有startchild的任务产生;于是开始执行它的value,先start它的pipeline,startinternal方法依然是standardpipeline的,按顺序start,由于到这的时候一个都没有,所以执行的是basic的,standardwrappervalve的initinternal中只有一句注释:don't register this valve in jmx;startinternal的最后是threadstart,但由于backgroundprocessordelay是-1所以并没有启动背景线程;setavailable(0l)设置可用,它的说明 the date and time at which this servlet will become available (in milliseconds since the epoch), or zero if the servlet is available;然后发送一个消息j2ee.state.running,objectname是tomcat:j2eetype=servlet,webmodule=//localhost/,name=default,j2eeapplication=none,j2eeserver=none;
standardwrapper就启动完了,回到standardcontext,start它的pipeline;与standardwrapper的pipeline不同,它之前被注册过nonloginauthenticator,它的startinternal方法定义在authenticatorbase,方法中设置了jaspicappcontextid(例如:tomcat/localhost ),然后获取上级容器也就是host的pipeline中的所有value,并找到其中singlesignon类型的value,明显是用于单点登录的,我这里没有,于是又去找了上一级容器engine当然还是没有,于是就往下走了;实例化了一个standardsessionidgenerator,设置安全随机数生成算法我这里是sha1prng,生成器类名为null,生成器provider也是null,然后就是下一个value对象standardcontextvalve的start,只不过它的start是标准的什么额外事都没干,于是回到了standardcontext中。下面一段主要是执行了tomcatembeddedcontext中的setmanager方法:
@override public void setmanager(manager manager) { if (manager instanceof managerbase) { ((managerbase) manager).setsessionidgenerator(new lazysessionidgenerator()); } super.setmanager(manager); }
这里判断是true,lazysessionidgenerator整个的代码:
class lazysessionidgenerator extends standardsessionidgenerator {
@override protected void startinternal() throws lifecycleexception { setstate(lifecyclestate.starting); } }
tomcatembeddedcontext的super.setmanager(manager)的super是standardcontext,在写锁中执行的,spring中多数的set都是交换的方式,先set个old保存下来,然后判断新值和old是否相同,不相同用新的并将新值绑定容器,相同直接返回;getservletcontext().setattribute(globals.resources_attr, getresources())没什么好解释的;setnamingresources(new namingresourcesimpl());然后init这个namingresources,namingresourcesimpl的initinternal,在设置当前已知命名资源前设置resourcerequireexplicitregistration用于避免时序问题,重复注册是正常的,后面一段我不想解释:
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中只发布了个configure_start事件。
setinstancemanager(new defaultinstancemanager(context, injectionmap, this, this.getclass().getclassloader())),instancemanager主要是用于创建和回收实例,然后绑定:
getservletcontext().setattribute( instancemanager.class.getname(), getinstancemanager()); instancemanagerbindings.bind(getloader().getclassloader(), getinstancemanager());
还有:
getservletcontext().setattribute( jarscanner.class.getname(), getjarscanner());
合并参数mergeparameters由于我这里是空的,所以什么也没做;然后遍历initializers并onstartup:
先是进入到tomcatstarter的onstartup,这里又是:
for (servletcontextinitializer initializer : this.initializers) { initializer.onstartup(servletcontext); }
先是执行:
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,如果启动info级别日志,会打印类似这样的日志:root webapplicationcontext: initialization completed in 3150193 ms;然后new existingwebapplicationscopes,这玩意的注释说它允许与非嵌入式相同的方式注册作用域到applicationcontextinitializer,先执行了一个静态代码块:
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白做了,因为构造函数中从bean工厂并没取到scope实例:
this.beanfactory = beanfactory; for (string scopename : scopes) { scope scope = beanfactory.getregisteredscope(scopename); if (scope != null) { this.scopes.put(scopename, scope); } }
真正注册作用域是在下一句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将类型与对应的装配对象注册进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())把相应的变量key与值注册给bean工厂,如servletcontext、contextparameters和contextattributes;从bean工厂中获取所有org.springframework.boot.web.servlet.servletcontextinitializer类型的bean,如filterregistrationbean和dispatcherservletregistration然后add给servletcontextinitializerbeans实例的initializers;addadaptablebeans方法先从bean工厂中获取javax.servlet.multipartconfigelement类型的对象,然而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); }
然后是对找到的进行排序:
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; }
然后对这些初始化器进行beans.onstartup(servletcontext);filterregistrationbean执行的abstractfilterregistrationbean的,主要执行了这两句:
filterregistration.dynamic added = servletcontext.addfilter(name, filter);
...
configure(added);
name:characterencodingfilter,filter:orderedcharacterencodingfilter,它的配置中这里设定了过滤器转发模式有forward、include、request、async,拦截路径:"/*";然后是hiddenhttpmethodfilter和orderedhiddenhttpmethodfilter,httpputformcontentfilter和orderedhttpputformcontentfilter,requestcontextfilter和orderedrequestcontextfilter,cipherfilter和cipherfilter(我这自定义的)。servletregistrationbean的:dispatcherservlet和dispatcherservlet,asyncsupported是true,url映射是‘/',设置standardwrapper的loadonstartup、 multipartconfigelement。
到了下一个初始化器sessionconfiguringinitializer:
public void onstartup(servletcontext servletcontext) throws servletexception { if (this.session.gettrackingmodes() != null) { servletcontext.setsessiontrackingmodes(this.session.gettrackingmodes()); } configuresessioncookie(servletcontext.getsessioncookieconfig()); }
将session中的cookie信息补充进applicationsessioncookieconfig的实例中,例如:
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());
实际中我这里一个都没执行,因为我这的session中cookie信息都是null。
下一个初始化器initparameterconfiguringservletcontextinitializer由于参数没有,所以进去就出来了。
回到listenerstart,listenerstart:org.apache.tomcat.websocket.server.wscontextlistener,用前面的defaultinstancemanager的newinstance创建,然后加到lifecyclelisteners中,然后传给applicationlifecyclelistenersobjects,然后是newservletcontextlistenerallowed=false:当listener发生调用后不允许添加,发布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 规范 1.0 并不允许第一个服务端点开始 websocket 握手之后进行程序性部署。默认情况下,tomcat 继续允许额外的程序性部署。 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创建了 wsservercontainer之后,将它设置给servletcontext的javax.websocket.server.servercontainer属性,然后servletcontext.addlistener(new wssessionlistener(sc))加进前面的applicationlifecyclelistenersobjects中,init结束,回到standardcontext发布aftercontextinitialized事件,我这到这里listenerstart结束。
checkconstraintsforuncoveredmethods(findconstraints())因为我这里find出来的并没有,所以pass;start standardmanager startinternal先是super(managerbase),一进方法先是将两个双端队列sessioncreationtiming和sessionexpirationtiming根据常量timing_stats_cache_size用null填满,设置jvmroute(jvmroute用于区分多tomcat节点,根据jvmroute的值来确定当前会话属于哪个节点 ),从engine上取得,之前设置过,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,这个start没做什么特别的,于是回到standardmanager,加载文件(例:/tmp/tomcat.7550276477249965168.80/work/tomcat/localhost/root/sessions.ser),用于session持久化的,这时候找不到的。
filterstart对filterconfigs同步锁,filterconfigs.put(name, filterconfig):
loadonstartup(findchildren()),其实都一起start过了就不用了:
该启动standardcontext的后天线程了super.threadstart(),当然因为backgroundprocessordelay所以也没启,unbindthread说是解绑,其实只是把classloader还原了,别的没做什么,对应着之前的bind。
设置standardcontext的starttime=system.currenttimemillis(),发j2ee.state.running的通知,objectname是tomcat:j2eeapplication=none, j2eeserver=none, j2eetype=webmodule, name=//localhost/;getresources().gc()因为webresources引用了一些jar,有些平台可能会对jar加锁,这里先清理,但实际上这里的实现是空的。
disablepersistsessionlistener由于并没有配置session持久化,所以会触发这个监听器,实际只执行了((standardmanager) manager).setpathname(null)。memoryleaktrackinglistener只走了个过场。
发布after_start事件,这回终于执行了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()); } }
子容器就启动完成了。
咱最近用的github:
以上所述是小编给大家介绍的spring boot启动过程(六)之内嵌tomcat中standardhost、standardcontext和standardwrapper的启动教程详解,希望对大家有所帮助
上一篇: 基于Spring框架的Shiro配置方法
下一篇: VML应用实例大全