自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring Boot 啟動注解分析

開發(fā) 項目管理
個人感覺應該是比較少的,Spring Boot 本質(zhì)上還是曾經(jīng) SSM 那一套,只是通過各種 starter 簡化了配置而已,其他都是一模一樣的,所以 Spring Boot 中很多面試題還是得回歸到 Spring 中去解答!當然這并不是說 Spring Boot 中沒什么可問的,Spring Boot 中其實也有一個非常經(jīng)典的面試題,那就是 Spring Boot 中的自動化配置是怎么實現(xiàn)的?

雖然我們在日常開發(fā)中,Spring Boot 使用非常多,算是目前 Java 開發(fā)領(lǐng)域一個標配了,但是小伙伴們仔細想想自己的面試經(jīng)歷,和 Spring Boot 相關(guān)的面試題都有哪些?個人感覺應該是比較少的,Spring Boot 本質(zhì)上還是曾經(jīng) SSM 那一套,只是通過各種 starter 簡化了配置而已,其他都是一模一樣的,所以 Spring Boot 中很多面試題還是得回歸到 Spring 中去解答!當然這并不是說 Spring Boot 中沒什么可問的,Spring Boot 中其實也有一個非常經(jīng)典的面試題,那就是 Spring Boot 中的自動化配置是怎么實現(xiàn)的?今天松哥就來和各位小伙伴聊一下這個問題。

其實松哥之前和小伙伴們聊過相關(guān)的問題,不過都是零散的,沒有系統(tǒng)梳理過,之前也帶領(lǐng)小伙伴們自定義過一個 starter,相信各位小伙伴對于 starter 的原理也有一定了解,所以今天這篇文章一些過于細節(jié)的內(nèi)容我就不贅述了,大家可以翻看之前的文章。

1.  @SpringBootApplication

要說 Spring Boot 的自動化配置,那必須從項目的啟動類 @SpringBootApplication 說起,這是整個 Spring Boot 宇宙的起點,我們先來看下這個注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

}

可以看到,@SpringBootApplication 注解組合了多個常見注解的功能,其中:

  • 前四個是元注解,這里我們不做討論。
  • 第五個 @SpringBootConfiguration 是一個支持配置類的注解,這里我們也不做討論。
  • 第六個 @EnableAutoConfiguration 這個注解就表示開啟自動化配置,這是我們今天要聊得重點。
  • 第七個 @ComponentScan 是一個包掃描注解,為什么 Spring Boot 項目中的 Bean 只要放對位置就會被自動掃描到,和這個注解有關(guān)。

別看這里注解多,其實真正由 Spring Boot 提供的注解一共就兩個,分別是 @SpringBootConfiguration 和 @EnableAutoConfiguration 兩個,其他注解在 Spring Boot 出現(xiàn)之前就已經(jīng)存在多年了。

2. @EnableAutoConfiguration

接下來我們來看看 @EnableAutoConfiguration 是如何實現(xiàn)自動化配置的。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

}

這個注解起關(guān)鍵作用的就是兩個東西:

  • @AutoConfigurationPackage:這個表示自動掃描各種第三方的注解,在之前的文章中松哥已經(jīng)和大家聊過這個注解的作用了,傳送門:@AutoConfigurationPackage 和 @ComponentScan 有何區(qū)別?
  • @Import 則是在導入 AutoConfigurationImportSelector 配置類,這個配置類里邊就是去加載各種自動化配置類的。

3. AutoConfigurationImportSelector

AutoConfigurationImportSelector 類中的方法比較多,入口的地方則是 process 方法,所以我們這里就從 process 方法開始看起:

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
 Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
   () -> String.format("Only %s implementations are supported, got %s",
     AutoConfigurationImportSelector.class.getSimpleName(),
     deferredImportSelector.getClass().getName()));
 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
  .getAutoConfigurationEntry(annotationMetadata);
 this.autoConfigurationEntries.add(autoConfigurationEntry);
 for (String importClassName : autoConfigurationEntry.getConfigurations()) {
  this.entries.putIfAbsent(importClassName, annotationMetadata);
 }
}

從類名就可以看出來,跟自動化配置相關(guān)的對象是由 AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); 進行加載的。

當然這里的 getAutoConfigurationEntry 方法實際上就是當前類提供的方法,我們來看下該方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
 if (!isEnabled(annotationMetadata)) {
  return EMPTY_ENTRY;
 }
 AnnotationAttributes attributes = getAttributes(annotationMetadata);
 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
 configurations = removeDuplicates(configurations);
 Set<String> exclusions = getExclusions(annotationMetadata, attributes);
 checkExcludedClasses(configurations, exclusions);
 configurations.removeAll(exclusions);
 configurations = getConfigurationClassFilter().filter(configurations);
 fireAutoConfigurationImportEvents(configurations, exclusions);
 return new AutoConfigurationEntry(configurations, exclusions);
}

這里源碼的方法命名都做的不錯,基本上都能做到見名知意,小伙伴們?nèi)粘i_發(fā)中,應該向這樣的命名思路看齊。接下來我們就來挨個看一下這里的關(guān)鍵方法。

