Spring Boot2.0版本源碼(二):Spring Boot初始化器

1. 系統初始化器

通過ApplicationContextInitializer類可以實現在springboot容器刷新之前註冊屬性
在這裏插入圖片描述

2. 演示

FirstInitializer類實現ApplicationContextInitializer接口,重寫其中的initialize方法,可以通過下面的代碼,向springboot中注入map.put("key1", "value1");這樣的鍵值對。

在這裏插入圖片描述
spring.factories文件中配置剛剛的FirstInitializer
在這裏插入圖片描述
實現ApplicationContextAware接口,獲取到applicationContext,可以從其中獲取到剛注入的key1數據

在這裏插入圖片描述
寫一個controller調用上面的TestService.test()方法,看到如下結果:

在這裏插入圖片描述

3.SpringFactoriesLoader類如何實現將初始化器加載到spring容器

在這裏插入圖片描述
在下面的地方打上斷點,跟進代碼
在這裏插入圖片描述
不斷跟進源碼,發現在SpringApplication類的構造函數中發現了ApplicationContextInitializer初始化器的初始化過程。
在這裏插入圖片描述
不斷跟進源碼,getSpringFactoriesInstances大概分爲下面三步:
1.通過"META-INF/spring.factories"文件,獲得所有的重寫了帶有ApplicationContextInitializer的全路徑名
2.將這些類實例化
3.對這些類排序
在這裏插入圖片描述

3.1 SpringFactoriesLoader.loadFactoryNames(type, classLoader)

/**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		// 傳入的是ApplicationContextInitializer的工廠的名字
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	//  將某個類加載器的實現類實例化加入到Map(以此實現加入到spring容器中)
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		// 如果之前已經初始化過了,那麼緩存中存在全路徑名
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					// FACTORIES_RESOURCE_LOCATION默認就是"META-INF/spring.factories"
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			// 判斷META-INF/spring.factories中的文件是否存在
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 利用PropertiesLoaderUtils將我們的資源路徑中的"META-INF/spring.factories"加載成Properties對象
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				// properties是一個<k,v>類型,k就是我們的ApplicationContextInitializer,v就是它的實現類
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					// v因爲是實現類,所以可能是一個數組,將他加到result中
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

3.2 createSpringFactoriesInstances源碼

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		// 構造一個集合,大小和我們ApplicationContextInitializer的實現類相同
		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);
				// 通過constructor構造器實例化一個對象
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				// 加入instances數組
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

整體流程

在這裏插入圖片描述

4. 初始化器的調用和使用原理

ApplicationContextInitializer的官方描述
在這裏插入圖片描述
中文大概是下面三種意思:
在這裏插入圖片描述
回顧一下springboot啓動的大致流程:
在遍歷initalizers的inittialize方法中加入斷點。
在這裏插入圖片描述
在系統初始化器中加入斷點
在這裏插入圖片描述
此時會調用ApplicationContextInitializer實現類的initialize()方法
在這裏插入圖片描述

總結

在這裏插入圖片描述

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