SpringApplication 深入探索

在 Spring Boot 項目的啓動類中常見代碼如下:

@SpringBootApplication
public class SpringbotApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringbotApplication.class, args);
	}
}

其中也就兩個比較引人注意的地方:

  • @SpringBootApplication
  • SpringApplication.run()

對於第一個註解 @SpringBootApplication,我已經講解了。接下來就是深入探究第二個了 SpringApplication.run() 

換個姿勢

上面的姿勢太簡單了,只一行代碼就完事了。

SpringApplication.run(SpringbotApplication.class, args);

其實是支持做一些個性化的設置,接下來我們換個姿勢瞧瞧:

@SpringBootApplication
public class SpringbotApplication {  
	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(SpringbotApplication.class);
 		// 自定義應用程序的配置
 		//app.setXxx()
 		app.run(args)
	}
}

沒錯,就是通過一個構造函數,然後設置相關的屬性,從而達到定製化服務。有哪些屬性呢?

SpringApplicationFileds

屬性對應的 get/set 方法

springapplication_getset

看到沒,還很多呢!

舉個例子:你想把 Spring Boot 項目的默認 Banner 換成你自己的,就需要在這裏如下:

public static void main(String[] args) {
//		SpringApplication.run(Springboot2Application.class, args);
  SpringApplication application = new SpringApplication(Springboot2Application.class);
  application.setBanner((environment, sourceClass, out) -> {
    //這裏打印一個logo
    System.out.println("      _      _       _\n" +
                       "     | |    (_)     | |\n" +
                       " ____| |__   _  ___ | |__    ___  _ __    __ _\n" +
                       "|_  /| '_ \\ | |/ __|| '_ \\  / _ \\| '_ \\  / _` |\n" +
                       " / / | | | || |\\__ \\| | | ||  __/| | | || (_| |\n" +
                       "/___||_| |_||_||___/|_| |_| \\___||_| |_| \\__, |\n" +
                       "                                          __/ |\n" +
                       "                                         |___/\n");
  });
  application.setBannerMode(Banner.Mode.CONSOLE);
  //你還可以幹其他的定製化初始設置
  application.run(args);
}

現在重啓項目,你就會發現,控制檯的 logo 已經換成你自己的了。

banner

banner

當然了,你可能會覺得這樣寫有點複雜,嗯嗯,確實,這樣硬編碼在代碼裏確實不太友好。你還可以在src/main/resources路徑下新建一個banner.txt文件,banner.txt中填寫好需要打印的字符串內容即可。

從該類中可以看到在 Spring Boot 2 中引入了個新的 WebApplicationType 和 WebEnvironment。

springapplication-002

webapplicationtype

springapplication-003

確實,這也是 Spring Boot 2 中比較大的特性,它是支持響應式編程的。我之前在文章中也介紹過,以後有機會會介紹它的。

SpringApplication 初始化

SpringApplication.run() 的實現纔是我們要深入探究的主角,該方法代碼如下:

//靜態方法,可用於使用默認配置運行 SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource,
      String... args) {
  return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
  return new SpringApplication(primarySources).run(args);
}

在這個靜態方法中,創建 SpringApplication 對象,並調用該對象的 run 方法。

public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}
//創建一個 SpringApplication 實例,應用上下文會根據指定的主要資源加載 beans ,實例在調用 run 方法之前可以定製化
@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));
  this.webApplicationType = deduceWebApplicationType();
  setInitializers((Collection) getSpringFactoriesInstances(
    ApplicationContextInitializer.class));
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  this.mainApplicationClass = deduceMainApplicationClass();
}

首先是進入單個參數的構造方法,然後進入兩參數的構造方法(ResourceLoader 爲 null),然後進行初始化。

1、deduceWebApplicationType() : 推斷應用的類型 ,創建的是一個 SERVLET 應用還是 REACTIVE應用或者是 NONE

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

private WebApplicationType deduceWebApplicationType() {
  if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
      && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
    return WebApplicationType.REACTIVE;	//該程序是 REACTIVE 程序
  }
  for (String className : WEB_ENVIRONMENT_CLASSES) {
    if (!ClassUtils.isPresent(className, null)) {
      return WebApplicationType.NONE;	//該程序爲 NONE
    }
  }
  return WebApplicationType.SERVLET;	//默認返回是 SERVLET 程序
}

