java框架之SpringBoot(8)-嵌入式Servlet容器

前言

SpringBoot 默認使用的嵌入式 Servlet 容器爲 Tomcat,經過依賴關係就能夠看到:html

問題:java

  1. 如何定製和修改 Servlet 容器相關配置?
  2. SpringBoot 可否支持其它 Servlet 容器?

相關配置

方式一:配置文件

在普通 web 程序中咱們若是須要修改 Tomcat 配置則可經過 Tomcat 目錄下 conf/server.xml 修改,而在 SpringBoot 中咱們只須要在項目配置文件中經過 server 節下的相關屬性便可修改容器相關配置,如:web

# 通用 Servlet 容器配置
server.port=8080
server.context-path=/
# Tomcat 配置
server.tomcat.uri-encoding=utf-8

方式二:容器的定製器

除了上述配置的方式,SpringBoot 還爲咱們提供了嵌入式 Servlet 容器的定製器來定製相關配置,例:spring

@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
    return new EmbeddedServletContainerCustomizer() {
        // 定製嵌入式 Servlet 容器相關規則
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            // container 即爲容器對象
            container.setPort(8088);
        }
    };
}

配置文件中 server 節對應的配置屬性類內部也是經過該方式定義容器配置的。apache

註冊三大組件

若是是在一個標準的 web 工程中,咱們能夠經過 WEB-INF/web.xml 來註冊三大組件,即 Servlet、Filter、Listener。而 SpringBoot 項目默認是以 jar 包的方式經過嵌入式的 Servlet 容器來啓動 web 應用,沒有 web.xml,註冊三大組件則須要按照如下方式:tomcat

註冊Servlet-ServletRegistrationBean

@Bean
public ServletRegistrationBean myServlet(){
    return new ServletRegistrationBean(new MyServlet(), "/servlet/hello");
}

註冊Filter-FilterRegistrationBean

@Bean
public FilterRegistrationBean myFilter(){
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new MyFilter());
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/servlet/hello","/login"));
    return filterRegistrationBean;
}

註冊Listener-ListenerRegistrationBean

@Bean
public ServletListenerRegistrationBean myListener(){
    return new ServletListenerRegistrationBean(new MyListener());
}

