手写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 Spring
中BeanDefinition
是以怎么样的形式存在的?
需要说明的是,这些信息我其实不是一次性定义出来的(甚至如今都还会有缺少的内容),在自己设计时,我也会去看看Spring
人家是怎么做的,自己应该要怎么做。
- 1.最开始我设计时就只有
beanName
、beanType
、scope
这些必要信息(甚至原型Bean
我现在都还没去进行实现,暂时提供了支持)。 - 2.到后面因为
@Bean
方法的处理,新增加了factoryMethodName
、factoryBeanName
等属性去进行支持,不然就没办法去进行很好的处理。 - 3.后面发现还有
@Primary
、@DependsOn
以及初始化方法等内容时,又增加了相关的属性。 - 4.后面实现
xml
的配置文件时,发现还有构造器参数/属性值的保存,又增加了PropertyValues
、ConstructorArgumentValues
以及autowireMode
这些属性,都是为了支持xml
配置文件的解析。
一个个的BeanDefinition
在Spring
容器中是如何存在的?在设计时,在DefaultListableBeanFactory
中维护了三个容器:
- 1.
beanDefinitionMap
的key是beanName
,value
是BeanDefinition
,也就是方便根据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
的方式,实现了byName
和byType
两种方式,如果配置了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; }
value
和locations
属性,都是配置要导入的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
。
它们在继承关系上,呈现如下图:
其实对于3
、4
、5
这几个类别的BeanDefinition
它们都只是Spring
在内部的特定场合下去进行使用的BeanDefinition
,只是在导入时,使用了不同的类型的BeanDefinition
。在真正要执行getBean
时,有一个步骤称为getMergedLocalBeanDefinition
,它就是将别的类型的BeanDefinition
全部都合并到RootBeanDefinition
这个类型过来。
在设计时,我都是直接使用RootBeanDefinition
作为BeanDefinition
的实现,没有新增加别的类型的BeanDefinition
去区分对应的场合导入进来的BeanDefinition
,因此在我的项目中只有RootBeanDefinition
这一种实现。