溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

BeanDefinition繼承與容器拓展點是什么

發(fā)布時間:2021-12-02 17:17:28 來源:億速云 閱讀:137 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細講解有關(guān)BeanDefinition繼承與容器拓展點是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

1.7 Bean Definition繼承

bean定義包含一些配置信息,包括構(gòu)造函數(shù)參數(shù)、屬性值、和容器特定信息,例如初始化方法、靜態(tài)工廠方法名等等。子bean定義繼承父bean定義配置數(shù)據(jù)。子bean定義能夠覆蓋一些值或者增加其他需要。使用父bean和子bean定義能夠保存一些類型。實際上,這是一種模版模式。

如果你編程式使用ApplicationContext接口,子bean定義通過ChildBeanDefinition類描述。大多數(shù)用戶不在這個級別使用(備注:應(yīng)用開發(fā)人員一般不會接觸)。相反,它們在例如ClassPathXmlApplicationContext之類的類中聲明性地配置bean定義。當(dāng)你使用基于XML配置元數(shù)據(jù),你可以通過使用parent屬性指示一個子bean的定義,指定parent bean作為這個屬性的值。下面例子顯示怎樣做:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>
<!--parent指定繼承父類-->
<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

如果沒有指定,子bean定義將使用父定義中的bean類,但也可以覆蓋它。在后面這種情況下,子bean類必須兼容父bean定義(也就是說,必須接受父bean的屬性值)。

bean定義繼承作用域、構(gòu)造參數(shù)值、屬性值、和覆蓋父類方法,并可以添加新值。任何作用域、初始化方法、銷毀方法或static工廠方法設(shè)置都會覆蓋對應(yīng)的父bean設(shè)置。

其余設(shè)置始終從子bean定義中獲?。阂蕾?、自動裝配、依賴檢查、單例和懶加載。

前面的例子通過使用abstract屬性顯示標記父bean定義作用一個抽象,如果父bean定義沒有指定類,顯示地標記父bean定義為abstract是需要的,像下面例子展示:

<bean id="inheritedTestBeanWithoutClass" abstract="true">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBeanWithoutClass" init-method="initialize">
    <property name="name" value="override"/>
    <!-- age will inherit the value of 1 from the parent bean definition-->
</bean>

bean不能被實例化,因為它是不完整的,并且它被顯示的標記為abstract。當(dāng)一個定義是abstract,它僅僅作為一個bean定義的模版且父bean定義為子bean定義服務(wù)。嘗試自己使用這樣的抽象父bean,通過將其引用為另一個beanref屬性或使用父bean ID進行顯式的getBean()調(diào)用將返回錯誤。類似地,容器的內(nèi)部preInstantiateSingletons()方法將忽略定義為抽象的bean定義。

默認情況下,ApplicationContext會預(yù)先實例化所有單例。因此,重要的是(至少對于單例bean),如果有一個(父)bean定義僅打算用作模板,并且此定義指定了一個類,則必須確保將abstract屬性設(shè)置為true ,否則應(yīng)用程序上下文將實際上(試圖)預(yù)先實例化抽象Bean。

參考實例:com.liyong.ioccontainer.starter.XmlBeanDefinitionInheritanceContainer

1.8 容器拓展點

典型地,應(yīng)用程序開發(fā)者不需要實現(xiàn)ApplicationContext類。相反,Spring IoC容器通過插件實現(xiàn)指定的集成接口去擴展。下面的章節(jié)描述這些接口的集成。

1.8.1 通過使用BeanPostProcessor自定義bean

BeanPostProcessor接口定義回調(diào)方法,你可以實現(xiàn)這個接口提供你自己的(或者覆蓋容器的默認設(shè)置)初始化邏輯、依賴解析邏輯等等。如果你想實現(xiàn)一些自定義邏輯,在Spring容器完成實例化、配置、初始化bean之后,你可以插入一個或多個自定義BeanPostProcessor實現(xiàn)。

