文章目錄
前言
隨着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.未完待續]