手写Spring系列2-BeanDefinition的设计

1.BeanDefinition的简单介绍

1.1 为什么要有BeanDefinition

BeanDefinition,我们从名字可以将其翻译成为Bean的定义信息。Spring将那些要交给Spring去容器管理的类,全部进行扫描和处理,最终封装成为一个个的BeanDefinition存到容器当中,等待后续创建Bean时去进行使用。

一个BeanDefinition中保存了一个这个Bean在进行创建时,需要用到的相关信息,它就决定了这个Bean在创建时,将会以何种方式去进行创建。比如我们使用了@Component注解、@Configuration注解、@Bean注解等方式配置的Bean的相关信息,都会被封装成为一个BeanDefinition;再比如xml配置文件中的一个<bean></bean>标签,也会被封装成为一个BeanDefinition

1.2 对应根接口BeanDefinition的设计

我们定义如下的BeanDefinition接口:

public interface BeanDefinition<T> {

    /**
     * beanName/beanId
     */
    public String getBeanName();

    /**
     * beanClass/beanType
     */
    public Class<T> getBeanType();

    public void setBeanType(Class<?> beanType);

    public void setBeanName(String beanName);

    /**
     * 设置初始化方法,用来对Bean中相关属性进行初始化
     */
    public String getInitMethodName();

    public void setInitMethodName(String initMethodName);

    /**
     * 容器摧毁时,需要回调的方法
     */
    public String getDestroyMethodName();

    public void setDestroyMethodName(String destroyMethodName);

    /**
     * 设置作用域
     */
    public void setScope(String scope);

    /**
     * 是否单例?
     */
    public boolean isSingleton();

    /**
     * 是否原型?
     */
    public boolean isPrototype();

    /**
     * 是否懒加载
     */
    public boolean isLazyInit();

    /**
     * 设置懒加载
     */
    public void setLazyInit(boolean lazyInit);

    /**
     * 是否是首要的Bean,在按照类型去进行注入时
     * 如果遇到多个相同类型的bean,如果设置了这个属性
     * 那么在注入时,就不会保存了,按照PrimaryBean去进行注入
     */
    public boolean isPrimary();

    public void setPrimary(boolean primary);

    /**
     * 获取Bean的描述信息
     */
    public String getDescription();

    public void setDescription(String description);

    /**
     * 获取Bean的角色
     */
    public int getRole();

    public void setRole(int role);

    /**
     * 获取创建Bean中需要依赖的Bean列表(beanName列表)
     */
    public String[] getDependsOn();

    public void setDependsOn(String[] dependsOn);

    /**
     * 给当前BeanDefinition添加属性值
     *
     * @param name  name
     * @param value value
     */
    public void addPropertyValue(String name, Object value);

    /**
     * 获取到属性值列表
     *
     * @return 属性值列表
     */
    public PropertyValues getPropertyValues();

    /**
     * 给当前BeanDefinition添加属性值
     */
    public void addPropertyValue(PropertyValue pv);

    /**
     * 设置构造器的参数,最终将通过这个构造器参数列表去创建对象
     */
    public void setConstructorArgumentValues(ConstructorArgumentValues cav);

    /**
     * 获取构造器的参数,最终将通过这个构造器参数列表去创建对象
     */
    public ConstructorArgumentValues getConstructorArgumentValues();

    /**
     * 获取自动注入的模式
     */
    public int getAutowireMode();

    /**
     * 设置自动注入模式
     */
    public void setAutowireMode(int autowireMode);
}

1.3 定义一个实现类RootBeanDefinition

public class RootBeanDefinition<T> implements BeanDefinition<T> {

    public static final String SCOPE_SINGLETON = "singleton";

    public static final String SCOPE_PROTOTYPE = "prototype";

    /**
     * 不进行自动注入
     */
    public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;

    /**
     * byName去进行注入
     */
    public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

    /**
     * byType去进行注入
     */
    public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

    /**
     * 使用构造器去进行注入
     */
    public static final int AUTOWIRE_BY_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_BY_CONSTRUCTOR;

    /**
     * beanName
     */
    private String beanName;

    /**
     * bean的类型
     */
    private Class<T> beanType;

    /**
     * Bean的作用域
     */
    private String scope;

    /**
     * 是否factoryMethod是唯一的?
     */
    boolean isFactoryMethodUnique;

    /**
     * 如果一个Bean是factoryBean
     */
    public volatile boolean isFactoryBean;

    /**
     * 缓存factoryMethod的返回类型
     */
    volatile Class<?> factoryMethodReturnType;

    /**
     * 缓存一个唯一的factoryMethod
     */
    volatile Method factoryMethodToIntrospect;

    /**
     * 导入@Bean的类的beanName
     */
    private String factoryBeanName;

    /**
     * factoryMethodName
     */
    private String factoryMethodName;

    /**
     * 初始化方法的name
     */
    private String initMethodName;

