之前在spring boot启动过程(二)提到过createembeddedservletcontainer创建了内嵌的servlet容器,我用的是默认的tomcat。
private void createembeddedservletcontainer() { embeddedservletcontainer localcontainer = this.embeddedservletcontainer; servletcontext localservletcontext = getservletcontext(); if (localcontainer == null && localservletcontext == null) { embeddedservletcontainerfactory containerfactory = getembeddedservletcontainerfactory(); this.embeddedservletcontainer = containerfactory .getembeddedservletcontainer(getselfinitializer()); } else if (localservletcontext != null) { try { getselfinitializer().onstartup(localservletcontext); } catch (servletexception ex) { throw new applicationcontextexception("cannot initialize servlet context", ex); } } initpropertysources(); }
getembeddedservletcontainerfactory方法中调用了serverproperties,从serverproperties的实例方法customize可以看出springboot支持三种内嵌容器的定制化配置:tomcat、jetty、undertow。
这里直接说tomcatembeddedservletcontainerfactory的getembeddedservletcontainer方法了,原因在前面那篇里说过了。不过首先是getselfinitializer方法先执行的:
private org.springframework.boot.web.servlet.servletcontextinitializer getselfinitializer() { return new servletcontextinitializer() { @override public void onstartup(servletcontext servletcontext) throws servletexception { selfinitialize(servletcontext); } }; }
将初始化的servletcontextinitializer传给了getembeddedservletcontainer方法。进入了getembeddedservletcontainer方法直接就是实例化了一个tomcat:
tomcat tomcat = new tomcat();
然后生成一个临时目录,并tomcat.setbasedir,setbasedir方法的注释说tomcat需要一个目录用于临时文件并且它应该是第一个被调用的方法;如果方法没有被调用会使用默认的几个位置system properties - catalina.base, catalina.home - $pwd/tomcat.$port,另外/tmp从安全角度来说不建议。
接着:
connector connector = new connector(this.protocol);
创建Connector过程中,静态代码块:单独抽出来写了。RECYCLE_FACADES属性可以通过启动参数JAVA_OPTS来配置: -Dorg.apache.catalina.connector.RECYCLE_FACADES=,默认是false,配置成true可以提高安全性但同时性能会有些损耗,参考:http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html和http://bztax.gov.cn/docs/security-howto.html。其他属性不细说了,Connector构造的逻辑主要是在NIO和APR选择中选择一个协议,我的是org.apache.coyote.http11.Http11NioProtocol,然后反射创建实例并强转为ProtocolHandler。关于apr,似乎是更native,性能据说更好,但我没测,相关文档可参考:http://tomcat.apache.org/tomcat-8.5-doc/apr.html。这里简单提一下coyote,它的主要作用是将socket接受的信息封装为request和response并提供给上Servlet容器,进行上下层之间的沟通,文档我没找到比较新的:http://tomcat.apache.org/tomcat-4.1-doc/config/coyote.html。STRICT_SERVLET_COMPLIANCE也是启动参数控制,默认是false,配置项是org.apache.catalina.STRICT_SERVLET_COMPLIANCE,默认情况下会设置URIEncoding = "UTF-8"和URIEncodingLower = URIEncoding.toLowerCase(Locale.ENGLISH),相关详细介绍可参考:http://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html。Connector的创建过程比较关键,容我单独写一篇吧。
connector实例创建好了之后tomcat.getservice().addconnector(connector),getservice的getserver中new了一个standardserver,standardserver的初始化主要是创建了globalnamingresources(globalnamingresources主要用于管理明明上下文和jdni上下文),并根据catalina.usenaming判断是否注册namingcontextlistener监听器给lifecyclelisteners。创建server之后initbasedir,先读取catalina.home配置system.getproperty(globals.catalina_base_prop),如果没取到则使用之前生成的临时目录,这段直接看代码吧:
protected void initbasedir() { string catalinahome = system.getproperty(globals.catalina_home_prop); if (basedir == null) { basedir = system.getproperty(globals.catalina_base_prop); } if (basedir == null) { basedir = catalinahome; } if (basedir == null) { // create a temp dir. basedir = system.getproperty("user.dir") + "/tomcat." + port; } file basefile = new file(basedir); basefile.mkdirs(); try { basefile = basefile.getcanonicalfile(); } catch (ioexception e) { basefile = basefile.getabsolutefile(); } server.setcatalinabase(basefile); system.setproperty(globals.catalina_base_prop, basefile.getpath()); basedir = basefile.getpath(); if (catalinahome == null) { server.setcatalinahome(basefile); } else { file homefile = new file(catalinahome); homefile.mkdirs(); try { homefile = homefile.getcanonicalfile(); } catch (ioexception e) { homefile = homefile.getabsolutefile(); } server.setcatalinahome(homefile); } system.setproperty(globals.catalina_home_prop, server.getcatalinahome().getpath()); }
然后又实例化了个standardservice,代码并没有什么特别的:
service = new standardservice(); service.setname("tomcat"); server.addservice( service )
server.addservice( service )这里除了发布了一个propertychangeevent事件,也没做什么特别的,最后返回这个server。addconnector的逻辑和上面addservice没什么区别。然后是customizeconnector,这里设置了connector的端口、编码等信息,并将“bindoninit”和对应值false写入了最开头说的静态代码块中的replacements集合,introspectionutils.setproperty(protocolhandler, repl, value)通过反射的方法将protocolhandler实现对象的setbindoninit存在的情况下(拼字符串拼出来的)set为前面的false,这个方法里有大量的判断比如参数类型及setter的参数类型,比如返回值类型以及没找到还会try a setproperty("name", "value")等,setproperty可以处理比如abstractendpoint中有个hashmap<string, object> attributes的属性时会attributes.put(name, value)。如果是ssl还会执行customizessl方法,设置一些ssl用的属性比如协议比如秘钥还有可以用上秘钥仓库等。如果配置了压缩,这里还会给协议的相关setter设置值。tomcat.setconnector(connector)不解释。tomcat.gethost().setautodeploy(false),gethost方法中创建了standardhost并设置host名(例如localhost),并getengine().addchild( host );然后设置host的自动部署。configureengine(tomcat.getengine()),getengine中如果engine为null就初始化标准引擎,设置名字为tomcat,设置realm和service.setcontainer(engine),不过这里engine已经在gethost初始化过了所以直接返回;configureengine方法先设置引擎的后台进程延迟,并将引擎的value对象注册给引擎的pipeline,此时尚无value对象实例。这里简单说明一下:value对象在tomcat的各级容器中都有标准类型,并且各级容器都有一个pipeline,在请求处理过程中会从各级的第一个value对象开始依次执行一遍,value用于加入到对应的各级容器的逻辑,默认有一个标注value实现,名字类似standardhostvalue。
preparecontext(tomcat.gethost(), initializers),initializers这里是annotationconfigembeddedwebapplicationcontext,context级的根;准备context的过程主要设置base目录,new一个tomcatembeddedcontext并在构造中判断了下loadonstartup方法是否被重写;注册一个fixcontextlistener监听,这个监听用于设置context的配置状态以及是否加入登录验证的逻辑;context.setparentclassloader;设置各种语言的编码映射,我这里是en和fr设置为utf-8,此处可以使用配置文件org/apache/catalina/util/charsetmapperdefault .properties;设置是否使用相对地址重定向userelativeredirects=false,此属性应该是tomcat 8.0.30版本加上的;接着就是初始化webapploader,这里和完整版的tomcat有点不一样,它用的是虚拟机的方式,会将加载类向上委托loader.setdelegate(true),context.setloader(loader);之后就开始创建wapper了,至此engine,host,context及wrapper四个层次的容器都创建完了:
private void adddefaultservlet(context context) { wrapper defaultservlet = context.createwrapper(); defaultservlet.setname("default"); defaultservlet.setservletclass("org.apache.catalina.servlets.defaultservlet"); defaultservlet.addinitparameter("debug", "0"); defaultservlet.addinitparameter("listings", "false"); defaultservlet.setloadonstartup(1); // otherwise the default location of a spring dispatcherservlet cannot be set defaultservlet.setoverridable(true); context.addchild(defaultservlet); addservletmapping(context, "/", "default"); }
connector从socket接收的数据,解析成httpservletrequest后就会经过这几层容器,有容器各自的value对象链依次处理。
接着是是否注册jspservlet,jasperinitializer和storemergedwebxmllistener我这里是都没有的。接着的mergeinitializers方法:
protected final servletcontextinitializer[] mergeinitializers( servletcontextinitializer... initializers) { list<servletcontextinitializer> mergedinitializers = new arraylist<servletcontextinitializer>(); mergedinitializers.addall(arrays.aslist(initializers)); mergedinitializers.addall(this.initializers); return mergedinitializers .toarray(new servletcontextinitializer[mergedinitializers.size()]); }
configurecontext(context, initializerstouse)对context做了些设置工作,包括tomcatstarter(实例化并set给context),lifecyclelistener,contextvalue,errorpage,mime,session超时持久化等以及一些自定义工作:
tomcatstarter starter = new tomcatstarter(initializers); if (context instanceof tomcatembeddedcontext) { // should be true ((tomcatembeddedcontext) context).setstarter(starter); } context.addservletcontainerinitializer(starter, no_classes); for (lifecyclelistener lifecyclelistener : this.contextlifecyclelisteners) { context.addlifecyclelistener(lifecyclelistener); } for (valve valve : this.contextvalves) { context.getpipeline().addvalve(valve); } for (errorpage errorpage : geterrorpages()) { new tomcaterrorpage(errorpage).addtocontext(context); } for (mimemappings.mapping mapping : getmimemappings()) { context.addmimemapping(mapping.getextension(), mapping.getmimetype()); }
session如果不需要持久化会注册一个disablepersistsessionlistener。其他定制化操作是通过tomcatcontextcustomizer的实现类实现的:
context配置完了作为child add给host,add时给context注册了个内存泄漏跟踪的监听memoryleaktrackinglistener。postprocesscontext(context)方法是空的,留给子类重写用的。
getembeddedservletcontainer方法的最后一行:return gettomcatembeddedservletcontainer(tomcat)。
protected tomcatembeddedservletcontainer gettomcatembeddedservletcontainer( tomcat tomcat) { return new tomcatembeddedservletcontainer(tomcat, getport() >= 0); }
tomcatembeddedservletcontainer的构造函数:
public tomcatembeddedservletcontainer(tomcat tomcat, boolean autostart) { assert.notnull(tomcat, "tomcat server must not be null"); this.tomcat = tomcat; this.autostart = autostart; initialize(); }
initialize的第一个方法addinstanceidtoenginename对全局原子变量containercounter+1,由于初始值是-1,所以addinstanceidtoenginename方法内后续的获取引擎并设置名字的逻辑没有执行:
private void addinstanceidtoenginename() { int instanceid = containercounter.incrementandget(); if (instanceid > 0) { engine engine = this.tomcat.getengine(); engine.setname(engine.getname() + "-" + instanceid); } }
initialize的第二个方法removeserviceconnectors,将上面new的connection以service(这里是standardservice[tomcat])做key保存到private final map<service, connector[]> serviceconnectors中,并将standardservice中的protected connector[] connectors与service解绑(connector.setservice((service)null);),解绑后下面利用lifecyclebase启动容器就不会启动到connector了。
之后是this.tomcat.start(),这段比较复杂,我单独总结一篇吧。
tomcatembeddedservletcontainer的初始化,接下来是rethrowdeferredstartupexceptions,这个方法检查初始化过程中的异常,如果有直接在主线程抛出,检查方法是tomcatstarter中的private volatile exception startupexception,这个值是在context启动过程中记录的:
@override public void onstartup(set<class<?>> classes, servletcontext servletcontext) throws servletexception { try { for (servletcontextinitializer initializer : this.initializers) { initializer.onstartup(servletcontext); } } catch (exception ex) { this.startupexception = ex; // prevent tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.iserrorenabled()) { logger.error("error starting tomcat context. exception: " + ex.getclass().getname() + ". message: " + ex.getmessage()); } } }
context context = findcontext():
private context findcontext() { for (container child : this.tomcat.gethost().findchildren()) { if (child instanceof context) { return (context) child; } } throw new illegalstateexception("the host does not contain a context"); }
绑定命名的上下文和classloader,不成功也无所谓:
try { contextbindings.bindclassloader(context, getnamingtoken(context), getclass().getclassloader()); } catch (namingexception ex) { // naming is not enabled. continue }
startdaemonawaitthread方法的注释是:与jetty不同,tomcat所有的线程都是守护线程,所以创建一个非守护线程(例:thread[container-0,5,main])来避免服务到这就shutdown了:
private void startdaemonawaitthread() { thread awaitthread = new thread("container-" + (containercounter.get())) { @override public void run() { tomcatembeddedservletcontainer.this.tomcat.getserver().await(); } }; awaitthread.setcontextclassloader(getclass().getclassloader()); awaitthread.setdaemon(false); awaitthread.start(); }
这个await每10秒检查一次是否关闭了:
try { awaitthread = thread.currentthread(); while(!stopawait) { try { thread.sleep( 10000 ); } catch( interruptedexception ex ) { // continue and check the flag } } } finally { awaitthread = null; } return;
回到embeddedwebapplicationcontext,initpropertysources方法,用初始化好的servletcontext完善环境变量:
/** * {@inheritdoc} * <p>replace {@code servlet}-related property sources. */ @override protected void initpropertysources() { configurableenvironment env = getenvironment(); if (env instanceof configurablewebenvironment) { ((configurablewebenvironment) env).initpropertysources(this.servletcontext, null); } }
createembeddedservletcontainer就结束了,内嵌容器的启动过程至此结束。
==========================================================
咱最近用的github:https://github.com/saaavsaaa
原文链接:http://www.cnblogs.com/saaav/p/6323350.html