導航:首頁 > 源碼編譯 > spring源碼轉換器

spring源碼轉換器

發布時間:2023-01-11 03:14:22

A. spring類型轉換器(三)

Converter用來將源數據類型轉換目標數據類型,不過有時候一個數據類型會對應不同格式的字元串,如日期類型在不同國家顯示的字元是不一樣的,需要根據Locale進行轉換,或者需要將日期類型轉換為不同格式化的字元串,spring針對這種情況提供了Formatter介面來針對格式化進行處理。

格式化Formatter其實也是一種Converter,只是兩個互轉類型之間, 有一個固定是String類型 ,Formatter實現了不同格式的String類型與其他類型的類型互轉

Formatter主要針對的是Number類型和日期類型。Spring對這兩種類型的子類和字元串之間的轉換提供了格式化實現,下面以日期類型的轉換為例說明。

Formatter介面繼承了Printer和Parser介面,一個用於將對象格式化為本地化的字元串,一個將字元串轉換為對象。

AnnotationFormatterFactory集成了註解和Formatter。可以創建帶有Annotation性質的Printer和Parser對象。

在上章節的繼承圖中可以看到FormattingConversionService繼承了GenericConversionService。同時實現了FormatterRegistry和ConverterRegistry介面

FormatterRegistry擴展了ConverterRegistry介面,額外提供了注冊Formatter、AnnotationFormatterFactory的功能

FormatterRegistrar介面用於批量注冊Formatter的介面

spring提供了FormattingConversionService的默認實現,使用的時候一般都是直接使用這個類。

首先來看,在BeanFactory中只接收一個ConversionService變數,所以只能給spring容易配置一個ConversionService。那麼到底應該用還是用DefaultConversionService?讓我們來看中的實現

源碼可以看出,完全是對DefaultConversionService的擴展,在構造函數中調用了DefaultConversionService的addDefaultConverters完全擁有了DefaultConversionService所有的功能,所以只需要使用就可以

創建完成之後除了添加Converters,還注冊了一些Formatters,這些Formaters主要是spring提供的用於針對 日期類型 Number類型 貨幣金額 進行格式化轉換

接下來分析下Formater的注冊功能。通過上述分析我們可以知道,Formater實質上是Class<?>和String之間的互轉,所以在注冊的時候,只需要提供Class<?>、Printer和Parser。來看FormattingConversionService類中的實現

可以看到的是注冊Formater的過程,就是注冊了一對Converter。注冊Converter的過程已經在前文分析過,在這里我們來一起看下PrinterConverter和ParserConverter類。

可以看出PrinterConverter類型實現GenericConverter,用於實現傳入類型到字元串的轉換,具體的轉換功能委託給參數Printer實現類對象實現。這里相當於是一個插件類保留。可以通過Printer實現任何類型到String類型的轉換。

再來看ParserConverter

ParserConverter跟PrinterConverter實現了一個反方向的轉換,至此注冊通過一個Formater介面實現類,就可以完成Formater實現類中泛型到String類型之間的互轉。

例如注冊 LocalDateTime的轉換器,實現LocalDateTime和String類型的互轉

可以看到使用一個類TemporalAccessorPrinter和TemporalAccessorParser,並都傳入了一個參數DateTimeFormatter,眾所周知LocalDateTime和String格式化就是通過DateTimeFormatter來實現。那麼我們猜測TemporalAccessorParser其實就是對DateTimeFormatter的封裝調用

可以看到TemporalAccessorPrinter就是調用的DateTimeFormatter完成格式化的,不過這里使用到了ThreadLocal,可以先不管,TemporalAccessorParser中同理也會使用DateTimeFormatter完成String到日期的轉換

Formatter的注冊最終是注冊了一對Converter,所以格式化轉換完全就是Converter邏輯的實現,在前文已經分析過了,這里就不再贅述。

分析完了Formatter的注冊和轉換過程,一起來看下FormatConversionService提供了另外一種注冊。前文提到了可以通過在對象欄位上聲明一個註解,在註解中指定格式化後字元串格式。這個功能就是通過AnnotationFormatterFactory來實現的。來看FormatConversionService的 () 方法

AnnotationFormatterFactory的注冊,首先需要獲取的就是AnnotationFormatterFactory泛型中的註解類型。然後通過 getFieldTypes() 獲取所有聲明的可以進行轉換類型集合,然後循環注冊了兩個類型轉換器AnnotationPrinterConverter和AnnotationParserConverter。那麼來看一下AnnotationPrinterConverter到底如何完成可配置的類型轉換的

