六、SpringBoot配置嵌入式Servlet容器

Spring Boot默認(rèn)使用Tomcat作為嵌入式的Servlet容器,只要引入了spring-boot-start-web依賴,則默認(rèn)是用Tomcat作為Servlet容器:

嵌入式Servlet容器.png

1.1、定制和修改Servlet容器的相關(guān)配置

1)、修改和server有關(guān)的配置(ServerProperties,它其實(shí)也是EmbeddedServletContainerCustomizer的子類):

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

# tomcat相關(guān)設(shè)置
server.tomcat.uri-encoding=UTF-8

2)、編寫EmbeddedServletContainerCustomizer(嵌入式的Servlet容器的定制器)來修改Servlet容器的配置,返回一個(gè)自定義的定制器Bean:

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

1.2、注冊Servlet容器的三大組件(Servlet、Filter、Listener)

? 由于Spring Boot默認(rèn)是以jar包的形式啟動(dòng)嵌入式的Servlet容器,從而來啟動(dòng)Spring Boot的web應(yīng)用,沒有web.xml文件。

? 以前編寫三大組件大多都需要在web.xml文件中進(jìn)行配置(使用注解除外,@WebServlet,@WebListener@WebFilter),而現(xiàn)在使用SpringBoot作為框架,如果需要編寫三大組件,則需要使用配置的方式進(jìn)行注冊。

要注冊三大組件:

  • ServletRegistrationBean:注冊Servlet
//Servlet定義
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("這是一個(gè)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啟動(dòng)");
    }

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

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

? 最熟悉的莫過于,在Spring Boot在自動(dòng)配置SpringMVC的時(shí)候,會自動(dòng)注冊SpringMVC前端控制器:DispatcherServlet,該控制器主要在DispatcherServletAutoConfiguration自動(dòng)配置類中進(jìn)行注冊的。

@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());
        //默認(rèn)攔截 / 所有請求,包括靜態(tài)資源,但是不攔截jsp請求;/*會攔截jsp
        //可以通過修改server.servlet-path來修改默認(rèn)的攔截請求
        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進(jìn)行注冊,并將urlPattern設(shè)為了/,這樣就類似原來的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默認(rèn)支持Tomcat,Jetty,和Undertow作為底層容器。如圖:

繼承樹.png

而Spring Boot默認(rèn)使用Tomcat,一旦引入spring-boot-starter-web模塊,就默認(rèn)使用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容器自動(dòng)配置原理

? 其中EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自動(dòng)配置類,該類在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...
}

? 在這個(gè)自動(dòng)配置類中配置了三個(gè)容器工廠的Bean,分別是:

  • TomcatEmbeddedServletContainerFactory

  • JettyEmbeddedServletContainerFactory

  • UndertowEmbeddedServletContainerFactory

    ?

    這里以大家熟悉的Tomcat為例,首先Spring Boot會判斷當(dāng)前環(huán)境中是否引入了Servlet和Tomcat依賴,并且當(dāng)前容器中沒有自定義的EmbeddedServletContainerFactory的情況下,則創(chuàng)建Tomcat容器工廠。其他Servlet容器工廠也是同樣的道理。

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

public interface EmbeddedServletContainerFactory {

    EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers);
}

內(nèi)部只有一個(gè)方法,用于獲取嵌入式的Servlet容器。

該工廠接口主要有三個(gè)實(shí)現(xiàn)類,分別對應(yīng)三種嵌入式Servlet容器的工廠類,如圖所示:

工廠繼承樹.png

2)、EmbeddedServletContainer:嵌入式Servlet容器

同樣道理,對應(yīng)三種嵌入式Servlet容器,如圖所示:

3)、以Tomcat容器工廠TomcatEmbeddedServletContainerFactory類為例:

public class TomcatEmbeddedServletContainerFactory
        extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
    
    //other code...
    
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers) {
        //創(chuàng)建一個(gè)Tomcat
        Tomcat tomcat = new Tomcat();
        
        //配置Tomcat的基本環(huán)節(jié)
        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對象,返回一個(gè)嵌入式Tomcat容器,內(nèi)部會啟動(dòng)該tomcat容器
        return getTomcatEmbeddedServletContainer(tomcat);
    }
}

看看TomcatEmbeddedServletContainerFactory#getTomcatEmbeddedServletContainer函數(shù):

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

該函數(shù)很簡單,就是來創(chuàng)建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容器,并啟動(dòng)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
                      //啟動(dòng)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);
            }
        }
    }
}

