溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

springboot-autoConfiguration的原理是什么

發(fā)布時間:2021-07-02 17:37:32 來源:億速云 閱讀:241 作者:chen 欄目:大數(shù)據(jù)

這篇文章主要介紹“springboot-autoConfiguration的原理是什么”,在日常操作中,相信很多人在springboot-autoConfiguration的原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”springboot-autoConfiguration的原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

springboot-AutoConfiguration原理

springboot自動配置的原理,基于springboot 2.1.5.RELEASE版本 這里是示例工程.

閑話不說,先來看看主類。

@SpringBootApplication
public class BootStartMain {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(BootStartMain.class);
        app.setBanner(new ResourceBanner(new ClassPathResource("banner.txt")));
		app.run(args);
	}
}

上一篇springboot的啟動流程分析了run方法的邏輯,闡述了springboot的啟動流程,最后在load資源的時候,主類以bean的形式被注冊到了bean當中,而在之前部分我們也提到了在本部分會用到的ConfigurationClassPostProcessor這個家伙,不熟悉的就回過頭再去看看吧。

既然注冊成了bean,那bean就要初始化,初始化的時候事情就發(fā)生了,所有的事件導火索就是@SpringBootApplication這個牛x的注解。

@SpringBootApplication

@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM,
            classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}

這家伙其實是個復合注解,看到了我們想要的了吧,@EnableAutoConfiguration這家伙出現(xiàn)了。

@EnableAutoConfiguration

@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

這家伙也是個復合注解。

@AutoConfigurationPackage

@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

我們來看@Import的AutoConfigurationPackages.Registrar這個東西到底注冊了個啥子。

public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   register(registry, new PackageImport(metadata).getPackageName());
}

springboot-autoConfiguration的原理是什么

metadata其實就包含了我們的主類信息。

我們看看PackageImport的構(gòu)造方法,初始化了packageName,也就是我們主類所在的包名。

PackageImport(AnnotationMetadata metadata) {
	this.packageName = ClassUtils.getPackageName(metadata.getClassName());
}

我們繼續(xù)進register方法看。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	//檢查是否已經(jīng)注冊了org.springframework.boot.autoconfigure.AutoConfigurationPackages
	if (registry.containsBeanDefinition(BEAN)) {
		//如果已經(jīng)注冊了,就直接獲取
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition
				.getConstructorArgumentValues();
		//把新的packageName加進去
		constructorArguments.addIndexedArgumentValue(0,
				addBasePackages(constructorArguments, packageNames));
	}
	else {
		//如果沒有注冊,就重新注冊一個
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		//設(shè)置bean類型
		beanDefinition.setBeanClass(BasePackages.class);
		//添加packageNames
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
				packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		//注冊bean
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

邏輯很清晰,如果沒有注冊指定的bean,那么就新注冊一個,把packageName加進去;如果已經(jīng)有了指定的bean,那么就直接把packageName加進去。這就是為什么我們的主類不用加@CompomnentScan也能掃描我們的bean了,掃描的只是主類package以及其子package

掃描部分就不做過多解釋了。

ConfigurationClassPostProcessor

介紹AutoConfigurationImportSelector之前我先來初略介紹一下ConfigurationClassPostProcessor這個東西。

springboot-autoConfiguration的原理是什么

可以看到這個家伙實現(xiàn)了BeanDefinitionRegistryPostProcessor,這東西在哪里調(diào)用的我就不解釋了哈。我們直接來看核心方法。

// BeanDefinitionRegistryPostProcessor.java
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   //...
   processConfigBeanDefinitions(registry);
}

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry){
    // 由于代碼過多,就不貼了,只介紹我們需要的部分
    ConfigurationClassParser parser = new ConfigurationClassParser(...);
    parser.parse(candidates);
    //...
}

繼續(xù)看parser.parse()方法.

public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
         if (bd instanceof AnnotatedBeanDefinition) {
             //這個地方會把初始化deferredImportSelectorHandler,即把所有的selector都放到這個家伙當中
             //邏輯很深,下面會說在哪里
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         }
       //...
   }
   this.deferredImportSelectorHandler.process();
}

上圖parse方法的邏輯很深,我們的我們的selector加入deferredImportSelectorHandler這個家伙的邏輯是在這個地方,parse->processConfigurationClass->doProcessConfigurationClass->processImports,然后在這個方法中的this.deferredImportSelectorHandler.handle()方法。

ImportSelectorHandler的處理

我們的selector加入之后,就要處理selector了,來看this.deferredImportSelectorHandler.process();的process方法。

// ConfigurationClassParser.DeferredImportSelectorHandler.java
public void process() {
    //賦值DeferredImportSelectorHandler持有的所有selectors
   List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
   this.deferredImportSelectors = null;
   try {
      if (deferredImports != null) {
         DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
         deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
          //對deferredImports的每一個對象,都調(diào)用handler的register方法,即注冊到handler當中
         deferredImports.forEach(handler::register);
          //祖冊到handler當中之后,執(zhí)行handler的processGroupImports方法
         handler.processGroupImports();
      }
   }
   finally {
       //初始化deferredImportSelectors
      this.deferredImportSelectors = new ArrayList<>();
   }
}