SpringMVC 的核心 Servlet 在 SpringBoot 中就是以此種方式註冊,以下:springboot

 1 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
 2 public DispatcherServlet dispatcherServlet() {
 3     DispatcherServlet dispatcherServlet = new DispatcherServlet();
 4     dispatcherServlet.setDispatchOptionsRequest(
 5             this.webMvcProperties.isDispatchOptionsRequest());
 6     dispatcherServlet.setDispatchTraceRequest(
 7             this.webMvcProperties.isDispatchTraceRequest());
 8     dispatcherServlet.setThrowExceptionIfNoHandlerFound(
 9             this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
10     return dispatcherServlet;
11 }
12 
13     @Configuration
14     @Conditional(DispatcherServletRegistrationCondition.class)
15     @ConditionalOnClass(ServletRegistration.class)
16     @EnableConfigurationProperties(WebMvcProperties.class)
17     @Import(DispatcherServletConfiguration.class)
18     protected static class DispatcherServletRegistrationConfiguration {
19 
20         private final ServerProperties serverProperties;
21 
22         private final WebMvcProperties webMvcProperties;
23 
24         private final MultipartConfigElement multipartConfig;
25 
26         public DispatcherServletRegistrationConfiguration(
27                 ServerProperties serverProperties, WebMvcProperties webMvcProperties,
28                 ObjectProvider<MultipartConfigElement> multipartConfigProvider) {
29             this.serverProperties = serverProperties;
30             this.webMvcProperties = webMvcProperties;
31             this.multipartConfig = multipartConfigProvider.getIfAvailable();
32         }
33 
34         @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
35         @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
36         public ServletRegistrationBean dispatcherServletRegistration(
37                 DispatcherServlet dispatcherServlet) {
38             ServletRegistrationBean registration = new ServletRegistrationBean(
39                     dispatcherServlet, this.serverProperties.getServletMapping());
40             registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
41             registration.setLoadOnStartup(
42                     this.webMvcProperties.getServlet().getLoadOnStartup());
43             if (this.multipartConfig != null) {
44                 registration.setMultipartConfig(this.multipartConfig);
45             }
46             return registration;
47         }
48 
49     }
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration

切換容器

咱們知道,SpringBoot 默認使用的嵌入式容器爲 Tomcat,SpringBoot 默認還支持將其切換爲 Jetty(對長鏈接有更好的支持) 或 Undertow(不支持 JSP)。服務器

其實經過容器接口的實現類咱們就能夠看到其它兩個容器工廠的實現:app

切換爲Jetty

首先咱們要排除默認依賴的 Tomcat 場景啓動器:less

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

再引入 Jetty 的場景啓動器便可:

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

切換爲Undertow

同 Jetty 先排除 Tomcat 的場景啓動器,接着引入 Undertow 的場景啓動器:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

自動配置原理

依舊是從嵌入式 Servlet 容器的自動配置類看起:

 1 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
 2 @Configuration
 3 @ConditionalOnWebApplication
 4 @Import(BeanPostProcessorsRegistrar.class)
 5 public class EmbeddedServletContainerAutoConfiguration {
 6     @Configuration
 7     @ConditionalOnClass({ Servlet.class, Tomcat.class }) // 判斷當前環境是否引入了嵌入式 Tomcat 依賴
 8     @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) // 當 IoC 容器中沒有自定義的嵌入式 Servlet 容器工廠下方代碼才生效
 9     public static class EmbeddedTomcat {
10         @Bean
11         public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
12             return new TomcatEmbeddedServletContainerFactory();
13         }
14     }
15 
16     @Configuration
17     @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
18             WebAppContext.class })  // 判斷當前環境是否引入了嵌入式 Jetty 依賴
19     @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) // 當 IoC 容器中沒有自定義的嵌入式 Servlet 容器工廠下方代碼才生效
20     public static class EmbeddedJetty {
21         @Bean
22         public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
23             return new JettyEmbeddedServletContainerFactory();
24         }
25     }
26 
27     @Configuration
28     @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class }) // 判斷當前環境是否引入了嵌入式 Undertow 依賴
29     @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT) // 當 IoC 容器中沒有自定義的嵌入式 Servlet 容器工廠下方代碼才生效
30     public static class EmbeddedUndertow {
31         @Bean
32         public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
33             return new UndertowEmbeddedServletContainerFactory();
34         }
35     }
36 
37     public static class BeanPostProcessorsRegistrar
38             implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
39 
40         private ConfigurableListableBeanFactory beanFactory;
41 
42         @Override
43         public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
44             if (beanFactory instanceof ConfigurableListableBeanFactory) {
45                 this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
46             }
47         }
48 
49         @Override
50         public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
51                 BeanDefinitionRegistry registry) {
52             if (this.beanFactory == null) {
53                 return;
54             }
55             registerSyntheticBeanIfMissing(registry,
56                     "embeddedServletContainerCustomizerBeanPostProcessor",
57                     EmbeddedServletContainerCustomizerBeanPostProcessor.class);
58             registerSyntheticBeanIfMissing(registry,
59                     "errorPageRegistrarBeanPostProcessor",
60                     ErrorPageRegistrarBeanPostProcessor.class);
61         }
62 
63         private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
64                 String name, Class<?> beanClass) {
65             if (ObjectUtils.isEmpty(
66                     this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
67                 RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
68                 beanDefinition.setSynthetic(true);
69                 registry.registerBeanDefinition(name, beanDefinition);
70             }
71         }
72 
73     }
74 }
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration

切換容器

很明瞭,自動配置類中使用三個靜態內部類來分別註冊不一樣的嵌入式的 Servlet 容器工廠,按順序從上到下分別爲 Tomcat、Jetty、Undertow。具體要註冊哪一個 Servlet 容器工廠須要根據當前環境的依賴來決定,若是當前環境只引入了 Tomcat 場景依賴,那麼就僅僅會註冊 Tomcat 的容器工廠,其它兩個 Servlet 容器工廠就不會被註冊。這就是咱們引入依賴便能切換 Servlet 容器的緣由。

