【Spring Boot】10.外部servlet容器

簡介

前面咱們講了關於嵌入式servlet容器的配置及其工做原理,其優勢毫無疑問:java

  • 簡單、便攜
  • 無需安裝tomcat等容器

缺點:web

  • 默認不支持JSP
  • 定製比較複雜(使用定製器(ServerProperties、自定義定製器),本身編寫嵌入式servlet的建立工廠)

但不少時候,仍是須要支持JSP,或者須要將應用部署到固定的web容器中,即咱們以war對應用進行打包(springboot默認是jar)。spring

建立一個war發佈的項目

和以前建立項目的方式同樣,只不過在選擇發佈方式的時候從jar變爲war便可,項目名爲jspweb。docker

因爲這是一個以war發佈的項目,咱們應該建立webapp文件夾以及web.xml描述文件,可是如今沒有,所以須要咱們去建立。tomcat

建立web應用目錄

在編輯器的右上角有一個名爲Project Structure的按鈕,打開項目結構配置對話框(ctrl+alt+shift+s).springboot

  1. 點擊Models菜單欄
  2. 展開jspweb目錄樹
  3. 點擊Web模塊。咱們能夠看到右邊有兩個提示,先點右下部分的,雙擊彈出提示,點確認,編輯器變爲咱們建立好了相應的web目錄;
  4. 在注意上面的一個框,即添加描述文件的(web.xml),點擊+按鈕,添加一個web.xml文件,注意,路徑那裏寫在咱們下面建立的那個web路徑下。即xxx\jspweb\src\main\webapp\WEB-INF\web.xml,其中XXXX是項目所在的本地路徑。

配置外部服務器

很顯然,此時要項目運行起來,咱們得添加外部的servlet容器。在進行以下步驟前,請記得先去tomcat官網下載一個tomcat,安裝在本地(解壓到本地一個目錄便可)。服務器

  1. 導入本地tomcat容器 咱們須要將本地的tomcat容器整合到idea編輯器中:
  • 點擊右上角的下拉框,選擇edit configuration;
  • 在彈出的對話框中點擊左上角的+號,爲咱們的項目添加運行組件(即tomcat)
  • 往下拉,找到一個名爲tomcat server的項,點擊,選擇local 即本地的tomcat
  • 在對話框右方進行配置信息,例如設置這個項的名稱,tomcat的路徑兩個。
  • 點擊上方的選項卡Deployment,配置咱們的發佈項目,進行關聯;
  • 點擊右方的+按鈕-> artifact -> jspweb:war explorded
  • 點擊ok

直接運行項目,就能夠直接訪問咱們的首頁了(固然,若是是新項目,固然會跳到找不到頁面的錯誤頁面)。app

咱們能夠直接在webapp下面新增一個hello.jsp,而後訪問localhost:8080/hello.jsp便可。less

使用外部容器的步驟

總結一下,咱們若是想要用外部容器的話,大概須要以下步驟:webapp

  1. 必須建立一個war發佈的項目,利用idea把webapp目錄以及描述文件建立好;
  2. 將嵌入式的tomcat場景啓動器指定爲provided;
  3. 必須編寫一個SpringBootServletInitializer的子類,目的是爲了調用configure方法
public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        // 傳入springboot應用的主程序
        return application.sources(JspwebApplication.class);
    }

}
  1. 配置外部容器,而且將項目發佈包進行關聯;
  2. 啓動服務器就可使用了。

外部容器啓動原理

回想一下咱們以前。

  1. jar: 直接執行了springboot的main方法,啓動ioc容器,並建立嵌入式的servlet容器;
  2. war: 啓動服務器,由服務器來啓動springboot應用,以後才能啓動ioc容器等;

服務器如何啓動springboot的?看上一節的代碼就能夠知道

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        // 傳入springboot應用的主程序
        return application.sources(JspwebApplication.class);
    }

}

探究原理

參考文檔servlet3.0(Spring註解版)中的8.2.4節: Shared libraries / runtimes pluggability,裏面定義了這樣的規則:

  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;

  1. SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)標註的全部這個類型的類都傳入到onStartup方法的Set<Class<?>>,接下來爲這些WebApplicationInitializer類型的類建立實例;

  2. 每個WebApplicationInitializer都調用本身的onStartup;

  3. 至關於咱們的SpringBootServletInitializer的類會被建立對象,並執行onStartup方法

  4. SpringBootServletInitializer實例執行onStartup的時候會createRootApplicationContext;建立容器

protected WebApplicationContext createRootApplicationContext(
      ServletContext servletContext) {
    //一、建立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);
}
  1. 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);
   }
}

總結一下啓動原理:

  • 按照Servlet3.0標準ServletContainerInitializer掃描全部jar包中METAINF/services/javax.servlet.ServletContainerInitializer文件指定的類並加載
  • 加載spring web包下的SpringServletContainerInitializer
  • 掃描@HandleType(WebApplicationInitializer)
  • 加載SpringBootServletInitializer並運行onStartup方法
  • 加載@SpringBootApplication主類,啓動容器等

先是啓動servlet容器,再啓動springboot應用,恰好和咱們的內嵌方式相反。

至此章節開始以後有一節是講關於docker相關的知識,這點推薦你們本身去學習相關的書籍和視頻就好,不在插入此係列文檔之中了。

docker是一個很棒的理念,相關的衍生技術已經獲得了普遍的運用,做爲一名it相關的人員,必須得學,由於不是三言兩語說得完的,所以,就不在springboot這裏說明了。

相關文章
相關標籤/搜索