3.1 isEnabled

首先調(diào)用 isEnabled 方法去判斷自動化配置到底有沒有開啟,這個主要是因為我們及時在項目中引入了 spring-boot-starter-xxx 之后,我們也可以通過在 application.properties 中配置 spring.boot.enableautoconfiguration=false 來關(guān)閉所有的自動化配置。

相關(guān)源碼如下:

protected boolean isEnabled(AnnotationMetadata metadata) {
 if (getClass() == AutoConfigurationImportSelector.class) {
  return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
 }
 return true;
}

3.2 getCandidateConfigurations

接下來調(diào)用 getCandidateConfigurations 方法去獲取所有候選的自動化配置類,這些候選的自動化配置類主要來自兩個地方:

  • 在之前的自定義 starter 中松哥和大家聊過,我們需要在 claspath\:META-INF/spring.factories 中定義出來所有的自動化配置類,這是來源一。
  • Spring Boot 自帶的自動化配置類,這個在之前的 vhr 視頻中也和小伙伴們多次講過,Spring Boot 自帶的自動化配置類位于 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中。

相關(guān)源碼如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 List<String> configurations = new ArrayList<>(
   SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
 ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
 Assert.notEmpty(configurations,
   "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
     + "are using a custom packaging, make sure that file is correct.");
 return configurations;
}

這里加載到的自動化配置類的全路徑被存入到 configurations 對象中,該對象有兩個獲取的地方:

  • 調(diào)用 SpringFactoriesLoader.loadFactoryNames 方法獲取,這個方法細節(jié)我就不帶大家看了,比較簡單,本質(zhì)上就是去加載 META-INF/spring.factories 文件,這個文件中定義了大量的自動化配置類的全路徑。
  • 調(diào)用 ImportCandidates.load 方法去加載,這個就是加載 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的自動化配置類。

如果這兩個地方都沒有加載到任何自動化配置類,那么就會拋出一個異常。

3.3 removeDuplicates

removeDuplicates 方法表示移除候選自動化配置類中重復的類,移除的思路也很有意思,就用一個 LinkedHashSet 中轉(zhuǎn)一下就行了,源碼如下:

protected final <T> List<T> removeDuplicates(List<T> list) {
 return new ArrayList<>(new LinkedHashSet<>(list));
}

可以看到這些源碼里有時候一些解決思路也很有意思。

3.4 getExclusions

getExclusions 方法表示需要獲取到所有被排除的自動化配置類,這些被排除的自動化配置類可以從三個地方獲?。?/p>

  • 當前注解的 exclude 屬性。
  • 當前注解的 excludeName 屬性。
  • application.properties 配置文件中的 spring.autoconfigure.exclude 屬性。

來看一下相關(guān)源碼:

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 Set<String> excluded = new LinkedHashSet<>();
 excluded.addAll(asList(attributes, "exclude"));
 excluded.addAll(asList(attributes, "excludeName"));
 excluded.addAll(getExcludeAutoConfigurationsProperty());
 return excluded;
}

跟上面講解的三點剛好對應。

3.5 checkExcludedClasses

這個方法是檢查所有被排除的自動化配置類,由于 Spring Boot 中的自動化配置類可以自定義,并不需要統(tǒng)一實現(xiàn)某一個接口或者統(tǒng)一繼承某一個類,所以在寫排除類的時候,如果寫錯了編譯是校驗不出來的,像下面這種:

@SpringBootApplication(exclude = HelloController.class)
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

由于 HelloController 并不是一個自動化配置類,所以這樣寫項目啟動的時候就會報錯,如下:

圖片

這個異常從哪來的呢?其實就是來自 checkExcludedClasses 方法,我們來看下該方法:

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
 List<String> invalidExcludes = new ArrayList<>(exclusions.size());
 for (String exclusion : exclusions) {
  if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
   invalidExcludes.add(exclusion);
  }
 }
 if (!invalidExcludes.isEmpty()) {
  handleInvalidExcludes(invalidExcludes);
 }
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
 StringBuilder message = new StringBuilder();
 for (String exclude : invalidExcludes) {
  message.append("\t- ").append(exclude).append(String.format("%n"));
 }
 throw new IllegalStateException(String.format(
   "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
   message));
}

可以看到,在 checkExcludedClasses 方法中,會首先找到所有位于當前類路徑下但是卻不包含在 configurations 中的所有被排除的自動化配置類,由于 configurations 中的就是所有的自動化配置類了,所以這些不存在于 configurations 中的類都是有問題的,都不是自動化配置類,將這些有問題的類收集起來,存入到 invalidExcludes 變量中,然后再進行額外的處理。

所謂額外的處理就是在 handleInvalidExcludes 方法中拋出異常,前面截圖中的異常就是來自這里。

3.6 removeAll

這個方法就一個任務,就是從 configurations 中移除掉那些被排除的自動化配置類。configurations 本身就是 List 集合,exclusions 則是一個 Set 集合,所以這里直接移除即可。

3.7 filter

現(xiàn)在我們已經(jīng)加載了所有的自動化配置類了,但是這些配置類并不是都會生效,具體是否生效,還要看你的項目是否使用了具體的依賴。

