美文网首页SpringSpring
Spring Framework:BeanDefinition

Spring Framework:BeanDefinition

作者: 烟波一棹知何许 | 来源:发表于2020-08-26 21:50 被阅读0次

    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 配置文件。

    相关文章

      网友评论

        本文标题:Spring Framework:BeanDefinition

        本文链接:https://www.haomeiwen.com/subject/bvqyjktx.html