register

調(diào)用handler的register的時候,會涉及到一個group的東西,主要作用就是將不同的selector分組存放。來看一下這個方法吧。

// ConfigurationClassParser.DeferredImportSelectorGroupingHandler.java
public void register(DeferredImportSelectorHolder deferredImport) {
    //這個地方的getImportGroup就是調(diào)用我們的importSelector來獲取一個所屬的組
   Class<? extends Group> group = deferredImport.getImportSelector()
         .getImportGroup();
    //放到map當中
   DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
         (group != null ? group : deferredImport),
         key -> new DeferredImportSelectorGrouping(createGroup(group)));
   grouping.add(deferredImport);
   this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
         deferredImport.getConfigurationClass());
}

來看一下我們的AutoConfigurationImportSelector所屬的組。

// AutoConfigurationImportSelector.java
public Class<? extends Group> getImportGroup() {
	return AutoConfigurationGroup.class;
}

handler.processGroupImports

繼續(xù)來看handler.processGroupImports.

// ConfigurationClassParser.DeferredImportSelectorGroupingHandler.java
public void processGroupImports() {
    //遍歷所有的組
   for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
       //注意這個蔭蔽的方法getImports()
      grouping.getImports().forEach(entry -> {
         ConfigurationClass configurationClass = this.configurationClasses.get(
               entry.getMetadata());
         try {
             //方法里頭會注冊相應的bean
            processImports(configurationClass, asSourceClass(configurationClass),
                  asSourceClasses(entry.getImportClassName()), false);
         }
         //...
      });
   }
}

繼續(xù)來看看getImports。

// DeferredImportSelectorGrouping.java
public Iterable<Group.Entry> getImports() {
    //遍歷組里面的所有DeferredImportSelectorHolder
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
       //執(zhí)行process方法,這個group就是我們的selector所屬的AutoConfigurationGroup組
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
    //返回獲取到的bean,這個方法會接下來的在selector講
   return this.group.selectImports();
}

ConfigurationClassPostProcessor的分析暫時就到這里,我們討論的部分,大體上就是處理bean的注解,然后處理Selectors,當然selectors里面獲取到的bean肯定也是有注解的bean,然后通過這個類去注冊,處理。

至于這最后出現(xiàn)的this.group.process()和this.group.selectImports()這兩個方法我們留在Selector中去分析。主要是這倆方法都是他們所屬的組當中的。

里面邏輯有點長,這里來一個sequence圖,大家看一下.。

springboot-autoConfiguration的原理是什么

AutoConfigurationImportSelector

@EnableAutoConfiguration注解上除了上面說到的@AutoConfigurationPackage之外,還有一個@Import,我們來看哈這個import又引入了什么東西。

先來初略看哈這個類。

public class AutoConfigurationImportSelector implements ...{
    //這個方法在老版本中會使用這個方法去獲取所有配置,但是在我使用的版本中,這個方法沒有使用
    //而是使用的內(nèi)部類AutoConfigurationGroup的selectImports方法
	public String[] selectImports(){...}
    //重要方法,獲取了所有自動配置的類
    protected AutoConfigurationEntry getAutoConfigurationEntry(){...}
    //返回所屬的組
    public Class<? extends Group> getImportGroup() {
		return AutoConfigurationGroup.class;
	}
    private static class AutoConfigurationGroup implements ...{
        //新版本使用的這個方法來獲取的配置
        public String[] selectImports(){...}
        //重要方法
        public void process(){...}
    }
    
    //對獲取到的配置的一些封裝
    protected static class AutoConfigurationEntry {
        ...
    }
}

我們在分析ConfigurationImportSelector的最后。在getImports方法后就沒有繼續(xù)分析了,這個方法里頭出現(xiàn)this.group.process()和this.group.selectImports()兩個方法,這兩個方法在這里進行分析。

注意:這里分析的process()和selectImports()是group的哦!不是Selector的!所以我們要打開我們的AutoConfigurationImportSelector,然后找到它所屬的group,也就是AutoConfigurationGroup。然后再來看這倆方法。

process()

// AutoConfigurationImportSelector.utoConfigurationGroup.java
public void process(AnnotationMetadata annotationMetadata,
      DeferredImportSelector deferredImportSelector) {
   //...
    //獲取所有的自動配置類
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
               annotationMetadata);
    //添加到當前group的集合當中,因為一個group會有很多個selector,這里用個結(jié)合,在外層每一個selector被調(diào)用該方法的時候,都可以將結(jié)果放到這個結(jié)合當中,最終調(diào)用selectImports()處理的時候就可以直接用這個集合了
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

