Spring Framework:BeanDefinition

IOC 容器:BeanDefinition 篇

相關(guān)文檔:

  • 參考文檔
  1. Spring 官方文檔
  2. 《Spring 技術(shù)內(nèi)幕:深入解析 Spring 架構(gòu)與設(shè)計(jì)原理》

注: 筆者經(jīng)常喜歡引用一些官方文檔以及其他文獻(xiàn)中的內(nèi)容跨琳,一來舅列,這些內(nèi)容確實(shí)存在著其自身的價(jià)值與意義窃页。二來啤覆,也是為了使得自己的文章盡量言出有據(jù),不瞎叨叨宅楞。但由于個(gè)人能力以及理解問題针姿,錯(cuò)誤仍然無法保證完全避免。如有問題厌衙,歡迎及時(shí)指正距淫。

涉及問題

  1. 什么是配置元數(shù)據(jù)(ConfigurationMetadata)?
  2. 什么是 BeanDefinition 婶希?
  3. BeanDefinition 與配置元數(shù)據(jù)的關(guān)系榕暇?包含了哪些配置元數(shù)據(jù)?
  4. 配置元數(shù)據(jù)有哪些表現(xiàn)形式饲趋?以及相應(yīng)的載入方式拐揭。

配置元信息(ConfigurationMetadata)

  1. 什么是配置元數(shù)據(jù)撤蟆?

官方文檔:

A Spring IoC container manages one or more beans. These beans are created with the configuration metadata that you supply to the container (for example, in the form of XML <bean/> definitions).

This configuration metadata represents how you, as an application developer, tell the Spring container to instantiate, configure, and assemble the objects in your application.

由此可知奕塑,配置元數(shù)據(jù)是指我們提供給 Spring 容器的一些配置信息,其中包含了 Bean 的定義信息家肯,通過這些配置信息龄砰,我們可以告訴 Spring 容器如何去實(shí)例化、配置以及組裝 Bean讨衣。

官方文檔:

Within the container itself, these bean definitions are represented as BeanDefinition objects.

在容器內(nèi)部换棚,這些 Bean 的定義信息經(jīng)過封裝后表現(xiàn)為一個(gè)或多個(gè) BeanDefinition 對(duì)象。spring 就是通過解析這些 BeanDefinition 實(shí)例來生成所定義的 Bean 實(shí)例反镇。

Bean 的定義類(BeanDefinition)

  1. BeanDefinition 設(shè)計(jì)意圖:

Java Doc 注釋:

A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.

This is just a minimal interface: The main intention is to allow a {@link BeanFactoryPostProcessor} to introspect and modify property values and other bean metadata.

BeanDefinitionspring 設(shè)計(jì)的用來描述一個(gè) Bean 實(shí)例的接口固蚤。包含了許多關(guān)于 Bean 實(shí)例的信息。并且 spring 還設(shè)計(jì)了一個(gè) BeanFactoryPostProcessor 容器后置處理器接口歹茶,可以在容器初始化階段對(duì) BeanDefinition 元數(shù)據(jù)或?qū)傩灾颠M(jìn)行修改夕玩。

  1. BeanDefinition 繼承關(guān)系:
    image.png

    BeanDefinitionspring 定義的一個(gè)用來描述 bean 實(shí)例的接口你弦。它繼承了 AttributeAccessorBeanMetadataElement 接口。
  • AttributeAccessor 要求 BeanDefinition 需要具備屬性存儲(chǔ)的能力燎孟。此處的屬性并非指對(duì)象的 Field禽作,而是近似于一個(gè)標(biāo)簽的作用,例如揩页,ConfigurationClass 對(duì)應(yīng)的 BeanDefinition 會(huì)在容器啟動(dòng)過程中標(biāo)記一個(gè) org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass 的屬性旷偿,來標(biāo)記為是配置類。
  • BeanMetadataElement 允許 BeanDefinition 配置一個(gè)類型為 Object 的配置源爆侣。
  1. BeanDefinition 接口中聲明的方法:
/**
 * Set the name of the parent definition of this bean definition, if any.
 */
void setParentName(@Nullable String parentName);

/**
 * Return the name of the parent definition of this bean definition, if any.
 */
@Nullable
String getParentName();

說明 BeanDefinition 具備父子層級(jí)關(guān)系萍程,子 BeanDefinition 可以復(fù)用父 BeanDefinition 中配置的元數(shù)據(jù)信息,從而減少不必要的重復(fù)配置兔仰。