容器建立與啓動

如今能夠知道的是,在自動配置類中只是註冊了一個嵌入式 Servlet 容器的工廠 bean,而並非註冊了嵌入式 Servlet 容器的實例 bean,顧名思義,嵌入式 Servlet 容器的工廠確定是用來建立嵌入式 Servlet 容器的實例,那麼這個嵌入式 Servlet 容器的實例是在什麼時候被建立和啓動的呢?

以默認的 Tomcat 容器爲例,當咱們啓動一個 SpringBoot 程序時,咱們會發現嵌入式 Tomcat 會隨之啓動,咱們直接從 SpringBoot 程序的入口 run 方法開始看起:

 1 public static ConfigurableApplicationContext run(Object source, String... args) {
 2     return run(new Object[] { source }, args);
 3 }
 4 
 5 public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
 6     return new SpringApplication(sources).run(args);
 7 }
 8 
 9 public ConfigurableApplicationContext run(String... args) {
10     StopWatch stopWatch = new StopWatch();
11     stopWatch.start();
12     ConfigurableApplicationContext context = null;
13     FailureAnalyzers analyzers = null;
14     configureHeadlessProperty();
15     SpringApplicationRunListeners listeners = getRunListeners(args);
16     listeners.starting();
17     try {
18         ApplicationArguments applicationArguments = new DefaultApplicationArguments(
19                 args);
20         ConfigurableEnvironment environment = prepareEnvironment(listeners,
21                 applicationArguments);
22         Banner printedBanner = printBanner(environment);
23         context = createApplicationContext();
24         analyzers = new FailureAnalyzers(context);
25         prepareContext(context, environment, listeners, applicationArguments,
26                 printedBanner);
27         refreshContext(context);
28         afterRefresh(context, applicationArguments);
29         listeners.finished(context, null);
30         stopWatch.stop();
31         if (this.logStartupInfo) {
32             new StartupInfoLogger(this.mainApplicationClass)
33                     .logStarted(getApplicationLog(), stopWatch);
34         }
35         return context;
36     }
37     catch (Throwable ex) {
38         handleRunFailure(context, listeners, analyzers, ex);
39         throw new IllegalStateException(ex);
40     }
41 }
org.springframework.boot.SpringApplication#run

依次執行這幾個 run 方法,接着進到 27 行的 refreshContext(context) 方法:

 1 private void refreshContext(ConfigurableApplicationContext context) {
 2     refresh(context);
 3     if (this.registerShutdownHook) {
 4         try {
 5             context.registerShutdownHook();
 6         }
 7         catch (AccessControlException ex) {
 8             // Not allowed in some environments.
 9         }
10     }
11 }
org.springframework.boot.SpringApplication#refreshContext

接着執行第 2 行的 refresh(context) 方法:

1 protected void refresh(ApplicationContext applicationContext) {
2     Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
3     ((AbstractApplicationContext) applicationContext).refresh();
4 }
org.springframework.boot.SpringApplication#refresh

繼續到第 3 行的 ((AbstractApplicationContext) applicationContext).refresh() 方法:

 1 @Override
 2 public final void refresh() throws BeansException, IllegalStateException {
 3     try {
 4         super.refresh();
 5     }
 6     catch (RuntimeException ex) {
 7         stopAndReleaseEmbeddedServletContainer();
 8         throw ex;
 9     }
10 }
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#refresh

調用父類的 refresh() 方法:

 1 public void refresh() throws BeansException, IllegalStateException {
 2     synchronized(this.startupShutdownMonitor) {
 3         this.prepareRefresh();
 4         ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
 5         this.prepareBeanFactory(beanFactory);
 6 
 7         try {
 8             this.postProcessBeanFactory(beanFactory);
 9             this.invokeBeanFactoryPostProcessors(beanFactory);
10             this.registerBeanPostProcessors(beanFactory);
11             this.initMessageSource();
12             this.initApplicationEventMulticaster();
13             this.onRefresh();
14             this.registerListeners();
15             this.finishBeanFactoryInitialization(beanFactory);
16             this.finishRefresh();
17         } catch (BeansException var9) {
18             if (this.logger.isWarnEnabled()) {
19                 this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
20             }
21 
22             this.destroyBeans();
23             this.cancelRefresh(var9);
24             throw var9;
25         } finally {
26             this.resetCommonCaches();
27         }
28 
29     }
30 }
org.springframework.context.support.AbstractApplicationContext#refresh

在第 13 行又調用了子類的 onRefresh() 方法:

 1 @Override
 2 protected void onRefresh() {
 3     super.onRefresh();
 4     try {
 5         createEmbeddedServletContainer();
 6     }
 7     catch (Throwable ex) {
 8         throw new ApplicationContextException("Unable to start embedded container",
 9                 ex);
10     }
11 }
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#onRefresh

根據方法名能夠看出,接着會經過 createEmbeddedServletContainer() 方法建立嵌入式 Servlet 容器:

 1 private void createEmbeddedServletContainer() {
 2     EmbeddedServletContainer localContainer = this.embeddedServletContainer;
 3     ServletContext localServletContext = getServletContext();
 4     if (localContainer == null && localServletContext == null) {
 5         EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
 6         this.embeddedServletContainer = containerFactory
 7                 .getEmbeddedServletContainer(getSelfInitializer());
 8     }
 9     else if (localServletContext != null) {
10         try {
11             getSelfInitializer().onStartup(localServletContext);
12         }
13         catch (ServletException ex) {
14             throw new ApplicationContextException("Cannot initialize servlet context",
15                     ex);
16         }
17     }
18     initPropertySources();
19 }
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext#createEmbeddedServletContainer

在第 5 行經過 getEmbeddedServletContainerFactory() 獲取到嵌入式 Servlet 容器工廠 bean,該 bean 在嵌入式 Servlet 容器自動配置類中就已經被註冊,此時爲 Tomcat 的工廠即: org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory ,而後在第 7 行經過 containerFactory.getEmbeddedServletContainer(getSelfInitializer()) 獲取嵌入式 Servlet 容器的實例即 Tomcat 容器實例:

 1 @Override
 2 public EmbeddedServletContainer getEmbeddedServletContainer(
 3         ServletContextInitializer... initializers) {
 4     // 建立一個 Tomcat 實例
 5     Tomcat tomcat = new Tomcat();
 6     // Tomcat 的基本配置
 7     File baseDir = (this.baseDirectory != null) ? this.baseDirectory
 8             : createTempDir("tomcat");
 9     tomcat.setBaseDir(baseDir.getAbsolutePath());
10     Connector connector = new Connector(this.protocol);
11     tomcat.getService().addConnector(connector);
12     customizeConnector(connector);
13     tomcat.setConnector(connector);
14     tomcat.getHost().setAutoDeploy(false);
15     configureEngine(tomcat.getEngine());
16     for (Connector additionalConnector : this.additionalTomcatConnectors) {
17         tomcat.getService().addConnector(additionalConnector);
18     }
19     prepareContext(tomcat.getHost(), initializers);
20     // 傳入配置完成的 Tomcat 實例
21     return getTomcatEmbeddedServletContainer(tomcat);
22 }
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer

在第 21 行將完成基本配置的 Tomcat 實例傳遞給了 getTomcatEmbeddedServletContainer(tomcat) 方法:

1 protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
2         Tomcat tomcat) {
3     return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
4 }
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory#getTomcatEmbeddedServletContainer

接着經過第 3 行將指定的端口不小於 0 的判斷結果做爲是否自動啓動的參數傳遞給了 Tomcat 容器類的構造方法,建立 Tomcat 容器實例:

1 public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
2     Assert.notNull(tomcat, "Tomcat Server must not be null");
3     this.tomcat = tomcat;
4     this.autoStart = autoStart;
5     initialize();
6 }
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer#TomcatEmbeddedServletContainer(org.apache.catalina.startup.Tomcat, boolean)

又在第 5 行執行了 initialize() :

 1 private void initialize() throws EmbeddedServletContainerException {
 2     TomcatEmbeddedServletContainer.logger
 3             .info("Tomcat initialized with port(s): " + getPortsDescription(false));
 4     synchronized (this.monitor) {
 5         try {
 6             addInstanceIdToEngineName();
 7             try {
 8                 final Context context = findContext();
 9                 context.addLifecycleListener(new LifecycleListener() {
10 
11                     @Override
12                     public void lifecycleEvent(LifecycleEvent event) {
13                         if (context.equals(event.getSource())
14                                 && Lifecycle.START_EVENT.equals(event.getType())) {
15                             removeServiceConnectors();
16                         }
17                     }
18 
19                 });
20                 
21                 this.tomcat.start();
22 
23                 rethrowDeferredStartupExceptions();
24 
25                 try {
26                     ContextBindings.bindClassLoader(context, getNamingToken(context),
27                             getClass().getClassLoader());
28                 }
29                 catch (NamingException ex) {
30                 }
31 
32                 startDaemonAwaitThread();
33             }
34             catch (Exception ex) {
35                 containerCounter.decrementAndGet();
36                 throw ex;
37             }
38         }
39         catch (Exception ex) {
40             stopSilently();
41             throw new EmbeddedServletContainerException(
42                     "Unable to start embedded Tomcat", ex);
43         }
44     }
45 }
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer#initialize

在第 21 行經過 Tomcat 實例的 start() 方法啓動了 Tomcat。

至此咱們能夠得出結論,隨着 SpringBoot 程序的啓動,SpringBoot 會使用註冊的嵌入式 Servlet 容器工廠 bean 來建立嵌入式 Servlet 容器,接着會隨着容器的建立來啓動嵌入式 Servlet 容器。

容器配置的生效

經過上面的學習咱們已經知道,能夠經過修改配置文件及編寫容器的定製器來修改嵌入式 Servlet 容器的配置,這些配置是如何生效的呢?

回到嵌入式 Servlet 容器的自動配置類中,咱們會發如今該類上有一個 @Import(BeanPostProcessorsRegistrar.class) 註解,該註解是用來快速註冊指定組件的,具體使用可參考【Import-快速註冊】。查看該類的 registerBeanDefinitions 方法:

 1 @Override
 2 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
 3         BeanDefinitionRegistry registry) {
 4     if (this.beanFactory == null) {
 5         return;
 6     }
 7     registerSyntheticBeanIfMissing(registry,
 8             "embeddedServletContainerCustomizerBeanPostProcessor",
 9             EmbeddedServletContainerCustomizerBeanPostProcessor.class);
10     registerSyntheticBeanIfMissing(registry,
11             "errorPageRegistrarBeanPostProcessor",
12             ErrorPageRegistrarBeanPostProcessor.class);
13 }
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar#registerBeanDefinitions

在第 7 行能夠看到註冊了一個 embeddedServletContainerCustomizerBeanPostProcessor 即後置處理器組件,後置處理器使用可參考:【實現BeanPostProcessor接口】,查看該組件:

 1 package org.springframework.boot.context.embedded;
 2 
 3 public class EmbeddedServletContainerCustomizerBeanPostProcessor
 4         implements BeanPostProcessor, BeanFactoryAware {
 5 
 6     private ListableBeanFactory beanFactory;
 7 
 8     private List<EmbeddedServletContainerCustomizer> customizers;
 9 
10     @Override
11     public void setBeanFactory(BeanFactory beanFactory) {
12         Assert.isInstanceOf(ListableBeanFactory.class, beanFactory,
13                 "EmbeddedServletContainerCustomizerBeanPostProcessor can only be used "
14                         + "with a ListableBeanFactory");
15         this.beanFactory = (ListableBeanFactory) beanFactory;
16     }
17 
18     // 初始化以前執行
19     @Override
20     public Object postProcessBeforeInitialization(Object bean, String beanName)
21             throws BeansException {
22         if (bean instanceof ConfigurableEmbeddedServletContainer) {
23             postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
24         }
25         return bean;
26     }
27 
28     @Override
29     public Object postProcessAfterInitialization(Object bean, String beanName)
30             throws BeansException {
31         return bean;
32     }
33 
34     private void postProcessBeforeInitialization(
35             ConfigurableEmbeddedServletContainer bean) {
36         // 遍歷全部的容器定製器
37         for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
38             customizer.customize(bean);
39         }
40     }
41     // 獲取全部的容器定製器
42     private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
43         if (this.customizers == null) {
44             // Look up does not include the parent context
45             this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
46                     this.beanFactory
47                             .getBeansOfType(EmbeddedServletContainerCustomizer.class,
48                                     false, false)
49                             .values());
50             Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
51             this.customizers = Collections.unmodifiableList(this.customizers);
52         }
53         return this.customizers;
54     }
55 
56 }
org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor

查看第 20-26 行的 postProcessBeforeInitialization 方法,該方法會在 IoC 容器建立一個 bean 實例後、初始化以前執行。在該方法中判斷了當前建立的 bean 是否是嵌入式 Servlet 容器,若是是,則經過 34 行的 postProcessBeforeInitialization 方法,在該方法中遍歷全部的容器定製器,經過容器定製器的 customize 方法來配置當前建立的 bean 即當前建立的嵌入式 Servlet 容器。由於在配置文件中作容器相關配置實際也是經過容器定製器來配置容器,因此修改配置文件及編寫容器的定製器來修改容器配置會對當前 IoC 容器中全部的嵌入式 Servlet 容器生效。

使用外部Servlet容器

使用

這裏以使用本地的 Tomcat 爲例。

一、建立一個 SpringBoot 項目,打包方式爲 war。

二、將嵌入式 Tomcat 依賴的 scope 指定爲 provided:

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

三、編寫一個類繼承 org.springframework.boot.web.support.SpringBootServletInitializer ,重寫 configure 方法,例如:

package com.springboot.webdev3;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Webdev3Application.class);
    }
}

四、建立 web 資源目錄:

五、最終目錄結構以下:

 

六、將其部署到本地 Tomcat 容器,啓動 Tomcat,SpringBoot 項目會隨之啓動:

原理

以前咱們啓動打包方式爲 jar 的 SpringBoot 項目時,首先是執行 SpringBoot 入口類的 main 方法,隨之啓動了 IoC 容器,嵌入式 Servlet 容器也隨之建立並啓動了。

而咱們如今是啓動打包方式爲 war 的Spring項目,直接啓動服務器,SpringBoot 應用就隨之啓動了。

問題來了,爲何 SpringBoot 程序會隨着外部 Servlet 容器啓動而啓動?

Servlet 3.0後有一個新規範:

  1. 服務器啓動(web 應用啓動)時會當前 web 應用中(包含全部依賴 jar 中)尋找目錄 WEB-INF/services 下名爲 javax.servlet.ServletContainerInitializer 的文件。
  2. 在 javax.servlet.ServletContainerInitializer 文件中可指定全類名,對應類爲 javax.servlet.ServletContainerInitializer 的實現類,這些實現類會隨服務器的啓動而建立實例並會執行類中的 onStartup 方法。
  3. 還能夠經過 @HandlesTypes 註解加載咱們須要的類,經過被標註類的構造方法注入。

在 SpringBoot 的 web 場景啓動器依賴中就有一個 javax.servlet.ServletContainerInitializer 文件:

org.springframework.web.SpringServletContainerInitializer
spring-web-4.3.22.RELEASE.jar!/META-INF/services/javax.servlet.ServletContainerInitializer

查看該類:

 1 package org.springframework.web;
 2 
 3 import java.lang.reflect.Modifier;
 4 import java.util.LinkedList;
 5 import java.util.List;
 6 import java.util.ServiceLoader;
 7 import java.util.Set;
 8 import javax.servlet.ServletContainerInitializer;
 9 import javax.servlet.ServletContext;
