Spring 源碼(7)Spring的註解是如何解析的?

来源:https://www.cnblogs.com/redwinter/archive/2022/04/27/16198942.html
-Advertisement-
Play Games

上一篇 https://www.cnblogs.com/redwinter/p/16196359.html 介紹了BeanFactoryPostProcessor的執行過程,這篇文章介紹Spring中配置的註解是如何通過ConfigurationClassPostProcessor解析的,另外分析下 ...


上一篇 https://www.cnblogs.com/redwinter/p/16196359.html 介紹了BeanFactoryPostProcessor的執行過程,這篇文章介紹Spring中配置的註解是如何通過ConfigurationClassPostProcessor解析的,另外分析下Spring Boot自動裝配是如何處理的。

ConfigurationClassPostProcessor 解析了哪些註解?

在上一篇文章https://www.cnblogs.com/redwinter/p/16196359.html 我們知道ConfigurationClassPostProcessor實際上是BeanFactoryPostProcessor的一個實現類,他特殊的地方是他還實現了BeanDefinitionRegisterPostProcessor介面,所以ConfigurationClassPostProcessor 既要實現BeanFactoryPostProcessor的介面方法postProcessBeanFactory也要實現BeanDefinitionRegisterPostProcessor的介面方法postProcessBeanDefinitionRegistry,並且在解析的時候先執行了postProcessBeanDefinitionRegistry方法,再執行了postProcessBeanDefinitionRegistry方法。

接下來我們看看postProcessBeanDefinitionRegistry做了什麼?

上源碼:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  int registryId = System.identityHashCode(registry);
  if (this.registriesPostProcessed.contains(registryId)) {
    throw new IllegalStateException(
      "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
  }
  if (this.factoriesPostProcessed.contains(registryId)) {
    throw new IllegalStateException(
      "postProcessBeanFactory already called on this post-processor against " + registry);
  }
  this.registriesPostProcessed.add(registryId);
  // 處理配置的BeanDefinition
  processConfigBeanDefinitions(registry);
}

整個方法核心是執行了processConfigBeanDefinitions方法,這個方法非常的長並且邏輯也複雜,代碼我就不貼了,說一下大概的流程(較詳細):

  • 先進行合格的beanDefinition的檢查
    • 獲取到註解的元數據信息
    • 判斷是包含@Configuration註解,包含則合格,否則判斷是否包含了@Component@ComponentScan@Import@ImportResource註解,包含則合格,如果都不包含則不合格
  • 對合格的BeanDefinition排序
  • 創建一個解析@Configuration註解的解析器
  • 對合格的BeanDefinition集合進行解析
    • 迴圈解析,最終調用processConfigurationClass方法
    • 判斷是否跳過解析,比如配置了@Conditional註解的
    • 調用doProcessConfigurationClass方法開始解析(下麵的解析中可能會存在遞歸調用)
      • 解析@Component註解
        • 判斷是否包含內部類標記了@Component,比如在標有@Component註解的類裡面創建一個內部類也標記了@Component註解,如果有就會進行遞歸調用processConfigurationClass方法
      • 解析@PropertySources@PropertySource註解
        • 比如標記@PropertySource("classpath:jdbc.properties"),這樣就會把這個屬性的值全部解析到環境信息的propertySources屬性中
      • 解析@ComponetScans@ComponentScan註解
        • 比如配置了掃描的包,那麼就會掃描出合格的BeanDefinition,然後遞歸解析
      • 解析@Import註解(Spring Boot自動裝配的實現)
        • 遞歸解析出標記了@Import註解的類放在imports屬性中
        • 解析ImportSelector介面的實現類
        • 調用ImportSelector#selectImports方法解析需要註冊的類
        • 遞歸調用processImports方法,然後將需要註冊的類註冊到importBeanDefinitionRegistrars(這裡會在後面進行loadBeanDefinition
      • 解析@ImportResource註解
        • 比如解析配置的Springxml配置文件,最終放到importedResources屬性中(後面會進行loadBeanDefinition
      • 解析@Bean註解
        • 比如解析當前類標記了@Bean的方法
        • 然後放在beanMethods屬性中(後面會進行loadBeanDefinition
    • 載入BeanDefinition從上面解析出來的類中
      • 迴圈遍歷載入BeanDefinition
      • 判斷是否跳過,比如實現了Condition介面的類
      • 載入標有@BeanBeanDefinition
      • 載入從ImportResource中解析的BeanDefinition
      • 載入從ImportSelector中配置的解析的BeanDefinition

整個過程非常複雜,而且存在遞歸操作,讀者可以按照我寫的步驟進行debug調試,當然可能會出現到處跳轉不知所措的情況,多調幾遍就好了,只要知道大致的流程,應該還是不難的。

總的來說就是解析了這些註解:@Component@PropertySource@PropertySources@ComponentScan@ComponentScans@Import@ImportResource@Bean,然後將標有這些註解的解析成BeanDefinition,如果加上了@Conditionnal註解,那麼按照條件進行解析。

自定義自動裝配

現在開發都是用SpringBoot,原因在於他非常的方便,引入即可使用,那麼他是做到的呢?眾所周知Spring Boot有幾個註解非常重要,比如:@SpringBootApplication@EnableAutoConfiguration@SpringBootConfiguration,其中最重要的是@EnableAutoConfiguration,這個註解裡面標記了@Import(AutoConfigurationImportSelector.class),當然還標記了其他的,我們現在只關心這個@Import,裡面放入了一個AutoConfigurationImportSelector類。

AutoConfigurationImportSelector類實現了DeferredImportSelector介面,這個DeferredImportSelector介面是ImportSelector的子介面,表示延遲導入的意思。在上面的分析中,其實最主要的是實現他的介面selectImports,直接源碼:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  // 獲取自動裝配的實體
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

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);
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  // 載入配置,根據factoryType,這裡的FactoryType就是@EnableAutoConfiguration註解
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                                                                       getBeanClassLoader());
  Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
                  + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
  // 直接返回@EnableAutoConfiguration 註解
  return EnableAutoConfiguration.class;
}


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  String factoryTypeName = factoryType.getName();
  // 載入spring.factories文件並解析
  return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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 
    // 這裡獲取的url就是:
    // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    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);
      // 讀取屬性文件,獲取到key為EnableAutoConfiguration,value為需要載入的類
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for (Map.Entry<?, ?> entry : properties.entrySet()) {
        String factoryTypeName = ((String) entry.getKey()).trim();
        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
          result.add(factoryTypeName, factoryImplementationName.trim());
        }
      }
    }
    cache.put(classLoader, result);
    return result;
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
}