2、setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)):初始化 classpath 下的所有的可用的 ApplicationContextInitializer。

1)、getSpringFactoriesInstances()

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
  return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//獲取所有的 Spring 工廠實例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  // Use names and ensure unique to protect against duplicates
  Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //獲取所有 Spring Factories 的名字
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                                                     classLoader, args, names);
  AnnotationAwareOrderComparator.sort(instances); //Spring 工廠實例排序
  return instances;
}
//根據讀取到的名字創建對象(Spring 工廠實例)
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
 Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
  List<T> instances = new ArrayList<>(names.size());
  for (String name : names) {
    try {
      Class<?> instanceClass = ClassUtils.forName(name, classLoader);
      Assert.isAssignable(type, instanceClass);
      Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
      T instance = (T) BeanUtils.instantiateClass(constructor, args);
      instances.add(instance);
    }
    catch (Throwable ex) {
      throw new IllegalArgumentException(
        "Cannot instantiate " + type + " : " + name, ex);
    }
  }
  return instances;
}

上面的 SpringFactoriesLoader.loadFactoryNames() ,是從 META-INF/spring.factories 的資源文件中,讀取 key 爲org.springframework.context.ApplicationContextInitializer 的 value。

springfactoriesloader

而 spring.factories 的部分內容如下:

2018-05-01_22-21-20

可以看到,最近的得到的,是 ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer 這四個類的名字。

2)、setInitializers():

public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
  this.initializers = new ArrayList<>();
  this.initializers.addAll(initializers);
}

所以,這裏 setInitializers() 所得到的成員變量 initializers 就被初始化爲ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer 這四個類的對象組成的 list。

3、setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)):初始化 classpath 下的所有的可用的 ApplicationListener。

1)、getSpringFactoriesInstances() 和上面的類似,但是它是從 META-INF/spring.factories 的資源文件中,獲取到 key 爲 org.springframework.context.ApplicationListener 的 value。

2018-05-01_22-33-56

2)、setListeners():

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
  this.listeners = new ArrayList<>();
  this.listeners.addAll(listeners);
}

所以,這裏 setListeners() 所得到的成員變量 listeners 就被初始化爲 ClearCachesApplicationListener,ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener ,ConfigFileApplicationListener,DelegatingApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener,LiquibaseServiceLocatorApplicationListener 這九個類的對象組成的 list。

4、deduceMainApplicationClass() :根據調用棧,推斷出 main 方法的類名

private Class<?> deduceMainApplicationClass() {
  try {
    StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
    for (StackTraceElement stackTraceElement : stackTrace) {
      if ("main".equals(stackTraceElement.getMethodName())) {
        return Class.forName(stackTraceElement.getClassName());
      }
    }
  }
  catch (ClassNotFoundException ex) {
    // Swallow and continue
  }
  return null;
}

run 方法背後的祕密

上面看完了構造方法後,已經初始化了一個 SpringApplication 對象,接下來調用其 run 方法,代碼如下:

//運行 Spring 應用程序,創建並刷新一個新的 ApplicationContext
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);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			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;
	}

可變個數參數 args 即是我們整個應用程序的入口 main 方法的參數。StopWatch 是來自 org.springframework.util 的工具類,可以用來方便的記錄程序的運行時間。

再來看看 1.5.12 與 2.0.1 版本的 run 方法 有什麼不一樣的地方?

difference-1.5-2.0

接下來好好分析上面新版本(2.0.1)的 run 方法的代碼並配合比較舊版本(1.5.12)。

1、configureHeadlessProperty():設置 headless 模式

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private boolean headless = true;

private void configureHeadlessProperty() {
  System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
    SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

實際上是就是設置系統屬性 java.awt.headless,該屬性會被設置爲 true。

2、getRunListeners():加載 SpringApplicationRunListener 對象

 //TODO:  xxx
SpringApplicationRunListeners listeners = getRunListeners(args);//初始化監聽器
listeners.starting();
try {
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  refreshContext(context);
  afterRefresh(context, applicationArguments);
  listeners.started(context);
  callRunners(context, applicationArguments);
}
try {
  listeners.running(context);
}

private SpringApplicationRunListeners getRunListeners(String[] args) {
  Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
    SpringApplicationRunListener.class, types, this, args));
}

上面的 getRunListeners() 中也利用 SpringFactoriesLoader 加載 META-INF/spring.factories 中 key 爲 SpringApplicationRunListener 的值,然後再將獲取到的值作爲參數傳遞到 SpringApplicationRunListeners 的構造方法中去創建對象。

2018-05-02_23-11-01

3、new DefaultApplicationArguments(args) :獲取啓動時傳入參數 args(main 方法傳進來的參數) 並初始化爲 ApplicationArguments 對象。

public DefaultApplicationArguments(String[] args) {
  Assert.notNull(args, "Args must not be null");
  this.source = new Source(args);
  this.args = args;
}

4、prepareEnvironment(listeners, applicationArguments):根據 listeners 和 applicationArguments 配置SpringBoot 應用的環境。

private ConfigurableEnvironment prepareEnvironment(
  SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
  // Create and configure the environment
  ConfigurableEnvironment environment = getOrCreateEnvironment();
  configureEnvironment(environment, applicationArguments.getSourceArgs());
  listeners.environmentPrepared(environment);
  bindToSpringApplication(environment);
  if (this.webApplicationType == WebApplicationType.NONE) {
    environment = new EnvironmentConverter(getClassLoader())
      .convertToStandardEnvironmentIfNecessary(environment);
  }
  ConfigurationPropertySources.attach(environment);
  return environment;
}
//如果 environment 不爲空,直接 get 到,否則創建
private ConfigurableEnvironment getOrCreateEnvironment() {
  if (this.environment != null) {
    return this.environment;
  }
  if (this.webApplicationType == WebApplicationType.SERVLET) {
    return new StandardServletEnvironment();
  }
  return new StandardEnvironment();
}
//配置環境
protected void configureEnvironment(ConfigurableEnvironment environment,String[] args) {
  configurePropertySources(environment, args);//配置要使用的PropertySources
  configureProfiles(environment, args);//配置要使用的Profiles
}
//將環境綁定到 SpringApplication
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
  try {
    Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
  }
  catch (Exception ex) {
    throw new IllegalStateException("Cannot bind to SpringApplication", ex);
  }
}

5、configureIgnoreBeanInfo(environment):根據環境信息配置要忽略的 bean 信息

public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
  if (System.getProperty(
    CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
    Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
                                             Boolean.class, Boolean.TRUE);
    System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
                       ignore.toString());
  }
}

6、printBanner(environment):打印標誌,上面我已經說過了。

private Banner printBanner(ConfigurableEnvironment environment) {
  if (this.bannerMode == Banner.Mode.OFF) {	//如果設置爲 off,不打印 Banner
    return null;
  }
  ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
    : new DefaultResourceLoader(getClassLoader());
  SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
    resourceLoader, this.banner);
  if (this.bannerMode == Mode.LOG) {
    return bannerPrinter.print(environment, this.mainApplicationClass, logger);
  }
  return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

7、createApplicationContext():根據應用類型來確定該 Spring Boot 項目應該創建什麼類型的 ApplicationContext ,默認情況下,如果沒有明確設置的應用程序上下文或應用程序上下文類,該方法會在返回合適的默認值。

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
  Class<?> contextClass = this.applicationContextClass;
  if (contextClass == null) {
    try {
      switch (this.webApplicationType) {	//根據應用程序的類型來初始化容器
        case SERVLET:	//servlet 應用程序
          contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
          break;
        case REACTIVE:	//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);
    }
  }
  //最後通過Spring的工具類 BeanUtils 初始化容器類 bean
  return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

來看看在 1.5.12 中是怎麼樣的?

createApplicationContext

8、exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  // Use names and ensure unique to protect against duplicates
  Set<String> names = new LinkedHashSet<>(
    SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
       classLoader, args, names);//根據類型 key 爲 SpringBootExceptionReporter 去加載
  AnnotationAwareOrderComparator.sort(instances);//對實例排序
  return instances;
}

