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 程序随服务器的启动而启动了。

相关文章
相关标签/搜索