Spring Framework:BeanDefinition

IOC 容器:BeanDefinition 篇

相关文档:

  • 参考文档
  1. Spring 官方文档
  2. 《Spring 技术内幕:深入解析 Spring 架构与设计原理》

注: 笔者经常喜欢引用一些官方文档以及其他文献中的内容,一来,这些内容确实存在着其自身的价值与意义。二来,也是为了使得自己的文章尽量言出有据,不瞎叨叨。但由于个人能力以及理解问题,错误仍然无法保证完全避免。如有问题,欢迎及时指正。

涉及问题

  1. 什么是配置元数据(ConfigurationMetadata)?
  2. 什么是 BeanDefinition
  3. BeanDefinition 与配置元数据的关系?包含了哪些配置元数据?
  4. 配置元数据有哪些表现形式?以及相应的载入方式。

配置元信息(ConfigurationMetadata)

  1. 什么是配置元数据?

官方文档:

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.

由此可知,配置元数据是指我们提供给 Spring 容器的一些配置信息,其中包含了 Bean 的定义信息,通过这些配置信息,我们可以告诉 Spring 容器如何去实例化、配置以及组装 Bean。

官方文档:

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

在容器内部,这些 Bean 的定义信息经过封装后表现为一个或多个 BeanDefinition 对象。spring 就是通过解析这些 BeanDefinition 实例来生成所定义的 Bean 实例。

Bean 的定义类(BeanDefinition)

  1. BeanDefinition 设计意图:

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 设计的用来描述一个 Bean 实例的接口。包含了许多关于 Bean 实例的信息。并且 spring 还设计了一个 BeanFactoryPostProcessor 容器后置处理器接口,可以在容器初始化阶段对 BeanDefinition 元数据或属性值进行修改。

  1. BeanDefinition 继承关系:
    image.png

    BeanDefinitionspring 定义的一个用来描述 bean 实例的接口。它继承了 AttributeAccessorBeanMetadataElement 接口。
  • AttributeAccessor 要求 BeanDefinition 需要具备属性存储的能力。此处的属性并非指对象的 Field,而是近似于一个标签的作用,例如,ConfigurationClass 对应的 BeanDefinition 会在容器启动过程中标记一个 org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass 的属性,来标记为是配置类。
  • BeanMetadataElement 允许 BeanDefinition 配置一个类型为 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 具备父子层级关系,子 BeanDefinition 可以复用父 BeanDefinition 中配置的元数据信息,从而减少不必要的重复配置。

/**
     * 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 对应的 Bean 实例化时所对应的 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 获取方式的内部实现策略可能会存在差异。 spring 提供了 singletonprototype 两种常用的作用域,分别对应设计模式中的单例模式和原型模式。

/**
     * 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();

设置 Bean 是否延迟初始化,如果设置为 true ,在容器对单例对象进行预先初始化时,会跳过该 BeanDefinition 所对应 Bean 的实例化,而是在向容器第一次获取该 Bean 的时候才进行实例化。但如果该 Bean 是别的非延迟加载单例 Bean 的依赖对象时,则在容器对单例对象进行预先初始化是,也会被实例化。

/**
     * 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();

设置该 Bean 实例所依赖的其他 Beanspring 容器会先去初始化所依赖的其他 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();

设置该 Bean 是否为依赖注入候选者(把当前 Bean 注入到其他 Bean 中)。该属性仅影响基于类型的依赖注入。如果是通过 beanName 来进行依赖注入,则该属性不生效。

/**
     * 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();

设置该 Bean 是否为优先候选者,该属性同样是仅影响基于类型的依赖注入,当存在多个候选者时,会优先选用 primarytrue 的候选者(只能有一个)。否则系统将会因为无法选定候选者而抛出异常。

/**
     * 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 对应设计模式中的工厂模式。 setFactoryBeanName(String factoryBeanName) 指定生产 Bean 的工厂实例,setFactoryMethodName 指定对应的工厂方法名称。

/**
     * 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();
    }

获取构造参数值,对应类实例化时调用构造函数的参数值。如果是通过工厂方法来生成实例的话,则对应工厂方法的参数值。

/**
     * 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 实例的属性值,Mutable 为可修改的意思,获取 MutablePropertyValues 对象后可以对里面的 PropertyValue 进行编辑修改。

    void setInitMethodName(@Nullable String initMethodName);

    @Nullable
    String getInitMethodName();

    void setDestroyMethodName(@Nullable String destroyMethodName);

    @Nullable
    String getDestroyMethodName();

以上方法分别为设置 Bean 实例的初始化方法,和销毁方法。

    void setRole(int role);

    int getRole();

设置 Bean 的角色,该方法没有什么功能上的意义。仅用来对 Bean 做一个功能上的标识。

    void setDescription(@Nullable String description);

    @Nullable
    String getDescription();

设置类的描述信息。

    /**
     * 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 所描述 BeanResolvableTypeResolvableTypespring 设计的一个支持获取泛型信息的接口,方便泛型操作。

  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 包含了哪些配置元数据信息?

在了解了 BeanDefinition 接口后,我们则可以简单地总结一个 BeanDefinition 中都包含了哪些 Bean 定义信息。不过,Spring 官方文档已经对此做了总结,这里再做一个简单的阐述:

配置项 作用
Class 实例化 Bean 所对应的类信息
Name Bean 的名称
Constructor arguments Bean 的构造参数
Properties Bean 的属性值
Scope Bean 的作用域
Autowiring mode Bean 的自动装配模式, 默认为 NONE
Lazy initialization mode Bean 的延迟初始化模式
Initialization method Bean 的初始化方法
Destruction method Bean 的销毁方法

BeanDefinition 的具体实现

image.png

BeanDefinition 的具体实现类有许多,这里仅挑选以下几个做出简略说明:

  • RootBeanDefinition
  • ChildBeanDefinition
  • GenericBeanDefinition
  • AnnotatedBeanDefinition

上文提到了 BeanDefinition 是可以支持父子层级关系的。RootBeanDefinitionChildBeanDefinition 这两个实现类,则直接从名字上表明了这一问题。Root 代表根部的意思,则 RootBeanDefinition 代表了最上层的父级 BeanDefinition ,它不能设置 parentName,否则会抛出异常。而 ChildBeanDefinition 则代表了子 BeanDefinition ,可以指定父级 BeanDefinition, 并复用 parentBeanDefinition 的配置元数据信息。

GenericBeanDefinition 是一个相对比较灵活的实现类,相比于以上两种去掉了一些层级关系上的约束。可以随意指定 parentName 。如果我们使用 Java 编码的形式来生成 BeanDefinition,通常可以使用此类。

AnnotatedBeanDefinition 相对于其他的 BeanDefinition 实现类,提供了一些对类上注解以及方法注解的支持。

以上内容比较简单,可以自行熟悉了解。也可以参考下面提供一些简单代码,对 BeanDefinition 有一个基本的理解。

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 的载入

以上,我们已经了解到,配置元信息(ConfigurationMetadata) 是我们提供给 spring 的用来定义描述 Bean 的一些配置信息。不过,在能够让 spring 容器真正使用之前,我们需要先将这些配置元信息(ConfigurationMetadata) 转化成 BeanDefinition 对象。而这一过程呢,在 《Spring 技术内幕...》一书中称为 BeanDefinition 的载入。这里沿用此称法。

上面我们已经演示了如何通过 Java 编码的方式去生成 BeanDefinition 。但是在实际项目应用中,我们往往不会采用这样的方式,操作复杂,且不便于维护。

那么,我们通常都是如何向 spring 提供这些元数据信息呢?
Spring 官方文档:

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

通常呢,我们会采用以下三种方式来向容器提供 Bean 的配置元数据信息。

  1. XML Configuration . 传统的以 XML 文件为载体的元数据配置方式。
  2. Annotation-based Configuration. 将元数据信息由 XML 文件中转移到实例 Bean 所对应的类源码文件中,转由注解的方式来表达。此方式需要开启注解驱动,并配置包扫描来完成实现。可以简化 XML 文件中繁琐的 Bean 元数据配置信息。典型用法,就是在 XML 文件中,引入 context 命名空间,并开启注解配置,以及包扫描。此方法涉及的一些关键注解是:@Component@Autowired@Qualifier 等。
  3. Java-based Configuration. 此方式是基于注解配置方式的再一次演进。核心注解为 @Bean@Configuration 。通过声明一种 ConfigurationClass 来实现完全代替 XML 文件配置。

下面呢,我们会对这三种方式进行一一说明。

XML Configuration

XML 文件配置方式是传统的配置方式。如果做过传统项目,以下会是我们很熟悉的配置方式。

  • 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">
    <!-- 引入另一个 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 数量 : {}", loadCount);

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

运行结果如下:

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

XML 文件方式将配置信息与应用程序代码分离了开来,对实体类无侵入,便于维护。

缺点呢,主要是当 Bean 比较多,依赖关系复杂的情况下,配置过于繁琐,又显得不那么便于维护。

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 的配置元数据信息转移到该 Bean 实例所对应的类上。通过在类、方法、以及属性上声明注解来实现 Bean 依赖元数据信息的配置。

例如:通过在类上声明 @Component 以及它的派生注解来声明一个类的实例为 spring 所管理的 Bean。在方法或属性上声明 @Autowired 注解来表明该类实例的依赖关系。

@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

通过将元数据信息转移到 class 上后,我们便可以在 XML 文件中省去一些 Bean 的配置信息。但是为了寻找这些 class 文件并解析元数据信息,我们需要配合包的扫描以及开启注解支持来配合实现。所以,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>

由于依然使用的是 XML 文件,所以,同样可以使用 XmlBeanDefinitionReader 来进行载入:

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 数量 : {}", 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);
    }

}

运行结果如下:

2020-08-26 20:07:02 [INFO] 已解析 BeanDefinition 数量 : 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 个 BeanDefinition。它们就是用来处理注解的,这些 BeanDefinition 是从哪里来的呢?
spring-context 模块下存在这样一个文件 META-INF/spring.handlers

image.png

内容如下:

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

当我们在 XML 文件中开启注解配置之后,XmlBeanDefinitionReader 在载入 BeanDefinition 过程中,会激活

ContextNamespaceHandler 来处理,ContextNamespaceHandler 会注册一个 AnnotationConfigBeanDefinitionParser 的解析器。它会调用 AnnotationConfigUtils 来注册以上 4 个 BeanDefinition。代码如下:(这个 AnnotationConfigUtils 需要关注一下,以后会经常遇到。)

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 个BeanDefinition 分别对应的是(隐式注册的相关 post-processors):

  • ConfigurationClassPostProcessor (这个暂时可以忽略,对应的是下面提到的 Java-based Configuration

  • AutowiredAnnotationBeanPostProcessor

  • CommonAnnotationBeanPostProcessor

  • PersistenceAnnotationBeanPostProcessor

这也映照了 Java 官方文档中的一段话:

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

Java-based Container Configuration

以上,在开启了注解配置之后呢,我们仍然还余留了一个配置 XML 文件和一个我们未处理的 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。标注了 @Configuration 注解的类,会被 spring 容器视为 ConfigurationClass ,即配置类。而该类中标注了 @Bean 注解的方法,会在容器启动过程中,以工厂方法模式的 BeanDefinition 注册到容器里。

注意是容器启动过程中,假如没有调用容器的 refresh 方法,配置类中声明的 BeanDefinition 是不会注册到容器中的。这利用的是 spring 容器提供的 BeanFactoryPostProcessor 容器后置处理机制,而在这里起作用的则是上文提到的由 AnnotationConfigUtils 注册的 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;
    }
}

然后,我们发现,这个ConfigurationClassXML 配置文件其实是很相似的。

  • @Bean 对应了 <bean/>

  • @Import(UserConfiguration.class) 对应了 <import resource="user-beans.xml"/>

  • @PropertySource("xx.properties") 对应了 <context:property-placeholder location="xx.properties"/>

<context:property-placeholder.../> 其实还有另一种写法:

<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>

然后,我们发现,ConfigurationClass 其实和 XML 配置文件在概念上非常的类似。

然后,我们使用 AnnotationConfigApplicationContext容器来进行载入并启动:

/**
 * @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);
    }
}

运行结果为:

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}

最后呢,我们已经没有再依赖 XML 配置文件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350