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:
SpringBoot 启动流程追踪(第一篇)
initializers:
SpringBoot 启动流程追踪(第一篇)
listeners:
SpringBoot 启动流程追踪(第一篇)
这些属性咋来的上一篇文章中有提到过,会扫描 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; 	} 

加载结果如下所示:
SpringBoot 启动流程追踪(第一篇)
以后有些地方加载类的时候,就会直接从缓存取了。

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
SpringBoot 启动流程追踪(第一篇)
然后会调用该类的 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 方法没有分析,这也是一个很重要的方法。

发表评论

评论已关闭。

相关文章