1. 程式人生 > >SpringBoot原始碼解析 內建Tomcat啟動流程

SpringBoot原始碼解析 內建Tomcat啟動流程

開啟原始碼過程略去不談,找到入口方法之後發現有兩次呼叫,而我們實際需要開始關注的是下面這個方法。

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return (new SpringApplication(sources)).run(args);
}

可以看到首先呼叫了有參構造方法,然後呼叫run(String[] args)進行後續操作。
構造方法中需要關注的是initialize()方法,其中跟此啟動內建Tomcat相關的則是deduceWebEnvironment()

這個方法,從命名可以得知此方法用於判斷當前是否為Web環境。

//專案初始化
private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }

        //確定是否是Web環境
        this.webEnvironment =    this.deduceWebEnvironment();

        this
.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); } //跟Web環境相關Class private static final String[] WEB_ENVIRONMENT_CLASSES = new
String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"}; //判斷是否web環境 只有陣列中的Class全部存在才會判定為Web環境 private boolean deduceWebEnvironment() { String[] var1 = WEB_ENVIRONMENT_CLASSES; int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { String className = var1[var3]; if (!ClassUtils.isPresent(className, (ClassLoader)null)) { return false; } } return true; }

構造方法完成之後則是run(String[] args)方法。

  public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            Banner printedBanner = this.printBanner(environment);
            //建立ApplicationContext
            context = this.createApplicationContext();
            new FailureAnalyzers(context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //重新整理Context
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            listeners.finished(context, (Throwable)null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
            throw new IllegalStateException(var9);
        }
}

其中跟啟動內建Tomcat相關的則是createApplicationContext()refreshContext(context)方法,第一個方法大家顧名思義,功能在此不過多解釋。下面是它的具體實現。

 //建立AnnotationConfigEmbeddedWebApplicationContext
  protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment ? "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext" : "org.springframework.context.annotation.AnnotationConfigApplicationContext");
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
    }

可以看到,其主體是一個基於this.webEnvironment變數的判斷,此處我們獲取到的ConfigurableApplicationContext的具體實現是AnnotationConfigEmbeddedWebApplicationContext。然而Tomcat是在哪裡啟動的呢?接下來讓我們看下refreshContext(context)的實現。

private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
                ;
            }
        }

}

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

可以看到,其首先呼叫的refresh方法,而在此方法中,applicationContext被轉形為AbstractApplicationContext,然後呼叫了其中的refresh()方法。
開啟原始碼瞄兩眼,原來是個抽象類。嗯,依賴抽象而不是實現。再看下依賴關係,
image
AnnotationConfigEmbeddedWebApplicationContext赫然在列。稍微看下refresh(),其中關鍵是onRefresh()這個方法。

/**
 * Template method which can be overridden to add context-specific refresh work.
 * Called on initialization of special beans, before instantiation of singletons.
 * <p>This implementation is empty.
 * @throws BeansException in case of errors
 * @see #refresh()
 */
protected void onRefresh() throws BeansException {
    // For subclasses: do nothing by default.
}

但是我們在AnnotationConfigEmbeddedWebApplicationContext其實是沒有找到關於onRefresh()的實現的。那父類呢?我們可以看到其是繼承於EmbeddedWebApplicationContext的,觀察下其關於onRefresh()的具體實現。

protected void onRefresh() {
    super.onRefresh();

    try {
        //建立Web容器
        this.createEmbeddedServletContainer();
    } catch (Throwable var2) {
        throw new ApplicationContextException("Unable to start embedded container", var2);
    }
}

從命名可以看出,這裡是真正Tomcat開始建立的入口,其實現如下:

private void createEmbeddedServletContainer() {
        EmbeddedServletContainer localContainer = this.embeddedServletContainer;
        ServletContext localServletContext = this.getServletContext();
        if (localContainer == null && localServletContext == null) {
            EmbeddedServletContainerFactory containerFactory = this.getEmbeddedServletContainerFactory();
            this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (localServletContext != null) {
            try {
                this.getSelfInitializer().onStartup(localServletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
}

至此,Spring boot內建ServletContainer啟動流程完成。其流程如下:

Created with Raphaël 2.1.2呼叫構造方法,判斷是否Web環境獲取ApplicationContext具體實現向上轉型呼叫EmbeddedWebApplicationContext中的onRefresh()呼叫createEmbeddedServletContainer,啟動完成