SpringBoot使用外部Web容器的解決方案

Evan1024發表於2024-03-07

Spring Boot 預設內嵌了Web容器(如Tomcat、Jetty或Undertow),這使得應用可以作為獨立的可執行JAR或WAR檔案執行,無需外部Web容器。然而,在某些情況下,你可能想要將Spring Boot應用部署到外部的Web容器中,比如Apache Tomcat或Jetty。

嵌入式的Web容器:應用可以打包成可執行的Jar。
優點:簡單、便攜。
缺點:預設不支援JSP、最佳化定製比較複雜(使用定製器ServerProperties、自定義EmbeddedServletContainerCustomizer,自己編寫嵌入式Servlet容器的建立工廠EmbeddedServletContainerFactory)。

外部的Web容器:外面安裝Tomcat伺服器,應用war包的方式打包執行。

解決步驟

將Spring Boot應用部署到外部的Web容器的步驟:
1.建立一個Maven專案,宣告為WAR。
2.排除內嵌Tomcat容器

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

3.重新匯入Tomcat啟動器,依賴範圍改為provided

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
   <scope>provided</scope>
</dependency>

4.必須編寫一個SpringBootServletInitializer的子類,並呼叫configure方法

public class ServletInitializer extends SpringBootServletInitializer {

   @Override
   protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
       //傳入SpringBoot應用的主程式
      return application.sources(SpringBootWebApplication.class);
   }
}

5.啟動伺服器就可以使用。

底層原理

JAR包:執行SpringBoot主類的main方法,啟動ioc容器,建立嵌入式的Servlet容器。
WAR包:啟動Tomcat伺服器,伺服器啟動SpringBoot應用SpringBootServletInitializer,然後啟動ioc容器。
檢視Servlet3.0及以上的執行規則
1)伺服器啟動(web應用啟動)會建立當前web應用裡面每一個jar包裡面ServletContainerInitializer例項。

2)ServletContainerInitializer的實現放在jar包的META-INF/services資料夾下,有一個名為javax.servlet.ServletContainerInitializer的檔案,內容就是ServletContainerInitializer的實現類的全類名。

3)還可以使用@HandlesTypes,在應用啟動的時候載入我們感興趣的類。

流程
1.啟動Tomcat
2.檢視檔案內容
org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer:
Spring的web模組裡面有這個檔案:org.springframework.web.SpringServletContainerInitializer
3.SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)標註的所有這個型別的類都傳入到onStartup方法的泛型引數Set<Class<?>>中;為這些WebApplicationInitializer型別的類建立例項
4.每一個WebApplicationInitializer都呼叫自己的onStartup
image

5.相當於我們的SpringBootServletInitializer的類會被建立物件,並執行onStartup方法
6.SpringBootServletInitializer例項執行onStartup的時候會createRootApplicationContext,建立容器。原始碼檢視:

protected WebApplicationContext createRootApplicationContext(
      ServletContext servletContext) {
    //1、建立SpringApplicationBuilder
   SpringApplicationBuilder builder = createSpringApplicationBuilder();
   StandardServletEnvironment environment = new StandardServletEnvironment();
   environment.initPropertySources(servletContext, null);
   builder.environment(environment);
   builder.main(getClass());
   ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
   if (parent != null) {
      this.logger.info("Root context already created (using as parent).");
      servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
      builder.initializers(new ParentContextApplicationContextInitializer(parent));
   }
   builder.initializers(
         new ServletContextApplicationContextInitializer(servletContext));
   builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
    
    //呼叫configure方法,子類重寫了這個方法,將SpringBoot的主程式類傳入了進來
   builder = configure(builder);
    
    //使用builder建立一個Spring應用
   SpringApplication application = builder.build();
   if (application.getSources().isEmpty() && AnnotationUtils
         .findAnnotation(getClass(), Configuration.class) != null) {
      application.getSources().add(getClass());
   }
   Assert.state(!application.getSources().isEmpty(),
         "No SpringApplication sources have been defined. Either override the "
               + "configure method or add an @Configuration annotation");
   // Ensure error pages are registered
   if (this.registerErrorPageFilter) {
      application.getSources().add(ErrorPageFilterConfiguration.class);
   }
    //啟動Spring應用
   return run(application);
}

7.Spring的應用就啟動並且建立IOC容器

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   FailureAnalyzers analyzers = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      analyzers = new FailureAnalyzers(context);
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
       
       //重新整理IOC容器
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      listeners.finished(context, null);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      return context;
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, analyzers, ex);
      throw new IllegalStateException(ex);
   }
}

結論:啟動Servlet容器,再啟動SpringBoot應用

相關文章