來看一下重點的方法,getAutoConfigurationEntry(),這個方法就是我們的Selector的了,看它被強轉(zhuǎn)成了AutoConfigurationImportSelector,來看看吧。

// AutoConfigurationImportSelector.java
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	//檢查是否開啟自動配置功能
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	//獲取注解的屬性值,exclude,excludeName
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	//獲取所有的自動配置類
	List<String> configurations = getCandidateConfigurations(annotationMetadata,
			attributes);
	//去重,當前版本其實就是換了個集合,使用了LinkedHashList
	configurations = removeDuplicates(configurations);
	//把不需要自動配置的(exclude的)放到集合
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	//檢查不需要自動配置的class
	checkExcludedClasses(configurations, exclusions);
	//移除掉不需要的自動配置或者不存在的自動配置
	configurations.removeAll(exclusions);
	//過濾,這個地方也會用到spring.factories哦
	configurations = filter(configurations, autoConfigurationMetadata);
	//發(fā)送通知,這個地方獲取監(jiān)聽自動配置的Listener,也會用到spring.factories哦
	fireAutoConfigurationImportEvents(configurations, exclusions);
	//封裝并返回
	return new AutoConfigurationEntry(configurations, exclusions);
}

protected boolean isEnabled(AnnotationMetadata metadata) {
	if (getClass() == AutoConfigurationImportSelector.class) {
        //獲取自動配置是否開啟,默認未配置,返回true,也就是說默認就是開啟的
        //ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"
		return getEnvironment().getProperty(
				EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
				true);
	}
	return true;
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
		AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
			getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
	//... 日志
	return configurations;
}

基本上很簡單,就不多解釋了,只解釋下核心方法getCandidateConfigurations

Selector.getCandidateConfigurations

從上面的getCandidateConfigurations這個方法可以看到,這個方法直接調(diào)用了SpringFactoriesLoader.loadFactoryNames,很熟悉吧,來看看吧。

// SpringFactoriesLoader.java
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
	String factoryClassName = factoryClass.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
   MultiValueMap<String, String> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   try {
       //讀取classpath配置文件
      Enumeration<URL> urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryClassName = ((String) entry.getKey()).trim();
            for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
               result.add(factoryClassName, factoryName.trim());
            }
         }
      }
      cache.put(classLoader, result);
      return result;
   }
   //...
}

這個方法就不解釋了吧,很熟悉吧!啟動流程里頭說過哦,就是那個META-INF/spring.factories哦!貼個代碼回顧一下,接下來的就不解釋了哈。這個地方拿到了所有的AutoConfiguration配置類。來嘛,貼個圖給你看哈。

# META-INF/spring.factories
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
...還有很多

selectImports()

public Iterable<Entry> selectImports() {
   if (this.autoConfigurationEntries.isEmpty()) {
      return Collections.emptyList();
   }
   //調(diào)用AutoConfigurationEntry的getExclusions轉(zhuǎn)成集合
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions)
         .flatMap(Collection::stream).collect(Collectors.toSet());
   //調(diào)用AutoConfigurationEntry的getConfigurations轉(zhuǎn)成集合
   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations)
         .flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   //移除掉不需要自動配置的
   processedConfigurations.removeAll(allExclusions);
   //排序并返回
   return sortAutoConfigurations(processedConfigurations,
         getAutoConfigurationMetadata())
         .stream()
         .map((importClassName) -> new Entry(
               this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}

這個方法就比較簡單了,無非就是將當前組里面的所有Entry進行處理,排除掉不需要自動配置的,然后封裝一下,調(diào)用一下排隊的方法sortAutoConfigurations,最后返回結(jié)果。

還記得在介紹ConfigurationClassPostProcessor的最后有一個方法吧,來看看,回憶一下。

grouping.getImports().forEach(entry -> {
    ConfigurationClass configurationClass = this.configurationClasses.get(
        entry.getMetadata());
    try {
        //方法里頭會注冊相應的bean
        processImports(configurationClass, asSourceClass(configurationClass),
                  asSourceClasses(entry.getImportClassName()), false);
    }
}

最終這些自動配置的bean都會在這里processImports被注冊成bean。被注冊成bean之后就會被spring處理了。我們來看一個熟悉的MybatisAutoConfiguration自動配置類吧。

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
    @Bean
    public Xxx getXxx(){...}
    //其他就不寫了,自己去看類吧,都是些配置
}

看到了吧,這個bean其實就是個配置bean,就跟我們自己寫@Configuration的配置一樣,只不過springboot把這些都給我們寫好了,我們只需要加個依賴,這些配置就全部進來了,于是就有了這些配置,我們只需要再配置上一些數(shù)據(jù)庫啊參數(shù)啊啥的就能啟用相應的功能了。

這些都是springboot-starter的功勞哦!懂了autoConfiguration,離starter就只差個demo了哦,哈哈~爽吧。

到此,關(guān)于“springboot-autoConfiguration的原理是什么”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI