中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

springboot-autoConfiguration的原理是什么

發布時間:2021-07-02 17:37:32 來源:億速云 閱讀:248 作者:chen 欄目:大數據

這篇文章主要介紹“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就要初始化,初始化的時候事情就發生了,所有的事件導火索就是@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這家伙出現了。

@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的構造方法,初始化了packageName,也就是我們主類所在的包名。

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

我們繼續進register方法看。

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

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

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

ConfigurationClassPostProcessor

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

springboot-autoConfiguration的原理是什么

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

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

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

繼續看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的每一個對象,都調用handler的register方法,即注冊到handler當中
         deferredImports.forEach(handler::register);
          //祖冊到handler當中之后,執行handler的processGroupImports方法
         handler.processGroupImports();
      }
   }
   finally {
       //初始化deferredImportSelectors
      this.deferredImportSelectors = new ArrayList<>();
   }
}

register

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

// ConfigurationClassParser.DeferredImportSelectorGroupingHandler.java
public void register(DeferredImportSelectorHolder deferredImport) {
    //這個地方的getImportGroup就是調用我們的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

繼續來看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);
         }
         //...
      });
   }
}

繼續來看看getImports。

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

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

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

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

springboot-autoConfiguration的原理是什么

AutoConfigurationImportSelector

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

先來初略看哈這個類。

public class AutoConfigurationImportSelector implements ...{
    //這個方法在老版本中會使用這個方法去獲取所有配置,但是在我使用的版本中,這個方法沒有使用
    //而是使用的內部類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方法后就沒有繼續分析了,這個方法里頭出現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,這里用個結合,在外層每一個selector被調用該方法的時候,都可以將結果放到這個結合當中,最終調用selectImports()處理的時候就可以直接用這個集合了
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}

來看一下重點的方法,getAutoConfigurationEntry(),這個方法就是我們的Selector的了,看它被強轉成了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);
	//發送通知,這個地方獲取監聽自動配置的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這個方法可以看到,這個方法直接調用了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();
   }
   //調用AutoConfigurationEntry的getExclusions轉成集合
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getExclusions)
         .flatMap(Collection::stream).collect(Collectors.toSet());
   //調用AutoConfigurationEntry的getConfigurations轉成集合
   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進行處理,排除掉不需要自動配置的,然后封裝一下,調用一下排隊的方法sortAutoConfigurations,最后返回結果。

還記得在介紹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把這些都給我們寫好了,我們只需要加個依賴,這些配置就全部進來了,于是就有了這些配置,我們只需要再配置上一些數據庫啊參數啊啥的就能啟用相應的功能了。

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

到此,關于“springboot-autoConfiguration的原理是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

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

AI

资溪县| 祁东县| 丰台区| 灵丘县| 潜山县| 天津市| 泸西县| 林周县| 衡东县| 黄骅市| 望都县| 澜沧| 凌云县| 厦门市| 政和县| 平安县| 十堰市| 资兴市| 文水县| 广水市| 清涧县| 中卫市| 鸡东县| 台州市| 连州市| 四川省| 民勤县| 盐津县| 吉首市| 永城市| 桦南县| 乌拉特后旗| 贵德县| 长沙县| 长海县| 张北县| 钦州市| 辽宁省| 旬邑县| 浮梁县| 绥德县|