4、spring核心源碼解析之自定義bean三種方式@import、ImportSelector、ImportBeanDefinitionRegistrar

前言

​ 隨着spring boot的流行,@Enable*設計模式漸漸興起,通常一個註解就可以幫我們完成一個很使用功能。而在大多數的帶有@Enable註解中,我們通常會看到 一個註解@Import去幫助我們導入一些類(bean)。個人覺得這是一個非常重要的spring ioc容器的擴展點之一,它可以讓我們通過一個註解去給容器中注入很多bean,這也是spring boot中最常用、最基礎的註解。

​ 本文將主要介紹@Import、ImportSelector、ImportBeanDefinitionRegistrar的使用場景,以及源碼分析他們註冊bean的原理。(其實本文的源碼分析原理再上一篇文章中已經講到了,所以本文只會分析主要源碼)。

1. 自定義註冊bean之@Import的使用

 單純的使用@Import註解注入bean,這種用法比較少,最起碼要配合類似於@Enable*註解類,來使用@Impor。比如通過一個@Enable的功能註解來控制某個功能的開關。
public class TestImport {

	@Bean
	public Person person(){
		Person person = new Person();
		person.setName("coyhzx");
		return person;
	}
}

​ 同時在啓動類上導入TestImport類

@Import(TestImport.class)
@Configuration
public class MyApp {}
#輸出結果
person
com.upanda.app.test.Person@ef9296d

2. 自定義註冊bean之ImportSelector的使用

​ ImportSelector是一個接口,接口中提供了一個方法selectImports。實現該方法,返回要導入的bean的名稱數組,即可導入bean,importingClassMetadata是註解的元數據。

	/**
	 * 選擇並返回應基於導入@Configuration類的{@link AnnotationMetadata}導入的類的名稱。
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

使用方法:

public class TestImportSelect implements ImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[]{"com.upanda.app.test.Person"};
	}
#輸出結果
person
com.upanda.app.test.Person@gd2296d

​ 同時在啓動類上導入TestImportSelect類,該導入方式與直接導入一個類的區別就是,它可以拿到一個註解的元數據以及它可以同時註冊多個類,返回類名即可。比如說,我們可以根據註解裏面的某個屬性值類注入一個或倒戈bean。

3. 自定義註冊bean之ImportBeanDefinitionRegistrar的使用

​ TestImportBeanDefinitionRegistrar是一個接口,目前有兩個默認實現default方法,可以通過registerBeanDefinitions方法獲取我們的容器,同時可以自定義創建bean以及向容器中註冊bean的邏輯。

public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		BeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClassName("com.upanda.app.test.Person");
		beanDefinition.setScope("singleton");
		registry.registerBeanDefinition("person",beanDefinition);
	}
}
#輸出結果
person
com.upanda.app.test.Person@da9296d

4. 自定義註冊bean之spring經典實現–spring開啓動態代理功能@EnableAspectJAutoProxy

​ sprinig framework開啓aop動態代理功能,使用@EnableAspectJAutoProxy註解實現,而該註解使用@Import註解導入自定義的bean的這種方式是最經典、也是spring framework框架中內部使用的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	//是否使用cglib動態代理,默認使用jdk動態代理
	boolean proxyTargetClass() default false;

	//代理應由AOP框架公開爲{@code ThreadLocal} ,以便通過{@link org.springframework.aop.framework.AopContext}類進行檢索。 默認情況下爲關閉,即不能保證{@code AopContext}訪問將起作用
	boolean exposeProxy() default false;

}

​ 該註解會爲我們導入AspectJAutoProxyRegistrar這個類,該類通過實現ImportBeanDefinitionRegistrar接口來自定義bean的注入邏輯。具體不涉及到AOP的核心功能,簡單理解通過該方式注入了aop相關的後置處理器bean對象(後續aop源碼中在具體講解)。

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	/**
	 * Register, escalate, and configure the AspectJ auto proxy creator based on the value
	 * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
	 * {@code @Configuration} class.
	 */
	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

5. 自定義Bean三種方式的源碼解析

​ 其實在上一篇文章@Configuration源碼的解析中,以及提及到了@import註解在哪裏進行處理了,這邊繼續進行上一章進行分析,側重於@import的導入。

​ 在ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法中parser.parse(candidates);解析配置類裏,有一個處理import的方法processImports。

// Process any @Import annotations
//處理@Import註解,getImports(sourceClass)方法獲取類上@Import導入的類
processImports(configClass, sourceClass, getImports(sourceClass), true);

