SpringBoot如何使用內(nèi)嵌Tomcat問題
前言
Springboot使用了內(nèi)置tomcat,在服務部署時無需額外安裝Tomcat,直接啟動jar包即可。
這里會有很多疑問
- 1 內(nèi)置Tomcat長什么樣,它與原來的Tomcat有啥區(qū)別
- 2 Springboot是如何使用的內(nèi)置tomcat
- 3 DispatcherServlet是如何加載到tomcat容器的
對于以上的種種疑問在這里做了一個總結
一、原來的Tomcat啟動流程
1 運行catalina.sh start腳本 最終會執(zhí)行Bootstrap的mian方法
eval exec "\"$_RUNJDB\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \ -classpath "$CLASSPATH" \ -sourcepath "$CATALINA_HOME"/../../java \ -Dcatalina.base="$CATALINA_BASE" \ -Dcatalina.home="$CATALINA_HOME" \ -Djava.io.tmpdir="$CATALINA_TMPDIR" \ //這里會運行Bootstrap的main方法 并傳入start參數(shù) org.apache.catalina.startup.Bootstrap "$@" start fi
2 執(zhí)行Bootstrap的mian方法 構建Catalina對象 并執(zhí)行其load和start方法
//全局變量 用于保存Bootstrap實例 private static volatile Bootstrap daemon = null; //全局變量 用于保存Catalina對象 private Object catalinaDaemon = null; public static void main(String args[]) { synchronized (daemonLock) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { //這里 構建Catalina對象并賦值給全局變量catalinaDaemon bootstrap.init(); } catch (Throwable t) { ... } //這里初始化了全局變量 daemon = bootstrap; } else { ... } try { ... if (command.equals("start")) { daemon.setAwait(true); //本質(zhì)是調(diào)用了Catalina對象的load方法 daemon.load(args); //本質(zhì)上是調(diào)用了Catalina的start方法 daemon.start(); ... } } catch (Throwable t) { ... } ... } //構建Catalina對象并賦值給全局變量catalinaDaemon public void init() throws Exception { ... //通過反射構建Catalina對象 Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.getConstructor().newInstance(); ... //這里把Catalina對象賦予了全局變量catalinaDaemon catalinaDaemon = startupInstance; } //本質(zhì)是調(diào)用了Catalina對象的load方法 private void load(String[] arguments) throws Exception { // Call the load() method String methodName = "load"; ... Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes); //這里就是調(diào)用了Catalina對象的load方法 method.invoke(catalinaDaemon, param); } //本質(zhì)上是調(diào)用了Catalina的start方法 public void start() throws Exception { Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null); method.invoke(catalinaDaemon, (Object [])null); }
3 Catalina的load方法
//全局變量Server對象 該對象通過解析server.xml生成 protected Server server = null; public void load() { ... // Parse main server.xml // 解析server.xml文件 初始化server對象 parseServerXml(true); Server s = getServer() ... // Start the new server try { getServer().init(); } catch (LifecycleException e) { ... } ... }
server.xml的結構是一個4層嵌套的樹狀結構。一層也就是根節(jié)點是server元素,二層是service元素,三層是Engine元素,四層是Host元素。
最終其被解析Server對象。該對象內(nèi)部包含一組service對象,每個service對象包含一個Engine對象,每個Engine對象包含一組Host對象。
其實每個Host對象還對應一組Context對象也就是我們常說的Servlet容器,只是在server.xml文件中體現(xiàn)的比較隱晦。Host對象有一個屬性叫做appBase,該屬性的默認值是webapps,最終解析時會去Tomcat根目錄下的webapps文件中找web.xml,找到一個就生成一個Context對象
4 Catalina的start方法
//本質(zhì)上就是調(diào)用server的start方法 public void start() { ... // Start the new server try { getServer().start(); } catch (LifecycleException e) { ... } ... } //返回全局變量server public Server getServer() { return server; }
這里蘊含這一個設計模式值得一提,通過load方法可以知道Server內(nèi)部有一組service,每個service內(nèi)部有一個Engine,每個Engine內(nèi)部有一組host,每個host內(nèi)部有一組context。
這里提到的每一個對象都有init方法和start方法,在server的start方法被調(diào)用后需要執(zhí)行其下每個service對象的init方法和start方法,當service的start方法被調(diào)用后需要執(zhí)行其下Engine的init方法和start方法以此類推一直到調(diào)用完Context的init方法和start方法。
Tomcat使用抽象模板的設計模式完成了該流程的實現(xiàn)。
首先看看抽象模板類LifecycleBase,上述提到的所有對象都繼承該類,該類有4個主要方法,其中start是模板類的核心方法
public abstract class LifecycleBase implements Lifecycle { //抽象模板類提供的公共方法 public final synchronized void init() throws LifecycleException { if (!state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try { setStateInternal(LifecycleState.INITIALIZING, null, false); //該方法是一個抽象方法由子類完成實現(xiàn) //server類的實現(xiàn)方式 就是便利其內(nèi)部的sercie對象 挨個調(diào)用其init方法 //service類的實現(xiàn)方法 就是調(diào)用engine的 init方法 //engine的實現(xiàn)方法 就是便利其內(nèi)部的host對象 挨個調(diào)用其init方法 //以此類推。。。 initInternal(); //這里會發(fā)生狀態(tài)變更 防止重復init用的 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) { handleSubClassException(t, "lifecycleBase.initFail", toString()); } } //抽象模板類提供的公共方法 public final synchronized void start() throws LifecycleException { if (state.equals(LifecycleState.NEW)) { //start方法中會先執(zhí)行init方法 init(); } else if (state.equals(LifecycleState.FAILED)) { ... } else if (!state.equals(LifecycleState.INITIALIZED) && ... } ... try { setStateInternal(LifecycleState.STARTING_PREP, null, false); //該方法是一個抽象方法由子類完成實現(xiàn) //server類的實現(xiàn)方式 就是便利其內(nèi)部的sercie對象 挨個調(diào)用其start方法 //service類的實現(xiàn)方法 就是調(diào)用engine的 start方法 //engine的實現(xiàn)方法 就是便利其內(nèi)部的host對象 挨個調(diào)用其start方法 //以此類推。。。 startInternal(); ... } catch (Throwable t) { ... } } //子類實現(xiàn) protected abstract void initInternal() throws LifecycleException; //子類實現(xiàn) protected abstract void startInternal() throws LifecycleException; }
基于對LifecycleBase的4個方法的分析,我們看看當server的start方法被調(diào)用時會發(fā)生什么
1 server的start方法會調(diào)用其父類LifecycleBase的公共start方法
2 接著會調(diào)用LifecycleBase的init方法
3 接著會調(diào)用LifecycleBase的initInternal方法,該方法由子類server實現(xiàn),便利其下的service對象挨個調(diào)用init方法
4 service對象的init方法是由父類LifecycleBase實現(xiàn)的,所以會執(zhí)行LifecycleBase的init方法。這里有一個狀態(tài)變更即元素的state狀態(tài)由LifecycleState.NEW變成了LifecycleState.INITIALIZING防止在start方法中再次執(zhí)行init方法
5 以此類推最終所有元素的init方法會被調(diào)用并且狀態(tài)變成了LifecycleState.INITIALIZING,最終又回到了server的start方法此時init方法已經(jīng)執(zhí)行完了
6繼續(xù)向下走執(zhí)行startInternal方法,該方法由子類server實現(xiàn),便利其下的service對象挨個調(diào)用start方法
7start方法由父類LifecycleBase實現(xiàn)的,所以會執(zhí)行LifecycleBase的start方法,此時因為對象狀態(tài)已經(jīng)不是new狀態(tài)了,init方法不會執(zhí)行,繼續(xù)執(zhí)行startInternal方法,以此類推最終所有元素的start方法會被執(zhí)行
最終各個元素的init和start方法都被執(zhí)行了一遍
二、內(nèi)嵌Tomcat
阿帕奇提供了一個類,名字就叫Tomcat。該類和Catalina類十分相似,內(nèi)部也有一個Server對象并且提供了start方法,本質(zhì)也是調(diào)用的server.start。
接下來看看這個類
public class Tomcat { //全局變量 protected Server server; //啟動方法 public void start() throws LifecycleException { getServer(); //本質(zhì)是server的start方法 server.start(); } //重點在后邊的這幾個方法 //獲取server public Server getServer() { ... if (server != null) { return server; } //這里直接new對象了 不像Catalina那樣需要解析server.xml文件 server = new StandardServer(); initBaseDir(); ... //順便為其創(chuàng)建了一個service對象 Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; } //獲取service 內(nèi)部調(diào)用了getServer 一樣的道理 沒有就new public Service getService() { return getServer().findServices()[0]; } //獲取引擎 一樣的邏輯 沒有就new public Engine getEngine() { Service service = getServer().findServices()[0]; if (service.getContainer() != null) { return service.getContainer(); } Engine engine = new StandardEngine(); engine.setName( "Tomcat" ); engine.setDefaultHost(hostname); engine.setRealm(createDefaultRealm()); service.setContainer(engine); return engine; } //獲取host 同上沒有就new public Host getHost() { Engine engine = getEngine(); if (engine.findChildren().length > 0) { return (Host) engine.findChildren()[0]; } Host host = new StandardHost(); host.setName(hostname); getEngine().addChild(host); return host; } }
最終可以發(fā)現(xiàn)內(nèi)嵌Tomcat本質(zhì)上和Catalina對象一樣,都是通過初始化一個Server對象然后調(diào)用Server對象的start方法完成tomcat啟動的。
區(qū)別就是初始化Server的過程不在需要解析server.xml文件了,各種get就能完成初始化。
三、Springboot啟動Tomcat的時機
springboot啟動類的mian方法中會執(zhí)行SpringApplication.run方法,該方法會創(chuàng)建并啟動一個容器[AnnotationConfigServletWebServerApplicationContext],容器啟動會執(zhí)行祖先類AbstractApplicationContext的refresh方法,該方法中的onRefresh方法被AnnotationConfigServletWebServerApplicationContext的父類ServletWebServerApplicationContext重寫了,內(nèi)置Tomcat就在onRefresh方法中被啟動了
接下來看下ServletWebServerApplicationContext的onRefresh方法
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { //重寫了父類的方法 @Override protected void onRefresh() { super.onRefresh(); try { //重點在這里 createWebServer(); } .... } //創(chuàng)建WebServer,其內(nèi)部封裝了Tomcat對象 private void createWebServer() { WebServer webServer = this.webServer; //這里取Tomcat容器對象 首次是取不到的 ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { //獲取工廠對象 該對象是自動裝配中裝配的 默認是TomcatServletWebServerFactory ServletWebServerFactory factory = getWebServerFactory(); //通過工廠創(chuàng)建WebServer對象,WebServer對象創(chuàng)建完Tomcat就會啟動 所以重點跟下這里 this.webServer = factory.getWebServer(getSelfInitializer()); getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { ... } initPropertySources(); } }
最終會通過TomcatServletWebServerFactory工廠類構建WebServer對象,跟getWebServer方法
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { //主要方法 @Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { Registry.disableRegistry(); } //內(nèi)嵌Tomcat對象 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); //這里可以知道 其創(chuàng)建了server對象和service對象 并為service對象設置了connector tomcat.getService().addConnector(connector); customizeConnector(connector); tomcat.setConnector(connector); //這里可以知道 其創(chuàng)建了engine對象和host對象 tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } //這里創(chuàng)建了Servlet容器 prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } //創(chuàng)建Servlet容器 protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); //直接new了一個容器 該類是StandardContext的子類 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } ... ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); //將容器放入host對象中 host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); } //構造TomcatWebServer對象將Tomcat對象封裝其中 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown()); } }
工廠類中會創(chuàng)建Tomcat對象,并初始化其內(nèi)部的Server對象。
最終將Tomcat對象封裝到TomcatWebServer中返回,接著看下TomcatWebServer的構造器
public class TomcatWebServer implements WebServer { //用于封裝Tomcat對象 private final Tomcat tomcat; public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) { Assert.notNull(tomcat, "Tomcat Server must not be null"); //初始化Tomcat對象 this.tomcat = tomcat; this.autoStart = autoStart; this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null; //重點看這里 這里啟動了Tomcat initialize(); } //啟動了Tomcat private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { ... // Start the server to trigger initialization listeners this.tomcat.start(); ... } catch (Exception ex) { ... } } } }
到這里可以知道工廠類在構造WebServer之后,Tomcat就被啟動了,這里就是內(nèi)嵌Tomcat的啟動時機。
和原來相比,原來的啟動類是Tomcat,再由Tomcat啟動觸發(fā)容器的創(chuàng)建和啟動,而現(xiàn)在的啟動類是容器,由容器啟動觸發(fā)了Tomcat的啟動
四、SpringBoot中的Tomcat如何加載Servlet
4.1 Servlet3.0標準可以不使用web.xml完成Servlet的注冊
早期的項目一個web.xml文件最終被解析成一個Context對象【容器對象】,web.xml內(nèi)部可以配置很多servlet,最終在解析完web.xml會將解析出來的servlet對象注冊到容器中。
而springboot項目中并沒有web.xml文件,所以引發(fā)了一個問題。
Servlet對象是如何被注冊到Tomcat容器中的呢?
servlet3.0標準中提供了一個不用web.xml也能加載Servlet的方法。
需要三步:
- 1 寫一個類實現(xiàn)ServletContainerInitializer接口
- 2 實現(xiàn)ServletContainerInitializer接口的onStartup方法
- 3 在/META-INF/services目錄下創(chuàng)建javax.servlet.ServletContainerInitializer文件,將實現(xiàn)類的全名稱寫入到配置文件中
實現(xiàn)完以上步驟,Tomcat啟動后會回調(diào)實現(xiàn)類的onStartup方法,并將Servlet容器的裝飾類【ServletContext】當作入?yún)魅雘nStartup方法。
看下ServletContext這個類的方法
public interface ServletContext { public ServletRegistration.Dynamic addServlet(String servletName, String className); public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet); public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass); }
這個類有很多方法,其中新增servlet的就有3個重載方法。
也就是說我們寫的實現(xiàn)類在實現(xiàn)onStartup的方法中就可以調(diào)用ServletContext的addServlet方法完成Servlet的注冊了。
4.2 SpringBoot中的ServletContainerInitializer的實現(xiàn)類
那么SpringBoot中的Tomcat就是用這個方式加載的Servlet嗎?是也不全是。
springboot確實搞了一個實現(xiàn)類TomcatStarter來實現(xiàn)ServletContainerInitializer接口并實現(xiàn)了onStartup方法。
但是和web.xml文件一樣javax.servlet.ServletContainerInitializer文件在springboot項目中也沒有。
其實與寫javax.servlet.ServletContainerInitializer文件的方式相比還有一種更加簡單粗暴的方式,在Context對象創(chuàng)建好后直接調(diào)用其addServletContainerInitializer方法將ServletContainerInitializer的實現(xiàn)類傳進去。
再次看下創(chuàng)建Context對象的地方
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { //創(chuàng)建Servlet容器 protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); //直接new了一個容器 該類是StandardContext的子類 TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } ... ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); //將容器放入host對象中 host.addChild(context); //這個方法之前沒根 這次下這個方法 configureContext(context, initializersToUse); postProcessContext(context); } protected void configureContext(Context context, ServletContextInitializer[] initializers) { //創(chuàng)建了ServletContainerInitializer的實現(xiàn)類 TomcatStarter starter = new TomcatStarter(initializers); ... //這里直接將其放入到了容器中 context.addServletContainerInitializer(starter, NO_CLASSES); ... } }
4.3 ServletContainerInitializer的實現(xiàn)類執(zhí)行onStartup方法的時機
之前分析過server.start方法執(zhí)行后各個元素的init、start、initInternal、startInternal都會被調(diào)用,Context對象也不例外。
接著看下Context的startInternal方法。
雖然我們的Context對象類型是TomcatEmbeddedContext,但是startInternal方法是由其父類StandardContext實現(xiàn)的。
所以看下StandardContext類
public class StandardContext extends ContainerBase implements Context, NotificationEmitter { //內(nèi)部有一個集合 用于保存所有ServletContainerInitializer的實現(xiàn)類 private Map<ServletContainerInitializer,Set<Class<?>>> initializers = new LinkedHashMap<>(); //還記得這個方法嗎TomcatEmbeddedContext就是通過該方法將TomcatStarter添加進來的 public void addServletContainerInitializer( ServletContainerInitializer sci, Set<Class<?>> classes) { initializers.put(sci, classes); } //Tomcat啟動時會執(zhí)行該方法 這個方法巨長無比 我只把關鍵的保留了 protected synchronized void startInternal() throws LifecycleException { //便利集合 for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) { try { //集合中的key就是ServletContainerInitializer的實現(xiàn)類 這里調(diào)用了onStartup方法 entry.getKey().onStartup(entry.getValue(), //最后看下getServletContext方法,看看容器的裝飾類到底是什么 getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } } //這里可以知道容器最終把自己封裝到了ApplicationContext對象中, //最終將ApplicationContext對象暴露給ServletContainerInitializer實現(xiàn)類 public ServletContext getServletContext() { if (context == null) { context = new ApplicationContext(this); if (altDDName != null) context.setAttribute(Globals.ALT_DD_ATTR,altDDName); } return context.getFacade(); } }
也就是容器對象啟動后,在執(zhí)行其startInternal方法是會調(diào)用ServletContainerInitializer的實現(xiàn)類的onStartup方法并將容器對象的裝飾類ApplicationContext當作入?yún)魅雘nStartup方法。
4.4 分析TomcatStarter的onStartup方法
鋪墊了那么多,我們看下TomcatStarter的onStartup方法
class TomcatStarter implements ServletContainerInitializer { //一堆ServletContextInitializer接口的實現(xiàn)類 private final ServletContextInitializer[] initializers; //構造器 初始化內(nèi)部的initializers屬性 TomcatStarter(ServletContextInitializer[] initializers) { this.initializers = initializers; } //這個方法里沒有任何servlet的添加操作,而是便利了initializers,并執(zhí)行initializers每一個實例的onStartup方法,將servletContext當入?yún)魅肫渲? @Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { .... } } }
和想象中的不一樣,onStartup方法中并沒有添加servlet,而是將ServletContext對象再次傳給了ServletContextInitializer的實現(xiàn)類去完成后續(xù)工作。
為什么要這樣做呢?
其實原因很簡單,到目前為止要想拿到ServletContext對象就必須實現(xiàn)ServletContainerInitializer接口。
而ServletContainerInitializer接口并不是spring的類。
所以spring搞了一個自己的接口ServletContextInitializer并且內(nèi)部也有一個待實現(xiàn)的方法onStartup。
spring想實現(xiàn)的目標是所有實現(xiàn)了ServletContextInitializer接口的bean都能拿到ServletContext對象。
最終借助TomcatStarter類中的onStartup完成了實現(xiàn)。
大致看下實現(xiàn)過程,起點在ServletWebServerApplicationContext類中
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { //最終這個私有方法會被調(diào)用,可以看出如果TomcatStarter中的onStartup方法能調(diào)用到該方法,上述說的spirng目的就達成了 private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); //這里拿到容器中所有實現(xiàn)了ServletContextInitializer接口的bean并依次執(zhí)行其onStartup方法 入?yún)⑹莝ervletContext for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } } //ServletContextInitializer本身是一個@FunctionalInterface //這里將上述的私有方法封裝成了一個ServletContextInitializer實例 很感慨既然還能這樣干 private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } //還記得這個方法嗎,這里通過factory完成了WebServer的創(chuàng)建,也就是tomcat啟動的位置 private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); //這里將ServletContextInitializer實例傳入到了TomcatServletWebServerFactory中 this.webServer = factory.getWebServer(getSelfInitializer()); ... } .... } }
上述可以看到spring用一種很詭異的方式將一個私有方法封裝成了ServletContextInitializer實例并傳給了TomcatServletWebServerFactory的getWebServer方法中,再次根下TomcatServletWebServerFactory類。
這次主要看ServletContextInitializer實例的傳遞過程
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware { //這里ServletContextInitializer實例被傳入 public WebServer getWebServer(ServletContextInitializer... initializers) { ... //被傳入到該方法 prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } protected void prepareContext(Host host, ServletContextInitializer[] initializers) { //這里做了依次封裝 之前的ServletContextInitializer實例就在其中 ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); //根這里 configureContext(context, initializersToUse); postProcessContext(context); } //做了一層擴展 protected final ServletContextInitializer[] mergeInitializers(ServletContextInitializer... initializers) { List<ServletContextInitializer> mergedInitializers = new ArrayList<>(); mergedInitializers.add((servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)); mergedInitializers.add(new SessionConfiguringInitializer(this.session)); //被傳入到mergedInitializers集合中 mergedInitializers.addAll(Arrays.asList(initializers)); mergedInitializers.addAll(this.initializers); //集合轉數(shù)組 return mergedInitializers.toArray(new ServletContextInitializer[0]); } //最終會將ServletContextInitializer傳入TomcatStarter的構造函數(shù),和之前說的完全對應上了 protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); ... } }
通過對TomcatStarter的onStartup方法的分析可以知道,所有實現(xiàn)了ServletContextInitializer接口的bean都能拿到ServletContext對象,完成servlet對象的添加
4.5 DispatcherServlet如何加載到Tomcat容器
springboot會自動裝配springmvc,而springmvc的核心類就是DispatcherServlet。
上邊鋪墊了那么多最終看看DispatcherServlet是如何加載到tomcat中的
首先看下自動裝配類DispatcherServletAutoConfiguration
...省略注解 public class DispatcherServletAutoConfiguration { ...省略注解 protected static class DispatcherServletConfiguration { //這里創(chuàng)建了DispatcherServlet類 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { ...省略DispatcherServlet構造內(nèi)容 return dispatcherServlet; } ... ...省略注解 protected static class DispatcherServletRegistrationConfiguration { ...省略注解 //重點是這個類,上邊的DispatcherServlet會被傳入到該類中,最終由該類完成DispatcherServlet向Tomcat容器的注冊 public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) { DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath()); registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME); registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); multipartConfig.ifAvailable(registration::setMultipartConfig); return registration; } } } }
可以看到自動裝配時向spring容器中注冊了DispatcherServletRegistrationBean類,該類構造器中包含DispatcherServlet對象。
看下DispatcherServletRegistrationBean類的家譜
可以看到該類實現(xiàn)了ServletContextInitializer接口也就是其能拿到Tomcat容器對象。
看下其祖先類RegistrationBean的onStartup方法
public abstract class RegistrationBean implements ServletContextInitializer, Ordered { public final void onStartup(ServletContext servletContext) throws ServletException { String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } //根這個方法 該方法由子類DynamicRegistrationBean實現(xiàn) register(description, servletContext); } }
DynamicRegistrationBean類
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean { protected final void register(String description, ServletContext servletContext) { //servlet注冊在這里完成 該方法由子類ServletRegistrationBean實現(xiàn) //servlet注冊完后會返回一個registration對象,用于完成servlet-mapping的配置 D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } //servlet的mapping配置在這里完成 該方法由子類ServletRegistrationBean實現(xiàn) configure(registration); } }
ServletRegistrationBean類
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> { //這里可以看到servletContext.addServlet方法終于被調(diào)用了 @Override protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) { String name = getServletName(); //this.servlet就是DispatcherServlet return servletContext.addServlet(name, this.servlet); } //這里來配置servlet-mapping @Override protected void configure(ServletRegistration.Dynamic registration) { super.configure(registration); String[] urlMapping = StringUtils.toStringArray(this.urlMappings); if (urlMapping.length == 0 && this.alwaysMapUrl) { urlMapping = DEFAULT_MAPPINGS; } if (!ObjectUtils.isEmpty(urlMapping)) { registration.addMapping(urlMapping); } registration.setLoadOnStartup(this.loadOnStartup); if (this.multipartConfig != null) { registration.setMultipartConfig(this.multipartConfig); } } }
總結
1 springboot使用內(nèi)嵌Tomcat完成了tomcat的啟動。內(nèi)嵌Tomcat本質(zhì)上和正常Tomcat的Catalina對象一樣都是通過初始化內(nèi)部的server對象,最終調(diào)用server對象的start方法來完成啟動的。區(qū)別就是server對象的創(chuàng)建構成,前者直接new后者解析server.xml文件
2 springboot中tomcat的啟動時機是在容器啟動時,執(zhí)行onRefresh方法中。創(chuàng)建webServer對象時啟動的。
3 springboot基于servlet3.0標準。創(chuàng)建了ServletContainerInitializer的實現(xiàn)類TomcatStarter最終拿到Tomcat容器對象
4 springboot基于TomcatStarter拿到的tomcat容器對象做了進一步優(yōu)化。最終實現(xiàn)了所有實現(xiàn)ServletContextInitializer接口的bean都能拿到tomcat容器
5 ServletContextInitializer的實現(xiàn)類之一DispatcherServletRegistrationBean完成了DispatcherServlet向tomcat容器的注冊
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Intellij IDEA 如何通過數(shù)據(jù)庫表生成帶注解的實體類(圖文詳細教程)
這篇文章主要介紹了Intellij IDEA 如何通過數(shù)據(jù)庫表生成帶注解的實體類(圖文詳細教程),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11SpringBoot整合mybatisplus和druid的示例詳解
這篇文章主要介紹了SpringBoot整合mybatisplus和druid的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-08-08MyBatis使用annonation定義類型映射的簡易用法示例
這篇文章主要介紹了MyBatis使用annonation定義類型映射的簡易用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09