    /**
     * 摧毁Bean需要回调的方法name
     */
    private String destroyMethodName;

    /**
     * 是否懒加载
     */
    private boolean lazyInit;

    /**
     * 是否是Primary Bean
     */
    private boolean primary;

    /**
     * Bean的描述信息
     */
    private String description;

    /**
     * Bean的角色Role
     */
    private int role;

    /**
     * 自动注入的类型
     */
    private int autowireMode;

    /**
     * 所依赖的Bean
     */
    private String[] dependsOn;


    /**
     * 维护的是需要进行注入的属性值,在xml配置文件的解析中会往这里放入元素
     * 在进行依赖注入(自动装配)时会从这个属性值列表当中去拿出来并进行遍历处理
     */
    private final PropertyValues pvs = new MutablePropertyValues();

    /**
     * 构造器参数列表,最终将通过这个构造器参数列表去创建对象
     */
    public ConstructorArgumentValues constructorArgumentValues;

    // 无参数构造器默认是单例的,beanName和beanType都是null
    public RootBeanDefinition() {
        this(null, null, SCOPE_SINGLETON);
    }

    public RootBeanDefinition(String beanName, Class<T> beanType) {
        this(beanName, beanType, SCOPE_SINGLETON);
    }

    public RootBeanDefinition(String beanName, Class<T> beanType, String scope) {
        this.beanName = beanName;
        this.beanType = beanType;
        this.scope = scope;
    }

    // 后面的是一大堆的`getter`和`setter`(也就是提供根接口中的方法的实现),就不在这里去进行展示了
}

1.4 SpringBeanDefinition是以怎么样的形式存在的?

需要说明的是,这些信息我其实不是一次性定义出来的(甚至如今都还会有缺少的内容),在自己设计时,我也会去看看Spring人家是怎么做的,自己应该要怎么做。

  • 1.最开始我设计时就只有beanNamebeanTypescope这些必要信息(甚至原型Bean我现在都还没去进行实现,暂时提供了支持)。
  • 2.到后面因为@Bean方法的处理,新增加了factoryMethodNamefactoryBeanName等属性去进行支持,不然就没办法去进行很好的处理。
  • 3.后面发现还有@Primary@DependsOn以及初始化方法等内容时,又增加了相关的属性。
  • 4.后面实现xml的配置文件时,发现还有构造器参数/属性值的保存,又增加了PropertyValuesConstructorArgumentValues以及autowireMode这些属性,都是为了支持xml配置文件的解析。

一个个的BeanDefinitionSpring容器中是如何存在的?在设计时,在DefaultListableBeanFactory中维护了三个容器:

  • 1.beanDefinitionMap的key是beanNamevalueBeanDefinition,也就是方便根据beanName去找到一个BeanDefinition
  • 2.beanDefinitionNames维护的就是beanDefinitionMap当中的key的列表,也就是维护的是beanName的列表。
  • 3.beanDefinitions维护的是beanDeinitionMap当中的value的列表,也就是维护的BeanDefinition列表。
    /**
     * BeanDefinitionMap,用来存放BeanDefinition,key-beanName,value-beanDefinition
     */
    private final Map<String, BeanDefinition<?>> beanDefinitionMap = new HashMap<>();

    /**
     * 存放BeanDefinition的Name,和BeanDefinition对应,只有key,没有value
     */
    private final List<String> beanDefinitionNames = new ArrayList<>();

    /**
     * 存放BeanDefinition,和beanDefinitionMap对应,只有value,没有key
     */
    private final List<BeanDefinition<?>> beanDefinitions = new ArrayList<>();

2.BeanDefinition的加载

BeanDefinition的加载其实有很多的途径,比如XML配置文件、注解等方式。

  • 1.对于注解版来说,主要用到ClassPathBeanDefinitionScanner去做包下的候选@Component组件(支持注解递归扫描,而不是直接注解扫描)的扫描,然后使用ConfigurationClassBeanDefinitionReader去做@Bean注解等的处理。
  • 2.对于XML版来说,主要用到了XmlBeanDefinitionReader这个类,去解析XML的配置文件信息,然后去将<bean>封装成为一个个BeanDefintion

目前项目中对于注解版和xml版的IOC容器,都已经提供相关的支持(虽然并不完善,甚至还有bug)。项目Github地址:https://github.com/wanna280/WebServer

2.1 在注解版IOC容器下对于XML的提供支持

在类App中,在注解版的使用环境下,通过@ImportSource(value = "classpath:application.xml")这样的代码,就可以导入application.xml这个配置文件。

下面是application.xml的内容:

