切換語言為:簡體
SpringBoot的嵌入式Web伺服器實現原理

SpringBoot的嵌入式Web伺服器實現原理

  • 爱糖宝
  • 2024-07-24
  • 2071
  • 0
  • 0

我們SpringBoot應用都是透過SpringApplication.run()這一行程式碼啟動起來的,那麼我們有理由懷疑實現邏輯就在這個裡面

應用跑起來-run()

 public ConfigurableApplicationContext run(String... args) {
 
     // 省略程式碼
     ...
         
     ConfigurableApplicationContext context = null;
 
     // 省略程式碼
     ...
     try {
         ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
         // 準備上下文環境
         ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
         // 配置忽略的Bean資訊
         configureIgnoreBeanInfo(environment);
         // 列印Banner資訊
         Banner printedBanner = printBanner(environment);
         // 建立ioc容器
         context = createApplicationContext();
         // 準備ioc容器【核心】
         prepareContext(context, environment, listeners, applicationArguments, printedBanner);
         // 重新整理ioc容器
         refreshContext(context);
 
         // 省略程式碼
         ...
     }
 
     // 省略程式碼
     ...
     return context;
 }

重新整理ioc容器-refreshContext()

 private void refreshContext(ConfigurableApplicationContext context) {
     if (this.registerShutdownHook) {
         try {
             context.registerShutdownHook();
         }
         catch (AccessControlException ex) {
             // Not allowed in some environments.
         }
     }
     // 進一步呼叫 refresh()
     refresh((ApplicationContext) context);
 }
 
 protected void refresh(ApplicationContext applicationContext) {
     Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
     // 進一步呼叫 refresh()
     refresh((ConfigurableApplicationContext) applicationContext);
 }
 
 // 呼叫 ConfigurableApplicationContext 的refresh()
 protected void refresh(ConfigurableApplicationContext applicationContext) {
     applicationContext.refresh();
 }

我們可以發現最終呼叫了ConfigurableApplicationContextrefresh()來完成重新整理容器的操作

ServletWebServerApplicationContext

 public final void refresh() throws BeansException, IllegalStateException {
     try {
         // 呼叫父類的 refresh() 
         super.refresh();
     }
     catch (RuntimeException ex) {
         // 如果出現異常,並且web伺服器建立好了,就會停止當前web伺服器
         WebServer webServer = this.webServer;
         if (webServer != null) {
             webServer.stop();
         }
         throw ex;
     }
 }

AbstractApplicationContext

Spring容器重新整理經典12大步

 public void refresh() throws BeansException, IllegalStateException {
     synchronized (this.startupShutdownMonitor) {
         // Prepare this context for refreshing.
         // 1、準備重新整理
         prepareRefresh();
 
         // Tell the subclass to refresh the internal bean factory.
         // 2、獲取一個BeanFactory
         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
         // Prepare the bean factory for use in this context.
         // 3、準備BeanFactory
         prepareBeanFactory(beanFactory);
 
         try {
             // Allows post-processing of the bean factory in context subclasses.
             // 4、BeanFactory的後置處理【留給子類的擴充套件】
             postProcessBeanFactory(beanFactory);
 
             // Invoke factory processors registered as beans in the context.
             // 5、執行BeanFactory的後置處理器
             invokeBeanFactoryPostProcessors(beanFactory);
 
             // Register bean processors that intercept bean creation.
             // 6、註冊Bean的後置處理器
             registerBeanPostProcessors(beanFactory);
 
             // Initialize message source for this context.
             // 7、初始化MessageSource【國際化相關】
             initMessageSource();
 
             // Initialize event multicaster for this context.
             // 8、初始化事件派發工具
             initApplicationEventMulticaster();
 
             // Initialize other special beans in specific context subclasses.
             // 9、重新整理【留給子類的擴充套件點,重點關注】
             onRefresh();
 
             // Check for listener beans and register them.
             // 10、主備監聽器
             registerListeners();
 
             // Instantiate all remaining (non-lazy-init) singletons.
             // 11、完成BeanFactory的初始化工作【例項化剩下的單例項Bean】
             finishBeanFactoryInitialization(beanFactory);
 
             // Last step: publish corresponding event.
             // 12、重新整理完成【釋出事件】
             finishRefresh();
         }
 
         catch (BeansException ex) {
             if (logger.isWarnEnabled()) {
                 logger.warn("Exception encountered during context initialization - " +
                             "cancelling refresh attempt: " + ex);
             }
 
             // Destroy already created singletons to avoid dangling resources.
             destroyBeans();
 
             // Reset 'active' flag.
             cancelRefresh(ex);
 
             // Propagate exception to caller.
             throw ex;
         }
 
         finally {
             // Reset common introspection caches in Spring's core, since we
             // might not ever need metadata for singleton beans anymore...
             // 重置快取
             resetCommonCaches();
         }
     }
 }