/**
     * Specify the bean class name of this bean definition.
     * <p>The class name can be modified during bean factory post-processing,
     * typically replacing the original class name with a parsed variant of it.
     * @see #setParentName
     * @see #setFactoryBeanName
     * @see #setFactoryMethodName
     */
    void setBeanClassName(@Nullable String beanClassName);

    @Nullable
    String getBeanClassName();

指定 BeanDefinition 對(duì)應(yīng)的 Bean 實(shí)例化時(shí)所對(duì)應(yīng)的 class 類尘喝。

/**
     * Override the target scope of this bean, specifying a new scope name.
     * @see #SCOPE_SINGLETON
     * @see #SCOPE_PROTOTYPE
     */
    void setScope(@Nullable String scope);

    @Nullable
    String getScope();

scope 為范圍的意思,這里我們通痴悖可以理解為作用域朽褪。不同作用域的 Bean 獲取方式的內(nèi)部實(shí)現(xiàn)策略可能會(huì)存在差異。 spring 提供了 singletonprototype 兩種常用的作用域无虚,分別對(duì)應(yīng)設(shè)計(jì)模式中的單例模式和原型模式缔赠。

/**
     * Set whether this bean should be lazily initialized.
     * <p>If {@code false}, the bean will get instantiated on startup by bean
     * factories that perform eager initialization of singletons.
     */
    void setLazyInit(boolean lazyInit);

    boolean isLazyInit();

設(shè)置 Bean 是否延遲初始化友题,如果設(shè)置為 true 嗤堰,在容器對(duì)單例對(duì)象進(jìn)行預(yù)先初始化時(shí),會(huì)跳過該 BeanDefinition 所對(duì)應(yīng) Bean 的實(shí)例化度宦,而是在向容器第一次獲取該 Bean 的時(shí)候才進(jìn)行實(shí)例化踢匣。但如果該 Bean 是別的非延遲加載單例 Bean 的依賴對(duì)象時(shí),則在容器對(duì)單例對(duì)象進(jìn)行預(yù)先初始化是戈抄,也會(huì)被實(shí)例化离唬。

/**
     * Set the names of the beans that this bean depends on being initialized.
     * The bean factory will guarantee that these beans get initialized first.
     */
    void setDependsOn(@Nullable String... dependsOn);

    @Nullable
    String[] getDependsOn();

設(shè)置該 Bean 實(shí)例所依賴的其他 Beanspring 容器會(huì)先去初始化所依賴的其他 Bean划鸽。

/**
     * Set whether this bean is a candidate for getting autowired into some other bean.
     * <p>Note that this flag is designed to only affect type-based autowiring.
     * It does not affect explicit references by name, which will get resolved even
     * if the specified bean is not marked as an autowire candidate. As a consequence,
     * autowiring by name will nevertheless inject a bean if the name matches.
     */
    void setAutowireCandidate(boolean autowireCandidate);

    boolean isAutowireCandidate();

設(shè)置該 Bean 是否為依賴注入候選者(把當(dāng)前 Bean 注入到其他 Bean 中)输莺。該屬性僅影響基于類型的依賴注入。如果是通過 beanName 來進(jìn)行依賴注入裸诽,則該屬性不生效嫂用。

/**
     * Set whether this bean is a primary autowire candidate.
     * <p>If this value is {@code true} for exactly one bean among multiple
     * matching candidates, it will serve as a tie-breaker.
     */
    void setPrimary(boolean primary);

    boolean isPrimary();

設(shè)置該 Bean 是否為優(yōu)先候選者,該屬性同樣是僅影響基于類型的依賴注入丈冬,當(dāng)存在多個(gè)候選者時(shí)嘱函,會(huì)優(yōu)先選用 primarytrue 的候選者(只能有一個(gè))。否則系統(tǒng)將會(huì)因?yàn)闊o法選定候選者而拋出異常埂蕊。

/**
     * Specify the factory bean to use, if any.
     * This the name of the bean to call the specified factory method on.
     * @see #setFactoryMethodName
     */
    void setFactoryBeanName(@Nullable String factoryBeanName);

    @Nullable
    String getFactoryBeanName();

    /**
     * Specify a factory method, if any. This method will be invoked with
     * constructor arguments, or with no arguments if none are specified.
     * The method will be invoked on the specified factory bean, if any,
     * or otherwise as a static method on the local bean class.
     * @see #setFactoryBeanName
     * @see #setBeanClassName
     */
    void setFactoryMethodName(@Nullable String factoryMethodName);

    @Nullable
    String getFactoryMethodName();