例如,現(xiàn)在加載的自動化配置里里邊就包含了 RedisAutoConfiguration,這個是自動配置 Redis 的,但是由于我的項目中并沒有使用 Redis,所以這個自動化配置類并不會生效。這個過程就是由 getConfigurationClassFilter().filter(configurations); 來完成的。

先說一個預備知識:

由于我們項目中的自動化配置類特別多,每一個自動化配置類都會依賴別的類,當別的類存在時,這個自動化配置類才會生效,這一堆互相之間的依賴關(guān)系,存在于 spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties 文件之中,我隨便舉一個該文件中的配置:

  • org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.Cnotallow=org.springframework.amqp.rabbit.annotation.EnableRabbit 表示 RabbitAnnotationDrivenConfiguration 類要生效有一個必備條件就是當前項目類路徑下要存在 org.springframework.amqp.rabbit.annotation.EnableRabbit。

我們來看看 RabbitAnnotationDrivenConfiguration 類的注解:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
}

這個類和配置文件中的內(nèi)容一致。

這個預備知識搞懂了,接下來的內(nèi)容就好理解了。

先來看 getConfigurationClassFilter 方法,這個就是獲取所有的過濾器,如下:

private ConfigurationClassFilter getConfigurationClassFilter() {
 if (this.configurationClassFilter == null) {
  List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
  for (AutoConfigurationImportFilter filter : filters) {
   invokeAwareMethods(filter);
  }
  this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
 }
 return this.configurationClassFilter;
}

可以看到,這里獲取到的過濾器都是 AutoConfigurationImportFilter 類型的,這個類型的過濾器只有三個實例,如下圖:

圖片

從這三個實例的名字中,基本上就能看出來各自的作用:

  • OnClassCondition:這個就是條件注解 @ConditionalOnClass 的判定條件,看名字就知道用來判斷當前 classpath 下是否存在某個類。
  • OnWebApplicationCondition:這個是條件注解 ConditionalOnWebApplication 的判定條件,用來判斷當前系統(tǒng)環(huán)境是否是一個 Web 環(huán)境。
  • OnBeanCondition:這個是條件注解 @ConditionalOnBean 的判定條件,就是判斷當前系統(tǒng)下是否存在某個 Bean。

這里獲取到的三個 AutoConfigurationImportFilter 過濾器其實就是上面這三個。接下來執(zhí)行 filter 方法,如下:

List<String> filter(List<String> configurations) {
 long startTime = System.nanoTime();
 String[] candidates = StringUtils.toStringArray(configurations);
 boolean skipped = false;
 for (AutoConfigurationImportFilter filter : this.filters) {
  boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
  for (int i = 0; i < match.length; i++) {
   if (!match[i]) {
    candidates[i] = null;
    skipped = true;
   }
  }
 }
 if (!skipped) {
  return configurations;
 }
 List<String> result = new ArrayList<>(candidates.length);
 for (String candidate : candidates) {
  if (candidate != null) {
   result.add(candidate);
  }
 }
 return result;
}

這里就是遍歷這三個過濾器,然后分別調(diào)用各自的 match 方法和 144 個自動化配置類進行匹配,如果這些自動化配置類所需要的條件得到滿足,則 match 數(shù)組對應的位置就為 true,否則就為 false。

然后遍歷 match 數(shù)組,將不滿足條件的自動化配置類置為 null,最后再把這些 null 移除掉。

這樣就獲取到了我們需要進行自動化配置的類了。

最后一句 fireAutoConfigurationImportEvents 則是觸發(fā)自動化配置類導入事件,這個沒啥好說的~

當這些自動化配置類加載進來之后,接下來就是各種條件注解來決定這些配置類是否生效了,這些都比較簡單了,之前在 vhr 種也和小伙伴們講過多次了,這里就不再啰嗦了~

責任編輯:武曉燕 來源: 江南一點雨
相關(guān)推薦

2023-09-27 08:14:56

2017-08-02 14:44:06

Spring Boot開發(fā)注解

2024-12-16 08:10:00

Spring開發(fā)

2021-04-13 20:24:57

Spring Boot注解spring

2024-10-14 17:18:27

2017-03-06 15:43:33

Springboot啟動

2020-11-02 07:00:29

Spring Boo注解自動化

2020-03-31 15:03:56

Spring Boot代碼Java

2023-04-17 23:49:09

開發(fā)代碼Java

2023-01-10 07:52:15

2017-05-12 15:47:15

Spring BootMybatis Ann Web

2025-04-08 02:22:22

SpringJackson注解

2023-03-20 13:41:00

IoC容器Spring

2025-01-14 17:00:00

SpringBoot開發(fā)代碼

2025-04-10 00:22:22

Spring@JsonView字段

2020-07-03 08:00:11

Spring BootSpring Clou流程

2024-02-26 00:00:00

GolangRedisDocker

2023-12-14 13:28:00

Spring流程Web

2020-12-15 10:46:29

事件監(jiān)聽器Spring Boot

2024-05-31 14:06:55

SpringCDSGraalVM
點贊
收藏

51CTO技術(shù)棧公眾號