Spring 源碼(9)Spring Bean的創建過程的前期準備

来源:https://www.cnblogs.com/redwinter/archive/2022/05/07/16241328.html
-Advertisement-
Play Games

回顧總結 到目前為止,Spring源碼中AbstractApplicationContext#refresh方法的已經解讀到第11個方法finishBeanFactoryInitialization,前10個方法介紹了: BeanFactory的準備,創建,刷新,個性化BeanFactory的擴展點 ...


回顧總結

到目前為止,Spring源碼中AbstractApplicationContext#refresh方法的已經解讀到第11個方法finishBeanFactoryInitialization,前10個方法介紹了:

  • BeanFactory的準備,創建,刷新,個性化BeanFactory的擴展點,自定義屬性解析;
  • 環境信息Environment的載入(包括環境變數、系統變數等);
  • BeanDefinition的載入,解析,自定義xml的方式;
  • BeanFactoryPostProcessor的註冊與執行流程,BeanDefinitionRegistryPostProcessor的解析,ConfigurationClassPostProcessorSpring註解的解析過程(@Component、@PropertySources、@PropertySource、@ComponentScans、@ComponentScan、@Import等註解的解析),Spring Boot 是如何通過@Configuration+@Import + ImportSelector進行自動裝配的等;
  • BeanPostProcessor的註冊流程;
  • 國際化,Spring事件驅動的載入執行過程;

finishBeanFactoryInitialization 解析過程

接下來開始解析SpringBean的創建過程,上源碼:

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  // Initialize conversion service for this context.
  if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
      beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
    // 設置轉換服務,轉換服務用來對屬性值進行解析的
    beanFactory.setConversionService(
      beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
  }

  // Register a default embedded value resolver if no BeanFactoryPostProcessor
  // (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
  // at this point, primarily for resolution in annotation attribute values.
  // 如果之前沒有註冊過任何 BeanFactoryPostProcessor(例如 PropertySourcesPlaceholderConfigurer bean),
  // 則註冊一個預設的嵌入值解析器:此時,主要用於解析註釋屬性值。
  if (!beanFactory.hasEmbeddedValueResolver()) {
    beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
  }

  // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
  String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
  for (String weaverAwareName : weaverAwareNames) {
    getBean(weaverAwareName);
  }

  // Stop using the temporary ClassLoader for type matching.
  beanFactory.setTempClassLoader(null);

  // Allow for caching all bean definition metadata, not expecting further changes.
  // 允許緩存所有 bean 定義元數據,而不是期望進一步的更改
  beanFactory.freezeConfiguration();

  // Instantiate all remaining (non-lazy-init) singletons.
  // 實例化所有剩餘的(非惰性初始化)單例
  beanFactory.preInstantiateSingletons();
}
  • 判斷是否存在轉換服務,有就設置
  • 判斷是否有內置的值解析器,沒有就創建一個處理占位符的解析器
  • 實例化LoadTimeWeaverAware,進行早期的Bean的創建
  • 停止使用臨時的類載入器
  • 凍結BeanDefinition的元數據信息,防止被修改
  • 開始實例化所有的單例bean對象

除了beanFactory.preInstantiateSingletons() 方法,其他都是Bean創建的準備,接下來一個一個分析,首先是轉換服務的設置。

轉換服務ConversionService的初始化

方法一開始設置了一個轉換服務,這個轉換服務在Spring中還是非常的重要的,比如我們xml中配置一個String 類型的屬性值,但是在Bean的定義中是一個Integer類型的,這時Spring就會自動幫我們轉出來,他是怎麼做的呢?

Spring中有幾個比較重要的介面:

  • Converer 用於將對象S轉換為對象T
  • ConverterFactory 一個轉換工廠,能夠將對象S轉成一類對象R的子集T,比如可以將字元串S轉換為TInteger、Long等)Number類型R的子集
  • GenericConverter支持多種類型之間互相轉換。

Spring轉換器介面ConversionService 的預設實現是DefaultConversionService,這個預設的轉換器實現中,內置了很多的轉換器,比如:

public static void addDefaultConverters(ConverterRegistry converterRegistry) {
  addScalarConverters(converterRegistry);
  addCollectionConverters(converterRegistry);

  converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
  converterRegistry.addConverter(new StringToTimeZoneConverter());
  converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
  converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

  converterRegistry.addConverter(new ObjectToObjectConverter());
  converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
  converterRegistry.addConverter(new FallbackObjectToStringConverter());
  converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}