setFactoryBeanNamesetFactoryMethodName 對(duì)應(yīng)設(shè)計(jì)模式中的工廠模式往弓。 setFactoryBeanName(String factoryBeanName) 指定生產(chǎn) Bean 的工廠實(shí)例橄浓,setFactoryMethodName 指定對(duì)應(yīng)的工廠方法名稱。

/**
     * Return the constructor argument values for this bean.
     * <p>The returned instance can be modified during bean factory post-processing.
     * @return the ConstructorArgumentValues object (never {@code null})
     */
    ConstructorArgumentValues getConstructorArgumentValues();

    default boolean hasConstructorArgumentValues() {
        return !getConstructorArgumentValues().isEmpty();
    }

獲取構(gòu)造參數(shù)值亮航,對(duì)應(yīng)類實(shí)例化時(shí)調(diào)用構(gòu)造函數(shù)的參數(shù)值荸实。如果是通過工廠方法來生成實(shí)例的話,則對(duì)應(yīng)工廠方法的參數(shù)值缴淋。

/**
     * Return the property values to be applied to a new instance of the bean.
     * <p>The returned instance can be modified during bean factory post-processing.
     * @return the MutablePropertyValues object (never {@code null})
     */
    MutablePropertyValues getPropertyValues();

    default boolean hasPropertyValues() {
        return !getPropertyValues().isEmpty();
    }

獲取所描述Bean 實(shí)例的屬性值准给,Mutable 為可修改的意思,獲取 MutablePropertyValues 對(duì)象后可以對(duì)里面的 PropertyValue 進(jìn)行編輯修改重抖。

    void setInitMethodName(@Nullable String initMethodName);

    @Nullable
    String getInitMethodName();

    void setDestroyMethodName(@Nullable String destroyMethodName);

    @Nullable
    String getDestroyMethodName();

以上方法分別為設(shè)置 Bean 實(shí)例的初始化方法露氮,和銷毀方法。

    void setRole(int role);

    int getRole();

設(shè)置 Bean 的角色钟沛,該方法沒有什么功能上的意義畔规。僅用來對(duì) Bean 做一個(gè)功能上的標(biāo)識(shí)。

    void setDescription(@Nullable String description);

    @Nullable
    String getDescription();

設(shè)置類的描述信息恨统。

    /**
     * Return a resolvable type for this bean definition,
     * based on the bean class or other specific metadata.
     * <p>This is typically fully resolved on a runtime-merged bean definition
     * but not necessarily on a configuration-time definition instance.
     * @return the resolvable type (potentially {@link ResolvableType#NONE})
     * @since 5.2
     * @see ConfigurableBeanFactory#getMergedBeanDefinition
     */
    ResolvableType getResolvableType();

獲取該 BeanDefinition 所描述 BeanResolvableType叁扫。ResolvableTypespring 設(shè)計(jì)的一個(gè)支持獲取泛型信息的接口,方便泛型操作畜埋。

  boolean isSingleton();

    boolean isPrototype();

    /**
     * Return whether this bean is "abstract", that is, not meant to be instantiated.
     */
    boolean isAbstract();

是否單例莫绣、是否原型、是否抽象類悠鞍。

/**
     * Return a description of the resource that this bean definition
     * came from (for the purpose of showing context in case of errors).
     */
    @Nullable
    String getResourceDescription();

返回該 Bean 所來自資源的描述对室。

  @Nullable
    BeanDefinition getOriginatingBeanDefinition();

獲取原始的 BeanDefinition 信息,如果存在的話咖祭。

  1. BeanDefinition 包含了哪些配置元數(shù)據(jù)信息掩宜?

在了解了 BeanDefinition 接口后,我們則可以簡(jiǎn)單地總結(jié)一個(gè) BeanDefinition 中都包含了哪些 Bean 定義信息么翰。不過牺汤,Spring 官方文檔已經(jīng)對(duì)此做了總結(jié),這里再做一個(gè)簡(jiǎn)單的闡述:

配置項(xiàng) 作用
Class 實(shí)例化 Bean 所對(duì)應(yīng)的類信息
Name Bean 的名稱
Constructor arguments Bean 的構(gòu)造參數(shù)
Properties Bean 的屬性值
Scope Bean 的作用域
Autowiring mode Bean 的自動(dòng)裝配模式硬鞍, 默認(rèn)為 NONE
Lazy initialization mode Bean 的延遲初始化模式
Initialization method Bean 的初始化方法
Destruction method Bean 的銷毀方法