到這里就啟動(dòng)了嵌入式的Servlet容器,其他容器類似。

那么問題來了,我們對嵌入式容器的修改配置是如何生效的?

? 之前講過可以通過修改ServerProperties相關(guān)配置或者自定義EmbeddedServletContainerCustomizer定制器兩種方式來修改默認(rèn)配置。而ServerProperties其實(shí)就是EmbeddedServletContainerCustomizer的子類,所以說到底還是EmbeddedServletContainerCustomizer起了修改的作用。

? 其實(shí)在EmbeddedServletContainerAutoConfiguration類上導(dǎo)入了一個(gè)BeanPostProcessorsRegistrar類:

@Import(BeanPostProcessorsRegistrar.class)

該類主要用于給容器導(dǎo)入組件,看定義:

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;
        }
        //注冊了一個(gè)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初始化前(創(chuàng)建完成,還未屬性賦值),會執(zhí)行初始化工作。

所以重點(diǎn)是在registerBeanDefinitions方法中向容器中導(dǎo)入了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) {
        //獲取所有的定制器,調(diào)用每個(gè)定制器的customize方法,給Servlet容器進(jìn)行屬性賦值
        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...
}

所以之前介紹可以向容器中添加一個(gè)自定義的EmbeddedServletContainerCustomizer類型的組件,用于自定義屬性配置,然后在導(dǎo)入的后置處理器中獲取到該組件,并調(diào)用該自定義組件的customize方法,來修改默認(rèn)的屬性配置。

總結(jié):

? (1)、Spring Boot根據(jù)導(dǎo)入容器類型的依賴情況,會給容器中添加相應(yīng)的EmbeddedServletContainerFactory嵌入式Servlet容器工廠;

? (2)、應(yīng)用程序一旦導(dǎo)入了嵌入式Servlet容器依賴,就會觸動(dòng)后置處理器EmbeddedServletContainerCustomizerBeanPostProcessor

? (3)、后置處理器從IOC容器中獲取所有的EmbeddedServletContainerCustomizer類型的嵌入式Servlet容器定制器,并調(diào)用每個(gè)定制器的定制方法customize,從而修改默認(rèn)屬性配置。

1.5、嵌入式Servlet容器啟動(dòng)原理

過程:

? 1)、Spring Boot應(yīng)用啟動(dòng)運(yùn)行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);
        
        //創(chuàng)建一個(gè)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();創(chuàng)建IOC容器,如果是web應(yīng)用,則創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext的IOC容器;如果不是,則創(chuàng)建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 {
            //根據(jù)應(yīng)用環(huán)境,創(chuàng)建不同的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容器【創(chuàng)建IOC容器對象,并初始化容器,創(chuàng)建容器中每一個(gè)組件】;

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

?

? 4)、refresh(context);刷新剛才創(chuàng)建的IOC容器;

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

?

? 5)、調(diào)用抽象父類的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();
        //根據(jù)容器工廠來獲取對應(yīng)的嵌入式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就會自動(dòng)配置一個(gè)嵌入式Servlet容器工廠。接著就使用后置處理器,來獲取所有的定制器來定制配置,所以上述源碼中會從IOC容器中獲取EmbeddedServletContainerFactory類型的容器工廠,并返回。

?

? 9)、使用Servlet容器工廠獲取嵌入式的Servlet容器,具體使用哪個(gè)容器工廠需要看配置環(huán)境依賴:

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

? 獲取嵌入式的Servlet容器后,會自動(dòng)啟動(dòng)Servlet容器。

? 10)、上述過程是首先啟動(dòng)IOC容器,接著啟動(dòng)嵌入式的Servlet容器,再接著將IOC容器中剩下沒有創(chuàng)建的對象獲取出來,比如自己創(chuàng)建的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方法來通過反射將所有未創(chuàng)建的實(shí)例創(chuàng)建出來,并加入到IOC容器中。

9、使用外置的Servlet容器

嵌入式Servlet容器:

? 優(yōu)點(diǎn):簡單,便攜;

? 缺點(diǎn):默認(rèn)不支持jsp,優(yōu)化定制比較復(fù)雜;

使用外置Servlet容器的步驟:

? 1)、必須創(chuàng)建一個(gè)war項(xiàng)目,需要建立好web項(xiàng)目的目錄結(jié)構(gòu),特別是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)、必須編寫一個(gè)SpringBootServletInitializer類的子類,并重寫configure方法;

public class ServletInitializer extends SpringBootServletInitializer {

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

? 4)、啟動(dòng)服務(wù)器。

jar包和war包啟動(dòng)區(qū)別:

