Serlvet容器与Web应用
对启动顺序的错误认识
之前一直有个观点,应用运行在servlet容器中,因为从servlet容器与web应用的使用方式来看,确实很有这种感觉。
我们每次都是启动servlet容器,然后再启动我们的应用程序,比如如果web应用使用spring框架的话,先启动servlet容器,然后才是spring容器的初始化。
这样就会产生一种错觉,我们写的程序代码,是运行时servlet容器的,而容器这个词,更是加深了这种误会。
然后遇到了springboot的内嵌servlet容器,这种情况下,是先初始化我们的spring容器,在初始化springcontext的过程中,去启动我们的servlet容器。
这就尴尬了,颠覆了之前的认知,于是稍微看了下spring启动servlet容器的过程,重新理解下servlet容器。
先servlet容器后spring容器
以前我们使用servlet容器来部署java的web应用时,需要在web.xml中做如下配置
<!-- 配置servletcontext 参数 --> <context-param> <!-- 参数名,这个是固定的,不能变 --> <param-name>contextconfiglocation</param-name> <param-value> <!-- spring 配置文件路径 --> classpath:applicationcontext.xml </param-value> </context-param> <!-- 上下文加载器的监听器 --> <listener> <listener-class>org.springframework.web.context.contextloaderlistener</listener-class> </listener>
在web.xml中作如上的配置,在servlet容器启动成功后,就可以初始化我们的spring applicationcontext了
怎么做的呢? 稍微记录下
首先,配置的监听器,会在servlet容器启动后,由servlet容器进行一个事件发布,将此事件发布给配置的所有的监听器,以及servlet容器内部的一些监听器。
org.springframework.web.context.contextloaderlistener implements servletcontextlistener
public class contextloaderlistener implements servletcontextlistener { private contextloader contextloader; public contextloaderlistener() { } public void contextinitialized(servletcontextevent event) { this.contextloader = this.createcontextloader(); // 从servletcontextevent事件中,获取servletcontext对象 this.contextloader.initwebapplicationcontext(event.getservletcontext()); } protected contextloader createcontextloader() { return new contextloader(); } public contextloader getcontextloader() { return this.contextloader; } public void contextdestroyed(servletcontextevent event) { if (this.contextloader != null) { this.contextloader.closewebapplicationcontext(event.getservletcontext()); } } }
然后看下初始化webapplicationcontext
在org.springframework.web.context.contextloader#createwebapplicationcontext
方法中我们可以看到如下的一段内容
protected webapplicationcontext createwebapplicationcontext(servletcontext servletcontext, applicationcontext parent) throws beansexception { class contextclass = this.determinecontextclass(servletcontext); if (!configurablewebapplicationcontext.class.isassignablefrom(contextclass)) { throw new applicationcontextexception("xxxx"); } else { configurablewebapplicationcontext wac = (configurablewebapplicationcontext)beanutils.instantiateclass(contextclass); wac.setparent(parent); wac.setservletcontext(servletcontext); // 这里是从servletcontext对象中,获取我们配置的spring上下文配置文件路径 wac.setconfiglocation(servletcontext.getinitparameter("contextconfiglocation")); this.customizecontext(servletcontext, wac); wac.refresh(); return wac; } }
通过上面的两个类,一个配置,我们对之前使用servlet容器来启动spring容器,就有了一个比较直观的认识。
先spring容器后servlet容器
接下来我们看下springboot是如何启动servlet容器的
我们启动springboot一般都是如此
springapplication.run(application.class, args);
在static run
方法中,实例化springapplication
对象
public springapplication(resourceloader resourceloader, class<?>... primarysources) { this.resourceloader = resourceloader; assert.notnull(primarysources, "primarysources must not be null"); this.primarysources = new linkedhashset<>(arrays.aslist(primarysources)); // 决定webapplication类型 this.webapplicationtype = webapplicationtype.deducefromclasspath(); setinitializers((collection) getspringfactoriesinstances(applicationcontextinitializer.class)); setlisteners((collection) getspringfactoriesinstances(applicationlistener.class)); this.mainapplicationclass = deducemainapplicationclass(); }
static webapplicationtype deducefromclasspath() { // 如果存在类 org.springframework.web.reactive.dispatcherhandler // 并且没有 org.springframework.web.servlet.dispatcherservlet // 和 org.glassfish.jersey.servlet.servletcontainer // 则认为是reactive类型的web应用 if (classutils.ispresent(webflux_indicator_class, null) && !classutils.ispresent(webmvc_indicator_class, null) && !classutils.ispresent(jersey_indicator_class, null)) { return webapplicationtype.reactive; } // 存在 javax.servlet.servlet // 和 org.springframework.web.context.configurablewebapplicationcontext // 则认为是servlet类型的web应用 for (string classname : servlet_indicator_classes) { if (!classutils.ispresent(classname, null)) { // 都没有出现就不是web应用 return webapplicationtype.none; } } return webapplicationtype.servlet; }
然后在方法org.springframework.boot.springapplication#createapplicationcontext
中
protected configurableapplicationcontext createapplicationcontext() { class<?> contextclass = this.applicationcontextclass; if (contextclass == null) { // 如果还没有决定好使用哪个applicationcontext的子类,根据webapplicationtype来决定 try { switch (this.webapplicationtype) { case servlet: // 加载 annotationconfigservletwebserverapplicationcontext类 contextclass = class.forname(default_servlet_web_context_class); break; case reactive: contextclass = class.forname(default_reactive_web_context_class); break; default: contextclass = class.forname(default_context_class); } } catch (classnotfoundexception ex) { throw new illegalstateexception( "unable create a default applicationcontext, please specify an applicationcontextclass", ex); } } // 实例化applicationcontext对象 return (configurableapplicationcontext) beanutils.instantiateclass(contextclass); }
之后在org.springframework.boot.springapplication#run(java.lang.string...)
方法中,调用`org.springframework.boot.springapplication#refreshcontext,然后调用下面的方法
protected void refresh(applicationcontext applicationcontext) { // 类型判断 assert.isinstanceof(abstractapplicationcontext.class, applicationcontext); // 调用refresh方法 这里利用多态,调用实际对象的refresh方法 ((abstractapplicationcontext) applicationcontext).refresh(); }
最终会调用到org.springframework.context.support.abstractapplicationcontext#refresh
refresh
方法中有一个onrefresh()
public void refresh() throws beansexception, illegalstateexception { synchronized (this.startupshutdownmonitor) { // ... 省略 try { // ... 省略 // initialize other special beans in specific context subclasses. // 在特定的上下文子类中,初始化一些特殊的bean onrefresh(); // ... 省略 } catch (beansexception ex) { // ... 省略 } finally { // ... 省略 } } }
这个onrefresh
方法由子类实现,这里是org.springframework.boot.web.servlet.context.servletwebserverapplicationcontext#onrefresh
protected void onrefresh() { super.onrefresh(); try { // 关键时刻来了,创建webserver createwebserver(); } catch (throwable ex) { throw new applicationcontextexception("unable to start web server", ex); } }
暂时看到这里就可以了,后面就是根据具体引入了哪个servlet容器的jar包,来进行启动操作,以tomcat为例
org.springframework.boot.web.embedded.tomcat.tomcatservletwebserverfactory#getwebserver
public webserver getwebserver(servletcontextinitializer... initializers) { if (this.disablembeanregistry) { registry.disableregistry(); } tomcat tomcat = new tomcat(); file basedir = (this.basedirectory != null) ? this.basedirectory : createtempdir("tomcat"); tomcat.setbasedir(basedir.getabsolutepath()); connector connector = new connector(this.protocol); connector.setthrowonfailure(true); tomcat.getservice().addconnector(connector); customizeconnector(connector); tomcat.setconnector(connector); tomcat.gethost().setautodeploy(false); configureengine(tomcat.getengine()); for (connector additionalconnector : this.additionaltomcatconnectors) { tomcat.getservice().addconnector(additionalconnector); } preparecontext(tomcat.gethost(), initializers); return gettomcatwebserver(tomcat); }
这一步走完后,servlet容器基本就被启动了,不过spring容器还没有初始化完成。
总结
无论是servlet容器先启动,还是spring容器先启动,其实都没有关系,区别就是先后。
这两个构成了一个整体,并不是你中有我,或者我中有你的关系。
在servlet容器启动时,或者spring容器启动时,都会开启一个虚拟机实例进程,后面加载的代码,全部都是位于这一个虚拟机进程中,servlet容器会负责监听一个端口,处理http请求,再与我们spring容器对接。
两种方式的启动先后顺序,并没有改变对http请求的处理流程。
也可以看出,这俩的相互独立性。
推荐阅读
-
set容器与map容器的简单应用
-
web应用安全框架选型:Spring Security与Apache Shiro
-
参加Web测试自动化与TDD应用的沙龙心得笔记 TDDWeb单元测试应用服务器百度
-
参加Web测试自动化与TDD应用的沙龙心得笔记 TDDWeb单元测试应用服务器百度
-
性能监控/优化系列——WEB容器/应用性能调优
-
Nginx与HAProxy在web应用中的比较
-
WebApplicationContext、DispatcherServlet与web容器的ServletContext关系
-
Unity容器的简单AOP与DI的应用Demo
-
与Web Worker线程交换数据应用
-
asp.net core系列 43 Web应用 Session分布式存储(in memory与Redis)