六吭敢、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容器的定制器)來(lái)修改Servlet容器的配置嫉嘀,返回一個(gè)自定義的定制器Bean:

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

1.2、注冊(cè)Servlet容器的三大組件(Servlet魄揉、Filter剪侮、Listener)

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

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

要注冊(cè)三大組件:

  • ServletRegistrationBean:注冊(cè)Servlet
//Servlet定義
public class MyServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("這是一個(gè)servlet請(qǐng)求...");
    }
}

//Servlet注冊(cè)
@Configuration
public class MyServletConfig {

    //注冊(cè)Servlet
    @Bean
    public ServletRegistrationBean myServlet(){
        return new ServletRegistrationBean(new MyServlet(), "/myServlet");
    }
}
  • FilterRegistrationBean:注冊(cè)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注冊(cè)
@Bean
public FilterRegistrationBean myFilter(){
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new MyFilter());
    bean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
    return bean;
}
  • ServletListenerRegistrationBean:注冊(cè)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注冊(cè)
@Bean
public ServletListenerRegistrationBean myListener(){
    return new ServletListenerRegistrationBean<>(new MyListener());
}

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

@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)攔截 / 所有請(qǐng)求毡泻,包括靜態(tài)資源,但是不攔截jsp請(qǐng)求粘优;/*會(huì)攔截jsp
        //可以通過(guò)修改server.servlet-path來(lái)修改默認(rèn)的攔截請(qǐng)求
        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)行注冊(cè),并將urlPattern設(shè)為了/雹顺,這樣就類似原來(lái)的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作為底層容器。如圖:

繼承樹(shù).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開(kāi)頭的配置不一樣外,其他都類似疯溺。

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會(huì)判斷當(dāng)前環(huán)境中是否引入了Servlet和Tomcat依賴墨闲,并且當(dāng)前容器中沒(méi)有自定義的EmbeddedServletContainerFactory的情況下今妄,則創(chuàng)建Tomcat容器工廠。其他Servlet容器工廠也是同樣的道理鸳碧。

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

public interface EmbeddedServletContainerFactory {

    EmbeddedServletContainer getEmbeddedServletContainer(
            ServletContextInitializer... initializers);
}

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

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

工廠繼承樹(shù).png

2)套利、EmbeddedServletContainer:嵌入式Servlet容器

同樣道理推励,對(duì)應(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對(duì)象验辞,返回一個(gè)嵌入式Tomcat容器,內(nèi)部會(huì)啟動(dòng)該tomcat容器
        return getTomcatEmbeddedServletContainer(tomcat);
    }
}

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

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

該函數(shù)很簡(jiǎn)單喊衫,就是來(lái)創(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容器族购,其他容器類似鼻听。

那么問(wèn)題來(lái)了,我們對(duì)嵌入式容器的修改配置是如何生效的联四?

? 之前講過(guò)可以通過(guò)修改ServerProperties相關(guān)配置或者自定義EmbeddedServletContainerCustomizer定制器兩種方式來(lái)修改默認(rèn)配置。而ServerProperties其實(shí)就是EmbeddedServletContainerCustomizer的子類撑教,所以說(shuō)到底還是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;
        }
        //注冊(cè)了一個(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)建完成,還未屬性賦值)收苏,會(huì)執(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方法钻哩,來(lái)修改默認(rèn)的屬性配置。

總結(jié):

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

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

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

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

過(guò)程:

? 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容器對(duì)象散址,并初始化容器乖阵,創(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方法中會(huì)獲取嵌入式的Servlet容器工廠蹈矮,并通過(guò)工廠來(lái)獲取Servlet容器:

//EmbeddedWebApplicationContext#createEmbeddedServletContainer
private void createEmbeddedServletContainer() {
    EmbeddedServletContainer localContainer = this.embeddedServletContainer;
    ServletContext localServletContext = getServletContext();
    if (localContainer == null && localServletContext == null) {
        //獲取嵌入式Servlet容器工廠
        EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
        //根據(jù)容器工廠來(lái)獲取對(duì)應(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就會(huì)自動(dòng)配置一個(gè)嵌入式Servlet容器工廠蝠咆。接著就使用后置處理器,來(lái)獲取所有的定制器來(lái)定制配置北滥,所以上述源碼中會(huì)從IOC容器中獲取EmbeddedServletContainerFactory類型的容器工廠刚操,并返回。

?

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

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

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

? 10)占卧、上述過(guò)程是首先啟動(dòng)IOC容器,接著啟動(dòng)嵌入式的Servlet容器联喘,再接著將IOC容器中剩下沒(méi)有創(chuàng)建的對(duì)象獲取出來(lái)华蜒,比如自己創(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 {
                //注冊(cè)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方法來(lái)通過(guò)反射將所有未創(chuàng)建的實(shí)例創(chuàng)建出來(lái)豁遭,并加入到IOC容器中叭喜。

9、使用外置的Servlet容器

嵌入式Servlet容器:

? 優(yōu)點(diǎn):簡(jiǎ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包:?jiǎn)?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))會(huì)創(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);
        }
    }
}

? 在上面一段長(zhǎng)長(zhǎng)的注釋中可以看到哮伟,SpringServletContainerInitializer@HandlesTypes(WebApplicationInitializer.class)標(biāo)注的所有WebApplicationInitializer這個(gè)類型的類都傳入到onStartup方法的Set參數(shù)中干花,并通過(guò)反射為這些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(記住我們繼承了該抽象類)修赞,則會(huì)調(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);
        
        //通過(guò)構(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í)候會(huì)通過(guò)createRootApplicationContext方法來(lái)執(zhí)行run方法割择,接下來(lái)的過(guò)程就同以jar包形式啟動(dòng)的應(yīng)用的run過(guò)程一樣了,在內(nèi)部會(huì)創(chuàng)建IOC容器并返回萎河,只是以war包形式的應(yīng)用在創(chuàng)建IOC容器過(guò)程中荔泳,不再創(chuàng)建Servlet容器了蕉饼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市玛歌,隨后出現(xiàn)的幾起案子昧港,更是在濱河造成了極大的恐慌,老刑警劉巖支子,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件创肥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡值朋,警方通過(guò)查閱死者的電腦和手機(jī)叹侄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)昨登,“玉大人趾代,你說(shuō)我怎么就攤上這事》崂保” “怎么了撒强?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)糯俗。 經(jīng)常有香客問(wèn)我尿褪,道長(zhǎng),這世上最難降的妖魔是什么得湘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任杖玲,我火速辦了婚禮,結(jié)果婚禮上淘正,老公的妹妹穿的比我還像新娘摆马。我一直安慰自己,他們只是感情好鸿吆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布囤采。 她就那樣靜靜地躺著,像睡著了一般惩淳。 火紅的嫁衣襯著肌膚如雪蕉毯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,215評(píng)論 1 299
  • 那天思犁,我揣著相機(jī)與錄音代虾,去河邊找鬼。 笑死激蹲,一個(gè)胖子當(dāng)著我的面吹牛棉磨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播学辱,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乘瓤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼环形!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起衙傀,我...
    開(kāi)封第一講書(shū)人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抬吟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后统抬,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拗军,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年蓄喇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片交掏。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妆偏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盅弛,到底是詐尸還是另有隱情钱骂,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布挪鹏,位于F島的核電站见秽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏讨盒。R本人自食惡果不足惜解取,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望返顺。 院中可真熱鬧禀苦,春花似錦、人聲如沸遂鹊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)秉扑。三九已至慧邮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舟陆,已是汗流浹背误澳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吨娜,地道東北人脓匿。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宦赠,于是被迫代替她去往敵國(guó)和親陪毡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子米母,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • SpringMVC原理分析 Spring Boot學(xué)習(xí) 5、Hello World探究 1毡琉、POM文件 1铁瞒、父項(xiàng)目...
    jack_jerry閱讀 1,287評(píng)論 0 1
  • https://github.com/cuzz1/springboot-learning 四、Web開(kāi)發(fā) 1桅滋、簡(jiǎn)介...
    cuzz_閱讀 2,436評(píng)論 0 5
  • 原文鏈接:https://docs.spring.io/spring-boot/docs/1.4.x/refere...
    pseudo_niaonao閱讀 4,692評(píng)論 0 9
  • 要加“m”說(shuō)明是MB慧耍,否則就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505閱讀 4,829評(píng)論 0 53
  • 危險(xiǎn)的還是取得成功之后,能否守住初心丐谋,堅(jiān)守底線芍碧。要成為圣人,必是歷經(jīng)磨難号俐,在艱難困苦中不迷失自己泌豆。
    耕耘_(tái)c7e6閱讀 159評(píng)論 0 0