我們重點關注第9步 onRefresh(),因為這是留給子類的擴充套件點

子類ioc容器的擴充套件點-onRefresh()

ServletWebServerApplicationContext

 protected void onRefresh() {
     // 1、呼叫父類的 onRefresh()
     super.onRefresh();
     try {
         // 2、建立Web伺服器
         createWebServer();
     }
     catch (Throwable ex) {
         throw new ApplicationContextException("Unable to start web server", ex);
     }
 }

建立Web伺服器-createWebServer()

這裏拿到的伺服器工廠預設是Tomcat的,所以會建立出Tomcat伺服器的例項然後啟動

因為我們引入的starter-web裡預設引入的就是Tomcatjar包

 private void createWebServer() {
     // 1、拿到Web伺服器【剛開始肯定是null,因為我們還沒建立呢】
     WebServer webServer = this.webServer;
     // 2、拿到ServletContext【剛開始也是null】
     ServletContext servletContext = getServletContext();
     if (webServer == null && servletContext == null) {
         // 3、從容器中得到一個Web伺服器的工廠【ServletWebServerFactory】
         ServletWebServerFactory factory = getWebServerFactory();
         // 4、使用工廠來建立Web伺服器
         this.webServer = factory.getWebServer(getSelfInitializer());
         getBeanFactory().registerSingleton("webServerGracefulShutdown",
                                            new WebServerGracefulShutdownLifecycle(this.webServer));
         getBeanFactory().registerSingleton("webServerStartStop",
                                            new WebServerStartStopLifecycle(this, this.webServer));
     }
     else if (servletContext != null) {
         try {
             // 遍歷所有的ServletContextInitializer呼叫其onStartup()來啟動
             getSelfInitializer().onStartup(servletContext);
         }
         catch (ServletException ex) {
             throw new ApplicationContextException("Cannot initialize servlet context", ex);
         }
     }
     initPropertySources();
 }

SpringBoot的嵌入式Web伺服器實現原理

getWebServer()

 public WebServer getWebServer(ServletContextInitializer... initializers) {
     if (this.disableMBeanRegistry) {
         Registry.disableRegistry();
     }
     // 1、直接建立一個Tomcat伺服器
     Tomcat tomcat = new Tomcat();
     // 2、設定一些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);
     // 3、得到一個Tomcat伺服器【會啟動Tomcat伺服器】
     return getTomcatWebServer(tomcat);
 }
 
 protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
     return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
 }

建立一個TomcatWebServer,用來封裝Tomat例項

 public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
     Assert.notNull(tomcat, "Tomcat Server must not be null");
     this.tomcat = tomcat;
     this.autoStart = autoStart;
     this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
     // 初始化:啟動Tomcat
     initialize();
 }
 
 private void initialize() throws WebServerException {
     logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
     synchronized (this.monitor) {
         try {
 
             // 省略程式碼
             ...
 
             // 啟動tomcat
             this.tomcat.start();
 
             // 省略程式碼
             ...
         }
         catch (Exception ex) {
             // 出現異常就停止、銷燬Tomcat
             stopSilently();
             destroySilently();
             throw new WebServerException("Unable to start embedded Tomcat", ex);
         }
     }
 }

