spring之我見--spring boot的組件化(以logging日誌初始化爲例)

之前寫了java的多級緩存,是一個簡單的util工具包,想着能不能跟springboot 做集成,順便了解下spring boot 組件原理,比如衆多的 xx-spring-boot-starter.這篇文章以 spring boot 2.x爲基礎。

如何讀取配置文件

這個是面臨的第一個問題,以logging 日誌的集成爲例。我們都知道logging日誌的配置可以配置以"logging.level"打頭,而後面跟上的是包名,有沒有想過這種配置讀取是怎麼做到的?

在spring 初始化啓動的過程中,會根據生命週期的不同階段,發出對應的動作。這就是Spring ApplicationListener,設計基於觀察者模式,而其中LoggingApplicationListener類便是負責logging日誌框架的初始化操作。

LoggingApplicationListener被配置在spring-boot-x.x.x.jar的spring.factories文件中,spring啓動的時候會去讀取這個文件

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

LoggingApplicationListener 的事件處理

@Override
//spring內置了很多event事件,LoggingApplicationListener根據spring生命週期的不同階段,做不同的處理
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		//這裏會觸發讀取"logging.level"的操作
		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent(
					(ApplicationEnvironmentPreparedEvent) event);
		}
		else if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
				.getApplicationContext().getParent() == null) {
			onContextClosedEvent();
		}
		else if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}

讀取操作就在於Binder,是spring boot 2.0版本推出新的屬性綁定,用來替換1.0中 RelaxedPropertyResolver等類的功能(所以多級緩存的包也會分2.x和1.x),從下面的代碼也可以看出,它提供了部分匹配查找的功能,所以可以實現類似"logging.level"配置的讀取.

	private static final ConfigurationPropertyName LOGGING_LEVEL = ConfigurationPropertyName
			.of("logging.level");

	protected void setLogLevels(LoggingSystem system, Environment environment) {
		if (!(environment instanceof ConfigurableEnvironment)) {
			return;
		}
		Binder binder = Binder.get(environment);
		Map<String, String[]> groups = getGroups();
		binder.bind(LOGGING_GROUP, STRING_STRINGS_MAP.withExistingValue(groups));
		Map<String, String> levels = binder.bind(LOGGING_LEVEL, STRING_STRING_MAP)
				.orElseGet(Collections::emptyMap);
		levels.forEach((name, level) -> {
			String[] groupedNames = groups.get(name);
			if (ObjectUtils.isEmpty(groupedNames)) {
				setLogLevel(system, name, level);
			}
			else {
				setLogLevel(system, groupedNames, level);
			}
		});
	}

在 何時/如何 初始化組件

不得不說Spring良好的設計給了我們很大的拓展空間, 光初始化一個對象就可以在多個層面操作,比如實現InitializingBean,BeanPostProcessor,Listener,我用的是BeanPostProcessor.

實現了BeanPostProcessor的類會接收到所有spring容器管理的對象,你可以在任何初始化回調前或後(比如實現了InitializingBean接口) 執行自己的方法,對這個bean加以改造,這就給自定義bean提供了很大的空間.

具體的執行方法在AbstractAutowireCapableBeanFactory類裏的applyBeanPostProcessorsBeforeInitialization方法,衆所周知這裏負責了bean 裝配的整個流程.從代碼可以看出它遍歷所有實現了BeanPostProcessor的類,然後執行方法.也可以看出這裏是用觀察者模式實現的.

	@Override
	public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessBeforeInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

spring.factories

完成BeanPostProcessor的編寫,並不是萬事大吉,因爲我們肯定是用starter組件形式來引入到其它工程,所以直接引入jar包 spring並不會幫我們把組件注入到容器,我們想到的可以用import註解在工程啓動類裏注入,但是這樣代碼會增加代碼耦合,並沒有達到starter組件即拔即用的特性(不需要直接在pom文件刪除對應引入即可),所以我們用上了spring.factories文件幫助我們

SpringFactoriesLoader 負責讀取工程和所有引用jar包裏的META-INF/spring.factories文件,可以根據key返回集合value,方便後面實例化。在spring啓動的時候應用如下:

spring需要拿到ApplicationContextInitializer類型對象的集合,所以它利用SpringFactoriesLoader工具去讀取。返回6個String字段,後面就是通過反射進行實例化的過程。

在這裏插入圖片描述

大功告成

上兩個問題搞定了基本上寫一個spring的組件就沒那麼難了,以前覺得提高需要多看spring的源碼,但是發現不從實際需求出發,還是很難形成印象.
上面的代碼已經上傳github,跨年後纔剛剛寫好,肯定還有問題,多多見諒

https://github.com/lovejj1994/simplify-cache-spring-boot-starter
https://github.com/lovejj1994/simplify-cache-spring-boot-starter-test

記得一年前寫過一篇年終總結,那時候還在北京,說要以後每年都要寫一次總結,然後今年就沒寫了,因爲太多給自己定的任務沒有去完成.而且跨年前後也在完善多級緩存的組件.感覺寫了也沒有意思.這是在上海的第一年,工作上感覺學的還有那麼多幫助的,說了再多還是繼續給自己加油.

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