所以我們也可以自己寫一個進行自動裝配,接下來實現一個簡單的自動裝配。

定義自動裝配註解

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
public @interface EnableRedwinterAutoConfiguration {
}

創建MyInportSelector類

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class MyImportSelector implements DeferredImportSelector {
  @Override
  public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    ClassLoader classLoader = this.getClass().getClassLoader();
    // 載入需要裝配的類
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getFactoryTypeClass(), classLoader);
    return configurations.toArray(new String[configurations.size()]);
  }

  private Class<?> getFactoryTypeClass() {
    return EnableRedwinterAutoConfiguration.class;
  }


}

創建啟動類

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
@Configuration
@EnableRedwinterAutoConfiguration
public class RedwinterApplication {
  	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.scan("com.redwinter.test.config");
		context.refresh();
	}
}

創建需要裝配的類

/**
 * @author <a href=""https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
@Configuration
public class MyConfiguration {

	@Bean
	@Conditional(RedwinterStrCondition.class)
	public String myStr() {
		return "redwinter";
	}

	public static class RedwinterStrCondition implements ConfigurationCondition {

		@Override
		public ConfigurationPhase getConfigurationPhase() {
			return ConfigurationPhase.REGISTER_BEAN;
		}

		@Override
		public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
			System.out.println("開始匹配。。。");
			return true;
		}
	}

}

創建spring.factories文件

com.redwinter.test.config.EnableRedwinterAutoConfiguration=\
  com.redwinter.test.config.MyConfiguration

啟動驗證

debug斷點:

這就是Spring Boot自動裝配的簡化版,總得來說我們完成了SpringBeanFactoryPostProcessor的執行過程的解析,包括Spring是如何進行註解解析的,其實就是Spring在對BeanDefinition在正式初始化為Bean的前置處理,所以我們可以這個階段進行很多擴展,比如占位符的處理PropertySourcesPlaceholderConfigurer等。

接下來接續解讀AbstractApplicationContext#refresh方法對BeanPostProcessor的註冊。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 微服務概覽 微服務是圍繞業務領域建模可獨立發佈的服務。服務封裝了對應功能並可以通過網路被其他服務訪問。 從外部來看,單個微服務被視為一個黑盒子。它使用最合適的協議在一個或多個網路端點(例如,隊列或REST API)上承載業務功能。消費者,無論他們是其他微服務還是其他類型的程式,都通過這些聯網的端點來 ...
  • 要寫好一份 B 端產品的技術方案,是非常有挑戰的事情,對最終項目價值達成起到決定性的作用,技術方案質量差可能直接毀滅一塊業務。下麵推薦一份 B 端產品的技術方案模板,供讀者參考。 ...
  • 業務需求 最近公司在做養老相關的業務,現在需要子女從小程式端對家裡的老人通過家庭終端交互屏進行實時看護。 解決方案 第三方的一些現成的服務:騰訊音視頻通話、直播功能; 阿裡的音視頻通信;兩者都挺好的,但是需要收費因此放棄決定自己搭建一套直播流服務; 先看效果(自己伺服器配置低有延遲、放到公司伺服器上 ...
  • 26名學生,每個人可以填寫10個交談對象: 10輪匹配結果: 1、pom.xml <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</sco ...
  • 教程說明 C++高性能網路服務保姆級教程 首發地址 day02 真正的高併發還得看IO多路復用 本節目的 使用epoll實現一個高併發的伺服器 從單進程講起 上節從一個基礎的socket服務說起我們實現了一個基本的socket伺服器,並留了個思考題 先啟動server,然後啟動一個client,不輸 ...
  • LinkedList底層結構 說明 LinkedList底層實現了雙向鏈表和雙端隊列特點 可以添加任意元素(元素可以重覆),包括null 線程不安全,沒有實現同步 操作機制: LInkedList底層維護了一個雙向鏈表 LinkedList中維護了兩個屬性first和last分別指向首節點和尾節點 ...
  • 可改不可改? 常量表達式是指在編譯器編譯時期就可以的到值的表達式,例如:const int a = 3,雖然聽起來很簡單,但實際上很容易犯錯。本文簡要總結它們的不同: 關於const:頂層const和底層const 一般,我們聲明const用來存儲不變的恆定量: const int MAXN = i ...
  • 前言 Python 在程式並行化方面多少有些聲名狼藉。撇開技術上的問題,例如線程的實現和 GIL,我覺得錯誤的教學指導才是主要問 題。常見的經典 Python 多線程、多進程教程多顯得偏"重"。而且往往隔靴搔癢,沒有深入探討日常工作中最有用的內容。 傳統的例子 簡單搜索下"Python 多線程教程" ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...