10 import javax.servlet.ServletException;
11 import javax.servlet.annotation.HandlesTypes;
12 
13 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
14 
15 @HandlesTypes(WebApplicationInitializer.class)
16 public class SpringServletContainerInitializer implements ServletContainerInitializer {
17 
18     @Override
19     public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
20             throws ServletException {
21 
22         List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
23 
24         if (webAppInitializerClasses != null) {
25             for (Class<?> waiClass : webAppInitializerClasses) {
26                 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
27                         WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
28                     try {
29                         initializers.add((WebApplicationInitializer) waiClass.newInstance());
30                     }
31                     catch (Throwable ex) {
32                         throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
33                     }
34                 }
35             }
36         }
37 
38         if (initializers.isEmpty()) {
39             servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
40             return;
41         }
42 
43         AnnotationAwareOrderComparator.sort(initializers);
44         servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);
45 
46         for (WebApplicationInitializer initializer : initializers) {
47             initializer.onStartup(servletContext);
48         }
49     }
50 
51 }
org.springframework.web.SpringServletContainerInitializer

看到源碼就很清晰了,應用在啓動時會建立該類實例執行它的 onStartup 方法,而在該類上經過標註 @HandlesTypes(WebApplicationInitializer.class) 將當前程序中的全部 WebApplicationInitializer 的實現類的字節碼對象經過構造方法注入,而 SpringBootServletInitializer 類就是 WebApplicationInitializer 的一個實現類,因此咱們本身編寫的 ServletInitializer 的字節碼對象將會被注入,而且在第 29 行建立實例,在第 47 行執行了咱們本身編寫的 ServletInitializer 類對象的 onStartup 方法:

 1 @Override
 2 public void onStartup(ServletContext servletContext) throws ServletException {
 3     this.logger = LogFactory.getLog(getClass());
 4     WebApplicationContext rootAppContext = createRootApplicationContext(
 5             servletContext);
 6     if (rootAppContext != null) {
 7         servletContext.addListener(new ContextLoaderListener(rootAppContext) {
 8             @Override
 9             public void contextInitialized(ServletContextEvent event) {
10             }
11         });
12     }
13     else {
14         this.logger.debug("No ContextLoaderListener registered, as "
15                 + "createRootApplicationContext() did not "
16                 + "return an application context");
17     }
18 }
org.springframework.boot.web.support.SpringBootServletInitializer#onStartup

接着執行 createRootApplicationContext(servletContext) 方法:

 1 protected WebApplicationContext createRootApplicationContext(
 2         ServletContext servletContext) {
 3     SpringApplicationBuilder builder = createSpringApplicationBuilder();
 4     builder.main(getClass());
 5     ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
 6     if (parent != null) {
 7         this.logger.info("Root context already created (using as parent).");
 8         servletContext.setAttribute(
 9                 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
10         builder.initializers(new ParentContextApplicationContextInitializer(parent));
11     }
12     builder.initializers(
13             new ServletContextApplicationContextInitializer(servletContext));
14     builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
15     builder = configure(builder);
16     builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
17     SpringApplication application = builder.build();
18     if (application.getSources().isEmpty() && AnnotationUtils
19             .findAnnotation(getClass(), Configuration.class) != null) {
20         application.getSources().add(getClass());
21     }
22     Assert.state(!application.getSources().isEmpty(),
23             "No SpringApplication sources have been defined. Either override the "
24                     + "configure method or add an @Configuration annotation");
25     if (this.registerErrorPageFilter) {
26         application.getSources().add(ErrorPageFilterConfiguration.class);
27     }
28     return run(application);
29 }
org.springframework.boot.web.support.SpringBootServletInitializer#createRootApplicationContext

接着在第 15 行又執行了 configure 方法,而這個 configure 方法正是咱們本身編寫的繼承 SpringBootServletInitializer 類重寫的 configure 方法:

1 @Override
2 protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
3     return application.sources(Webdev3Application.class);
4 }
com.springboot.webdev3.ServletInitializer#configure

在該方法中經過 application.sources(Webdev3Application.class) 傳入了當前 SpringBoot 項目的入口類,返回一個 Spring 程序構建器。回到上一步,在 15 行拿到該構建器,在第 17 行建立了 Spring 程序實例,最後在第 28 行執行了 Spring 程序實例的 run 方法,即 SpringBoot 程序隨服務器的啓動而啓動了。

相關文章
相關標籤/搜索