可以看到的是,AnnotationPrinterConverter實現了ConditionalGenericConverter介面,在 matches() 方法中聲明了該轉換器只會作用於Class<?>上有指定的註解的類型。在convert方法中最終創建了一個PrinterConverter對象,使用PrinterConverter完成格式化的功能,這個在上面已經分析過了。唯一的不同就是Printer的獲取。使用annotationFormatterFactory獲取printer,並將註解作為參數傳遞進去。所以可以我們可以實現AnnotationFormatterFactory的 getPrinter() 方法提供轉換為字元串的功能即可,通過看以通過參數annotation獲取用戶配置的格式。實現格式的可配置。

同樣在AnnotationParserConverter中實現String到Class<?>的轉換,調用AnnotationFormatterFactory獲取Parser然後創建一個ParserConverter來實現類型轉化。需要注意的是 註解只能添加在非String類型那一方上。

通過封裝封裝注冊AnnotationPrinterConverter和AnnotationParserConverter,用戶需要做的就只有是實現AnnotationFormatterFactory,在泛型中指定註解,然後實現了 getPrinter() 和 getParser() 來實現Class<?>和String之間的轉換,其他的調用直接走Converter流程。來看Date類型格式化的實現

在DateFormatterRegistrar中,在注冊Formatters之前,先注冊了日期類型的一些converter,這里先不去管這個。最主要的是 方法,通過這個方法完成對Formater的注冊

來看下實現了AnnotationFormatterFactory。並指定註解為DateTimeFormat。

可以看到的是在 getFieldType() 聲明了支持轉換的有Date、Calendar、Long。可以看到 getPrinter() 和 getFormatter() 方法返回了一個DateFormatter對象,並將用戶配置的註解信息注入到這個DateFormatter對象中

接下來就是最終的格式化實現就是這個DateFormatter對象了。大家想一下,一般針對Date類型的格式化都會用什麼呢?想必大家都猜到了,沒錯就是SimpleDateFormat。在DateFormatter中就是創建了一個SimpleDateFormat來實現類型和字元串的轉換的

至此就實現了Formatter的注冊和使用。那麼在spring中是如何使用的呢?

沒錯就注冊一個id為conversionService的對象。使用這個就可以了,不需要再注冊ConversionServiceFactoryBean了。

B. spring自定義類型轉換器,怎麼不起作用

用自動掃描啊。component-scan可以根據你的條件設定掃描哪些位置,掃描哪些介面的實現,掃描包名,類名正則表達式等,完全可以滿足你的需求。舉例:使用Spring組件掃描的方式來實現自動注入bean,但是要如何對bean中通過set方法依賴的其他bean實現自動注入呢?現在不想使用@Resource註解來處理,因為代碼已經寫好。現在的配置如下:通過該配置可以實現對指定包指定類名的類實現自動裝置bean,免去了在類上標注@Service註解。至於詳細的配置,你可以搜索:Spring組件掃描使用詳解

C. (新)springboot2.x ConversionService轉換器

首先引用網上大多數人喜歡的這一段:

Binding 利用了一個新的 ApplicationConversionService 類,它提供了一些對屬性綁定特別有用的額外轉換器。最引人注目的是轉換器的Duration類型和分隔字元串。

該Duration轉換器允許在任一 ISO-8601 格式中指定的持續時間,或作為一個簡單的字元串(例如10m,10 分鍾)。現有的屬性已更改為始終使用Duration。該@DurationUnit注釋通過設置如果沒有指定所使用的單元確保向後兼容性。例如,Spring Boot 1.5 中需要秒數的屬性現在必須@DurationUnit(ChronoUnit.SECONDS)確保一個簡單的值,例如10實際使用的值10s。

分隔字元串轉換允許您將簡單綁定String到Collection或Array不必分割逗號。例如,LDAP base-dn 屬性用 @Delimiter(Delimiter.NONE),所以 LDAP DN(通常包含逗號)不會被錯誤解釋。

其實上面起到了拋磚引引玉的作用,但是還不夠詳細,說白了就是指給了大的方向,下面我會給出具體詳解:

一、

註解@DurationUnit(ChronoUnit.SECONDS)指明了sessionTimeout的單位是 秒,因此yml中可以指定30, PT30S and 30s,這幾種寫法都是正確的,表示i30秒

二、

前端可以傳以逗號(,)分隔的string,後端集合或者List可以直接解析映射

三、

@ConfigurationProperties Validation

主要用於驗證欄位比如非空驗證

D. spring mvc怎麼轉換數據格式

spring mvc數據綁定

spring mvc通過反射機制對目標方法的簽名進行分析,將請求消息綁定到處理方法入參中。

數據綁定的核心部件是DataBinder。

spring
mvc主框架將servletRequest對象及處理方法入參對象實例傳遞給DataBinder,DataBinder調用裝配在spring
mvc上下文中的ConversionService組件進行數據類型轉換,數據格式化的工作,將servletRequest中的消息填充到入參對象中,然後再調用Validator組件對已綁定了請求消息數據的入參對象進行數據合法性檢驗,並最終生成數據綁定結果BindingResult對象,BindingResult包含了已完成數據綁定的入參對象,還包含相應的校驗錯誤對象。

數據轉換

conversionService是spring類型轉換體系的核心介面,位於org.springframework.core.convert包中,可以利用org.springframework.context.support.ConversionServiceFactoryBean在spring上下文中定義一個ConversionSerivce。spring
自動識別出上下文中的ConversionService,並在Bean屬性配置及springmvc處理方法入參綁定等場合使用它進行數據的轉換。

<bean id="conversionSerivce"

class="org.springframework.context.support.ConversionServiceFactoryBean"/>

該FactoryBean創建ConversionSerivce內建了很多轉換器,可通過該FactoryBean的convertors屬性注冊自定義的類型轉換器

<bean id="conversionSerivce"

class="org.springframework.context.support.ConversionServiceFactoryBean">

<propertyname="converters">

<list>

<bean class="xxxxx"/>

</list>

</property>

</bean>

spring 內置3中類型轉換器介面,分別是

Converter<S,T>

ConverterFactory<S,R>

GenericConverter

自定義的類型轉換器必須實現其中一個

Converter

Converter介面是spring最簡單的轉換器介面,僅包含一個方法

public interface Converter<S,T>{

Tconverter(S source);//負責將S類型對象轉換成T類型對象

}

ConverterFactory<S,R>

ConverterFactory介面定義如下

public interface ConverFactory<S,R>{

<T,R>Converter<S,T> getConverter(Class<T> targetType);

}

S為源類型,R為目標類型的基類,T為擴展於R基類的類型。如spring的stringToNumberConverFactory就實現了ConverterFactory介面,封裝了String轉換成各種數據類型的Converter

在spring mvc中使用conversionSerivce

<userName>:<password>:<realName>-> User

1、<mvc:annotation-drivenconversion-service="conversionService"/>裝配自定義conversionService

<mvc:annotation-driven>該標簽可以簡化springmvc相關配置,默認情況下,它會創建並注冊一個默認的和一個,如果上下文中存在自定義的對應組件bean,spring

mvc會自動利用自定義的bean覆蓋默認的,除此之外,<mvc:annotation-driven/>標簽還會注冊一個默認的ConversionService,即,由於要自定義converter,因此要顯示定義一個ConversionSerivce覆蓋默認的實現。

2、裝配自定義轉換器

<bean id="conversionSerivce"

class="org.springframework.context.support.ConversionServiceFactoryBean">

<propertyname="converters">

<list>

<bean class="StringToUserConverter"/>

</list>

</property>

</bean>

3、編寫轉換器類

public class StringToUserConverter implementsConverter<String,User>{

publicUser convert(String source){

Useruser = new User();

if(source!=null){

Stringitems = source.split(「:」);

user.setUserName(items[0]);

user.setPassword(items[1]);

user.setRealName(items[2]);

}

}

}

@InitBinder

在控制器中使用@InitBinder添加自定義編輯器

@InitBinder

public void initBinder(WebDataBinder binder){

binder.registerCustomEditor(User.class,newUserEditor());

}

spring mvc使用WebDataBinder處理請求消息和處理方法入參的綁定工作,自定義編輯器必須實現PropertyEditor介面。

全局范圍自定義編輯器

1、實現WebBindingInitalizer介面

public void initBinder(WebDataBinder binder,WebRequestrequest);

2、在web上下文中通過裝配

<bean class="org.springframework.web.servlet.mvc.annotation.">

<propertyname="webBindingInitializer">

<bean class="xxx"/>

</property>

</bean>

spring mvc將按以下順序查找類型轉換編輯器

@InitBinder->ConversionService->WebBindingInitializer

數據格式化

spring 使用轉換器進行源類型對象到目標類型對象的轉換,spring 轉換器並不提供輸入輸出信息格式化工作。

註解驅動格式化

註解驅動重要介面

spring在org.springframework.format包中提供了一個

AnnotationFormatterFactory<A extends Annotation>介面,介面方法如下:

Set<Class<?>> getFieldTypes():表示哪些類可以標注A註解

Parser<?> getParser(A annotation,Class<?>fieldType):根據註解A獲取特定類型的Parser

Printer<?> getPrinter(Aannotation,Class<?> fieldType):根據註解A獲取特定類型的Printer

spring提供2個內建實現類,分別支持數組及數字類型的註解驅動格式化

:支持數字類型的屬性使用(@NumberFormat)

:用於日期類型(@DateTimeFormat)

啟用註解驅動格式化功能

spring中定義了一個實現了ConversionSerivce實現類FormattingConversionService,該類擴展於GenericConversionService,它既有類型轉換功能也有格式化功能。

FormattingConversionService也擁有一個,後者用於在spring上下文中構造一個FormattingConversionSerivce,通過這個類即可以注冊自定義轉換器,還可以注冊自定義註解驅動邏輯。

/

會自動注冊到中,因此裝配該FactoryBean後,就可以在入參綁定及模型數據輸出使用註解驅動格式化功能。

<bean id="conversionService"

class="org.springframework.format.support.">

<propertyname="converters">

<list><bean class="xxxx"/></list>

</property>

</bean>

用替換原來的ConversionServiceFactoryBean

<mvc:annotation-driven/>標簽默認創建了

@DateTimeFormat

可以對java.util.Date,java.util.Calendar,java.lang.Long,joda時間類型屬性標注

@DateTimeFormat(partten=」yyyy-MM-dd」)

@NumberFormat可對數字類型屬性標注

@NumberFormat(pattern=」#,###.##」)

E. Spring源碼(三) -- Xml文件轉換Document

我們上一節探究了ClassPathResource通過輸入Path構造出一個Resource對象,我們這一節繼續學習XmlBeanFactory如何解析Resource,將Resource轉化成一個Document。

,我們可以看到XmlBeanDefinitionReader上層是BeanDefinitionReader介面,EnvironmentCapable介面已經父類AbstractBeanDefinitionReader。

我們可以看到上面的步驟分為五步。1:對source進行encode封裝,考慮到可能需要編碼。2:通過sax讀取xml文件方式構建一個inputSource。3:getValidationModeForResource獲取xml的驗證模式。4:載入xml文件,獲得對應的Document。5:通過Document注冊BeanDefinition。至此就完成了一個xml文件到document,document到BeanDefinition的轉換,這里轉換使用的sax解析xml文件的方法,有興趣的可以debug進去看看。

F. spring類型轉換器(一)

在spring容器初始化的時候,BeanDefinition中配置的bean的屬性值一般都為String類型,如何將String類型轉換為Bean中屬性對應的類型呢,在這個過程中就需要用到類型轉換器了。spring實例化bean過程中對屬性值的轉換主要是使用BeanWrapperImpl實現的。

首先來看下BeanWrapperImpl的使用

定義一個處理日期的轉換器

創建一個BeanWrapperImpl用於包裝目標bean(這里來模擬spring的內部實現)。然後注冊Date類型的轉換器,將值使用DatePropertyEditor轉換為Date類型。調用setPropertyValue的時候,給testBean的birthday欄位設置一個字元串類型的時間,在實際賦值的過程中會調用到類型轉換器將字元串轉換為日期類型。

同樣可以實現一個String trim的轉換器

BeanWrapper支持嵌套屬性的賦值,當存在嵌套屬性的時候需要設置 setAutoGrowNestedPaths=true。

接下來我們就來研究一下BeanWrapperImpl的實現過程

java.beans包中聲明了一個PropertyEditor介面,用於提供對屬性的訪問和賦值。

PropertyEditor有個默認實現類PropertyEditorSupport,如果要自定義屬性轉換器,直接繼承該類,然後覆寫setAsText、getAsText、setValue、getValue方法。在類型轉換的時候如果值是字元串會調用setAsText來賦值value,其他情況下會調用setValue來賦值value,然後再調用getValue獲取改變後的value值完成類型轉換。相當於將PropertyEditor當做一個加工作坊,傳進去一個值,返回想要類型的值

注意 :PropertyEditor是線程不安全的,有狀態的,因此每次使用時都需要創建一個,不可重用;

首先先看下BeanWrapperImpl的繼承圖。還有一個跟BeanWrapperImpl平級的實現類DirectFieldAccessor,用於處理getter和setter的欄位訪問

從圖可以看出,BeanWrapperImpl實現了3個頂級介面,提供了對象屬性訪問、類型轉換器的注冊和查找、類型轉換功能

從繼承圖可以看到PropertyEditorRegistrySupport實現了PropertyEditorRegistry介面,該類默認實現了類型轉換器的注冊和查找功能

在PropertyEditorRegistrySupport聲明了多個存儲結構用於存儲不同的類型轉換器來源。這里需要注意的是PropertyEditor是存在在Map中的,目標類型作為key,所以對於一個類型只能注冊一個PropertyEditor,後面注冊的會覆蓋前面注冊的

同時還看到一個conversionService變數,spring提供了另一種類型轉換介面Converter,通過conversionService調用對應的Converter進行類型轉換,在PropertyEditorRegistrySupport同樣可以注冊進來conversionService,用於使用Converter進行類型轉換。conversionService的詳細使用會在下篇文章中講到。

接下來來看PropertyEditor的注冊過程。

在上面提到過PropertyEditor存在在一個Map中,key是目標類型,那麼這個參數propertyPath是幹嘛的呢?這是為了給屬性名指定專屬的類型的轉換器。因為一個目標類型只能有一個PropertyEditor的限制。但是有時候確實某個屬性的類型轉換比較特殊,這個時候就可以給這個屬性名單獨注冊一個類型轉換器,不會覆蓋其他的哦。在類型轉換的時候,會先根據屬性名去customEditorsForPath中找可以用的PropertyEditor。

來看PropertyEditor的查找流程

首先根據屬性名從customEditorsForPath查找特定的類型轉換器

根據類型查找定義的類型轉換器,如果沒有對應的,則查找父類對應的類型轉換器

PropertyEditorRegistrySupport 中默認添加了一些轉換器,當調用 getDefaultEditor(requiredType)的時候會進行注冊

TypeConverter介面定義了將一個值轉換為目標類型的值的功能。在繼承圖中可以看出TypeConverterSupport對類型轉換提供了默認實現。

TypeConverterSupport將類型轉換功能委託給typeConverterDelegate實現

TypeConverterDelegate實現了類型轉換的功能,創建TypeConverterDelegate的時候需要一個propertyEditorRegistry對象,用於查找匹配的類型轉換器

首先通過propertyEditorRegistry查找自定義的類型轉換器PropertyEditor和ConversionService

當該類型沒有注冊自定義的PropertyEditor,並且存在conversionService的時候,使用conversionService進行類型轉換。如果conversionService中沒有配置對應的converter,那麼繼續嘗試使用默認注冊的PropertyEditor。

如果目標類型存在自定義PropertyEditor 或者 目標類型和值類型不一樣則需要進行類型轉換(當沒有找到ProperEditor的時候會嘗試查找默認注冊的PropertyEditor)

類型轉換,主要分三種情況處理,值類型不是字元串,值類型是字元串數組,值類型是字元串

最後,需要對轉換後的值和目標類型進行判斷,是否符合要求,如果不符合繼續處理。這里主要處理了集合類型還有Number類型、基本類型轉String、枚舉類型。

在attemptToConvertStringToEnum方法中自動根據枚舉的名稱轉換為枚舉的對象

PropertyAccessor介面定義了屬性訪問的功能。通過實現setPropertyValue 和 getPropertyValue方法實現對象屬性的賦值和訪問。

AbstractPropertyAccessor實現了PropertyAccessor介面,不過沒有對setPropertyValue和getPropertyValue進行實現,而是單獨提供了一個 setPropertyValues的方法, 用於批量設置屬性值,同時可以通過 ignoreUnknown和ignoreInvalid參數忽略未知的屬性

在類中實現了setPropertyValue和getPropertyValue功能。

由於存在嵌套屬性賦值的情況,對於嵌套的處理,其實只需要對嵌套的最底層進行類型轉換,上層每一層就創建默認的值然後set到再上層對象的屬性中。在這里spring使用了遞歸解決這個問題,創建每一層屬性的對象值,使用BeanWrapper包裝該對象,那麼又是一個BeanWrapperImpl的賦值流程。

來看這個遞歸的內部實現,在getNestedPropertyAccessor完成對外層屬性的初始化和將該值賦值到所依賴的對象中。然後使用BeanWrapper封裝屬性對象,後續走屬性對象的賦值流程

創建屬性對象,並將該對象set到宿主對象。因為對象是指針引用的,所以在這步已經完成對宿主對象的屬性賦值,接下來的流程只要對屬性對象中的依賴屬性進行賦值。

使用遞歸解決了嵌套賦值的問題,那麼接下來就是針對最底層BeanWrapperImpl的屬性賦值流程

在processLocalProperty方法中,首先通過子類獲取屬性處理器,通過PropertyHandler對屬性賦值。在賦值之前再次判斷屬性是否已經進行了類型轉換,如果沒有再次調用類型轉換器進行轉換,如果已經完成類型轉換,使用ConvertedValue

對屬性的訪問和設置spring進行了更小粒度的封裝。提供了 PropertyHandler抽象類。為什麼在這里進行抽象,看PropertyHandler的兩個實現,可以看到一個是BeanPropertyHandler,一個是FieldPropertyHandler,不難想像,屬性一種是由getter和setter方法進行訪問,一種是沒有getter和setter直接反射欄位進行的。

如果要對map進行控制,我們可以再提供一個專門處理map的實現了類handler

BeanWrapperImpl提供了BeanPropertyHandler,將setter和getter傳入

BeanPropertyHandler提供對setter和getter的訪問

的另一個實現類DirectFieldAccessor,專門用於給欄位賦值,不依賴setter和getter,那麼這個是怎麼實現的,看源碼發現是DirectFieldAccessor中提供了一個PropertyHandler的實現類,通過Field的反射實現了setValue和getValue

G. spring jpa之實體屬性類型轉換器AttributeConverter,自定義Converter,通用Converter

在JPA註解中,有個@Convert註解,其中需要傳入一個Class作為convert參數,該class需要實現AttributeConverter<X,Y>介面。下面來看看AttributeConverter介面的作用。

實體屬性類型轉換器。主要使用場景:

簡單化操作,用持久化enum枚舉來進行一個操作。 AttributeConverter<X,Y> 該介面中需要實現兩個方法:

場景:用戶登錄時,記錄用戶的動作:登錄,登出,注冊,重置密碼。

數據的加密和日期的轉換也就類似的操作了。

但是這樣的每個枚舉可能都要去寫這樣的轉換類,可能會存在重復的操作。可以嘗試寫個通用的枚舉轉換類。

H. [Spring boot源碼解析] 2 啟動流程分析

在了解 Spring Boot 的啟動流程的時候,我們先看一下一個Spring Boot 應用是如何啟動的,如下是一個簡單的 SpringBoot 程序,非常的簡潔,他是如何做到的呢,我們接下來就將一步步分解。

我們追蹤 SpringApplication.run() 方法,其實最終它主要的邏輯是新建一個 SpringApplication ,然後調用他的 run 方法,如下:

我們先來看一下創建 SpringApplication 的方法:

在將Main class 設置 primarySources 後,調用了 WebApplicationType.deceFromClasspath() 方法,該方法是為了檢查當前的應用類型,並設置給 webApplicationType 。 我們進入 deceFromClasspath 方法 :

這里主要是通過類載入器判斷是否存在 REACTIVE 相關的類信息,假如有就代表是一個 REACTIVE 的應用,假如不是就檢查是否存在 Servelt 和 ,假如都沒有,就代表應用為非 WEB 類應用,返回 NONE ,默認返回 SERVLET 類型,我們這期以我們目前最常使用的 SERVLET 類型進行講解,所以我們在應用中引入了 spring-boot-starter-web 作為依賴:

他會包含 Spring-mvc 的依賴,所以就包含了內嵌 tomcat 中的 Servlet 和 Spring-web 中的 ,因此返回了 SERVLET 類型。

回到剛才創建 SpringApplication 的構建方法中,我們設置完成應用類型後,就尋找所有的 Initializer 實現類,並設置到 SpringApplication 的 Initializers 中,這里先說一下 getSpringFactoriesInstances 方法,我們知道在我們使用 SpringBoot 程序中,會經常在 META-INF/spring.factories 目錄下看到一些 EnableAutoConfiguration ,來出發 config 類注入到容器中,我們知道一般一個 config 類要想被 SpringBoot 掃描到需要使用 @CompnentScan 來掃描具體的路徑,對於 jar 包來說這無疑是非常不方便的,所以 SpringBoot 提供了另外一種方式來實現,就是使用 spring.factories ,比如下面這個,我們從 Springboot-test 中找到的例子,這里先定義了一個ExampleAutoConfiguration,並加上了 Configuration 註解:

然後在 spring.factories 中定義如下:

那這種方式是怎麼實現的你,這就要回到我們剛才的方法 getSpringFactoriesInstances :

我們先來看一下傳入參數,這里需要注意的是 args,這個是初始化對應 type 的時候傳入的構造參數,我們先看一下 SpringFactoriesLoader#loadFactoryNames 方法:

首先是會先檢查緩存,假如緩存中存在就直接返回,假如沒有就調用 classLoader#getResources 方法,傳入 META-INF/spring.factories ,即獲取所有 jar 包下的對應文件,並封裝成 UrlResource ,然後使用 PropertiesLoaderUtils 將這些信息讀取成一個對一對的 properties,我們觀察一下 spring.factories 都是按 properties 格式排版的,假如有多個就用逗號隔開,所以這里還需要將逗號的多個類分隔開來,並加到 result 中,由於 result 是一個 LinkedMultiValueMap 類型,支持多個值插入,最後放回緩存中。最終完成載入 META-INF/spring.factories 中的配置,如下:

我們可以看一下我們找到的 initializer 有多少個:

在獲取到所有的 Initializer 後接下來是調用 方法進行初始化。

這里的 names 就是我們上面通過類載入器載入到的類名,到這里會先通過反射生成 class 對象,然後判斷該類是否繼承與 ApplicationContextInitializer ,最後通過發射的方式獲取這個類的構造方法,並調用該構造方法,傳入已經定義好的構造參數,對於 ApplicationContextInitializer 是無參的構造方法,然後初始化實例並返回,回到原來的方法,這里會先對所有的 ApplicationContextInitializer 進行排序,調用 #sort(instances) 方法,這里就是根據 @Order 中的順序進行排序。

接下來是設置 ApplicationListener ,我們跟進去就會發現這里和上面獲取 ApplicationContextInitializer 的方法如出一轍,最終會載入到如圖的 15 個 listener (這里除了 外,其他都是 SpringBoot 內部的 Listener):

在完成 SpringApplication 對象的初始化後,我們進入了他的 run 方法,這個方法幾乎涵蓋了 SpringBoot 生命周期的所有內容,主要分為九個步驟,每一個步驟這里都使用註解進行標識:

主要步驟如下:
第一步:獲取 SpringApplicationRunListener, 然後調用他的 staring 方法啟動監聽器。
第二步:根據 SpringApplicationRunListeners以及參數來准備環境。
第三步:創建 Spring 容器。
第四步:Spring 容器的前置處理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的後置處理器。
第七步:通知所有 listener 結束啟動。
第八步:調用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我們接下來一一講解這些內容。

我們首先看一下第一步,獲取 SpringApplicationRunListener :

這里和上面獲取 initializer 和 listener 的方式基本一致,都是通過 getSpringFactoriesInstances , 最終只找到一個類就是: org.springframework.boot.context.event.EventPublishingRunListener ,然後調用其構造方法並傳入產生 args , 和 SpringApplication 本身:

我們先看一下構造函數,首先將我們獲取到的 ApplicationListener 集合添加到initialMulticaster 中, 最後都是通過操作 來進行廣播,我,他繼承於 ,我們先看一下他的 addApplicationListener 方法:

我們可以看出,最後是放到了 applicationListenters 這個容器中。他是 defaultRetriever 的成員屬性, defaultRetriever 則是 的私有類,我們簡單看一下這個類:

我們只需要看一下這里的 getApplicationListeners 方法,它主要是到 beanFactory 中檢查是否存在多的 ApplicationListener 和舊的 applicationListeners 組合並返回,接著執行 listener 的 start 方法,最後也是調用了 的 multicastEvent 查找支持對應的 ApplicationEvent 類型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,這里除了會:

篩選的方法如下,都是調用了對應類型的 supportsEventType 方法 :

如圖,我們可以看到對 org.springframework.boot.context.event.ApplicationStartingEvent 感興趣的有5個 Listener

環境准備的具體方法如下:

首先是調用 getOrCreateEnvironment 方法來創建 environment ,我們跟進去可以發現這里是根據我們上面設置的環境的類型來進行選擇的,當前環境會創建 StandardServletEnvironment

我們先來看一下 StandardServletEnvironment 的類繼承關系圖,我們可以看出他是繼承了 AbstractEnvironment :

他會調用子類的 customizePropertySources 方法實現,首先是 StandardServletEnvironment 的實現如下,他會添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三種 properties,當前調試環境沒有配置 jndi properties,所以這里不會添加。接著調用父類的 customizePropertySources 方法,即調用到了 StandardEnvironment 。

我們看一下 StandardEnvironment#customizePropertySources 方法,與上面的三個 properties 創建不同,這兩個是會進行賦值的,包括系統環境變數放入 systemEnvironment 中,jvm 先關參數放到 systemProperties 中:

這里會添加 systemEnvironment 和 systemProperties 這兩個 properties,最終拿到的 properties 數量如下 4個:

在創建完成 Environment 後,接下來就到了調用 configureEnvironment 方法:

我們先看一下 configurePropertySources 方法,這里主要分兩部分,首先是查詢當前是否存在 defaultProperties ,假如不為空就會添加到 environment 的 propertySources 中,接著是處理命令行參數,將命令行參數作為一個 CompositePropertySource 或則 添加到 environment 的 propertySources 裡面,

接著調用 ConfigurationPropertySources#attach 方法,他會先去 environment 中查找 configurationProperties , 假如尋找到了,先檢查 configurationProperties 和當前 environment 是否匹配,假如不相等,就先去除,最後添加 configurationProperties 並將其 sources 屬性設置進去。

回到我們的 prepareEnvironment 邏輯,下一步是通知觀察者,發送 事件,調用的是 SpringApplicationRunListeners#environmentPrepared 方法,最終回到了 #multicastEvent 方法,我們通過 debug 找到最後對這個時間感興趣的 Listener 如下:

其主要邏輯如下:

這個方法最後載入了 PropertySourceLoader , 這里主要是兩種,一個是用於 Properties 的,一個是用於 YAML 的如下:

其中 apply 方法主要是載入 defaultProperties ,假如已經存在,就進行替換,而替換的目標 PropertySource 就是 load 這里最後的一個 consumer 函數載入出來的,這里列一下主要做的事情:
1、載入系統中設置的所有的 Profile 。
2、遍歷所有的 Profile ,假如是默認的 Profile , 就將這個 Profile 加到 environment 中。
3、調用load 方法,載入配置,我們深入看一下這個方法:

他會先調用 getSearchLocations 方法,載入所有的需要載入的路徑,最終有如下路徑:

其核心方法是遍歷所有的 propertySourceLoader ,也就是上面載入到兩種 propertySourceLoader ,最紅 loadForFileExtension 方法,載入配置文件,這里就不展開分析了,說一下主要的作用,因為每個 propertySourceLoader 都有自己可以載入的擴展名,默認擴展名有如下四個 properties, xml, yml, yaml,所以最終拿到文件名字,然後通過 - 拼接所有的真實的名字,然後加上路徑一起載入。

接下來,我們分析 BackgroundPreinitializer ,這個方法在接收 ApplicationPrepareEnvironment 事件的時候真正調用了這份方法:

1、 ConversionServiceInitializer 主要負責將包括 日期,貨幣等一些默認的轉換器注冊到 formatterRegistry 中。
2、 ValidationInitializer 創建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用於生成 xml 轉換器的。
接著回到我們將的主體方法, prepareEnvironment 在調用完成 listeners.environmentPrepared(environment) 方法後,調用 bindToSpringApplication(environment) 方法,將 environment 綁定到 SpirngApplication 中。
接著將 enviroment 轉化為 StandardEnvironment 對象。
最後將 configurationProperties 加入到 enviroment 中, configurationProperties 其實是將 environment 中其他的 PropertySource 重新包裝了一遍,並放到 environment 中,這里主要的作用是方便 進行解析。

它主要是檢查是否存在 spring.beaninfo.ignore 配置,這個配置的主要作用是設置 javaBean 的內省模式,所謂內省就是應用程序在 Runtime 的時候能檢查對象類型的能力,通常也可以稱作運行時類型檢查,區別於反射主要用於修改類屬性,內省主要用戶獲取類屬性。那麼我們什麼時候會使用到內省呢,java主要是通過內省工具 Introspector 來完成內省的工作,內省的結果通過一個 Beaninfo 對象返回,主要包括類的一些相關信息,而在 Spring中,主要是 BeanUtils#Properties 會使用到,Spring 對內省機制還進行了改進,有三種內省模式,如下圖中紅色框框的內容,默認情況下是使用 USE_ALL_BEANINFO。假如設置為true,就是改成第三中 IGNORE_ALL_BEANINFO

首先是檢查 Application的類型,然後獲取對應的 ApplicationContext 類,我們這里是獲取到了 org.springframework.boot.web.servlet.context. 接著調用 BeanUtils.instantiateClass(contextClass); 方法進行對象的初始化。

最終其實是調用了 的默認構造方法。我們看一下這個方法做了什麼事情。這里只是簡單的設置了一個 reader 和一個 scanner,作用於 bean 的掃描工作。

我們再來看一下這個類的繼承關系

這里獲取 ExceptionReporter 的方式主要還是和之前 Listener 的方式一致,通過 getSpringFactoriesInstances 來獲取所有的 SpringBootExceptionReporter 。

其主要方法執行如下:

閱讀全文

與spring源碼轉換器相關的資料

熱點內容
前端程序員幹嘛 瀏覽:562
女權主義pdf 瀏覽:458
阿里雲伺服器低價續費 瀏覽:337
python監控日誌腳本 瀏覽:134
雲伺服器實例是什麼意思 瀏覽:710
小尋app是做什麼的 瀏覽:649
c語言中編譯和運行 瀏覽:1000
畫流圖找循環編譯原理 瀏覽:153
oppo手機西瓜視頻的文件夾 瀏覽:867
騎手一般用哪個app 瀏覽:610
程序員老闆用什麼手機 瀏覽:848
比心app頭像不通過為什麼 瀏覽:105
加密幣市值前十走勢 瀏覽:190
單片機學習推薦課程 瀏覽:473
對數ln的運演算法則圖片 瀏覽:735
仿微博app源碼 瀏覽:781
怎麼取消調用app 瀏覽:545
程序員去哪裡求助 瀏覽:834
伺服器里的埠是什麼 瀏覽:975
aspnetjavaphp 瀏覽:399