spring boot 支援目前主流的 servlet 容器,包括 tomcat、jetty、undertow,可以在我們的專案中方便地整合這些 servlet 容器,減少了開發、運維的工作量。而傳統的應用開發,需要經過繁鎖的操作步驟:安裝tomcat –> 修改tomcat 設定–> 部署war 套件–> 啟動tomcat –> 運維……,這個工作工作量不小,尤其是叢集部署、應用遷移的時候。而採用 spring boot 之後,一切變得如此簡單,打包 –> java -jar –> 運維,只需要一個 jar 包便可以隨意部署安裝。
在分析原始碼前,我們先來了解下 spring 的 SPI 機制。我們知道,jdk 為了方便應用程式進行擴展,提供了預設的 SPI 實作(ServiceLoader),dubbo 也有自己的 SPI。 spring 也是如此,他為我們提供了SpringFactoriesLoader
,允許開發人員透過META-INF/spring.factories
檔案進行擴展,下面舉一個例子方便理解
#假如,我想要在spring 容器中加入一個ApplicationContextInitializer
做一些初始化工作,我們可以藉助spring 提供的這個SPI 功能完成這個需求。
首先,在專案中創建META-INF/spring.factories
文件,文件內容如下所示:
org.springframework.context.ApplicationContextInitializer= \
我們再寫個test case,便可以透過SPI 的方式取得我們定義的ApplicationContextInitializer
。看似很簡單的一個功能,但是spring boot 正是利用這個強大的擴展點,在spring framework 的基礎上為我們集成了常用的開源框架
@Test public void testSpringSpi() { List<ApplicationListener> listeners = SpringFactoriesLoader.loadFactories( ApplicationListener.class, ClassUtils.getDefaultClassLoader() ); System.out.println( listeners );
我們再來看看這個SpringFactoriesLoader
,關鍵程式碼如下所示,它透過讀取META-INF/spring.factories
文件,並且尋找方法參數指定的class,然後建立對應的實例對象,並且傳回。此外,也支援排序,可以使用以下幾種方式進行排序
org.springframework.core.Ordered:實作該介面
org .springframework.core.annotation.Order:註解
javax.annotation.Priority:註解
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) { List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); List<T> result = new ArrayList<T>(factoryNames.size()); for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result;
接下來,我們來分析下spring boot 是如何利用SPI 機制整合tomcat
在分析tomcat 整合的原始碼之前,我們先來了解下EmbeddedServletContainer
EmbeddedServletContainer:
spring 以EmbeddedServletContainer
封裝了內嵌的servlet 容器,提供了start
、stop
等介面用於控制容器的生命週期,並且spring 內建了tomcat 、jetty、undertow 容器的實現,類別圖所下所示
我們再來看看spring boot 中最常用的SpringBootApplication
註解,原來是多個註解的綜合體,而這個EnableAutoConfiguration
便是spring boot 用做自動化配置的註解
@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { // code......
我們在spring-boot-autoconfigure
模組可以看到大量的SPI 配置,部分如下所示
# #原來# Auto Configure
,\\\\
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAuto#Configuration,\\autoconfigure.web.EmbeddedServletContainerAuto
EnableAutoConfiguration註解引入了
EmbeddedServletContainerAutoConfiguration,而這個便是內嵌servlet 容器的設定類,tomcat、jetty、undertow 都在這個類別上面,透過
@ConditionalOnClass註解載入不同的servlet 容器。但是,這個類別只是註冊了
TomcatEmbeddedServletContainerFactory,不足以幫助我們解除所有的困惑。不要著急,我們先來看看
TomcatEmbeddedServletContainerFactory的類別圖。
EmbeddedServletContainer,即用於建立一個內嵌的Servlet 容器,這個介面裡面只有一個
getEmbeddedServletContainer方法
EmbeddedServletContainer
##說端口、上下文路徑等
分析了上面兩個接口,原來創建servlet 容器的工作是由
完成的,看下
getEmbeddedServletContainer方法的呼叫堆疊。在
EmbeddedWebApplicationContext###中重寫了###GenericWebApplicationContext#onRefresh()###方法,並且呼叫###getEmbeddedServletContainer###方法建立 servlet 容器,我們接下來分析這個建立過程。 ######關鍵程式碼如下(省略異常處理):###EmbeddedWebApplicationContext.java @Override protected void onRefresh() { super.onRefresh(); createEmbeddedServletContainer(); } private void createEmbeddedServletContainer() { EmbeddedServletContainer localContainer = this.embeddedServletContainer; ServletContext localServletContext = getServletContext(); if (localContainer == null && localServletContext == null) { // 从容器中获取bean,如果使用tomcat则返回TomcatEmbeddedServletContainerFactory EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer()); } else if (localServletContext != null) { getSelfInitializer().onStartup(localServletContext); } initPropertySources();###我們先畫出主要的流程圖###
由上图可知,EmbeddedWebApplicationContext
在执行onRefresh
方法的时候,首先调用父类的onRefresh
,然后从容器中获取EmbeddedServletContainerFactory
的实现类。由于我们在 classpath 下面可以获取 tomcat 的 jar 包,因此EmbeddedServletContainerAutoConfiguration
会在 spring 容器中注册TomcatEmbeddedServletContainerFactory
这个 bean。然后,由它创建TomcatEmbeddedServletContainer
,我们来看看具体的创建过程,代码如下所示:
TomcatEmbeddedServletContainerFactory.java @Override public EmbeddedServletContainer getEmbeddedServletContainer( ServletContextInitializer... initializers) { Tomcat tomcat = new Tomcat(); // 实例化 apache Tomcat File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat")); tomcat.setBaseDir(baseDir.getAbsolutePath()); // 创建 Connector 组件,默认使用org.apache.coyote.http11.Http11NioProtocol Connector connector = new Connector(this.protocol); tomcat.getService().addConnector(connector); // 支持对 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 getTomcatEmbeddedServletContainer(tomcat);
首先是实例化Tomcat
对象,然后创建Connector
组件,并且对Connector
进行相关的参数设置,同时也允许我们通过TomcatConnectorCustomizer
接口进行自定义的设置。OK,创建了Tomcat
实例之后,需要创建TomcatEmbeddedServletContainer
,它依赖Tomcat
对象,在构造方法中便会启动 Tomcat 容器,从而完成各个组件的启动流程
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); } private void initialize() throws EmbeddedServletContainerException { synchronized (this.monitor) { addInstanceIdToEngineName(); // Remove service connectors to that protocol binding doesn't happen yet removeServiceConnectors(); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); Context context = findContext(); ContextBindings.bindClassLoader(context, getNamingToken(context), getClass().getClassLoader()); // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); }
以上是SpringBoot整合tomcat的方法是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!