自拍偷在线精品自拍偷|国产无码一区二区久久|最新版天堂资源中文官网|国产精品第一页爽爽影院|国产精品一区二区av不卡|久久久波多野av一区无码|国产欧美日本亚洲精品一4区|亚洲精品天堂在线观看2020

當(dāng)前位置:首頁 > 網(wǎng)站建設(shè) > 正文內(nèi)容

html5app源代碼(html源代碼大全)

網(wǎng)站建設(shè)1年前 (2023-12-24)453

作 者 | 楊天逸(在田)

導(dǎo)語:本文就Spring配置項解析問題展開分析,這其中涉及到bean定義注冊表后置處理、bean工廠后置處理、工廠bean等Spring相關(guān)的概念。本文將以上述問題作為切入點,進行分析和展開介紹。

問題背景介紹

我們的項目中某次依賴了某個第三方包及其中的XML文件,相關(guān)代碼如下所示:XML文件中定義了Mybatis相關(guān)的bean,以及對自定義數(shù)據(jù)源myDataSource的引用。在@Configuration配置類中,我們引入了XML文件,并通過@Bean注解的方式聲明了數(shù)據(jù)源bean。

beanid= "thirdPartySqlSessionFactory"

class= "org.mybatis.spring.SqlSessionFactoryBean"

depends-on= "myDataSource"

propertyname= "dataSource"ref= "myDataSource"/

propertyname= "mapperLocations"value= "classpath:mybatis/third-party/*.xml"/

/ bean

beanid= "thirdPartyMapperScannerConfigurer"

class= "org.mybatis.spring.mapper.MapperScannerConfigurer"

depends-on= "thirdPartySqlSessionFactory"

展開全文

propertyname= "basePackage"value= "com.alibaba.thirdparty.dao"/

propertyname= "sqlSessionFactoryBeanName"value= "thirdPartySqlSessionFactory"/

/ bean

@Configuration

@EnableTransactionManagement(proxyTargetClass = true)

// 引入上述XML文件

@ImportResource( "classpath*:/mybatis-third-party-config.xml")

publicclassMyDataSourceConfiguration{

// 聲明自定義數(shù)據(jù)源

@Bean(name = "myDataSource")

publicDataSource createMyDataSource(Environment env) {

// 返回數(shù)據(jù)源實例,具體代碼略

}

}

項目啟動后,我們發(fā)現(xiàn)一個原有的通過XML定義的HSF(HSF全稱High-speed Service Framework,是阿里內(nèi)部主要使用的RPC服務(wù)框架)客戶端bean中的配置項無法被正常解析。由于這是一個與我們新引入的包無關(guān)的bean,大家都對問題產(chǎn)生的原因感到奇怪,也嘗試了各種不同的處理方式,然而都沒有效果。無奈之下,我們通過將整個XML文件改寫為Java注解聲明的形式,才最終解決了問題。相關(guān)代碼如下所示:

beanid= "myHsfClient"class= "com.taobao.hsf.app.spring.util.HSFSpringConsumerBean"init-method= "init"

propertyname= "interfaceName"

value com.taobao.custom.MyHsfClient / value

/ property

propertyname= "version"

value ${hsf.client.version} / value

/ property

/ bean

// 改寫后的Java注解聲明方式

@Configuration

publicclassMyHsfConfig{

@HSFConsumer(serviceVersion = " ${hsf.client.version}" )

privateMyHsfClient myHsfClient;

// 其余代碼省略

}

雖然問題得到了解決,但是大家仍舊對這其中的原因不明所以。筆者在事后通過本地調(diào)試的方式,找到了問題的原因。這其中涉及到bean定義注冊表后置處理、bean工廠后置處理、工廠bean等Spring相關(guān)的概念。本文將以上述問題作為切入點,進行分析和展開介紹。

html5app源代碼(html源代碼大全)

XML配置項解析

為了更好地解答上述問題產(chǎn)生的原因,我們先來看下Spring框架對bean使用的配置項的解析過程。我們知道,Spring會負(fù)責(zé)對我們在XML文件中聲明的bean的創(chuàng)建。不過,對其中的配置項解析,并不是在這個環(huán)節(jié)發(fā)生,而是在其前置環(huán)節(jié) —— bean工廠后置處理的過程中發(fā)生的。bean工廠(BeanFactory)是Spring的核心組件,除了負(fù)責(zé)初始化bean的實例,記錄單例外,它還維護了各個 bean的定義(BeanDefinition)。bean的定義中主要記錄了bean的類型、作用域(singleton/prototype)、屬性值、構(gòu)造函數(shù)參數(shù)值等信息。bean的實例化便是基于bean的定義進行的。而bean工廠的后置處理環(huán)節(jié),則可以在bean被創(chuàng)建之前,修改bean的定義,以達到影響最終生成的bean實例的效果。

對XML中配置項的解析工作,Spring是通過 PropertySourcesPlaceholderConfigurer這個bean工廠后置處理器(BeanFactoryPostProcessor)完成的。其核心代碼如下所示。總體思路比較簡單,即遍歷bean工廠中的bean定義,對于每個bean的定義,訪問其屬性值、構(gòu)造函數(shù)參數(shù)值等信息,解析其中的配置項占位符(placeholder)。這個環(huán)節(jié)完成之后,在bean工廠對bean進行初始化之前,bean定義中的配置項占位符就已經(jīng)被替換為實際的屬性值了。

// 處理屬性值

protectedvoiddoProcessProperties( ConfigurableListableBeanFactory beanFactoryToProcess,

StringValueResolver valueResolver ) {

BeanDefinitionVisitor visitor = newBeanDefinitionVisitor(valueResolver);

String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames;

// 遍歷bean工廠中的bean名稱集合

for(String curName : beanNames) {

// 跳過對自身的處理

if(!(curName. equals( this.beanName) beanFactoryToProcess. equals( this.beanFactory))) {

// 通過bean的名稱獲取bean的定義

BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);

try{

// 訪問bean的定義,解析并替換其中的配置項占位符

visitor.visitBeanDefinition(bd);

}

catch(Exception ex) {

thrownewBeanDefinitionStoreException(bd.getResourceDeion, curName, ex.getMessage, ex);

}

}

}

// 將配置項解析器注冊添加至bean工廠,供基于注解的配置項解析處理器使用(后文將詳細介紹)

// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.

beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);

// 其余代碼省略

}

了解了bean工廠后置處理環(huán)節(jié)后,讓我們再往前探究一步,看下bean定義本身是如何被加載到bean工廠中的(這將有助于我們理解文章開頭所提到的問題的產(chǎn)生原因)。bean定義主要是在 bean定義注冊表后置處理環(huán)節(jié)被加載到bean工廠中的。與我們前面提到的bean工廠后置處理環(huán)節(jié)類似,該環(huán)節(jié)也存在相應(yīng)的處理器(BeanDefinitionRegistryPostProcessor)完成相關(guān)工作。

其中典型的如 ConfigurationClassPostProcessor。以Spring Boot場景為例,簡單來說,該bean定義注冊表后置處理器會從包含了@SpringBootApplication注解的啟動引導(dǎo)類開始,根據(jù)其組合注解@ComponentScan,掃描被@Component,或者組合了@Component的注解(如@Configuration、@Service、@Repository等)標(biāo)注的類,將這些配置類(注1)的bean定義注冊至bean工廠。同時,處理器還會根據(jù)組合注解@EnableAutoConfiguration,獲取Spring Boot中的自動配置類。在這之后,ConfigurationClassPostProcessor會嘗試解析各個配置類中包含的@Bean、@ImportResource等注解,將對應(yīng)的bean定義也注冊到bean工廠中。

最后,對于配置項本身來說,Spring的環(huán)境抽象(Environment)會拉取并聚合JVM系統(tǒng)屬性、操作系統(tǒng)環(huán)境變量、應(yīng)用屬性配置文件等多個屬性源的數(shù)據(jù)(注2),以供bean工廠中的bean定義或者bean實例使用。如前面提到的PropertySourcesPlaceholderConfigurer處理器,便是從Spring環(huán)境中獲取bean定義中的配置項占位符所對應(yīng)的屬性值,并將其替換的。上文通過倒序的方式介紹了配置項解析的相關(guān)環(huán)節(jié),下面我們用順序表示的流程圖作結(jié),以便讀者更好地理解。

問題原因分析

現(xiàn)在,我們可以對文章開頭提到的問題作進一步分析了。仔細查看我們所引入的XML文件可以發(fā)現(xiàn),其中包含一個類型為 MapperScannerConfigurer的bean聲明。Spring借助該類完成對標(biāo)注有@Mapper注解的MyBatis映射接口的掃描。MapperScannerConfigurer實現(xiàn)了BeanDefinitionRegistryPostProcessor接口,是一個bean定義注冊表后置處理器。它對映射接口的掃描及其對應(yīng)的bean定義的注冊,便是在該環(huán)節(jié)進行的。

前面我們提到, ConfigurationClassPostProcessor這個bean定義注冊表后置處理器會掃描并加載@Configuration和@ImportResource注解相關(guān)的bean定義。我們所引入的XML文件中的bean的定義,便是通過這個動作被注冊到bean工廠中的(見上文MyDataSourceConfiguration配置類)。在ConfigurationClassPostProcessor完成其掃描及加載工作后,由于有新的bean定義被注冊,Spring會再次嘗試從bean工廠中找出并初始化其他的bean定義注冊表后置處理器,以觸發(fā)它們的處理動作。MapperScannerConfigurer便是在此時被實例化并觸發(fā)的。

觀察問題背景介紹章節(jié)中的相關(guān)代碼可以發(fā)現(xiàn),MapperScannerConfigurer的bean實例(thirdPartyMapperScannerConfigurer) 間接依賴了我們通過@Bean注解在配置類中聲明的數(shù)據(jù)源bean實例(myDataSource)。因此,在本文案例中,Spring在創(chuàng)建MapperScannerConfigurer實例時,會首先對數(shù)據(jù)源bean進行初始化。而對于通過@Bean注解聲明的bean,Spring是通過反射調(diào)用注解所在的工廠方法(factory method),完成bean的實例化的。我們的數(shù)據(jù)源myDataSource的實例化,便是通過反射調(diào)用其工廠方法createMyDataSource完成的。由于該方法包含了一個類型為Environment入?yún)?,Spring需要遍歷bean工廠中的bean定義,找到并創(chuàng)建匹配的bean,作為反射調(diào)用時的方法傳參。

而問題恰恰就出現(xiàn)在這里的 參數(shù)匹配環(huán)節(jié)。Spring在進行方法入?yún)⑵ヅ鋾r,會首先調(diào)用getBeanNamesForType方法,將符合參數(shù)類型的bean的名稱找出來,然后依據(jù)一定的策略(注3)將bean進行實例化,作為方法入?yún)⑹褂?。對于普通的bean來說,Spring只需要依據(jù)bean定義中包含的bean類型信息,與參數(shù)類型作匹配即可;而對于另一類較為特殊的工廠bean(FactoryBean)來說,其類型推斷方式就會更加復(fù)雜些。下文將會展開介紹工廠bean的概念和案例,對此不太熟悉的讀者,這里只需要了解,工廠bean的作用是負(fù)責(zé)產(chǎn)生某個我們最終實際需要使用的bean。因此,在進行參數(shù)匹配時,Spring關(guān)心的是這個最終產(chǎn)生的bean的類型,而不是工廠bean本身的類型。

在判斷工廠bean實際輸出的bean的類型時(注4),Spring首先會嘗試根據(jù)工廠bean定義中的某些元數(shù)據(jù)進行類型推斷;其次會嘗試對工廠bean進行一次簡單創(chuàng)建后,通過其getObjectType方法獲取目標(biāo)bean的類型。如果前兩種嘗試都失敗了,則會使用 兜底邏輯 —— 對工廠bean進行正式創(chuàng)建后,再通過getObjectType獲取類型信息。這里的「正式創(chuàng)建」,我們可以理解為Spring完成了工廠bean的實例化、屬性字段的賦值、單例信息的記錄等;而「簡單創(chuàng)建」僅僅指工廠bean的實例化,不包括后續(xù)的字段初始化等動作。

而我們在上文提到的myHsfClient,便是被聲明為了一個類型為HSFSpringConsumerBean的工廠bean。Spring在對createMyDataSource的方法入?yún)⑦M行類型匹配時,由于前述的前兩種類型推斷方式都沒有成功(其具體原因?qū)⒃诤笪墓Sbean小節(jié)中介紹),導(dǎo)致該工廠bean最終被「提前」正式創(chuàng)建了出來。讀者可能已經(jīng)發(fā)現(xiàn),此時Spring正處在 bean定義注冊表后置處理環(huán)節(jié)。而我們在XML配置項解析章節(jié)中提到的對bean定義中的配置項占位符的解析替換,則是在該環(huán)節(jié)之后的 bean工廠后置處理環(huán)節(jié)進行的 —— 這就是導(dǎo)致myHsfClient這個工廠bean中的配置項沒有被正常解析的原因。整體方法調(diào)用關(guān)系如下圖所示:

至此可能讀者會有疑問:難道我們的項目中之前沒有對@Mapper映射器接口的掃描動作嗎?答案是有掃描動作,不過是通過MapperScannerRegistrar這個bean定義注冊器觸發(fā)的。而由于其與我們通過XML所引入的MapperScannerConfigurer的一些細微區(qū)別,使得項目中原先不存在工廠bean被提前創(chuàng)建的問題。由于篇幅所限,這里不再對MapperScannerRegistrar作展開介紹。

知道了問題背后的原因后,尋找對應(yīng)的解法也就相對簡單了。對于文中案例,一方面,我們可以看到,由于thirdPartyMapperScannerConfigurer依賴了SqlSessionFactoryBean實例(這就是我們剛剛說的「細微區(qū)別」所在),導(dǎo)致其間接依賴了myDataSource。而考察源碼可以發(fā)現(xiàn),其實MapperScannerConfigurer只需要SqlSessionFactory的bean名稱(sqlSessionFactoryBeanName)作為輸入即可,因此我們可以把XML中相關(guān)的depends-on聲明去除。另一方面,由于createMyDataSource方法入?yún)⑹荢pring環(huán)境抽象,我們可以改由通過使配置類實現(xiàn)EnvironmentAware接口的方式,獲得應(yīng)用上下文中的Environment實例。這兩種方法都能解決我們的工廠bean被提前創(chuàng)建的問題。

在更一般化的場景中,如果在Spring啟動的早期階段,對某個bean的依賴注入無法避免,我們可以使相關(guān)的類實現(xiàn) ApplicationContextAware接口,嘗試通過應(yīng)用上下文(ApplicationContext)的getBean方法獲取我們想要的對象。不過需要注意的是,getBean方法存在兩類版本:根據(jù)bean名稱獲取實例,或是根據(jù)指定類型獲取實例;而如果我們選擇根據(jù)指定類型獲取實例,則仍舊會觸發(fā)上文提到的類型匹配機制,導(dǎo)致某些無法通過正常方式進行類型推斷的工廠bean被提前創(chuàng)建出來。最后,對于前文提到的,在使用注解形式改寫myHsfClient的bean聲明后,問題得到解決的原因,我們將在后文分析介紹。

一些引申擴展

經(jīng)過上文讓人感覺有些繞的分析,我們可以看到,文章開頭所提到的問題的本質(zhì)是,某些bean被Spring提前正式創(chuàng)建了出來,導(dǎo)致其bean聲明中的配置項占位符沒有來得及被解析和替換。這其中涉及到不少概念,諸如bean定義注冊表后置處理、bean工廠后置處理、工廠bean等。由于我們在日常開發(fā)中一般接觸得不多,讀者對它們的理解可能還比較模糊,下文將嘗試結(jié)合實際案例,進行一些引申和擴展介紹。

bean定義注冊表后置處理

我們在前文中已經(jīng)介紹了ConfigurationClassPostProcessor和MapperScannerConfigurer這兩個bean定義注冊表后置處理器。這類處理器的主要作用便是掃描并向bean工廠中注冊bean定義。其中, ConfigurationClassPostProcessor負(fù)責(zé)掃描配置類,處理其包含的注解,并將相關(guān)的bean定義注冊至bean工廠中。隨后,對于這些新增的bean定義,如果其中又包含了其他的bean定義注冊表后置處理器,Spring會將它們實例化,并觸發(fā)它們的處理動作(注5),繼續(xù)注冊可能被發(fā)現(xiàn)的新的bean定義……如此循環(huán)往復(fù),直到所有該類型的處理器都被觸發(fā),完成bean定義的注冊為止。如以下代碼所示:

publicstaticvoidinvokeBeanFactoryPostProcessors(

ConfigurableListableBeanFactory beanFactory, ListBeanFactoryPostProcessor beanFactoryPostProcessors ) {

// 部分代碼省略

// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.

boolean reiterate = true;

while(reiterate) {

reiterate = false;

// 從bean工廠中找出bean定義注冊表后置處理器

postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);

for(String ppName : postProcessorNames) {

// 如果當(dāng)前處理器尚未被觸發(fā)過

if(!processedBeans.contains(ppName)) {

// 初始化處理器,并加入到本次需要觸發(fā)的處理器集合中

currentRegistryProcessors. add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

// 標(biāo)記處理器為已被處理

processedBeans. add(ppName);

// 繼續(xù)循環(huán),因為當(dāng)前集合中的處理器被觸發(fā)后,可能會引入新的bean定義,其中可能包含新的bean定義注冊表后置處理器需要被觸發(fā)

reiterate = true;

}

}

sortPostProcessors(currentRegistryProcessors, beanFactory);

registryProcessors.addAll(currentRegistryProcessors);

// 觸發(fā)集合中的處理器的bean定義注冊表后置處理動作

// * 本文案例中,我們在第三方XML文件中引入的MapperScannerConfigurer,便是在此時被觸發(fā)的

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

currentRegistryProcessors.clear;

}

}

回到我們的問題案例, MapperScannerConfigurer便是在上述環(huán)節(jié)被創(chuàng)建出來并觸發(fā)的。這里,細心的讀者可能會有疑問:如果我們在使用XML聲明這個Mybatis的處理器時,對其中的某些屬性也使用了配置項占位符,那么Spring在創(chuàng)建它時,是否也會遇到同樣的解析問題?MapperScannerConfigurer的作者顯然是考慮到了這一點 —— 處理器被觸發(fā)后,支持首先嘗試對它的屬性字段進行配置項的解析和替換。其具體的實現(xiàn)方式,是構(gòu)造一個新的bean工廠,將自身的bean定義注冊其中,然后借助PropertySourcesPlaceholderConfigurer等處理器,對這個bean工廠執(zhí)行配置項的后置處理操作;最后,用bean定義中的被解析后的屬性值,替換自身實例中原有的屬性值。這在一定程度上相當(dāng)于模擬了Spring的bean工廠后置處理環(huán)節(jié)。其具體代碼如下:

/*

* BeanDefinitionRegistries are called early in application startup, before

* BeanFactoryPostProcessors. This means that PropertyResourceConfigurers will not have been

* loaded and any property substitution of this class' properties will fail. To avoid this, find

* any PropertyResourceConfigurers defined in the context and run them on this class' bean

* definition. Then update the values.

*/

// 上面這段英文注釋體現(xiàn)了作者的考慮,即文中描述的情況

private voidprocessPropertyPlaceHolders {

// 獲取配置項處理器實例,即PropertySourcesPlaceholderConfigurer處理器

Map String, PropertyResourceConfigurer prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer. class);

if(!prcs.isEmpty applicationContext instanceof ConfigurableApplicationContext) {

BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext)

.getBeanFactory.getBeanDefinition(beanName);

// 構(gòu)造一個新的bean工廠

DefaultListableBeanFactory factory= newDefaultListableBeanFactory;

// 將自身的bean定義注冊到這個bean工廠中

factory.registerBeanDefinition(beanName, mapperScannerBean);

// * 對這個bean工廠執(zhí)行配置項后置處理操作

for(PropertyResourceConfigurer prc : prcs.values) {

prc.postProcessBeanFactory( factory);

}

PropertyValues values = mapperScannerBean.getPropertyValues;

// 使用被解析處理過的值更新原有的值

this.basePackage = updatePropertyValue( "basePackage", values);

this.sqlSessionFactoryBeanName = updatePropertyValue( "sqlSessionFactoryBeanName", values);

this.sqlSessionTemplateBeanName = updatePropertyValue( "sqlSessionTemplateBeanName", values);

}

}

最后,值得一提的是,對于ConfigurationClassPostProcessor的bean定義本身,則是在Spring應(yīng)用上下文(ApplicationContext)初始化的過程中,通過硬編碼的形式被注冊到bean工廠中的(注6)。這里同時被注冊的還有諸如AutowiredAnnotationBeanPostProcessor等 bean后置處理器,我們將在后文對此作相應(yīng)介紹。

bean 工廠后置處理

當(dāng)bean定義注冊表后置處理環(huán)節(jié)完成后,基本上(注7)所有的bean定義都已經(jīng)被注冊至bean工廠中了。隨后,Spring會找出所有的bean工廠后置處理器,按照一定的順序?qū)嵗⒂|發(fā)它們的處理動作(優(yōu)先執(zhí)行實現(xiàn)了PriorityOrdered接口的,其次執(zhí)行實現(xiàn)了Ordered接口的,最后執(zhí)行沒有實現(xiàn)前兩個接口的)。這類處理器一般會遍歷bean工廠中所有的bean定義,執(zhí)行一些特定的操作。我們在前文提到的PropertySourcesPlaceholderConfigurer這個bean工廠后置處理器,便是在此時被觸發(fā)的。而在這個所有bean定義都已經(jīng)準(zhǔn)備就緒的階段,統(tǒng)一進行配置項占位符的解析和替換,其時機總體上也是恰當(dāng)合理的。

其他的比較典型的Spring內(nèi)置bean工廠后置處理器還有 ConfigurationBeanFactoryMetaData。這個處理器執(zhí)行的動作比較簡單:它會遍歷bean工廠中的bean定義,記錄其中的工廠方法等元數(shù)據(jù)信息。其核心代碼如下所示。而這份記錄的作用,我們將在后文說明。

public classConfigurationBeanFactoryMetaDataimplementsBeanFactoryPostProcessor{

private Map String, MetaData beans = newHashMap String, MetaData;

public voidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

this.beanFactory = beanFactory;

// 遍歷bean工廠中的bean定義

for( Stringname : beanFactory.getBeanDefinitionNames) {

BeanDefinition definition = beanFactory.getBeanDefinition(name);

Stringmethod = definition.getFactoryMethodName;

Stringbean = definition.getFactoryBeanName;

// 如果存在工廠方法元數(shù)據(jù)(如通過@Bean注解聲明的bean),則將相關(guān)信息記錄下來

if(method != null bean != null) {

this.beans.put(name, newMetaData(bean, method));

}

}

}

}

最后,我們來看另一個和我們的XML配置項解析問題相關(guān)的處理器。在問題背景介紹章節(jié)中我們提到,當(dāng)把myHsfClient的bean聲明改寫為由@HSFConsumer注解修飾的形式后,問題得到了解決。而這背后則是 HsfConsumerPostProcessor這個bean工廠后置處理器在發(fā)揮作用:對于每一個bean定義,如果它的類屬性字段上存在@HSFConsumer注解,處理器會動態(tài)生成并注冊一個類型為HSFSpringConsumerBean的工廠bean定義。雖然由于PropertySourcesPlaceholderConfigurer處理器實現(xiàn)了PriorityOrdered接口,在此之前已經(jīng)被優(yōu)先執(zhí)行過了,但是HsfConsumerPostProcessor考慮到了這一點 —— 在生成工廠bean定義的過程中,會主動嘗試解析相關(guān)屬性的配置項占位符,因此規(guī)避了我們在使用XML方式進行工廠bean聲明時遇到的問題。

工廠bean

前面我們提到,HSFSpringConsumerBean是一個工廠bean。不僅如此,我們詳細討論的Mybatis的MapperScannerConfigurer處理器,對于其基于@Mapper注解掃描到的映射接口,也會將其bean定義改寫為 MapperFactoryBean這個工廠bean類型。此外,在Spring中,用于創(chuàng)建Mybatis的SqlSession對象的SqlSessionFactory,也是由一個名為 SqlSessionFactoryBean的工廠bean生成的。那么,什么是工廠bean,它的作用又是什么呢?

工廠bean,即 FactoryBean,其基于工廠模式,創(chuàng)建我們最終需要的bean實例。根據(jù)Spring文檔中的介紹(注8),如果某個bean的初始化邏輯較為復(fù)雜,不適合使用XML的方式表達,那么我們可以通過使用工廠bean,以Java語言的方式完成目標(biāo)bean的初始化。工廠bean的概念早在Spring 0.9版本(注9)就已經(jīng)被引入,在Spring框架中的使用是比較普遍的,至今為止僅其自帶的實現(xiàn)就有50多個。下面我們就文中的案例展開介紹。

// MyBatis配置文件路徑。配置文件包含數(shù)據(jù)源、映射器等信息

Stringresource = "org/mybatis/example/mybatis-config.xml";

// 創(chuàng)建配置文件輸入流

InputStream inputStream = Resources.getResourceAsStream(resource);

// 創(chuàng)建SqlSessionFactory實例

SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder.build(inputStream);

// 創(chuàng)建SqlSession實例

try(SqlSession session = sqlSessionFactory.openSession) {

// 獲取BlogMapper映射器

BlogMapper mapper = session.getMapper(BlogMapper. class);

// 執(zhí)行查詢語句

Blog blog = mapper.selectBlog( 101);

}

在介紹Mybatis與Spring整合時使用的兩個工廠bean之前,我們先來看下相關(guān)功能單純基于Mybatis本身實現(xiàn)時的代碼。代碼片段摘自Mybatis官網(wǎng),如上所示??梢钥吹?,其中SqlSessionFactory實例是由SqlSessionFactoryBuilder創(chuàng)建的;而用于執(zhí)行查詢語句的映射器實例,則是由SqlSession實例的getMapper方法創(chuàng)建的。與之相對的,如果閱讀源碼可以發(fā)現(xiàn),在Mybatis-Spring中,用于創(chuàng)建SqlSessionFactory實例的SqlSessionFactoryBean和映射器實例的MapperFactoryBean這兩個工廠bean,在一定程度上可以看作是對上述代碼封裝和擴展。

bean id= "myInputStream"class= "org.apache.ibatis.io.Resources"

factory-method= "getResourceAsStream"

constructor-argvalue= "org/mybatis/example/mybatis-config.xml"/

/ bean

bean id= "mySqlSessionFactoryBuilder"class= "org.apache.ibatis.session.SqlSessionFactoryBuilder"/

beanid= "mySqlSessionFactory"class= "org.apache.ibatis.session.SqlSessionFactory"

factory-bean= "mySqlSessionFactoryBuilder"

factory-method= "build"

constructor-argref= "myInputStream"/

/ bean

可以看到,雖然借助如上所示的factory-bean和factory-method標(biāo)簽屬性,我們也能通過XML完成對SqlSessionFactory的聲明,但這種通過XML刻畫bean初始化過程的方式,與我們在問題背景介紹章節(jié)看到的基于工廠bean的聲明方式相比,不免顯得有些繁瑣了。不過,隨著Spring 3.0帶來的基于@Configuration的Java注解配置特性,工廠bean在這方面的優(yōu)勢也變得不再那么明顯了。

publicclassMapperFactoryBean T extendsSqlSessionDaoSupportimplementsFactoryBean T {

// 映射器接口類型

privateClassT mapperInterface;

// 通過該方法獲取我們實際需要的映射器實例

@Override

publicT getObjectthrowsException {

returngetSqlSession.getMapper( this.mapperInterface);

}

// 獲取實際的bean的類型,即映射器接口類型

@Override

publicClassT getObjectType{

returnthis.mapperInterface;

}

}

不過,當(dāng)我們考察如上MapperFactoryBean的源碼時,會發(fā)現(xiàn)它的bean初始化邏輯很簡單,與單純基于MyBatis的代碼實現(xiàn)如出一轍。其中僅有的不同是,這里getMapper方法的映射器類型入?yún)?,使用的是工廠bean中的mapperInterface屬性。前面我們提到,MapperScannerConfigurer在掃描被@Mapper注解標(biāo)注的映射器接口時,會為每個接口生成一個對應(yīng)的bean定義,并將bean定義的類型屬性改寫為工廠bean類型。而對于bean定義中mapperInterface屬性的設(shè)置,也是在此時完成的(屬性的值即為映射器接口的全限定名)。隨后,在bean的實例化環(huán)節(jié),Spring便可以基于這些bean定義,為每個映射器接口生成一個對應(yīng)的工廠bean,以此服務(wù)于我們開發(fā)中常用的映射器實例依賴注入場景。對此,如果通過Java注解配置或是XML聲明的方式實現(xiàn),則會顯得有些大費周章 —— 對于每一個Mybatis映射器接口,我們都需要作一次對應(yīng)的聲明;而如果一個項目中包含數(shù)十個映射器接口(這個量級在中大型項目中應(yīng)屬常見),則需要做數(shù)十次大同小異的聲明。

對于HSFSpringConsumerBean這個工廠bean來說,其作用也是類似。這類bean注入方式的共性是:基于注解(或接口)掃描以及一些相關(guān)的配置信息,為每個被標(biāo)注的接口生成一個對應(yīng)的工廠bean;而當(dāng)工廠bean通過getObject方法輸出我們最終需要的bean時,往往是基于配置信息為接口生成一個動態(tài)代理,供實際使用。這種做法常見于Spring與其他框架集成的場景。就我們文中分析的例子而言,在數(shù)據(jù)庫持久化領(lǐng)域,除了Mybatis外,Hibernate借助JpaRepositoryFactoryBean這個工廠bean生成其Repository接口的實例;在遠程調(diào)用領(lǐng)域,除了HSF外,Spring Cloud中的Feign通過FeignClientFactoryBean為標(biāo)注有@FeignClient注解的客戶端接口生成動態(tài)代理。由于篇幅所限,這里僅以MyBatis為例,展示其類結(jié)構(gòu)關(guān)系(見下圖)。對于其他的案例,我們不再一一展開分析,感興趣的讀者可以閱讀相關(guān)源碼作進一步了解。

回到我們文章中探討的配置項解析問題,可以看到,雖然工廠bean能為Spring與其他框架整合提供很多便利,但如果使用不慎,則可能導(dǎo)致一些隱蔽的問題。其實,在2015年,MyBatis的MapperFactoryBean也遇到了類似的與類型推斷相關(guān)的問題(詳見github - mybatis-spring issue #58及pull request #59),而社區(qū)對此的解決方式是:利用Spring對bean進行實例化時,會首先嘗試匹配有參構(gòu)造函數(shù)的特性,在MapperFactoryBean中新增一個以映射器類型為入?yún)⒌臉?gòu)造函數(shù);并在處理工廠bean定義的階段,將映射器類型作為構(gòu)造函數(shù)參數(shù),放入bean定義中(如下圖所示)。如此,在前文提到的「簡單創(chuàng)建」后,Spring便可以通過調(diào)用getObjectType方法獲取到當(dāng)前MapperFactoryBean實例所代表的映射器接口類型了。

最后,回到本次問題的關(guān)鍵點之一:HSFSpringConsumerBean。在使用XML聲明的方式時,雖然我們在工廠bean的interfaceName字段指定了客戶端接口類型,但Spring在嘗試對其進行「簡單創(chuàng)建」以做類型推斷時,并不會為實例中的屬性字段賦值。這導(dǎo)致我們無法通過調(diào)用該實例的getObjectType方法得到它所代表的客戶端接口類型,并最終導(dǎo)致該工廠bean被「正式創(chuàng)建」了出來。雖然通過@HSFConsumer注解聲明的形式,我們得以規(guī)避了配置項解析問題,但HSF作者可以考慮參考MapperFactoryBean的方式,增加一個以客戶端接口類型為入?yún)⒌臉?gòu)造函數(shù),來更好地兼容基于XML的聲明方式。

基于注解的配置項解析

上文主要圍繞基于XML聲明的配置項解析進行了分析探討,其實,自Spring引入基于Java注解的bean聲明能力以來,我們使用得更多的是基于注解的配置項解析特性。而對此特性的支持主要是通過Spring的bean后置處理器(BeanPostProcessor)完成的。絕大部分bean的后置處理是在bean的創(chuàng)建環(huán)節(jié)被觸發(fā)的:bean工廠首先對bean進行實例化,然后使用bean后置處理器對它們進行相應(yīng)的處理操作。下面我們進行簡單的介紹。

前面我們提到,Spring會以硬編碼的形式將AutowiredAnnotationBeanPostProcessor這個bean后置處理器注冊到bean工廠中。從字面上看,這個處理器是負(fù)責(zé)@Autowired注解的,其實,@Value注解也在它的處理范圍之內(nèi)。處理器會在bean實例化后的屬性賦值步驟(注10)被觸發(fā),對@Value注解中的配置項占位符進行解析,并將屬性值賦給被注解標(biāo)注的字段。而其使用的配置項解析器,其中之一就是通過PropertySourcesPlaceholderConfigurer這個bean工廠后置處理器添加的(詳見XML配置項解析章節(jié))。

另一個我們常見的配置項相關(guān)的注解是@ConfigurationProperties。該注解由ConfigurationPropertiesBindingPostProcessor這個bean后置處理器處理,處理動作在bean實例化后的初始化步驟(注11)被觸發(fā)。除了我們熟知的作用于類的使用方式外,@ConfigurationProperties還可以作用于被@Bean注解標(biāo)注的方法 —— 這主要是針對我們無法直接將注解加在第三方外部類上的情況。而這里對于方法級別的注解解析,處理器便是借助我們之前提到的ConfigurationBeanFactoryMetaData的工廠方法記錄完成的(詳見bean工廠后置處理小節(jié))。具體代碼如下所示:

@Override

publicObject postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

ConfigurationProperties annotation= AnnotationUtils.findAnnotation(bean.getClass,

ConfigurationProperties. class);

if( annotation!= null) {

postProcessBeforeInitialization(bean, beanName, annotation);

}

// 處理方法級別的@ConfigurationProperties注解

// 這里的this.beans即為ConfigurationBeanFactoryMetaData實例

annotation= this.beans.findFactoryAnnotation(beanName, ConfigurationProperties. class);

if( annotation!= null) {

postProcessBeforeInitialization(bean, beanName, annotation);

}

returnbean;

}

思考與總結(jié)

我們可以看到,隨著Spring從基于XML的bean聲明到基于Java注解的bean聲明能力的演化,對配置項的解析方式也在發(fā)生著變化。其中牽涉到bean工廠后置處理、bean后置處理等環(huán)節(jié),而它們彼此之間又存在一定的關(guān)聯(lián)。同時,如果某些bean(如工廠bean)由于某些原因,在Spring啟動的早期階段(如bean定義注冊表后置處理環(huán)節(jié))被提前創(chuàng)建了出來,則可能導(dǎo)致其中的配置項解析失敗。對此,我們一方面可以嘗試尋找規(guī)避手段,另一方面也可以從該bean本身的設(shè)計探究原因。

Spring后置處理器

處理器類型

說明

ConfigurationClassPostProcessor

bean定義注冊表后置處理器

在Spring Boot中,從包含了@SpringBootApplication注解的引導(dǎo)類開始,掃描并注冊bean定義至bean工廠。在本文案例中,MapperScannerConfigurer的bean定義便是在此時被注冊的。

MapperScannerConfigurer

bean定義注冊表后置處理器

掃描@Mapper注解標(biāo)注的映射器接口,生成并注冊對應(yīng)的MapperFactoryBean工廠bean定義。支持使用PropertySourcesPlaceholderConfigurer等處理器對自身的屬性字段進行配置項解析。

PropertySourcesPlaceholderConfigurer

bean工廠后置處理器

遍歷bean定義,解析其中的配置項占位符。

ConfigurationBeanFactoryMetaData

bean工廠后置處理器

遍歷bean定義,記錄工廠方法等信息。

HsfConsumerPostProcessor

bean工廠后置處理器

遍歷bean定義,對于被@HsfConsumer注解標(biāo)注的屬性字段,生成并注冊對應(yīng)的HSFSpringConsumerBean工廠bean定義。

AutowiredAnnotationBeanPostProcessor

bean后置處理器

解析@Value注解中的配置項占位符。解析器之一由PropertySourcesPlaceholderConfigurer提供。

ConfigurationPropertiesBindingPostProcessor

bean后置處理器

解析@ConfigurationProperties注解中的配置項。對@Bean方法級別的注解解析借助ConfigurationBeanFactoryMetaData中的bean工廠方法記錄完成。

為了方便讀者理解,以上表格整理了文中提到的各類Spring后置處理器,以及它們彼此的關(guān)聯(lián)??梢钥吹?,Spring框架在給我們提供了很多開發(fā)便利的同時,其整體的設(shè)計還是較為復(fù)雜的。在日常開發(fā)中,我們可能時不時會遇到一些「疑難雜癥」,而此時對框架的深入理解能幫助我們高效地解決問題。此外,善用對Spring代碼的調(diào)試,也能幫助我們在紛繁的思路或線索中定位到問題原因。最后,由于寫作時間倉促,且Spring不同版本間可能存在一定的行為差異,文中如有錯漏之處還請讀者包涵指正。

注釋:

1.除了被@Configuration注解標(biāo)注的類外,被@Component等注解標(biāo)注的類也被Spring視為配置類,不過是輕量級(lite)配置類,參見《Spring Core Technologies》1.12章節(jié) - Java-based Container Configuration。

2.參見《Spring實戰(zhàn)》6.1.1小節(jié) - 理解Spring的環(huán)境抽象。

3.對于匹配到多個bean的情況,會優(yōu)先取包含@Primary注解或者優(yōu)先級高的bean,如果無法判斷,則會拋出NoUniqueBeanDefinitionException異常;對于沒有匹配到bean的情況,拋出NoSuchBeanDefinitionException異常。

4.具體代碼詳見AbstractAutowireCapableBeanFactory#getTypeForFactoryBean方法。

5.即調(diào)用BeanDefinitionRegistryPostProcessor接口中定義的postProcessBeanDefinitionRegistry方法。

6.具體代碼詳見AnnotationConfigUtils#registerAnnotationConfigProcessors方法。

7.某些bean工廠后置處理器也會向bean工廠中添加新的bean定義,比如我們后文將討論的HsfConsumerPostProcessor處理器。

8.參見《Spring Core Technologies》1.8.3小節(jié) - Customizing Instantiation Logic with a FactoryBean:If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.

9.在FactoryBean的代碼注釋中,我們可以看到,該類是在2003年3月份被創(chuàng)建的。而根據(jù)《History of Spring Framework and Spring Boot》一文,Spring 0.9的發(fā)布時間為2003年6月。

10.具體代碼詳見AbstractAutowireCapableBeanFactory#populateBean方法。

11.具體代碼詳見AbstractAutowireCapableBeanFactory#initializeBean方法。

參考資料:

1.《Spring Core Technologies》:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html

4.《History of Spring Framework and Spring Boot》:https://www.quickprogrammingtips.com/spring-boot/history-of-spring-framework-and-spring-boot.html

6.mybatis - getting started:https://mybatis.org/mybatis-3/getting-started.html

7.github - mybatis-spring issue #58:https://github.com/mybatis/spring/issues/58

8.github - mybatis-spring pull request #59:https://github.com/mybatis/spring/pull/59

掃描二維碼推送至手機訪問。

版權(quán)聲明:本文由飛速云SEO網(wǎng)絡(luò)優(yōu)化推廣發(fā)布,如需轉(zhuǎn)載請注明出處。

本文鏈接:http://www.thonggone.com/post/73235.html

標(biāo)簽: html5app源代碼

“html5app源代碼(html源代碼大全)” 的相關(guān)文章

工作證制作網(wǎng)站(工作證網(wǎng)上制作)

工作證制作網(wǎng)站(工作證網(wǎng)上制作)

本篇文章給大家談?wù)劰ぷ髯C制作網(wǎng)站,以及工作證網(wǎng)上制作對應(yīng)的知識點,希望對各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、用什么軟件能制作工作證 2、電子版工作證怎么做 3、新聞網(wǎng)站制作的新聞工作證合法嗎? 4、工作證尺寸多大 用什么軟件能制作工作證 用word、ps軟件都可以制...

廣州網(wǎng)站優(yōu)化推廣(廣州網(wǎng)站優(yōu)化推廣外包)

廣州網(wǎng)站優(yōu)化推廣(廣州網(wǎng)站優(yōu)化推廣外包)

本篇文章給大家談?wù)剰V州網(wǎng)站優(yōu)化推廣,以及廣州網(wǎng)站優(yōu)化推廣外包對應(yīng)的知識點,希望對各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、網(wǎng)站百度seo優(yōu)化廣州營銷公司哪家靠譜? 2、廣州網(wǎng)站SEO優(yōu)化推廣要做些什么? 3、廣州seo排名優(yōu)化要怎樣做? 4、廣州優(yōu)化推廣百度網(wǎng)站seo排名找...

打印機標(biāo)簽?zāi)0逶O(shè)置系統(tǒng)(打印標(biāo)簽?zāi)0逶趺丛O(shè)置)

打印機標(biāo)簽?zāi)0逶O(shè)置系統(tǒng)(打印標(biāo)簽?zāi)0逶趺丛O(shè)置)

今天給各位分享打印機標(biāo)簽?zāi)0逶O(shè)置系統(tǒng)的知識,其中也會對打印標(biāo)簽?zāi)0逶趺丛O(shè)置進行解釋,如果能碰巧解決你現(xiàn)在面臨的問題,別忘了關(guān)注本站,現(xiàn)在開始吧!本文目錄一覽: 1、xprinter打印機怎么設(shè)置模板 2、芯華熱敏打印機怎么設(shè)置打印模板 3、標(biāo)簽打印機怎么設(shè)置標(biāo)簽?zāi)0澹?4、佳博條碼標(biāo)簽打...

海報模板素材免費(海報模板素材免費軟件)

海報模板素材免費(海報模板素材免費軟件)

本篇文章給大家談?wù)労竽0逅夭拿赓M,以及海報模板素材免費軟件對應(yīng)的知識點,希望對各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、那里有免費的海報模板素材? 2、哪里有免費的招聘海報模板下載? 3、做海報,素材有什么好的免費網(wǎng)站呢。? 4、海報設(shè)計模板上哪可以免費設(shè)計? 那里有免...

英語感謝信模板老師(感謝老師的信英語版)

英語感謝信模板老師(感謝老師的信英語版)

本篇文章給大家談?wù)動⒄Z感謝信模板老師,以及感謝老師的信英語版對應(yīng)的知識點,希望對各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、給老師的英文感謝信 2、感謝信英文老師 3、給老師的感謝信英文 給老師的英文感謝信 隨著感恩文化的傳播,需要使用感謝信的場合越來越多,寫感謝信的時候要注...

源代碼設(shè)計筆記app下載(安卓記事本app源碼)

源代碼設(shè)計筆記app下載(安卓記事本app源碼)

本篇文章給大家談?wù)勗创a設(shè)計筆記app下載,以及安卓記事本app源碼對應(yīng)的知識點,希望對各位有所幫助,不要忘了收藏本站喔。 本文目錄一覽: 1、手機永久刪除的照片怎么找回? 2、微信圖片已被清理怎么恢復(fù)? 3、恢復(fù)了微信聊天記錄在刪了能恢復(fù)嗎微信聊天記錄恢復(fù)了在刪還能找回來嗎? 手機永久刪...