BeanDefinition 的具體實(shí)現(xiàn)

image.png

BeanDefinition 的具體實(shí)現(xiàn)類有許多慧瘤,這里僅挑選以下幾個(gè)做出簡(jiǎn)略說明:

  • RootBeanDefinition
  • ChildBeanDefinition
  • GenericBeanDefinition
  • AnnotatedBeanDefinition

上文提到了 BeanDefinition 是可以支持父子層級(jí)關(guān)系的。RootBeanDefinitionChildBeanDefinition 這兩個(gè)實(shí)現(xiàn)類固该,則直接從名字上表明了這一問題。Root 代表根部的意思糖儡,則 RootBeanDefinition 代表了最上層的父級(jí) BeanDefinition 伐坏,它不能設(shè)置 parentName,否則會(huì)拋出異常握联。而 ChildBeanDefinition 則代表了子 BeanDefinition 桦沉,可以指定父級(jí) BeanDefinition每瞒, 并復(fù)用 parentBeanDefinition 的配置元數(shù)據(jù)信息。

GenericBeanDefinition 是一個(gè)相對(duì)比較靈活的實(shí)現(xiàn)類纯露,相比于以上兩種去掉了一些層級(jí)關(guān)系上的約束剿骨。可以隨意指定 parentName 埠褪。如果我們使用 Java 編碼的形式來生成 BeanDefinition浓利,通常可以使用此類钞速。

AnnotatedBeanDefinition 相對(duì)于其他的 BeanDefinition 實(shí)現(xiàn)類贷掖,提供了一些對(duì)類上注解以及方法注解的支持。

以上內(nèi)容比較簡(jiǎn)單渴语,可以自行熟悉了解苹威。也可以參考下面提供一些簡(jiǎn)單代碼,對(duì) BeanDefinition 有一個(gè)基本的理解驾凶。

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

GenericBeanDefinition userBeanDefinition = new GenericBeanDefinition();
userBeanDefinition.setBeanClass(User.class);
MutablePropertyValues tomPropertyValues = new MutablePropertyValues();
tomPropertyValues.addPropertyValue("id", 1);
tomPropertyValues.addPropertyValue("name", "tom");
tomPropertyValues.addPropertyValue("age", 18);
userBeanDefinition.setPropertyValues(tomPropertyValues);
beanFactory.registerBeanDefinition("tom", userBeanDefinition);

GenericBeanDefinition idGeneratorBeanDefinition = new GenericBeanDefinition();
idGeneratorBeanDefinition.setBeanClass(UserIdGenerator.class);
beanFactory.registerBeanDefinition("userIdGenerator", idGeneratorBeanDefinition);

GenericBeanDefinition userRepositoryBeanDefinition = new GenericBeanDefinition();
userRepositoryBeanDefinition.setBeanClass(UserRepository.class);
MutablePropertyValues userRepositoryPropertyValues = new MutablePropertyValues();
userRepositoryPropertyValues.addPropertyValue("idGenerator",
                                              // 引用容器中命名為 idGenerator 的 Bean
                                              new RuntimeBeanReference("userIdGenerator"));
userRepositoryBeanDefinition.setPropertyValues(userRepositoryPropertyValues);
beanFactory.registerBeanDefinition("userRepository", userRepositoryBeanDefinition);

GenericBeanDefinition userServiceBeanDefinition = new GenericBeanDefinition();
userServiceBeanDefinition.setBeanClass(UserService.class);
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
constructorArgumentValues.addIndexedArgumentValue(0, new RuntimeBeanReference("userRepository"));
userServiceBeanDefinition.setConstructorArgumentValues(constructorArgumentValues);
beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);

UserService userService = beanFactory.getBean(UserService.class);
User tom = beanFactory.getBean("tom", User.class);
userService.addUser(tom);
userService.listUser().forEach(System.out::println);

BeanDefinition 的載入

以上牙甫,我們已經(jīng)了解到,配置元信息(ConfigurationMetadata) 是我們提供給 spring 的用來定義描述 Bean 的一些配置信息调违。不過腹暖,在能夠讓 spring 容器真正使用之前,我們需要先將這些配置元信息(ConfigurationMetadata) 轉(zhuǎn)化成 BeanDefinition 對(duì)象翰萨。而這一過程呢脏答,在 《Spring 技術(shù)內(nèi)幕...》一書中稱為 BeanDefinition 的載入。這里沿用此稱法亩鬼。