<beans>

    <!--  支持使用构造器去进行注入,配置的字段的顺序就是目标构造器的参数顺序  -->
    <bean id="user1" class="com.wanna.webserver.test.User" primary="true" autowire="byConstructor">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="wanna"/>
        <constructor-arg name="helloController" ref="helloController"/>
    </bean>

    <!--  使用byType/byName进行自动注入,使用的是setter注入的方式去进行注入的  -->
    <!--  对于property属性,还可以另外进行set,会在byName/byType之后才会进行执行,也是执行的setter而不是字段直接注入  -->
    <!--  如果解析占位符成功,那么就会替换掉value,如果没有解析成功,那么就采用默认值,如果默认值也没用,那么就报错不变  -->
    <bean id="user" autowire="byType" class="com.wanna.webserver.test.User" primary="true">
        <property name="id" value="1"/>
        <property name="name" value="${JAVA_HOME:wanna}"/>
    </bean>

    <!--  它的作用是给容器中导入一个组件PropertySourcesPlaceholderConfigurer,用来处理占位符  -->
    <context:property-placeholder locations="classpath:application.properties"/>

</beans>

使用如下的代码去配置App.class作为配置类,从而启动注解版的IOC容器,而在注解版当中提供了@ImportSource的支持,因此可以很方便地使用注解版的方式去导入xml配置文件。

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(App.class);

我对XML版的IOC容器提供了哪些支持:

  • 1.支持解析xml配置文件中<bean>标签的解析,可以将配置的<property>标签中的内容使用Setter注入的方式去进行设置。
  • 2.<bean>标签的注入支持了autowire的方式,实现了byNamebyType两种方式,如果配置了byConstructor,那么就会分析<constructor-arg>标签配置的要注入的构造器的参数(按照顺序配置,就会对应构造器的对应的参数位置)。
  • 3.支持了从环境(环境变量和JVM系统属性)以及配置的properties配置文件中去获取属性从而去替换占位符`{user.name}" /><property ... value="${user.name}">`。

如果要使用原生的xml的配置文件的方式,可以使用ClassPathXmlApplicationContext这个类去作为IOC容器的启动方式(目前对该类的支持很少,暂时提供了这种实现方式,并未进行完善)。

2.2 @ImportSource注解的作用和实现

@ImportSource的作用,其实就是根据配置的xml配置文件去解析出来BeanDefinition,作用就是在注解版的IOC容器中依然对传统的XML配置文件的方式提供支持,而不是直接将XML配置文件的方式淘汰掉。

我们来看这个注解的定义信息

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ImportSource {

    /**
     * 指定的位置信息,和locations作用一致
     *
     * @return 指定的位置信息
     */
    public String[] value() default {};

    /**
     * 指定的位置信息,和value作用一致
     *
     * @return 指定的位置信息
     */
    public String[] locations() default {};

    /**
     * reader
     *
     * @return 处理这个注解的BeanDefinitionReader,默认为xml的方式
     */
    public Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}

valuelocations属性,都是配置要导入的xml配置文件的位置,而reader属性则是去指定要解析这个配置文件的BeanDefinitionReader

如果你不指定reader,默认值是BeanDefinitionReader.class,在解析这个注解时,会使用XmlBeanDefinitionReader去进行解析。

如果你配置了自己定义的BeanDefinitionReader,当然可以使用你提供的实现,在目前的项目中已经提供了相应的支持,只是默认使用的是XML版本的罢了,你当然也可以自己定义一个properties配置文件的BeanDefinitionReader去替换掉默认的XML的实现。

3.Spring当中对于BeanDefinition的设计

其实在真正的Spring Framework当中存在着很多类型的BeanDefinition,主要包括如下这几种类型:

  • 1.RootBeanDefinition
  • 2.GenericBeanDefinition,通用的BeanDefinition,我们一般使用它就足够了。
  • 3.ScannedGenericBeanDefinition,通过Component-Scan导入进来的BeanDefinition
  • 4.AnnotatedGenericBeanDefinition,通过@Import导入进来的和内部类使用的BeanDefinition
  • 5.ConfigurationClassBeanDefinition,通过@Bean注解导入进来时的BeanDefinition

它们在继承关系上,呈现如下图:

image.png

其实对于345这几个类别的BeanDefinition它们都只是Spring在内部的特定场合下去进行使用的BeanDefinition,只是在导入时,使用了不同的类型的BeanDefinition。在真正要执行getBean时,有一个步骤称为getMergedLocalBeanDefinition,它就是将别的类型的BeanDefinition全部都合并到RootBeanDefinition这个类型过来。

image.png

在设计时,我都是直接使用RootBeanDefinition作为BeanDefinition的实现,没有新增加别的类型的BeanDefinition去区分对应的场合导入进来的BeanDefinition,因此在我的项目中只有RootBeanDefinition这一种实现。

#Java学习##Java##学习路径##Spring#
全部评论
大佬带带我好不好!!!
点赞 回复
分享
发布于 2022-01-12 15:49

相关推荐

投递小红书等公司10个岗位
点赞 评论 收藏
转发
点赞 3 评论
分享
牛客网
牛客企业服务