你可以配置多個BeanPostProcessor實例并且你可以通過設(shè)置order屬性來控制這些BeanPostProcessor實例的執(zhí)行順序。僅僅BeanPostProcessor實現(xiàn)Ordered接口是可以設(shè)置這個屬性。如果自己實現(xiàn)BeanPostProcessor,你應(yīng)該考慮實現(xiàn)Ordered接口。更多詳情,查看 BeanPostProcessorOrdered接口。參見有關(guān)以編程方式注冊BeanPostProcessor實例的說明。

BeanPostProcessor接口在bean(對象)實例上操作。也就是說,Spring IoC容器實例化一個bean實例,然后BeanPostProcessor實例執(zhí)行它們的工作。(備注:在Spring容器初始化bean過程中執(zhí)行相關(guān)的回調(diào)操作)

BeanPostProcessor實例是按容器劃分作用域。僅僅在使用繼承容器才有意義。如果在一個容器中定義BeanPostProcessor,僅僅在那個容器中的bean被后置處理。換句話說,定義在一個容器中的bean不會被在其他容器定義的BeanPostProcessor所處理,即使兩個容器都屬于同一層次結(jié)構(gòu)。

改變實際bean定義(也就是定義bean的藍圖),你需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor自定義配置元數(shù)據(jù)中所述。

org.springframework.beans.factory.config.BeanPostProcessor接口恰好地由兩個回調(diào)方法組成。當(dāng)一個類作為后置處理起被注冊到容器中時,對于每個被容器創(chuàng)建bean實例,后置處理器從容器初始化方法(例如:InitializingBean.afterPropertiesSet()或者任何被聲明init方法)被調(diào)用之前,并且任何bean初始化回調(diào)之后獲得回調(diào)。后置處理器能夠處理bean實例任何操作,包括忽略所有的回調(diào)。Bean后處理器通常檢查回調(diào)接口,或者可以用代理包裝Bean。Spring AOP基礎(chǔ)設(shè)施類中實現(xiàn)bean的后置處理去提供一個代理包裝邏輯。

ApplicationContext自動的檢查所有bean,這些bean在配置元數(shù)據(jù)中實現(xiàn)了BeanPostProcessor接口。ApplicationContext注冊這些bean作為后置處理器,以便以后在創(chuàng)建bean時可以調(diào)用它們。Bean后處理器可以與其他bean相同的方式部署在容器中。

注意,當(dāng)通過在類上使用@Bean工廠方法聲明BeanPostProcessor時,工廠方法返回類型應(yīng)該是實現(xiàn)類本身或只是實現(xiàn)org.springframework.beans.factory.config.BeanPostProcessor接口,清晰地表明該bean的后處理器性質(zhì)。否則,ApplicationContext無法在完全創(chuàng)建之前按類型自動檢測它。由于BeanPostProcessor需要及早實例化才能應(yīng)用于上下文中其他bean的初始化,因此這種早期類型檢測至關(guān)重要。

編程式地注冊BeanPostProcessor實例

推薦的去注冊BeanPostProcessor方式是通過ApplicationContext自動檢測(前面描述),可以使用addBeanPostProcessor方法以編程方式針對ConfigurableBeanFactory注冊它們。當(dāng)注冊之前你需要評估條件邏輯甚至需要跨層次結(jié)構(gòu)的上下文復(fù)制后置處理器時,這是非常有用的。注意,然而,BeanPostProcessor實例編程式的添加不遵循Ordered排序接口。在這里,注冊的順序決定了執(zhí)行的順序。<u>需要注意是編程式的注冊BeanPostProcessor實例總是在這些通過自動檢測的后置處理器之后被處理,而不管顯示的順序</u>。

BeanPostProcessor實例和AOP自定代理

實現(xiàn)BeanPostProcessor接口的類是特殊的,并且容器對它們的處理方式有所不同。在啟動時會實例化它們直接引用的所有BeanPostProcessor實例和Bean,這是ApplicationContext特殊啟動階段的一部分。接下來,以排序方式注冊所有BeanPostProcessor實例,并將其應(yīng)用于容器中的所有其他bean。因為AOP自動代理是作為BeanPostProcessor本身實現(xiàn)的,所以BeanPostProcessor實例或它們直接引用的bean都不適合進行自動代理,因此,沒有編織的方面。

對于任何這樣的bean,你應(yīng)該查看日志消息:

Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying).

如果你有通過使用自動裝配或@Resource注入beanBeanPostProcessor中(可能回退到自動裝配),當(dāng)搜索類型匹配的依賴項候選者時,Spring可能會訪問意外的Bean,因此使它們不適合進行自動代理或其他類型的Bean后處理。例如,如果你有一個用@Resource標注的依賴項,其中字段或Setter名稱不直接與bean的聲明名稱相對應(yīng),并且不使用name屬性,那么Spring將訪問其他bean以按類型匹配它們。

下面的例子展示了在ApplicationContext中怎樣去編寫、注冊和使用BeanPostProcessor接口。

例子:Hello World

第一個例子說明基礎(chǔ)的使用。這個例子展示一個自定義BeanPostProcessor實現(xiàn)并調(diào)用通過容器創(chuàng)建的每個beantoString()方法并且打印結(jié)果字符串到控制臺。

下面的清單展示了自定義BeanPostProcessor實現(xiàn)類定義:

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // simply return the instantiated bean as-is
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // we could potentially return any object reference here...
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
      //打印bean信息
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

下面beans元素使用InstantiationTracingBeanPostProcessor

<?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:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
    when the above bean (messenger) is instantiated, this custom
    BeanPostProcessor implementation will output the fact to the system console
    -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

注意怎樣定義InstantiationTracingBeanPostProcessor。它甚至沒有名字,并且它是一個bean,可以像其他bean一樣被依賴注入。(前面的配置還定義了一個由Groovy腳本支持的bean。Spring動態(tài)語言支持詳情在“動態(tài)語言支持”一章中。)

下面的Java應(yīng)用程序運行前面的代碼和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

前面的應(yīng)用輸出結(jié)果類似下面:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961

例子:RequiredAnnotationBeanPostProcessor

將回調(diào)接口或與自定義BeanPostProcessor實現(xiàn)結(jié)合使用是擴展Spring IoC容器常用方法。SpringRequiredAnnotationBeanPostProcessor是一個示例,它是Spring發(fā)行版附帶的BeanPostProcessor實現(xiàn),可以確保標有(任意)注解的bean上的JavaBean屬性實際上(配置為)依賴注入了一個值。說明:就是常用的依賴注入。

參考代碼:com.liyong.ioccontainer.starter.XmlBeanPostProcessorIocContainer

1.8.2 BeanFactoryPostProcessor自定義配置元數(shù)據(jù)

下一個拓展點是org.springframework.beans.factory.config.BeanFactoryPostProcessor。這個接口語義類似BeanPostProcessor,一個重要的不同點:BeanFactoryPostProcessor是操作在bean的配置元數(shù)據(jù)上。也就是說,Spring IoC容器允許除BeanFactoryPostProcessor實例外其他任何bean被BeanFactoryPostProcessor讀取配置元數(shù)據(jù)和改變它。

你可以配置多個BeanFactoryPostProcessor實例,并且你可以通過設(shè)置order屬性在這些BeanFactoryPostProcessor實例上來控制順序。然而,如果BeanFactoryPostProcessor實現(xiàn)Ordered接口才能設(shè)置這個屬性。如果你寫自己的BeanFactoryPostProcessor,你應(yīng)該考慮實現(xiàn)Ordered接口。更多關(guān)于BeanFactoryPostProcessorOrdered接口詳細信息。

如果想去改變實際bean實例(也就是,這個對象是從配置元數(shù)據(jù)被創(chuàng)建的),你需要使用BeanPostProcessor(前面描述通過使用BeanPostProcessor自定義bean)。從技術(shù)上講,可以在BeanFactoryPostProcessor中使用Bean實例(例如,通過使用BeanFactory.getBean()),這樣做導(dǎo)致過早的初始化bean,違反了標準容器生命周期。這會導(dǎo)致負面的影響,例如:繞過后置處理。

同樣,BeanFactoryPostProcessor實例是按容器劃分。(如果你使用分層的容器才會有意義) <u>如果你定義在容器中定義一個BeanFactoryPostProcessor,它僅適用于該容器中的bean定義。在一個容器中的bean定義不會被其他的容器中BeanFactoryPostProcessor后置處理,即使兩個容器在同一個層級</u>。

Bean工廠后處理器在ApplicationContext中聲明時會自動執(zhí)行,以便將更改應(yīng)用于定義容器的配置元數(shù)據(jù)。Spring包括一些預(yù)定義的工廠后置處理器,例如PropertyOverrideConfigurer

PropertySourcesPlaceholderConfigurer。你可以使用一個自定義的BeanFactoryPostProcessor-例如,注冊一個自定義屬性編輯器。

ApplicationContext自動地檢測任何部署到容器中并實現(xiàn)BeanFactoryPostProcessor接口的實例bean。使用這些bean作為bean工廠后置處理器,在適當(dāng)?shù)臅r間。你可以像其他ban一樣部署這些后置處理器。

BeanPostProcessors一樣,通常不希望配置BeanFactoryPostProcessors進行延遲初始。如果沒有其他bean引用Bean(Factory)PostProcessor,這個后置處理器不會被初始化。因此,標記它為延遲初始化將被忽略并且Bean(Factory)PostProcessor將被提前初始化即使你的<beans />聲明default-lazy-init屬性為true。

參考代碼:com.liyong.ioccontainer.starter.XmlBeanFactoryPostProcessorIocContainer

例如:類名替換PropertySourcesPlaceholderConfigurer

可以使用PropertySourcesPlaceholderConfigurer通過使用標準Java屬性格式將屬性值從bean定義外部化到一個單獨的文件中(意思是:bean配置數(shù)據(jù)可以配置到一個單獨文件中)。這樣做使部署應(yīng)用程序的人員可以自定義特定于環(huán)境的屬性,例如數(shù)據(jù)庫URL和密碼,而無需為修改容器的主要XML定義文件而復(fù)雜或冒風(fēng)險。

考慮下面基于XML配置元數(shù)據(jù)片段,DataSource使用占位符值定義:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

這個例子顯示從外部化Properties文件的屬性配置。在運行時,PropertySourcesPlaceholderConfigurer應(yīng)用元數(shù)據(jù)替換DataSource的屬性值。將要替換的值指定為$ {property-name}格式的占位符,該格式遵循Ant、log4jJSPEL樣式。

真實值來自于標準的Properties格式的其他文件:

jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root

因此,${jdbc.username}字符串在運行時被替換為sa值,并且同樣應(yīng)用到其他的占位符在properties文件中匹配這些key。PropertySourcesPlaceholderConfigurer檢查Bean定義的大多數(shù)屬性和屬性中的占位符。此外,你可以自定義占位符的前綴和后綴。

context命名空間在Spring2.5中被引入,你可以配置一個專用配置元素的占位符。在location屬性中你可以提供一個或多個location通過逗號分隔,類似下面例子:

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertySourcesPlaceholderConfigurer不僅僅在你指定的Properties中查找屬性。默認情況,如果在指定的屬性文件中沒有找到屬性,它會再次檢查Spring Environment屬性和常規(guī)的Java System屬性。

你可以使用PropertySourcesPlaceholderConfigurer去替代類名稱,有時候者非常有用,當(dāng)你在運行時必須選擇一個特定的實現(xiàn)類。下面例子展示怎樣去做:

<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer">
 <property name="locations">
     <value>classpath:com/something/strategy.properties</value>
 </property>
 <property name="properties">
     <value>custom.strategy.class=com.something.DefaultStrategy</value>
 </property>
</bean>

<bean id="serviceStrategy" class="${custom.strategy.class}"/>

如果在運行時無法將類解析為有效的類,則在即將創(chuàng)建bean時,即在非lazy-init bean的ApplicationContextpreInstantiateSingletons()階段,bean的解析將失敗。

參考代碼:com.liyong.ioccontainer.starter.XmlPropertySourcesPlaceholderConfigurerIocContainer

例子:PropertyOverrideConfigurer

另一個bean工廠后處理器PropertyOverrideConfigurer類似于PropertySourcesPlaceholderConfigurer,但與后者不同,原始定義可以具有bean屬性的默認值,也可以完全沒有值。如果覆蓋的屬性文件對于一些bean屬性沒有符合內(nèi)容,默認的上下文定義被使用。

注意:bean定義沒有意識到被覆蓋,因此從XML定義文件中不能立即看出覆蓋的配置正在被使用。由于存在覆蓋機制,在多個PropertyOverrideConfigurer實例情況下對應(yīng)相同bean屬性不同的值,最后一個將被使用。

屬性文件配置格式:

beanName.property=value

下面清單顯示格式化例子:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb

此示例文件可與容器定義一起使用,容器定義包含一個名為dataSource的bean,該bean具有driverurl屬性。

復(fù)合屬性名也是被支持的,只要路徑的每個組件(被覆蓋的最終屬性除外)都是非空的(可能是由構(gòu)造函數(shù)初始化的)。下面的例子,bean的tom屬性fredsammy屬性設(shè)置只123:

tom.fred.bob.sammy=123

指定的重寫值總是文本值。它們沒有被轉(zhuǎn)譯為bean引用。當(dāng)XML bean定義中的原始值指定bean引用時,這種約定也適用。

context命名空間在Spring2.5中被引入,可以使用專用配置元素配置屬性覆蓋,類似下面例子:

<context:property-override location="classpath:override.properties"/>

參考代碼:com.liyong.ioccontainer.starter.XmlPropertyOverrideConfigurerIocContainer

1.8.3 FactoryBean自定義初始化邏輯

可以為本身就是工廠的對象實現(xiàn)org.springframework.beans.factory.FactoryBean接口。

這個FactoryBean接口是Spring IoC容器的實例化邏輯可插拔點。如果你有一個復(fù)雜的初始化代碼在Java中比冗余XML更好的表達,你可以創(chuàng)建你自己的FactoryBean,在實現(xiàn)類中寫復(fù)雜的初始化邏輯,然后插入你的自定義FactoryBean到容器中。

FactoryBean接口提供三個方法:

  • Object getObject():返回這個工廠創(chuàng)建的一個實例。這個實例可能被共享,依賴于這個工廠返回的是單例或原型。

  • boolean isSingleton():如果FactoryBean返回一個單例返回true否則為false

  • Class getObjectType():返回由getObject()方法返回的對象類型;如果類型未知,則返回null

FactoryBean概念和接口在Spring框架中一些地方被使用到。Spring有超過50個FactoryBean接口的實現(xiàn)。

當(dāng)你需要向容器獲取一個實際的FactoryBean實例本身而不是由它產(chǎn)生的bean時請在調(diào)用ApplicationContext的getBean()方法時在該bean的ID前面加上一個符號。因此,對于id為myBean的給定FactoryBean,在容器上調(diào)用getBean(“myBean”)將返回FactoryBean的產(chǎn)生的bean,而調(diào)用getBean(“&myBean”)將返回FactoryBean實例本身。

關(guān)于BeanDefinition繼承與容器拓展點是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI