美文网首页SpringBoot系列
5) 基于JAR方式Spring Boot组件初始化

5) 基于JAR方式Spring Boot组件初始化

作者: 涣涣虚心0215 | 来源:发表于2021-03-03 01:23 被阅读0次

基于SpingMVC组件初始化那一章节,了解了WebApplicationInitializer和SpringServletContainerInitializer的关系,以及在容器启动过程中的重要作用。
但是在Spring Boot应用启动的时候,却不能debug到这两个类相关类中,是不是SCI在Spring Boot项目不起作用?
对于Spring Boot应用来说,并没有使用SpringServletContainerInitializer来进行容器初始化,而是使用了TomcatStarter(也是ServletContainerInitializer的子类)。

class TomcatStarter implements ServletContainerInitializer {
    private static final Log logger = LogFactory.getLog(TomcatStarter.class);
    private final ServletContextInitializer[] initializers;
    private volatile Exception startUpException;
    TomcatStarter(ServletContextInitializer[] initializers) {
        this.initializers = initializers;
    }
    @Override
    public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
            throws ServletException {
        try {
            for (ServletContextInitializer initializer : this.initializers) {
                initializer.onStartup(servletContext);
            }
        }
        catch (Exception ex) {
            this.startUpException = ex;
            // Prevent Tomcat from logging and re-throwing when we know we can
            // deal with it in the main thread, but log for information here.
            if (logger.isErrorEnabled()) {
                logger.error("Error starting Tomcat context. Exception: "
                        + ex.getClass().getName() + ". Message: " + ex.getMessage());
            }
        }
    }
    public Exception getStartUpException() {
        return this.startUpException;
    }

}

从源码上看,TomcatStarter是无法通过SPI来进行加载实例化的:

  • 它所在的Jar包是spring-boot.jar,并没有提供META-INT/services/目录
  • 它的声明是class不是public的
    那么它使怎么启动的呢?
源码追溯

SpringApplication源码分析这一章里讲了SpringApplication.run()方法所完成的事情。其内部主要逻辑就是创建AnnotationConfigServletWebServerApplicationContext以及ConfigurableEnvironment环境的准备(Environment主要与PropertySource以及activeProfile相关),最后调用applicationContext的refresh()方法。这里就是很关键的地方。
AnnotationConfigServletWebServerApplicationContext继承自GenericWebApplicationContext,refresh()方法就在它这里实现。

@Override
public final void refresh() throws BeansException, IllegalStateException {
    try {
        //调用abstractApplicationContext里面的refresh()方法,
        //而该refresh()方法会调用onRefresh()方法,即下面的实现
        super.refresh();
    }
    catch (RuntimeException ex) {
        stopAndReleaseWebServer();
        throw ex;
    }
}
//abstractApplicationContext会调用子类实现的onRefresh()方法
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        //创建webServer
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}
//创建webServer
private void createWebServer() {
    //一开始webServer和servletContext为null
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        //获取WebServer的工厂类,由不同的厂家实现,这里返回的是TomcatServletWebServerFactory
        ServletWebServerFactory factory = getWebServerFactory();
        //getSelfInitializer会加载所配置的ServletRegistrationBean,FIlterRegistrationBean等
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    else if (servletContext != null) {
        try {
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context",
                    ex);
        }
    }
    initPropertySources();
}
//从BeanFactory中获取所配置的ServletRegistrationBean,FIlterRegistrationBean等加入到ServletContext中
private void selfInitialize(ServletContext servletContext) throws ServletException {
        prepareWebApplicationContext(servletContext);
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(
                beanFactory);
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,
                getServletContext());
        existingScopes.restore();
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,
                getServletContext());
        for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
            beans.onStartup(servletContext);
        }
    }
}

这部分代码主要就是调用abstractApplicationContext.refresh()方法,以及通过TomcatServletWebServerFactory工厂类创建webServer。

//TomcatServletWebServerFactory开始
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new 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);
    }
    //上面创建tomcat,prepareContext就是准备TomcatEmbeddedContext
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    context.setName(getContextPath());
    。。。这里给TomcatEmbeddedContext赋值
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    //mergeInitializers就是添加TomcatStarter所需要的ServletContextInitializer
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    //创建TomcatStarter的地方
    configureContext(context, initializersToUse);
    postProcessContext(context);
}
//添加TomcatStarter所需要的ServletContextInitializer,比如SessionConfiguringInitializer
protected final ServletContextInitializer[] mergeInitializers(
        ServletContextInitializer... initializers) {
    List<ServletContextInitializer> mergedInitializers = new ArrayList<>();
    mergedInitializers.add((servletContext) -> this.initParameters
            .forEach(servletContext::setInitParameter));
    mergedInitializers.add(new SessionConfiguringInitializer(this.session));
    mergedInitializers.addAll(Arrays.asList(initializers));
    mergedInitializers.addAll(this.initializers);
    return mergedInitializers.toArray(new ServletContextInitializer[0]);
}
//创建TomcatStarter的地方
protected void configureContext(Context context,
        ServletContextInitializer[] initializers) {
    TomcatStarter starter = new TomcatStarter(initializers);
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    context.addServletContainerInitializer(starter, NO_CLASSES);
    for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
        context.addLifecycleListener(lifecycleListener);
    }
    for (Valve valve : this.contextValves) {
        context.getPipeline().addValve(valve);
    }
    for (ErrorPage errorPage : getErrorPages()) {
        new TomcatErrorPage(errorPage).addToContext(context);
    }
    for (MimeMappings.Mapping mapping : getMimeMappings()) {
        context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
    }
    configureSession(context);
    for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
        customizer.customize(context);
    }
}

从这代码看到TomcatStarter是如何创建的。

与SpringMVC容器初始化类比
  1. SpringServletContainerInitializer对应TomcatStarter
  2. WebApplicationInitializer对应ServletContextInitializer
Spring Boot怎么添加Servlet,filter,Listener?

在上述代码示例中createWebServer的时候会调用selfInitialize(),这里面会调用这些RegistrationBean。
使用RegistrationBean(ServletRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean)。
RegistrationBean实现了ServletContextInitializer。

相关文章

网友评论

    本文标题:5) 基于JAR方式Spring Boot组件初始化

    本文链接:https://www.haomeiwen.com/subject/pwzzfltx.html