六、SpringBoot配置嵌入式Servlet容器

Spring Boot默认使用Tomcat作为嵌入式的Servlet容器,只要引入了spring-boot-start-web依赖,则默认是用Tomcat作为Servlet容器:

嵌入式Servlet容器.png

1.1、定制和修改Servlet容器的相关配置

1)、修改和server有关的配置(ServerProperties,它其实也是EmbeddedServletContainerCustomizer的子类):

server.port=8080
server.context-path=/

# tomcat相关设置
server.tomcat.uri-encoding=UTF-8

2)、编写EmbeddedServletContainerCustomizer(嵌入式的Servlet容器的定制器)来修改Servlet容器的配置,返回一个自定义的定制器Bean:

@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
    //定制嵌入式的Servlet容器相关的属性配置
    return container -> container.setPort(8083);
}

1.2、注册Servlet容器的三大组件(Servlet、Filter、Listener)

​ 由于Spring Boot默认是以jar包的形式启动嵌入式的Servlet容器,从而来启动Spring Boot的web应用,没有web.xml文件。

​ 以前编写三大组件大多都需要在web.xml文件中进行配置(使用注解除外,@WebServlet@WebListener@WebFilter),而现在使用SpringBoot作为框架,如果需要编写三大组件,则需要使用配置的方式进行注册。

要注册三大组件:

  • ServletRegistrationBean:注册Servlet
//Servlet定义
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("这是一个servlet请求...");
    }
}

//Servlet注册
@Configuration
public class MyServletConfig {

    //注册Servlet
    @Bean
    public ServletRegistrationBean myServlet(){
        return new ServletRegistrationBean(new MyServlet(), "/myServlet");
    }
}
  • FilterRegistrationBean:注册Filter
//Filter定义
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("MyFilter process...");
        //放行
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

//Filter注册
@Bean
public FilterRegistrationBean myFilter(){
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new MyFilter());
    bean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
    return bean;
}
  • ServletListenerRegistrationBean:注册Listener
//Listener定义
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized...web启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed...web销毁");
    }
}

//Listener注册
@Bean
public ServletListenerRegistrationBean myListener(){
    return new ServletListenerRegistrationBean<>(new MyListener());
}

​ 最熟悉的莫过于,在Spring Boot在自动配置SpringMVC的时候,会自动注册SpringMVC前端控制器:DispatcherServlet,该控制器主要在DispatcherServletAutoConfiguration自动配置类中进行注册的。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
    
    //other code...
    
    public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
    
    private String servletPath = "/";
    
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
                @ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public ServletRegistrationBean dispatcherServletRegistration(
        DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean registration = new ServletRegistrationBean(
            dispatcherServlet, this.serverProperties.getServletMapping());
        //默认拦截 / 所有请求,包括静态资源,但是不拦截jsp请求;/*会拦截jsp
        //可以通过修改server.servlet-path来修改默认的拦截请求
        registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        registration.setLoadOnStartup(
            this.webMvcProperties.getServlet().getLoadOnStartup());
        if (this.multipartConfig != null) {
            registration.setMultipartConfig(this.multipartConfig);
        }
        return registration;
    }
}

​ 正如源码中提到,它使用ServletRegistrationBean将dispatcherServlet进行注册,并将urlPattern设为了/,这样就类似原来的web.xml中配置的dispatcherServlet。

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

1.3、其他Servlet容器

Spring Boot默认支持TomcatJetty,和Undertow作为底层容器。如图:

继承树.png

而Spring Boot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。

切换其他Servlet容器:

1)、将tomcat依赖移除掉

2)、引入其他Servlet容器依赖

引入jetty:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

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

引入undertow:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

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

切换了Servlet容器后,只是以server开头的配置不一样外,其他都类似。

1.4、嵌入式Servlet容器自动配置原理

​ 其中EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自动配置类,该类在spring-boot-autoconfigure-xxx.jar中的web模块可以找到。

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {

    @Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory();
        }
    }
    
    @Configuration
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
            WebAppContext.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedJetty {

        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }

    }
    
    @Configuration
    @ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
    @ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedUndertow {

        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }

    }
    
    //other code...
}

