1.引言
現在JavaEE開發基本離不開spring全家桶,spring面世以來極大地簡化了開發過程和代碼量,但是隨着spring版本迭代,功能越來越豐富和強大,帶來的問題就是有大量的配置文件需要去開發人員去編寫 ,所以springboot 應運而生,springboot 的理念是約定大於配置,極大地縮減了配置文件的量,借助springboot的自動配置甚至可以實現0配置,快速搭建項目,同時另外一個亮點就是內置servlet容器,不用再將代碼打成war包,然后再去部署到tomcat,再啟動tomcat,直接將項目打成jar包啟動,也是特別方便。
2.內置servlet容器的使用方法
(1)使用約定的也就是默認的容器。默認使用的是tomcat,只需要引入web的依賴就可以自動使用Tomcat作為默認的servlet容器啟動項目
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
(2)使用其他的內置容器
除了tomcat ,springboot 還支持Undertow 和 Jetty,並且可以快速切換成任意一個。做法是排除tomcat,引入想要用的容器jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
(3)不使用內置容器
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 移除嵌入式tomcat插件 --> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <!--添加servlet-api依賴---> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
3.Tomcat 基本結構及其用
Tomcat中最頂層的容器是Server,代表着整個服務器,從上圖中可以看出,一個Server可以包含至少一個Service,用於具體提供服務。Service主要包含兩個部分:Connector和Container,是tomcat的核心。Connector用於處理連接相關的事情,並提供Socket與Request和Response相關的轉化,Container用於封裝和管理Servlet,以及具體處理Request請求。
一個Tomcat中只有一個Server,一個Server可以包含多個Service,一個Service只有一個Container,但是可以有多個Connectors,這是因為一個服務可以有多個連接,如同時提供Http和Https鏈接,也可以提供向相同協議不同端口的連接
多個 Connector 和一個 Container 就形成了一個 Service,有了 Service 就可以對外提供服務了,但是 Service必須依賴server 生存,所以整個 Tomcat 的生命周期由 Server 控制。
4.內置Tomcat啟動流程
tomcat 的啟動需要從main 函數入手,main 函數的run方法實際調用的是 SpringApplication 的run 方法
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); //獲取應用啟動的事件監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); // 發布了一個spring應用啟動事件 listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 准備應用啟動環境(StandardServletEnviroment) ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); //打印啟動banner Banner printedBanner = printBanner(environment); // 根據條件創建applicationContext,這里創建的是AnnotationConfigServletWebServerApplicationContext context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新上下文 refreshContext(context); //再次刷新上下文 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
createApplicationContext(); 創建了一個 AnnotationConfigServletWebServerApplicationContext 這個context 是注解版的servletContext ,它的本質還是applicationContext,繼承和實現關系如下
接下來執行refreshContext,刷新上下文,調用的是AbstractApplicationContext的refresh,這個方法已經不是在springboot 的包內了,而是在spring 中了,這個啟動流程是一個模板方法
public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShutdownMonitor) { this.prepareRefresh(); // 獲取beanFactory,這個就是springIoc容器的祖先 ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); this.prepareBeanFactory(beanFactory); try { //為容器子類添加特殊的postprocess this.postProcessBeanFactory(beanFactory); //執行postprocess this.invokeBeanFactoryPostProcessors(beanFactory); // 為Bean 添加后置處理器 this.registerBeanPostProcessors(beanFactory); // 初始化國際化信息源 this.initMessageSource(); //初始化時間傳播器 this.initApplicationEventMulticaster(); // 刷新,這個方法是留給子類擴展使用的,tomcat的啟動就在這里執行 this.onRefresh(); //注冊時間監聽器 this.registerListeners(); //初始化單實例Bean,循環依賴,后置處理器,initMethod,awear 接口的實現,自動裝配,都在這里完成,boot 在這里加載了一些默認的bean,mvc相關的,條件注解相關的,共26個 this.finishBeanFactoryInitialization(beanFactory); //初始化和發布Bean的生命周期事件 this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } }
接下來重點看onRefresh如何啟動tomcat,子類ServletWebServerApplicationContext 復寫了這個方法
protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
createWebServer
private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { ServletWebServerFactory factory = getWebServerFactory(); this.webServer = factory.getWebServer(getSelfInitializer()); } else if (servletContext != null) { try { getSelfInitializer().onStartup(servletContext); } catch (ServletException ex) { throw new ApplicationContextException("Cannot initialize servlet context", ex); } } initPropertySources(); }
先獲取ServletWebServerFactory,然后通過工廠獲取具體的webServer,此時獲取的是TomcatServletWebServerFactory,同時這個接口的實現還有undertow和jetty的工廠。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); }
主要就是創建tomcat 的server,service,connector,engine 這些核心組件,然后調用 getTomcatWebServer,初始化和啟動tomcat,
private void initialize() throws WebServerException { logger.info("Tomcat initialized with port(s): " + getPortsDescription(false)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
最后執行模板方法的最后一步finishRefresh,這個方法也被子類ServletWebServerApplicationContext復寫了,
protected void finishRefresh() { super.finishRefresh(); WebServer webServer = startWebServer(); if (webServer != null) { publishEvent(new ServletWebServerInitializedEvent(webServer, this)); } }
private WebServer startWebServer() { WebServer webServer = this.webServer; if (webServer != null) { webServer.start(); } return webServer; }
此時tomcat 還沒有真正啟動,當執行webServer.start()時會找到context ,並且load,此時才算項目啟動了
public void start() throws WebServerException { synchronized (this.monitor) { if (this.started) { return; } try { addPreviouslyRemovedConnectors(); Connector connector = this.tomcat.getConnector(); if (connector != null && this.autoStart) { performDeferredLoadOnStartup(); } checkThatConnectorsHaveStarted(); this.started = true; logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '" + getContextPath() + "'"); } catch (ConnectorStartFailedException ex) { stopSilently(); throw ex; } catch (Exception ex) { if (findBindException(ex) != null) { throw new PortInUseException(this.tomcat.getConnector().getPort()); } throw new WebServerException("Unable to start embedded Tomcat server", ex); } finally { Context context = findContext(); ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } } }
最后再到main函數的
listeners.started(context);
callRunners(context, applicationArguments);
這兩個方法,分別執行容器啟動的監聽器的回調,和執行 ApplicationRunner 和 ApplicationRunner 這些類型Bean的調用。至此基本描述了springboot 中tomcat 的啟動過程,順帶些了一下spring Ioc容器的啟動流程。