SpringBoot 切入式容器如何启动

789 阅读4分钟

> ​	你是否想过, 为什么SpringBoot可以不用外置的Tomcat容器. 那么我下文带你了解为什么 ?  代码有些枯燥. 
>
> 虽然SpringBoot的  `org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration` 可以实现自动注入, 难道你不想知道为什么吗 ? 

我们从 tomcat 类往下走 .

org.apache.catalina.startup.Tomcat 这个就是一个tomcat实例 , 需要传入大量参数.

​ Minimal tomcat starter for embedding/unit tests.

​ 用于嵌入/单元测试的最小tomcat启动器。 所以人家说了用于切入式和单元测试使用的 . 对于springboot也是基于这个的. 启动方式很简单,

public void start() throws LifecycleException {
    getServer();
    getConnector();
    server.start();
}

我们往上走 org.springframework.boot.web.embedded.tomcat.TomcatWebServer 实现了org.springframework.boot.web.server.WebServer 接口, 可以看出还有其他切入式容器.

org.springframework.boot.web.embedded.tomcat.TomcatWebServer#initialize 方法会调用start方法.

private void initialize() throws WebServerException {
	this.tomcat.start();
   //  .....
}

在这个对象实例化的时候就会被调用 org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer(org.apache.catalina.startup.Tomcat, boolean)

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    // 初始化tomcat
    initialize();
}

继续往上走 org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getTomcatWebServer

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

继续往上走 org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    // 1. 实例化 Tomcat
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory
            : createTempDir("tomcat");
    // 2. 设置basedir , 文件路径 , 默认在user用户下的.temp目录下. C:\Users\12986\AppData\Local\Temp\tomcat.4249223367467675192.8080
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    // 3. 初始化一个connector , 放入协议`org.apache.coyote.http11.Http11NioProtocol`
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    // 4. 设置connector
    tomcat.setConnector(connector);
    // 自动发布为false.其实就是热部署
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}

继续往上走 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        // 启动
        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();
}

继续往上走 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

继续往上走 org.springframework.context.support.AbstractApplicationContext#refresh 方法了 我们就先放到这里, 因为我们知道 spring的context在初始化完成会调用refresh刷新(也就是实例化和事件发布)机制.

所以我们回到了SpringApplication.run(SpringEmbedServletContainerApplication.class, args); 我们的入口

这个其实就是到头了 我们分析一下 SpringApplication

SpringApplication.run(SpringEmbedServletContainerApplication.class, args);
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
        String[] args) {
    return new SpringApplication(primarySources).run(args);
}

继续进入构造方法.

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 这里获取 webApplicationType
    this.webApplicationType = deduceWebApplicationType();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

this.webApplicationType = deduceWebApplicationType(); 这里要获取webApplicationType , 所以进入了org.springframework.boot.SpringApplication#deduceWebApplicationType 来判断类型. 在没有任何配置的情况的下默认是 WebApplicationType.SERVLET 类型. 这个玩意是有用,

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    // 返回SERVLET
    return WebApplicationType.SERVLET;
}

org.springframework.boot.SpringApplication#run(java.lang.String...) 启动了 . 这个是整个初始化过程.

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        //1. 通过反射创建一个 context . 
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        
        //2. 这里要刷新 context 
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

我们看第一步. context = createApplicationContext(); -> org.springframework.boot.SpringApplication#createApplicationContext 这里了

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
           // 显然我们是 SERVLET类型. 反射生成这个对象. 
            case SERVLET:
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

第二步会调用 refreshContext(context); -> org.springframework.boot.SpringApplication#refreshContext

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

继续进入refresh(context); -> org.springframework.boot.SpringApplication#refresh

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

这里的这个context是 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext 类型(在我们初始化的时候我们知道是Servlet类型,所以可以断定) , 他继承 了 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext

我们看他的 refresh方法org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh

@Override
public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh();
    }
    catch (RuntimeException ex) {
        stopAndReleaseWebServer();
        throw ex;
    }
}

继续走 调用的是 super.refresh(); 父类是 org.springframework.context.support.AbstractApplicationContext#refresh ,就找到了 org.springframework.context.support.AbstractApplicationContext#onRefresh 方法调用了. 就和我们上面接入了, 所以 整个tomcat初始化过程就实现了 .

@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();

            // 这里就是初始 tomcat容器了 . 
            // 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) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}