​ 在这个自动配置类中配置了三个容器工厂的Bean,分别是:

  • TomcatEmbeddedServletContainerFactory

  • JettyEmbeddedServletContainerFactory

  • UndertowEmbeddedServletContainerFactory

    这里以大家熟悉的Tomcat为例,首先Spring Boot会判断当前环境中是否引入了Servlet和Tomcat依赖,并且当前容器中没有自定义的EmbeddedServletContainerFactory的情况下,则创建Tomcat容器工厂。其他Servlet容器工厂也是同样的道理。

1)、EmbeddedServletContainerFactory:嵌入式Servlet容器工厂

public interface EmbeddedServletContainerFactory {

    EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers);
}

内部只有一个方法,用于获取嵌入式的Servlet容器。

该工厂接口主要有三个实现类,分别对应三种嵌入式Servlet容器的工厂类,如图所示:

工厂继承树.png

2)、EmbeddedServletContainer:嵌入式Servlet容器

同样道理,对应三种嵌入式Servlet容器,如图所示:

3)、以Tomcat容器工厂TomcatEmbeddedServletContainerFactory类为例:

public class TomcatEmbeddedServletContainerFactory
        extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    
    //other code...
    
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
        //创建一个Tomcat
        Tomcat tomcat = new Tomcat();
        
        //配置Tomcat的基本环节
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        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);
        
        //包装tomcat对象,返回一个嵌入式Tomcat容器,内部会启动该tomcat容器
        return getTomcatEmbeddedServletContainer(tomcat);
    }
}

看看TomcatEmbeddedServletContainerFactory#getTomcatEmbeddedServletContainer函数:

protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
    Tomcat tomcat) {
    return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
}

该函数很简单,就是来创建Tomcat容器并返回。

看看TomcatEmbeddedServletContainer类定义:

public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {

    public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        
        //初始化嵌入式Tomcat容器,并启动Tomcat
        initialize();
    }
    
    private void initialize() throws EmbeddedServletContainerException {
        TomcatEmbeddedServletContainer.logger
                .info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();
                try {
                    final Context context = findContext();
                    context.addLifecycleListener(new LifecycleListener() {

                        @Override
                        public void lifecycleEvent(LifecycleEvent event) {
                            if (context.equals(event.getSource())
                                    && Lifecycle.START_EVENT.equals(event.getType())) {
                                // Remove service connectors so that protocol
                                // binding doesn't happen when the service is
                                // started.
                                removeServiceConnectors();
                            }
                        }

                    });

                    // Start the server to trigger initialization listeners
                      //启动tomcat
                    this.tomcat.start();

                    // We can re-throw failure exception directly in the main thread
                    rethrowDeferredStartupExceptions();

                    try {
                        ContextBindings.bindClassLoader(context, getNamingToken(context),
                                getClass().getClassLoader());
                    }
                    catch (NamingException ex) {
                        // Naming is not enabled. Continue
                    }

                    // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                    // blocking non-daemon to stop immediate shutdown
                    startDaemonAwaitThread();
                }
                catch (Exception ex) {
                    containerCounter.decrementAndGet();
                    throw ex;
                }
            }
            catch (Exception ex) {
                stopSilently();
                throw new EmbeddedServletContainerException(
                        "Unable to start embedded Tomcat", ex);
            }
        }
    }
}

到这里就启动了嵌入式的Servlet容器,其他容器类似。

那么问题来了,我们对嵌入式容器的修改配置是如何生效的?

​ 之前讲过可以通过修改ServerProperties相关配置或者自定义EmbeddedServletContainerCustomizer定制器两种方式来修改默认配置。而ServerProperties其实就是EmbeddedServletContainerCustomizer的子类,所以说到底还是EmbeddedServletContainerCustomizer起了修改的作用。

​ 其实在EmbeddedServletContainerAutoConfiguration类上导入了一个BeanPostProcessorsRegistrar类:

