SpringBoot 啓動流程追蹤(第一篇)

1、初始化 SpringApplication

	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 = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在完成初始化工作後,可以看到設置瞭如下屬性:
bootstrapRegistryInitializers:
image
initializers:

listeners:

這些屬性咋來的上一篇文章中有提到過,會掃描 spring-boot、spring-boot-autoconfigure、spring-beans 包裏面 resource 目錄下 META-INF/spring.factories 文件進行加載,如果你想添加自己的配置,也可以在自己項目的 resource 目錄下添加配置。

2、加載 spring.factories

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

加載結果如下所示:
image
以後有些地方加載類的時候,就會直接從緩存取了。

3、環境準備前的工作(run 方法代碼片段)

		long startTime = System.nanoTime();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting(bootstrapContext, this.mainApplicationClass);

在 createBootstrapContext 方法裏面會調用 bootstrapRegistryInitializers 的 initializer 方法,不過 SpringBoot 該屬性沒值。
然後會調用 getRunListeners 方法加載 SpringApplicationRunListeners,該值同樣也是從 spring.factories 文件進行加載的。
該 listeners(SpringApplicationRunListeners) 下的 SpringApplicationRunListener 只有一個: EventPublishingRunListener
image
然後會調用該類的 starting 方法,會觸發 ApplicationStartingEvent 事件,該事件會被 SpringApplication 下的 listeners 監聽。
如果你感興趣的話,可以看看 8 個 ApplicationListener 幹了什麼!

4、準備環境(run->prepareEnvironment)

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

這裏是先創建一個默認的 Servlet 環境,然後爲該環境配置參數,在 configureEnvironment ->configurePropertySources 方法中可以看到會從命令行參數和 SpringApplication 的 defaultProperties 屬性獲取可配置參數。環境準備好後,會執行 listeners.environmentPrepared 方法,上文提到過,該方法只有一個實現類,調用該方法會觸發 ApplicationEnvironmentPreparedEvent 事件,同樣也會被監聽到。該方法目前就看這兩個就行了,其他的方法不知道在哪兒用的,看了也說不明白。

5、配置 Banner 和 上下文

		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();

如果想要知道怎麼自定義 Banner,可以看 printBanner,通過創建 banner.txt 文本格式或 banner.png、banner.gif、banner.gif 等圖片格式文件,可實現自定義 banner,文件默認放在 resource 目錄下就行,如果不嫌麻煩的話也可以自定義 banner 存放目錄。
接下來是 context 上下文,這在後面會經常用到,它會使用默認的 contextFactory 來創建 context,並且它是通過 loadSpringFactories 方法來獲取的,其實現類在 spring.factories 裏配置的是 AnnotationConfigServletWebServerApplicationContext。

6、準備上下文(run->prepareContext)

	private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		bootstrapContext.close(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
			((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
			if (beanFactory instanceof DefaultListableBeanFactory) {
				((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
			}
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

這裏調用了 applyInitializers 方法,之前 SpringApplication 加載了 7 個 ApplicationContextInitializer,這裏會調用每個 initializer 的 initialize 方法。接着就調用 listeners 的 contextPrepared 方法,還是之前的 EventPublishingRunListener,該方法會觸發 8 個 ApplicationListener 監聽 ApplicationContextInitializedEvent 事件。在這之後的 bootstrapContext.close 方法也會 BootstrapContextClosedEvent 事件。然後想 set、add、log 啥的可以直接跳過,beanFactory.registerSingleton 方法可以點進去看看,不過也是 add 啥的,這些其實都是爲後面實質性的操作做準備,後面可以再追溯數據來源。
接着看看 load 方法,load 方法的 source 來源一個是 primarySources,另一個是 sources,都是 SpringApplication 的屬性,該方法可以加載 Bean,並且該方法細節也是蠻多的,這裏先記着,後面再看。最後就是 listeners.contextLoaded 方法了,該方法會觸發 ApplicationPreparedEvent 事件。

7、添加上下文銷燬線程(refreshcontext...->addRuntimeShutdownHook)

	void addRuntimeShutdownHook() {
		try {
			Runtime.getRuntime().addShutdownHook(new Thread(this, "SpringApplicationShutdownHook"));
		}
		catch (AccessControlException ex) {
			// Not allowed in some environments
		}
	}
	@Override
	public void run() {
		Set<ConfigurableApplicationContext> contexts;
		Set<ConfigurableApplicationContext> closedContexts;
		Set<Runnable> actions;
		synchronized (SpringApplicationShutdownHook.class) {
			this.inProgress = true;
			contexts = new LinkedHashSet<>(this.contexts);
			closedContexts = new LinkedHashSet<>(this.closedContexts);
			actions = new LinkedHashSet<>(this.handlers.getActions());
		}
		contexts.forEach(this::closeAndWait);
		closedContexts.forEach(this::closeAndWait);
		actions.forEach(Runnable::run);
	}

這裏在線程末尾會執行上下文的 closeAndWait 方法,以及支持自定義的 actions。

8、收尾工作

在刷新完 context 之後,會執行 listeners.started、ready 方法,分別會觸發 ApplicationStartedEvent、ApplicationReadyEvent 事件。同樣會被 8 個 ApplicationListener 監聽到。
另外還有一個 callRunners 方法值得注意,任何實現了 ApplicationRunner、CommandLineRunner 接口的實現類都會得到執行。

9、加載 Bean(load)

前面提到過 load 方法也是一個值得注意的方法,他可以通過好幾種方式註冊 Bean:

	private void load(Object source) {
		Assert.notNull(source, "Source must not be null");
		if (source instanceof Class<?>) {
			load((Class<?>) source);
			return;
		}
		if (source instanceof Resource) {
			load((Resource) source);
			return;
		}
		if (source instanceof Package) {
			load((Package) source);
			return;
		}
		if (source instanceof CharSequence) {
			load((CharSequence) source);
			return;
		}
		throw new IllegalArgumentException("Invalid source type " + source.getClass());
	}

首先是 load(Class<?> source) 方法:它會判斷本地有沒有 groovy 環境,然後 source 對象是 GroovyBeanDefinitionSource 類或其子類的實例時,就實例化它,然後將 loader 對象的 bean 方法返回的 Bean 添加到 groovyReader 中。然後就判斷其是否有資格註冊爲 Bean。

	private void load(Class<?> source) {
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
		}
		if (isEligible(source)) {
			this.annotatedReader.register(source);
		}
	}

然後是 load(Resource source) 方法:總之它會從 grovvy 文件或者 xml 文件中註冊 Bean。

	private void load(Resource source) {
		if (source.getFilename().endsWith(".groovy")) {
			if (this.groovyReader == null) {
				throw new BeanDefinitionStoreException("Cannot load Groovy beans without Groovy on classpath");
			}
			this.groovyReader.loadBeanDefinitions(source);
		}
		else {
			if (this.xmlReader == null) {
				throw new BeanDefinitionStoreException("Cannot load XML bean definitions when XML support is disabled");
			}
			this.xmlReader.loadBeanDefinitions(source);
		}
	}

然後是 load(Package source) 方法:總之它會從 package 裏註冊 Bean。

	private void load(Package source) {
		this.scanner.scan(source.getName());
	}

最後就是 load(CharSequence source) 方法:它就很有意思了,它會嘗試將其作爲以上三種方式進行加載。

	private void load(CharSequence source) {
		String resolvedSource = this.scanner.getEnvironment().resolvePlaceholders(source.toString());
		// Attempt as a Class
		try {
			load(ClassUtils.forName(resolvedSource, null));
			return;
		}
		catch (IllegalArgumentException | ClassNotFoundException ex) {
			// swallow exception and continue
		}
		// Attempt as Resources
		if (loadAsResources(resolvedSource)) {
			return;
		}
		// Attempt as package
		Package packageResource = findPackage(resolvedSource);
		if (packageResource != null) {
			load(packageResource);
			return;
		}
		throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");
	}

10、總結

這篇文章我們瞭解了自動裝配的工作方式,也就是 spring.factories。然後就是 Banner 是如何打印的、context 環境準備完畢後如何執行自定義代碼,context 的銷燬工作以及最後的兩種 runner 怎麼使用。本文着重介紹了 load 方法通過幾種方式註冊 Bean的,包括 Groovy、xml等文件方式、Package包、Class類、CharSequence字符串等方式進行註冊。
最後遺留了一個刷新上下文 refresh 方法沒有分析,這也是一個很重要的方法。

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