方法getImports(sourceClass)獲取到@import導入的類。

	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		//1. 蒐集Imports導入的類
		collectImports(sourceClass, imports, visited);
		return imports;
	}

	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					//遞歸獲取@Import註解導入的類
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

在配置類解析器ConfigurationClassParser類中的processImports方法裏,該方法作用非常大。其作用我在源碼中有註釋,但是在這裏也總結一下。

1.處理了@import導入的類,並將其加入到ConfigurationClassParser的配置類集合configurationClasses中。

2.處理實現了ImportSelector接口導入的類,遞歸處理selectImports中導入的類,最終也會加入到ConfigurationClassParser的配置類集合configurationClasses中。

3.處理實現ImportBeanDefinitionRegistrar接口導入的類,同時將該類直接放入ConfigurationClassParser配置類的importBeanDefinitionRegistrars集合中。

​ 這裏並沒有註冊bean,但是這裏將@import、實現ImportSelector接口導入的類,以及實現ImportBeanDefinitionRegistrar接口的類,都放入了配置類的各種集合中,在後文中,註冊配置類中就會將這些類全部注入到容器中。

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		if (importCandidates.isEmpty()) {
			return;
		}

		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
						ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
								this.environment, this.resourceLoader, this.registry);
						if (selector instanceof DeferredImportSelector) {
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
							//2 處理ImportSelector,註冊自定義的bean
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
							//將讀取到的類名數組,轉換成SourceClass類數組
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
							//遞歸處理導入的類,同時將其加入到解析類的集合中,也因爲導入的類可能實現了ImportBeanDefinitionRegistrar接口 --默認導入的會最後的else中,當作普通的配置類進行處理
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitions
						// 處理ImportBeanDefinitionRegistrar,註冊自定義的bean
						Class<?> candidateClass = candidate.loadClass();
						ImportBeanDefinitionRegistrar registrar =
								ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
										this.environment, this.resourceLoader, this.registry);
						//3 將實現了ImportBeanDefinitionRegistrar接口的類,加入到配置類中的importBeanDefinitionRegistrars集合中
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
					else {
						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
						//1. 處理@Import導入的既不是實現ImportSelector ,也不是實現ImportBeanDefinitionRegistrar的類
						//且把它當作@Configuration類處理,也有一點遞歸調用
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						//將其加入到ConfigurationClassParser解析類的集合
						processConfigurationClass(candidate.asConfigClass(configClass));
					}
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
			finally {
				this.importStack.pop();
			}
		}
	}

​ processConfigurationClass這個遞歸處理配置類的方法中,會把每個配置類都加入到解析器的configurationClasses類中。

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}

		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
		if (existingClass != null) {
			if (configClass.isImported()) {
				if (existingClass.isImported()) {
					existingClass.mergeImportedBy(configClass);
				}
				// Otherwise ignore new imported config class; existing non-imported class overrides it.
				return;
			}
			else {
				// Explicit bean definition found, probably replacing an import.
				// Let's remove the old one and go with the new one.
				this.configurationClasses.remove(configClass);
				this.knownSuperclasses.values().removeIf(configClass::equals);
			}
		}

		// Recursively process the configuration class and its superclass hierarchy.
		SourceClass sourceClass = asSourceClass(configClass);
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass != null);
		//重點:如果souurceClass不爲空,則將器方法解析類的配置集合類中,後續會用到
		this.configurationClasses.put(configClass, configClass);
	}

​ 真正注入bean的是在ConfigurationClassPostProcessor的this.reader.loadBeanDefinitions(configClasses)中。

//讀取註冊配置類(包括beanMethod、@import、實現importSelect、實現),並且註冊到容器中
this.reader.loadBeanDefinitions(configClasses);

​ 該reader爲spring專門爲配置類讀取創建的一個類ConfigurationClassBeanDefinitionReader。reader.loadBeanDefinitions()方法會遍歷加載所有的配置bean。至於在往下面,調用我們的容器爲我們注入bean的邏輯相對而言很簡單,有興趣的可以打個斷點看看,也可以參考我的spring源碼分析的第一篇文章注入bean的流程圖。

	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}
	//爲配置類註冊bean的定義
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}

		if (configClass.isImported()) {
			//註冊@import導入的類
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			//註冊@Bean導入的類
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
		//註冊@ImportedResources導入的類
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		//註冊ImportBeanDefinitionRegistrars
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

​ bean的定義以及特殊bean的注入、自定義註冊源碼解析了很多了,接下來可能就會開始spriing的DI在源碼中是怎樣實現的了。

​ 請敬請期待我的下一篇文章~

上一篇:3、spring核心源碼解析之@Configuration註解詳解
[下一篇:5.未完待續]

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