這裏也是通過 SpringFactoriesLoader 加載 META-INF/spring.factories 中 key 爲 SpringBootExceptionReporter 的全類名的 value 值。

springbootexception

9、prepareContext(context, environment, listeners, applicationArguments, printedBanner):完成整個容器的創建與啓動以及 bean 的注入功能。

//裝配 Context
private void prepareContext(ConfigurableApplicationContext context,
   ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
   ApplicationArguments applicationArguments, Banner printedBanner) {
  //將之前準備好的 environment 設置給創建好的 ApplicationContext 使用
  context.setEnvironment(environment);
  //1、
  postProcessApplicationContext(context);
  //2、
  applyInitializers(context);
  listeners.contextPrepared(context);
  if (this.logStartupInfo) {//啓動日誌
    logStartupInfo(context.getParent() == null);
    logStartupProfileInfo(context);
  }
  // Add boot specific singleton beans
  context.getBeanFactory().registerSingleton("springApplicationArguments",
                                             applicationArguments);
  if (printedBanner != null) {
    context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
  }
  // Load the sources
  Set<Object> sources = getAllSources();
  Assert.notEmpty(sources, "Sources must not be empty");
  //3、
  load(context, sources.toArray(new Object[0]));
  listeners.contextLoaded(context);
}

1)、postProcessApplicationContext(context)

public static final String CONFIGURATION_BEAN_NAME_GENERATOR = "org.springframework.context.annotation.internalConfigurationBeanNameGenerator";

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
  if (this.beanNameGenerator != null) {
    context.getBeanFactory().registerSingleton(
      AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
      this.beanNameGenerator);
  }
  if (this.resourceLoader != null) {
    if (context instanceof GenericApplicationContext) {
      ((GenericApplicationContext) context)
      .setResourceLoader(this.resourceLoader);
    }
    if (context instanceof DefaultResourceLoader) {
      ((DefaultResourceLoader) context)
      .setClassLoader(this.resourceLoader.getClassLoader());
    }
  }
}

該方法對 context 進行了預設置,設置了 ResourceLoader 和 ClassLoader,並向 bean 工廠中添加了一個beanNameGenerator 。

2)、applyInitializers(context)

protected void applyInitializers(ConfigurableApplicationContext context) {
  for (ApplicationContextInitializer initializer : getInitializers()) {
    Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
      initializer.getClass(), ApplicationContextInitializer.class);
    Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
    initializer.initialize(context);
  }
}

在刷新之前將任何 ApplicationContextInitializer 應用於上下文

3)、load(context, sources.toArray(new Object[0]))

主要是加載各種 beans 到 ApplicationContext 對象中。

protected void load(ApplicationContext context, Object[] sources) {
  BeanDefinitionLoader loader = createBeanDefinitionLoader( //2
    getBeanDefinitionRegistry(context), sources);// 1
  if (this.beanNameGenerator != null) {
    loader.setBeanNameGenerator(this.beanNameGenerator);
  }
  if (this.resourceLoader != null) {
    loader.setResourceLoader(this.resourceLoader);
  }
  if (this.environment != null) {
    loader.setEnvironment(this.environment);
  }
  loader.load();//3
}

(1)、getBeanDefinitionRegistry(context)

獲取 bean 定義註冊表

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
  if (context instanceof BeanDefinitionRegistry) {
    return (BeanDefinitionRegistry) context;
  }
  if (context instanceof AbstractApplicationContext) {
    return (BeanDefinitionRegistry) ((AbstractApplicationContext) context)
      .getBeanFactory();
  }
  throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

(2)、createBeanDefinitionLoader()

通過 BeanDefinitionLoader 的構造方法把參數(註冊表、資源)傳進去,然後創建 BeanDefinitionLoader。

(3)、load()

把資源全部加載。

10、refreshContext(context)

private void refreshContext(ConfigurableApplicationContext context) {
  refresh(context);//1
  if (this.registerShutdownHook) {
    try {
      context.registerShutdownHook();
    }
    catch (AccessControlException ex) {
      // Not allowed in some environments.
    }
  }
}
//刷新底層的 ApplicationContext
protected void refresh(ApplicationContext applicationContext) {
  Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
  ((AbstractApplicationContext) applicationContext).refresh();
}