@Import(BeanPostProcessorsRegistrar.class)

该类主要用于给容器导入组件,看定义:

public static class BeanPostProcessorsRegistrar
            implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

    private ConfigurableListableBeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof ConfigurableListableBeanFactory) {
            this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
        }
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        if (this.beanFactory == null) {
            return;
        }
        //注册了一个EmbeddedServletContainerCustomizerBeanPostProcessor后置处理器的Bean
        registerSyntheticBeanIfMissing(registry,
                                       "embeddedServletContainerCustomizerBeanPostProcessor",
                                       EmbeddedServletContainerCustomizerBeanPostProcessor.class);
        registerSyntheticBeanIfMissing(registry,
                                       "errorPageRegistrarBeanPostProcessor",
                                       ErrorPageRegistrarBeanPostProcessor.class);
    }

    private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
                                                String name, Class<?> beanClass) {
        if (ObjectUtils.isEmpty(
            this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(name, beanDefinition);
        }
    }

}

后置处理器:在bean初始化前(创建完成,还未属性赋值),会执行初始化工作。

所以重点是在registerBeanDefinitions方法中向容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor类型的Bean:

public class EmbeddedServletContainerCustomizerBeanPostProcessor
        implements BeanPostProcessor, BeanFactoryAware {

    //初始化之前
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        if (bean instanceof ConfigurableEmbeddedServletContainer) {
            postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
        }
        return bean;
    }
    
    private void postProcessBeforeInitialization(
            ConfigurableEmbeddedServletContainer bean) {
        //获取所有的定制器,调用每个定制器的customize方法,给Servlet容器进行属性赋值
        for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
            customizer.customize(bean);
        }
    }
    
    //从IOC容器中获取所有类型为EmbeddedServletContainerCustomizer的定制器
    private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
                //类型为EmbeddedServletContainerCustomizer的Bean
                    this.beanFactory
                            .getBeansOfType(EmbeddedServletContainerCustomizer.class,
                                    false, false)
                            .values());
            Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }
    
    //other code...
}

所以之前介绍可以向容器中添加一个自定义的EmbeddedServletContainerCustomizer类型的组件,用于自定义属性配置,然后在导入的后置处理器中获取到该组件,并调用该自定义组件的customize方法,来修改默认的属性配置。

总结:

​ (1)、Spring Boot根据导入容器类型的依赖情况,会给容器中添加相应的EmbeddedServletContainerFactory嵌入式Servlet容器工厂;

​ (2)、应用程序一旦导入了嵌入式Servlet容器依赖,就会触动后置处理器EmbeddedServletContainerCustomizerBeanPostProcessor

​ (3)、后置处理器从IOC容器中获取所有的EmbeddedServletContainerCustomizer类型的嵌入式Servlet容器定制器,并调用每个定制器的定制方法customize,从而修改默认属性配置。

1.5、嵌入式Servlet容器启动原理

过程:

​ 1)、Spring Boot应用启动运行run方法;

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);
        
        //创建一个ApplicationContext容器
        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);
    }
}

​ 2)、createApplicationContext();创建IOC容器,如果是web应用,则创建AnnotationConfigEmbeddedWebApplicationContext的IOC容器;如果不是,则创建AnnotationConfigApplicationContext的IOC容器;

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

//SpringApplication#createApplicationContext
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            //根据应用环境,创建不同的IOC容器
            contextClass = Class.forName(this.webEnvironment
                                         ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                "Unable create a default ApplicationContext, "
                + "please specify an ApplicationContextClass",
                ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

​ 3)、refreshContext(context);Spring Boot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中每一个组件】;

//SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
   
    //Other code...
}

​ 4)、refresh(context);刷新刚才创建的IOC容器;

//SpringApplication#refresh
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

​ 5)、调用抽象父类的refresh()方法;

//AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
           //...
        }

        finally {
            //...
        }
    }
}

​ 6)、抽象父类AbstractApplicationContext类的子类EmbeddedWebApplicationContextonRefresh方法;

//EmbeddedWebApplicationContext#onRefresh
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createEmbeddedServletContainer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start embedded container",
                                              ex);
    }
}

​ 7)、在createEmbeddedServletContainer方法中会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器:

//EmbeddedWebApplicationContext#createEmbeddedServletContainer
private void createEmbeddedServletContainer() {
    EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    ServletContext localServletContext = getServletContext();
    if (localContainer == null && localServletContext == null) {
        //获取嵌入式Servlet容器工厂
        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        //根据容器工厂来获取对应的嵌入式Servlet容器
        this.embeddedServletContainer = containerFactory
            .getEmbeddedServletContainer(getSelfInitializer());
    }
    else if (localServletContext != null) {
        try {
            getSelfInitializer().onStartup(localServletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                                                  ex);
        }
    }
    initPropertySources();
}

​ 8)、从IOC容器中获取嵌入式Servlet容器工厂:

//EmbeddedWebApplicationContext#getEmbeddedServletContainerFactory
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
    // Use bean names so that we don't consider the hierarchy
    String[] beanNames = getBeanFactory()
        .getBeanNamesForType(EmbeddedServletContainerFactory.class);
    if (beanNames.length == 0) {
        throw new ApplicationContextException(
            "Unable to start EmbeddedWebApplicationContext due to missing "
            + "EmbeddedServletContainerFactory bean.");
    }
    if (beanNames.length > 1) {
        throw new ApplicationContextException(
            "Unable to start EmbeddedWebApplicationContext due to multiple "
            + "EmbeddedServletContainerFactory beans : "
            + StringUtils.arrayToCommaDelimitedString(beanNames));
    }
    return getBeanFactory().getBean(beanNames[0],
                                    EmbeddedServletContainerFactory.class);
}

在上文中解释到,如果加入了嵌入式的Servlet容器依赖,Spring Boot就会自动配置一个嵌入式Servlet容器工厂。接着就使用后置处理器,来获取所有的定制器来定制配置,所以上述源码中会从IOC容器中获取EmbeddedServletContainerFactory类型的容器工厂,并返回。

​ 9)、使用Servlet容器工厂获取嵌入式的Servlet容器,具体使用哪个容器工厂需要看配置环境依赖:

 this.embeddedServletContainer = containerFactory
            .getEmbeddedServletContainer(getSelfInitializer());

​ 获取嵌入式的Servlet容器后,会自动启动Servlet容器。

​ 10)、上述过程是首先启动IOC容器,接着启动嵌入式的Servlet容器,再接着将IOC容器中剩下没有创建的对象获取出来,比如自己创建的controller等等。

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

看看finishBeanFactoryInitialization方法:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    //other code...

    // Instantiate all remaining (non-lazy-init) singletons.
    beanFactory.preInstantiateSingletons();
}

大概看看preInstantiateSingletons方法:

@Override
public void preInstantiateSingletons() throws BeansException {
   
    List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                boolean isEagerInit;
                if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                    isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                        @Override
                        public Boolean run() {
                            return ((SmartFactoryBean<?>) factory).isEagerInit();
                        }
                    }, getAccessControlContext());
                }
                else {
                    isEagerInit = (factory instanceof SmartFactoryBean &&
                                   ((SmartFactoryBean<?>) factory).isEagerInit());
                }
                if (isEagerInit) {
                    getBean(beanName);
                }
            }
            else {
                //注册bean
                getBean(beanName);
            }
        }
    }

    // Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    @Override
                    public Object run() {
                        smartSingleton.afterSingletonsInstantiated();
                        return null;
                    }
                }, getAccessControlContext());
            }
            else {
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }
}

使用getBean方法来通过反射将所有未创建的实例创建出来,并加入到IOC容器中。

9、使用外置的Servlet容器

嵌入式Servlet容器:

​ 优点:简单,便携;

​ 缺点:默认不支持jsp,优化定制比较复杂;

使用外置Servlet容器的步骤:

​ 1)、必须创建一个war项目,需要建立好web项目的目录结构,特别是webapp/WEB-INF/web.xml

​ 2)、嵌入式的Tomcat依赖的scope指定为provided

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

​ 3)、必须编写一个SpringBootServletInitializer类的子类,并重写configure方法;

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringBoot04WebJspApplication.class);
    }
}

​ 4)、启动服务器。

jar包和war包启动区别:

jar包:执行SpringBootApplication的run方法,启动IOC容器,然后创建嵌入式的Servlet容器。

​ war包:启动Servlet服务器,服务器启动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)、根据上述描述的Servlet3.0+规则,可以在Spring的web模块里面找到有个文件名为javax.servlet.ServletContainerInitializer的文件,而文件的内容为org.springframework.web.SpringServletContainerInitializer,用于加载SpringServletContainerInitializer类;

​ 3)、看看SpringServletContainerInitializer类定义:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
    /**
     * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
     * implementations present on the application classpath.
     * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
     * Servlet 3.0+ containers will automatically scan the classpath for implementations
     * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
     * such types to the {@code webAppInitializerClasses} parameter of this method.
     * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
     * this method is effectively a no-op. An INFO-level log message will be issued notifying
     * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
     * no {@code WebApplicationInitializer} implementations were found.
     * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
     * they will be instantiated (and <em>sorted</em> if the @{@link
     * org.springframework.core.annotation.Order @Order} annotation is present or
     * the {@link org.springframework.core.Ordered Ordered} interface has been
     * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
     * method will be invoked on each instance, delegating the {@code ServletContext} such
     * that each instance may register and configure servlets such as Spring's
     * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
     * or any other Servlet API componentry such as filters.
     * @param webAppInitializerClasses all implementations of
     * {@link WebApplicationInitializer} found on the application classpath
     * @param servletContext the servlet context to be initialized
     * @see WebApplicationInitializer#onStartup(ServletContext)
     * @see AnnotationAwareOrderComparator
     */
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        //为所有的WebApplicationInitializer类型的类创建实例,并加入到集合中
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        //调用每一个WebApplicationInitializer实例的onStartup方法
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

​ 在上面一段长长的注释中可以看到,SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer这个类型的类都传入到onStartup方法的Set参数中,并通过反射为这些WebApplicationInitializer类型的类创建实例;

​ 4)、函数最后,每一个WebApplicationInitializer实例都调用自己的onStartup方法;

public interface WebApplicationInitializer {

    void onStartup(ServletContext servletContext) throws ServletException;
}

​ 5)、而WebApplicationInitializer有个抽象实现类SpringBootServletInitializer(记住我们继承了该抽象类),则会调用每一个WebApplicationInitializer实例(包括SpringBootServletInitializer)的onStartup方法:

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {

    //other code...
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Logger initialization is deferred in case a ordered
        // LogServletContextInitializer is being used
        this.logger = LogFactory.getLog(getClass());
        //创建IOC容器
        WebApplicationContext rootAppContext = createRootApplicationContext(
                servletContext);
        if (rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                @Override
                public void contextInitialized(ServletContextEvent event) {
                    // no-op because the application context is already initialized
                }
            });
        }
        else {
            this.logger.debug("No ContextLoaderListener registered, as "
                    + "createRootApplicationContext() did not "
                    + "return an application context");
        }
    }

    protected WebApplicationContext createRootApplicationContext(
            ServletContext servletContext) {
        //创建Spring应用构建器,并进行相关属性设置
        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方法,创建war类型的web项目后,由于编写SpringBootServletInitializer的子类重写configure方法,所以此处调用的是我们定义的子类重写的configure方法
        builder = configure(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);
    }
    
    //Spring应用启动,创建并返回IOC容器
    protected WebApplicationContext run(SpringApplication application) {
        return (WebApplicationContext) application.run();
    }
    
}

SpringBootServletInitializer实例执行onStartup方法的时候会通过createRootApplicationContext方法来执行run方法,接下来的过程就同以jar包形式启动的应用的run过程一样了,在内部会创建IOC容器并返回,只是以war包形式的应用在创建IOC容器过程中,不再创建Servlet容器了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容