到這裏Tomcat就建立並啟動成功了。

如何切換Web伺服器

案例:切換Web伺服器為Jetty。

我們知道了內嵌伺服器的原理,它是透過不同的Web伺服器工廠來建立出不同的Web伺服器例項,然後啟動

Web伺服器工廠透過條件註解進行註冊到容器中,然後從容器中拿到Web伺服器工廠,也就是匯入不同的jar包就會註冊不同的Web伺服器工廠。所以我們只需要把tomcat的jar包替換為jetty的jar包即可完成切換。

Tomcat的jar包是在starter-web中引入的:

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
     <version>2.3.12.RELEASE</version>
     <scope>compile</scope>
 </dependency>

我們只需要把這個jar包排除,然後加上jetty的jar包即可實現,SpringBoot幫我們把一切都在底層配置好了

 <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>

再次啟動,會發現已經切換成功了:

SpringBoot的嵌入式Web伺服器實現原理

這裏從容器中拿到的Web伺服器工廠就是Jetty的了:

SpringBoot的嵌入式Web伺服器實現原理

總結

我們發現內嵌Tomcat伺服器的本質其實是:透過Web伺服器工廠建立一個Tomcat例項,然後呼叫其start()來啟動。

其它伺服器也是一樣的道理

     // 1、web伺服器工廠建立伺服器例項
     factory.getWebServer(getSelfInitializer())
     
     // 2、建立
     Tomcat tomcat = new Tomcat();
     
     // 啟動
     tomcat.start();

Web伺服器工廠如何被註冊到容器中的?

使用ServletWebServerFactoryAutoConfiguration自動配置類

利用@Import()機制給容器中匯入元件,然後透過@ConditonalOnxxx()條件註解給容器中匯入不同的Web伺服器工廠

     @Configuration(proxyBeanMethods = false)
     @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
     @ConditionalOnClass(ServletRequest.class)
     @ConditionalOnWebApplication(type = Type.SERVLET)
     @EnableConfigurationProperties(ServerProperties.class)
     @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
             ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
             ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
             ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
     public class ServletWebServerFactoryAutoConfiguration {
     }
     
     // 嵌入式的Tomcat
     @Configuration(proxyBeanMethods = false)
     @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
     @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
     static class EmbeddedTomcat {
         // Tomcat Web伺服器工廠
         @Bean
         TomcatServletWebServerFactory tomcatServletWebServerFactory(
             ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
             ObjectProvider<TomcatContextCustomizer> contextCustomizers,
             ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
             TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
             factory.getTomcatConnectorCustomizers()
                 .addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
             factory.getTomcatContextCustomizers()
                 .addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
             factory.getTomcatProtocolHandlerCustomizers()
                 .addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
             return factory;
         }
     }
     
     // 嵌入式的Jetty
     @Configuration(proxyBeanMethods = false)
     @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
     @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
     static class EmbeddedJetty {
         // Jetty Web伺服器工廠
         @Bean
         JettyServletWebServerFactory JettyServletWebServerFactory(
             ObjectProvider<JettyServerCustomizer> serverCustomizers) {
             JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
             factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
             return factory;
         }
     }
     
     // 嵌入式的Undertow
     @Configuration(proxyBeanMethods = false)
     @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
     @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
     static class EmbeddedUndertow {
         // Undertow Web伺服器工廠
         @Bean
         UndertowServletWebServerFactory undertowServletWebServerFactory(
             ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
             ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
             UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
             factory.getDeploymentInfoCustomizers()
                 .addAll(deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
             factory.getBuilderCustomizers().addAll(builderCustomizers.orderedStream().collect(Collectors.toList()));
             return factory;
         }
     }

WebServerFactory

用來建立Web伺服器的工廠

SpringBoot的嵌入式Web伺服器實現原理

WebServer

Web伺服器

SpringBoot的嵌入式Web伺服器實現原理

WebServer實際是呼叫具體的伺服器例項來做的:Tomcat、Jetty、Undertow

public class Tomcat {}
     
public class Jetty {}

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.