? jar包:執(zhí)行SpringBootApplication的run方法,啟動(dòng)IOC容器,然后創(chuàng)建嵌入式的Servlet容器。

? war包:啟動(dòng)Servlet服務(wù)器,服務(wù)器啟動(dòng)SpringBoot應(yīng)用【SpringBootServletInitializer】,然后才啟動(dòng)IOC容器。

Servlet3.0+規(guī)則:

? 1)、服務(wù)器啟動(dòng)(web應(yīng)用啟動(dòng))會創(chuàng)建當(dāng)前web應(yīng)用里面每一個(gè)jar包里面ServletContainerInitializer實(shí)例;

** 2)、ServletContainerInitializer的實(shí)現(xiàn)放在jar包的META-INF/services文件夾下,有個(gè)名為javax.servlet.ServletContainerInitializer的文件,內(nèi)容就是ServletContainerInitializer實(shí)現(xiàn)類的全類名;**

** 3)、還可以使用@HandlesTypes注解,在應(yīng)用啟動(dòng)的時(shí)候加載指定的類。**

流程&原理:

? 1)、啟動(dòng)Tomcat;

? 2)、根據(jù)上述描述的Servlet3.0+規(guī)則,可以在Spring的web模塊里面找到有個(gè)文件名為javax.servlet.ServletContainerInitializer的文件,而文件的內(nèi)容為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類型的類創(chuàng)建實(shí)例,并加入到集合中
                        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);
        //調(diào)用每一個(gè)WebApplicationInitializer實(shí)例的onStartup方法
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

? 在上面一段長長的注釋中可以看到,SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)標(biāo)注的所有WebApplicationInitializer這個(gè)類型的類都傳入到onStartup方法的Set參數(shù)中,并通過反射為這些WebApplicationInitializer類型的類創(chuàng)建實(shí)例;

? 4)、函數(shù)最后,每一個(gè)WebApplicationInitializer實(shí)例都調(diào)用自己的onStartup方法;

public interface WebApplicationInitializer {

    void onStartup(ServletContext servletContext) throws ServletException;
}

? 5)、而WebApplicationInitializer有個(gè)抽象實(shí)現(xiàn)類SpringBootServletInitializer(記住我們繼承了該抽象類),則會調(diào)用每一個(gè)WebApplicationInitializer實(shí)例(包括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());
        //創(chuàng)建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) {
        //創(chuàng)建Spring應(yīng)用構(gòu)建器,并進(jìn)行相關(guān)屬性設(shè)置
        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);
        
        //調(diào)用configure方法,創(chuàng)建war類型的web項(xiàng)目后,由于編寫SpringBootServletInitializer的子類重寫configure方法,所以此處調(diào)用的是我們定義的子類重寫的configure方法
        builder = configure(builder);
        
        //通過構(gòu)建器構(gòu)建了一個(gè)Spring應(yīng)用
        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);
        }
        //啟動(dòng)Spring應(yīng)用
        return run(application);
    }
    
    //Spring應(yīng)用啟動(dòng),創(chuàng)建并返回IOC容器
    protected WebApplicationContext run(SpringApplication application) {
        return (WebApplicationContext) application.run();
    }
    
}

SpringBootServletInitializer實(shí)例執(zhí)行onStartup方法的時(shí)候會通過createRootApplicationContext方法來執(zhí)行run方法,接下來的過程就同以jar包形式啟動(dòng)的應(yīng)用的run過程一樣了,在內(nèi)部會創(chuàng)建IOC容器并返回,只是以war包形式的應(yīng)用在創(chuàng)建IOC容器過程中,不再創(chuàng)建Servlet容器了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • SpringMVC原理分析 Spring Boot學(xué)習(xí) 5、Hello World探究 1、POM文件 1、父項(xiàng)目...
    jack_jerry閱讀 1,484評論 0 1
  • https://github.com/cuzz1/springboot-learning 四、Web開發(fā) 1、簡介...
    cuzz_閱讀 2,506評論 0 5
  • 原文鏈接:https://docs.spring.io/spring-boot/docs/1.4.x/refere...
    pseudo_niaonao閱讀 4,895評論 0 9
  • 要加“m”說明是MB,否則就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505閱讀 5,068評論 0 53
  • 危險(xiǎn)的還是取得成功之后,能否守住初心,堅(jiān)守底線。要成為圣人,必是歷經(jīng)磨難,在艱難困苦中不迷失自己。
    耕耘_c7e6閱讀 178評論 0 0

友情鏈接更多精彩內(nèi)容