refreshContext(context) 方法又調用了 refresh(context)。在調用了 refresh(context) 方法之後,調用了 registerShutdownHook 方法。繼續看它的 refresh 方法:

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); //1
      // Last step: publish corresponding event.
      finishRefresh();
    } catch (BeansException 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();
    }
  }
}

到這裏,我們就看見重點了,仔細看上的註釋,正在做各種初始化工作,而今天我們關注的重點就是方法 finishBeanFactoryInitialization(beanFactory)。該方法進行了非懶加載 beans 的初始化工作。現在我們進入該方法內部,一探究竟。

finishbeanFactoryini

看上圖方法中的最後一步,調用了 beanFactory 的 preInstantiateSingletons() 方法。此處的 beanFactory 是哪個類的實例對象呢?

2018-05-02_16-17-50

可以看到 ConfigurableListableBeanFactory 接口的實現類只有 DefaultListableBeanFactory,我們看下實現類中的 preInstantiateSingletons 方法是怎麼做的。

public void preInstantiateSingletons() throws BeansException {
  // Iterate over a copy to allow for init methods which in turn register new bean definitions.
  // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
  List<String> beanNames = new ArrayList<>(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)) {
        Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
        if (bean instanceof FactoryBean) {
          final FactoryBean<?> factory = (FactoryBean<?>) bean;
          boolean isEagerInit;
         if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean){
            isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
             ((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext());
          } else {
            isEagerInit = (factory instanceof SmartFactoryBean &&
                           ((SmartFactoryBean<?>) factory).isEagerInit());
          }
          if (isEagerInit) {
            getBean(beanName);
          }
        }
      } else {
        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((PrivilegedAction<Object>) () -> {
          smartSingleton.afterSingletonsInstantiated();
          return null;
        }, getAccessControlContext());
      } else {
        smartSingleton.afterSingletonsInstantiated();
      }
    }
  }
}

從上面的代碼中可以看到很多調用了 getBean(beanName) 方法,跟蹤此方法進去後,最終發現 getBean 調用了AbstractBeanFactory 類的 doGetBean(xxx) 方法,doGetBean(xxx) 方法中有這麼一段代碼:

2018-05-02_17-17-39

2018-05-02_17-19-31

但是 createBean() 方法並沒有得到實現,實現類在 AbstractAutowireCapableBeanFactory 中。這纔是創建 bean 的核心方法。

createBean

不知不覺,代碼看的越來越深,感覺思維都差點回不去 run 方法了,切回大腦的上下文線程到 run 方法去。

11、afterRefresh(context, applicationArguments):在上下文刷新後調用該方法,其內部沒有做任何操作

2018-05-02_17-43-23

發現沒做任何操作了之後,就覺得有點奇怪,所以把當前版本和 1.5.12 對比了下,發現:

afterRefresh

在 1.5.12 中的 afterRefresh() 方法中調用了 callRunners() 方法,但是在 2.0.1 版本中的 run 方法中調用了 callRunners () 方法:

2018-05-02_17-57-52

這裏不得不說 SpringApplicationRunListeners 在 2.0.1 中的改變:

2018-05-02_18-28-17

可以發現在 run 方法中,SpringApplicationRunListeners 監聽器的狀態花生了變化,這也是通過對比不同版本的代碼才知道的區別,所以說我們看源碼需要多對比着看。

so,我們來看下這個 SpringApplicationRunListener 這個接口:

2018-05-02_18-33-20

started 狀態:The context has been refreshed and the application has started but CommandLineRunner and ApplicationRunner have not been called

running 狀態:Called immediately before the run method finishes, when the application context has been refreshed and all CommandLineRunner and ApplicationRunners have been called.

總結

本文從源碼級別分析了 Spring Boot 應用程序的啓動過程,着重看了 SpringApplication 類中的構造函數的初始化和其 run 方法內部實現,並把涉及到的流程代碼都過了一遍。

感悟:有時候跟代碼跟着跟着,發現越陷越深,好難跳出來!後面還需多向別人請教閱讀源碼的技巧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章