public static void addCollectionConverters(ConverterRegistry converterRegistry) {
  ConversionService conversionService = (ConversionService) converterRegistry;
  // 數組轉集合
  converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
  // 集合轉數組
  converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));

  converterRegistry.addConverter(new ArrayToArrayConverter(conversionService));
  converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService));
  converterRegistry.addConverter(new MapToMapConverter(conversionService));
  // 數組轉字元串
  converterRegistry.addConverter(new ArrayToStringConverter(conversionService));
  converterRegistry.addConverter(new StringToArrayConverter(conversionService));

  converterRegistry.addConverter(new ArrayToObjectConverter(conversionService));
  converterRegistry.addConverter(new ObjectToArrayConverter(conversionService));

  converterRegistry.addConverter(new CollectionToStringConverter(conversionService));
  converterRegistry.addConverter(new StringToCollectionConverter(conversionService));

  converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
  converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));

  converterRegistry.addConverter(new StreamConverter(conversionService));
}

private static void addScalarConverters(ConverterRegistry converterRegistry) {
  converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());

  converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
  converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());

  converterRegistry.addConverter(new StringToCharacterConverter());
  converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter());

  converterRegistry.addConverter(new NumberToCharacterConverter());
  converterRegistry.addConverterFactory(new CharacterToNumberFactory());

  converterRegistry.addConverter(new StringToBooleanConverter());
  converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());

  converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
  converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry));

  converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory());
  converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry));

  converterRegistry.addConverter(new StringToLocaleConverter());
  converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());

  converterRegistry.addConverter(new StringToCharsetConverter());
  converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter());

  converterRegistry.addConverter(new StringToCurrencyConverter());
  converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter());

  converterRegistry.addConverter(new StringToPropertiesConverter());
  converterRegistry.addConverter(new PropertiesToStringConverter());

  converterRegistry.addConverter(new StringToUUIDConverter());
  converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
}

可以說是非常的豐富的,基本上常見都是Spring提供了,非常貼心。

那麼怎麼使用呢?

不懂當然是上官網:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#core-convert ,這裡可以看到我們只需要將ConversionServiceFactoryBean 配置到Spring容器中就可以了,Spring內置的轉換器就可以工作了,非常方便。

ConversionServiceFactoryBean實現了FactoryBean介面和InitializingBean 介面,而InitializingBean#afterPropertiesSet是初始化Bean過程中需要執行的。ConversionServiceFactoryBean源碼中:

@Override
public void afterPropertiesSet() {
  this.conversionService = createConversionService();
  ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
}

protected GenericConversionService createConversionService() {
  return new DefaultConversionService();
}

// 構造函數
public DefaultConversionService() {
  // 添加預設的轉換器
  addDefaultConverters(this);
}

可以看到這個ConversionServiceFactroyBean就是用來初始化轉換器的,並且這個類還提供了擴展,可以自定義轉換器加入到轉換器集合中。

自定義轉換器

自定義String轉Integer類型的轉換器:

/**
 * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class StringToIntegerConverter implements Converter<String,Integer> , ConditionalConverter {
	@Override
	public Integer convert(String source) {
		return NumberUtils.parseNumber(source,Integer.class);
	}

	@Override
	public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
		System.out.println(sourceType.getType());
		System.out.println(targetType.getType());
		return true;
	}
}

邏輯非常簡單,直接調用Spring提供的工具類進行轉換。

配置xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="com.redwinter.selfconverter"/>
	<!--配置轉化器-->
	<bean class="org.springframework.context.support.ConversionServiceFactoryBean">
		<property name="converters">
			<set>
				<bean class="com.redwinter.selfconverter.StringToIntegerConverter"/>
			</set>
		</property>
	</bean>

</beans>

創建轉換器客戶端:

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

	private final ConversionService conversionService;

	public MyConverter(ConversionService conversionService) {
		this.conversionService = conversionService;
	}

	public void test(String source){
		System.out.println(conversionService.convert(source, Integer.class));
	}
}

創建測試:

/**
 * @author <a href="[email protected]">redwinter</a>
 * @since 1.0
 **/
public class FactoryBeanTest {

	@Test
	public void test(){
		MyClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("spring-factory.xml");
		MyConverter myConverter = context.getBean(MyConverter.class);
		myConverter.test("12345");
	}
}

輸出:

class java.lang.String
class java.lang.Integer
12345

分析完轉換服務,接下來分析 值解析器的添加。

預設的值解析器

// 省略代碼.....
// Register a default embedded value resolver if no BeanFactoryPostProcessor
// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
// 如果之前沒有註冊過任何 BeanFactoryPostProcessor(例如 PropertySourcesPlaceholderConfigurer bean),
// 則註冊一個預設的嵌入值解析器:此時,主要用於解析註釋屬性值。
if (!beanFactory.hasEmbeddedValueResolver()) {
  beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// 省略代碼.....


首先判斷了容器中是否存在嵌入的值解析器,如果沒有就添加一個進去,這裡添加進去的是StringValueResolver,點擊resolvePlaceHolders方法進去,最終會在AbstractPropertyResolver#resolvePlaceholders中創建一個PropertyPlaceholderHelper

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
  // 首碼為 ${ ,尾碼為 },值的分隔符為 : ,比如 ${username:zhansan} username沒有的話,後面的為預設的值
  return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
                                       this.valueSeparator, ignoreUnresolvablePlaceholders);
}

如果已經註冊過一個BFPP的占位符解析器的話,就不需要在註冊了,BFPP的占位符解析器就是PropertySourcesPlaceholderConfigurer ,專門用於解析占位符的,比如在xml中或者yaml中,配置類似於${jdbc.username} 這種格式的,就會被解析器解析。PropertySourcesPlaceholderConfigurer 這個解析器實現了BeanFactoryPostProcessor介面,能夠對BeanDefinition進行處理,當然也可以對屬性值進行處理。

分析完值解析器,繼續往下分析。

Bean創建前的其他準備

// 省略代碼.....
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
// 在prepareBeanFactory 準備BeanFactory時設置進去的,如果存在,則開始早期Bean的創建
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
  getBean(weaverAwareName);
}

// Stop using the temporary ClassLoader for type matching.
// 停止使用臨時的類載入器,這裡也是在準備BeanFactory時設置進去的
beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.
// 允許緩存所有 bean 定義元數據,而不是期望進一步的更改
beanFactory.freezeConfiguration();
// 省略代碼.....

這裡從容器中獲取了AOP的織入,如果有的話就開始進行早期的Bean的創建;然後停止了臨時的類載入器;然後就是凍結BeanDefinition的元數據信息。

public void freezeConfiguration() {
  this.configurationFrozen = true;
  this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames);
}

點擊進來,其實就是設置了標識,防止後期對BeanDefinition的修改。

這前面的幾個判斷和方法實際上都是Bean創建的準備工作,接下來開始分析preInstantiateSingletons 預實例化所有的單例Bean


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

-Advertisement-
Play Games
更多相關文章
  • 給開源項目尤其是Spring這種知名度高的項目貢獻代碼是比較難的,起碼胖哥是這麼認為的。有些時候我們的靈感未必契合作者的設計意圖,即使你的代碼十分優雅。 我曾經給Spring Security提交了一個我認為非常重要的一項優化,和作者溝通了幾十個來回無法說服他。人家說留了抽象介面,你覺得不對自己實現 ...
  • 作者:CadeCode 地址:https://juejin.cn/post/7043403364020781064 斷言 斷言是一個邏輯判斷,用於檢查不應該發生的情況 Assert 關鍵字在 JDK1.4 中引入,可通過 JVM 參數-enableassertions開啟 SpringBoot 中提 ...
  • Python 是一種極其多樣化和強大的編程語言!當需要解決一個問題時,它有著不同的方法。在本文中,將會展示列表解析式 (List Comprehension)。我們將討論如何使用它?什麼時候該或不該使用它? 列表解析式的優勢 •比迴圈更節省時間和空間。 •需要更少的代碼行。 •可將迭代語句轉換為公式 ...
  • Python代碼規範 代碼規範這東西也是很重要的,一定要註意 給你舉個例子: 你代碼全部寫對了,但是你的代碼規範沒弄好,找個半天,不知道錯哪了,那感覺,簡直不要太崩潰 好啦,下麵我們開始學習吧~ 一、簡明概述 1、編碼 如無特殊情況, 文件一律使用 UTF-8 編碼如無特殊情況, 文件頭部必須加入 ...
  • 一個工作了4年的小伙伴,他說他從線下培訓就開始接觸Spring,到現在已經快5年時間了。 從來沒有想過,為什麼要使用Spring 框架。 結果在面試的時候,竟然遇到一個這樣的問題。 大腦一時間短路了,來求助我,這類問題應該怎麼去回答。 下麵我們來看看普通人和高手的回答 普通人: 嗯。。。。。。。。。 ...
  • 二分查找實際上就是採用了分治法的思想 以下模板都以升序數組為準 模板一: 標準的二分查找 場景:數組元素有序且不重覆 有的話返回索引,沒有返回-1 int binarySearch(vector<int>& arr, int target) { int left = 0, right = nums. ...
  • 原創:微信公眾號 【阿Q說代碼】,歡迎分享,轉載請保留出處。 之前寫過一篇名為《看了同事寫的代碼,我竟然開始默默的模仿了。。。》的文章,今天偶然間看了下後臺數據,大吃一驚。該文章的閱讀量在微信公眾號內竟然達到了驚人的5W+ 。對於沒見過市面的我來說已經相當滿足了。 當然,能達到這樣的數據離不開各位大 ...
  • 近幾年網路發展的越來越好,其中的功勞離不開默默付出的程式員,正是他們任勞任怨的付出,才換來現在的便捷,在程式員匯聚的論壇,一名程式員卻道出另一種現象:好久沒打代碼了,回想以前辭職到老家礦洞里秘密開發的日子,二年整整敲了45萬行代碼。 這便是該程式員的原文,在貼文最下方,還曬出山洞的全景樣貌,山洞看起 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...