上面我們已經(jīng)演示了如何通過 Java 編碼的方式去生成 BeanDefinition 殖告。但是在實(shí)際項(xiàng)目應(yīng)用中,我們往往不會(huì)采用這樣的方式雳锋,操作復(fù)雜黄绩,且不便于維護(hù)。

那么玷过,我們通常都是如何向 spring 提供這些元數(shù)據(jù)信息呢爽丹?
Spring 官方文檔:

The configuration metadata is represented in XML, Java annotations, or Java code.

通常呢,我們會(huì)采用以下三種方式來向容器提供 Bean 的配置元數(shù)據(jù)信息辛蚊。

  1. XML Configuration . 傳統(tǒng)的以 XML 文件為載體的元數(shù)據(jù)配置方式粤蝎。
  2. Annotation-based Configuration. 將元數(shù)據(jù)信息由 XML 文件中轉(zhuǎn)移到實(shí)例 Bean 所對(duì)應(yīng)的類源碼文件中,轉(zhuǎn)由注解的方式來表達(dá)袋马。此方式需要開啟注解驅(qū)動(dòng)初澎,并配置包掃描來完成實(shí)現(xiàn)÷橇荩可以簡(jiǎn)化 XML 文件中繁瑣的 Bean 元數(shù)據(jù)配置信息碑宴。典型用法软啼,就是在 XML 文件中,引入 context 命名空間延柠,并開啟注解配置祸挪,以及包掃描。此方法涉及的一些關(guān)鍵注解是:@Component贞间,@Autowired贿条,@Qualifier 等。
  3. Java-based Configuration. 此方式是基于注解配置方式的再一次演進(jìn)榜跌。核心注解為 @Bean@Configuration 闪唆。通過聲明一種 ConfigurationClass 來實(shí)現(xiàn)完全代替 XML 文件配置。

下面呢钓葫,我們會(huì)對(duì)這三種方式進(jìn)行一一說明悄蕾。

XML Configuration

XML 文件配置方式是傳統(tǒng)的配置方式。如果做過傳統(tǒng)項(xiàng)目础浮,以下會(huì)是我們很熟悉的配置方式帆调。

  • user-beans.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="tom" class="com.grasswort.beans.model.User">
        <property name="id" value="${tom.id}"/>
        <property name="name" value="${tom.name}"/>
        <property name="age" value="${tom.age}"/>
    </bean>

    <bean id="jerry" class="com.grasswort.beans.model.User">
        <property name="id" value="${jerry.id}"/>
        <property name="name" value="${jerry.name}"/>
        <property name="age" value="${jerry.age}"/>
    </bean>

    <!-- 引入屬性文件 -->
    <context:property-placeholder
            location="classpath:com/grasswort/beans/beandefinition/configurationmeta/user-beans.properties"/>

</beans>
  • user-service.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 引入另一個(gè) XML 配置文件 -->
    <import resource="user-beans.xml"/>

    <bean id="idGenerator" class="com.grasswort.beans.model.UserIdGenerator"></bean>

    <bean id="userRepository" class="com.grasswort.beans.model.UserRepository">
        <property name="idGenerator" ref="idGenerator"/>
    </bean>

    <bean id="userService" class="com.grasswort.beans.model.UserService">
        <constructor-arg ref="userRepository"/>
    </bean>

</beans>
  • user-beans.properties
tom.id=1
tom.name=tom
tom.age=18

jerry.id=2
jerry.name=jerry
jerry.age=8

XML 文件配置,通常使用 XmlBeanDefinitionReader 工具類來完成 BeanDefinition 的載入豆同。

public class XmlBeanDefinitionConfigurationTest {

    private static Logger logger = LoggerFactory.getLogger(XmlBeanDefinitionConfigurationTest.class);

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        int loadCount = xmlBeanDefinitionReader.loadBeanDefinitions(
                "com/grasswort/beans/beandefinition/configurationmeta/user-service.xml");

        logger.info("已解析 BeanDefinition 數(shù)量 : {}", loadCount);

        Stream.of(beanFactory.getBeanDefinitionNames())
                .forEach(System.out::println);
    }
}

運(yùn)行結(jié)果如下:

2020-08-26 19:19:09 [INFO] 已解析 BeanDefinition 數(shù)量 : 6
tom
jerry
org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0
idGenerator
userRepository
userService

XML 文件方式將配置信息與應(yīng)用程序代碼分離了開來番刊,對(duì)實(shí)體類無侵入,便于維護(hù)影锈。

缺點(diǎn)呢芹务,主要是當(dāng) Bean 比較多,依賴關(guān)系復(fù)雜的情況下鸭廷,配置過于繁瑣枣抱,又顯得不那么便于維護(hù)。

Annotation-based Container Configuration

Spring 官方文檔:

Instead of using XML to describe a bean wiring, the developer moves the configuration into the component class itself by using annotations on the relevant class, method, or field declaration.
Annotation injection is performed before XML injection. Thus, the XML configuration overrides the annotations for properties wired through both approaches.

上面一段話的核心在于 moves the configuration into the component class itself by using annotations on the relevant class, method, or field declaration.

正是由于 XML 文件配置過于繁瑣辆床,所以基于注解的配置方式佳晶,則嘗試將 Bean 的配置元數(shù)據(jù)信息轉(zhuǎn)移到該 Bean 實(shí)例所對(duì)應(yīng)的類上。通過在類讼载、方法轿秧、以及屬性上聲明注解來實(shí)現(xiàn) Bean 依賴元數(shù)據(jù)信息的配置。

例如:通過在類上聲明 @Component 以及它的派生注解來聲明一個(gè)類的實(shí)例為 spring 所管理的 Bean咨堤。在方法或?qū)傩陨下暶?@Autowired 注解來表明該類實(shí)例的依賴關(guān)系菇篡。

@Repository
public class UserRepository {

    private final List<User> userList = new ArrayList<>(5);

    private IdGenerator idGenerator;

    /**
     * setter dependency injection
     * @param idGenerator
     */
    @Autowired
    public void setIdGenerator(IdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }
}

支持的注解:

  • spring 2.0
    • @Required
  • spring 2.5
    • @Component
    • @Autowired
    • @Qualifier
    • @Value
    • JSR-250(@PostConstruct@PreDestroy吱型、@Resource
  • spring 3.0
    • JSR-330(@Inject逸贾、@Named
    • @Primary

通過將元數(shù)據(jù)信息轉(zhuǎn)移到 class 上后,我們便可以在 XML 文件中省去一些 Bean 的配置信息津滞。但是為了尋找這些 class 文件并解析元數(shù)據(jù)信息铝侵,我們需要配合包的掃描以及開啟注解支持來配合實(shí)現(xiàn)躏吊。所以脯爪,XML 文件應(yīng)為:

  • user-service-annotation-based.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <context:component-scan base-package="com.grasswort.beans.model"/>

    <import resource="user-beans.xml"/>

</beans>

由于依然使用的是 XML 文件脖阵,所以骗爆,同樣可以使用 XmlBeanDefinitionReader 來進(jìn)行載入:

public class AnnotationBasedConfigurationTest {

    private static Logger logger = LoggerFactory.getLogger(AnnotationBasedConfigurationTest.class);

    public static void main(String[] args) {
        GenericApplicationContext beanFactory = new GenericApplicationContext();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        int loadCount = xmlBeanDefinitionReader.loadBeanDefinitions("com/grasswort/beans/beandefinition/configurationmeta/user-service-annotation-based.xml");
        logger.info("已解析 BeanDefinition 數(shù)量 : {}", loadCount);

        Stream.of(beanFactory.getBeanDefinitionNames())
                .forEach(System.out::println);

        beanFactory.refresh();

        UserService userService = beanFactory.getBean(UserService.class);
        beanFactory.getBeansOfType(User.class).values().forEach(userService::addUser);
        userService.listUser().forEach(System.out::println);
    }

}

運(yùn)行結(jié)果如下:

2020-08-26 20:07:02 [INFO] 已解析 BeanDefinition 數(shù)量 : 10
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
userIdGenerator
userRepository
userService
tom
jerry
org.springframework.context.support.PropertySourcesPlaceholderConfigurer#0
User{id=1, name='tom', age=18}
User{id=2, name='jerry', age=8}

我們看到唯竹,添加了 <context:annotation-config/> 之后垂涯,容器中多了 4 個(gè) BeanDefinition嵌巷。它們就是用來處理注解的重窟,這些 BeanDefinition 是從哪里來的呢鸟雏?
spring-context 模塊下存在這樣一個(gè)文件 META-INF/spring.handlers享郊。

image.png

內(nèi)容如下:

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

當(dāng)我們?cè)?XML 文件中開啟注解配置之后,XmlBeanDefinitionReader 在載入 BeanDefinition 過程中孝鹊,會(huì)激活

ContextNamespaceHandler 來處理炊琉,ContextNamespaceHandler 會(huì)注冊(cè)一個(gè) AnnotationConfigBeanDefinitionParser 的解析器。它會(huì)調(diào)用 AnnotationConfigUtils 來注冊(cè)以上 4 個(gè) BeanDefinition又活。代碼如下:(這個(gè) AnnotationConfigUtils 需要關(guān)注一下苔咪,以后會(huì)經(jīng)常遇到。)

public class AnnotationConfigBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);

        // Obtain bean definitions for all relevant BeanPostProcessors.
        Set<BeanDefinitionHolder> processorDefinitions =
                AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

        // 省略部分代碼
        ...

        return null;
    }

}

這 4 個(gè)BeanDefinition 分別對(duì)應(yīng)的是(隱式注冊(cè)的相關(guān) post-processors):

  • ConfigurationClassPostProcessor (這個(gè)暫時(shí)可以忽略柳骄,對(duì)應(yīng)的是下面提到的 Java-based Configuration

  • AutowiredAnnotationBeanPostProcessor

  • CommonAnnotationBeanPostProcessor

  • PersistenceAnnotationBeanPostProcessor

這也映照了 Java 官方文檔中的一段話:

(The implicitly registered post-processors include AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, and the aforementioned RequiredAnnotationBeanPostProcessor.)

Java-based Container Configuration

以上团赏,在開啟了注解配置之后呢,我們?nèi)匀贿€余留了一個(gè)配置 XML 文件和一個(gè)我們未處理的 user-beans.xml耐薯。

  • user-service-annotation-based.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <context:component-scan base-package="com.grasswort.beans.model"/>

    <import resource="user-beans.xml"/>

</beans>
  • user-beans.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="tom" class="com.grasswort.beans.model.User">
        <property name="id" value="${tom.id}"/>
        <property name="name" value="${tom.name}"/>
        <property name="age" value="${tom.age}"/>
    </bean>

    <bean id="jerry" class="com.grasswort.beans.model.User">
        <property name="id" value="${jerry.id}"/>
        <property name="name" value="${jerry.name}"/>
        <property name="age" value="${jerry.age}"/>
    </bean>

    <!-- 引入屬性文件 -->
    <context:property-placeholder
            location="classpath:com/grasswort/beans/beandefinition/configurationmeta/user-beans.properties"/>

</beans>

那么舔清,我們能否通過 Java Code 來替代它呢?這就是我們接下來要提到的 Java-based Container Configuration曲初。

Java-based Configuration 的核心注解是 @Bean@Configuration体谒。標(biāo)注了 @Configuration 注解的類,會(huì)被 spring 容器視為 ConfigurationClass 复斥,即配置類营密。而該類中標(biāo)注了 @Bean 注解的方法,會(huì)在容器啟動(dòng)過程中目锭,以工廠方法模式的 BeanDefinition 注冊(cè)到容器里评汰。

注意是容器啟動(dòng)過程中,假如沒有調(diào)用容器的 refresh 方法痢虹,配置類中聲明的 BeanDefinition 是不會(huì)注冊(cè)到容器中的被去。這利用的是 spring 容器提供的 BeanFactoryPostProcessor 容器后置處理機(jī)制,而在這里起作用的則是上文提到的由 AnnotationConfigUtils 注冊(cè)的 ConfigurationClassPostProcessor奖唯。

接下來惨缆,直接上代碼:

  • UsersConfiguration.java
@Configuration
@PropertySource("classpath:com/grasswort/beans/beandefinition/configurationmeta/user-beans.properties")
public class UsersConfiguration {

    @Bean
    public User tom(@Value("${tom.id}") Long id,
                    @Value("${tom.name}") String name,
                    @Value("${tom.age}") Integer age) {
        User tom = new User();
        tom.setId(id);
        tom.setAge(age);
        tom.setName(name);
        return tom;
    }

    @Bean
    public User jerry(@Value("${jerry.id}") Long id,
                    @Value("${jerry.name}") String name,
                    @Value("${jerry.age}") Integer age) {
        User jerry = new User();
        jerry.setId(id);
        jerry.setAge(age);
        jerry.setName(name);
        return jerry;
    }
}
  • UserServiceConfiguration.java
@Configuration
@Import(UsersConfiguration.class)
public class UserServiceConfiguration {

    @Bean
    public IdGenerator idGenerator() {
        return new UserIdGenerator();
    }

    @Bean
    public UserRepository userRepository(IdGenerator idGenerator) {
        UserRepository userRepository = new UserRepository();
        userRepository.setIdGenerator(idGenerator);
        return userRepository;
    }

    @Bean
    public UserService userService(UserRepository userRepository, Collection<User> users) {
        UserService userService = new UserService(userRepository);
        users.forEach(userService::addUser);
        return userService;
    }
}

然后,我們發(fā)現(xiàn),這個(gè)ConfigurationClassXML 配置文件其實(shí)是很相似的坯墨。

  • @Bean 對(duì)應(yīng)了 <bean/>

  • @Import(UserConfiguration.class) 對(duì)應(yīng)了 <import resource="user-beans.xml"/>

  • @PropertySource("xx.properties") 對(duì)應(yīng)了 <context:property-placeholder location="xx.properties"/>

<context:property-placeholder.../> 其實(shí)還有另一種寫法:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
   <property name="location">
     <value>xx.properties</value>
   </property>
    <property name="fileEncoding">
      <value>UTF-8</value>
    </property>
</bean>

然后寂汇,我們發(fā)現(xiàn),ConfigurationClass 其實(shí)和 XML 配置文件在概念上非常的類似捣染。

然后骄瓣,我們使用 AnnotationConfigApplicationContext容器來進(jìn)行載入并啟動(dòng):

/**
 * @author xuliangliang
 * @Description
 * @Date 2020/8/13
 * @see org.springframework.context.annotation.ConfigurationClassPostProcessor
 * @see org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader
 */
public class JavaBasedConfigurationTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(UserServiceConfiguration.class);
        context.refresh(); // can't get the BeanDefinition named tom if not refresh
        // this step will register a {@link ConfigurationClassPostProcessor} bean ,
        // it will invoke the {@link ConfigurationClassBeanDefinitionReader} to resolve inner bean.
        Stream.of(context.getBeanDefinitionNames())
                .forEach(System.out::println);

        BeanDefinition userConfigurationBd = context.getBeanDefinition("userServiceConfiguration");
        System.out.println(userConfigurationBd.getAttribute("org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass"));

        BeanDefinition beanDefinition = context.getBeanDefinition("tom");
        System.out.println(beanDefinition);

        UserService userService = context.getBean(UserService.class);
        userService.listUser().forEach(System.out::println);
    }
}

運(yùn)行結(jié)果為:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
userServiceConfiguration
com.grasswort.beans.beandefinition.configurationmeta.UsersConfiguration
tom
jerry
idGenerator
userRepository
userService
full
Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.grasswort.beans.beandefinition.configurationmeta.UsersConfiguration; factoryMethodName=tom; initMethodName=null; destroyMethodName=(inferred); defined in com.grasswort.beans.beandefinition.configurationmeta.UsersConfiguration
User{id=1, name='tom', age=18}
User{id=2, name='jerry', age=8}

最后呢,我們已經(jīng)沒有再依賴 XML 配置文件耍攘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末榕栏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕾各,更是在濱河造成了極大的恐慌扒磁,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件式曲,死亡現(xiàn)場(chǎng)離奇詭異妨托,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)检访,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門始鱼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脆贵,你說我怎么就攤上這事医清。” “怎么了卖氨?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵会烙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我筒捺,道長(zhǎng)柏腻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任系吭,我火速辦了婚禮五嫂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肯尺。我一直安慰自己沃缘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布则吟。 她就那樣靜靜地躺著槐臀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氓仲。 梳的紋絲不亂的頭發(fā)上水慨,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天得糜,我揣著相機(jī)與錄音,去河邊找鬼晰洒。 笑死朝抖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的欢顷。 我是一名探鬼主播槽棍,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼捉蚤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼抬驴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缆巧,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤布持,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后陕悬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體题暖,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年捉超,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胧卤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拼岳,死狀恐怖枝誊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情惜纸,我是刑警寧澤叶撒,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站耐版,受9級(jí)特大地震影響祠够,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粪牲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一古瓤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腺阳,春花似錦落君、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至痛侍,卻和暖如春朝氓,著一層夾襖步出監(jiān)牢的瞬間魔市,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工赵哲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留待德,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓枫夺,卻偏偏與公主長(zhǎng)得像将宪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橡庞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355