浅谈Spring源码之BeanDefinition

以云看科技2024-05-04 10:13:08  76

为什么需要读懂BeanDefinition

如果说java是由对象组成,那么spring-framework框架可以说是由BeanDefinition所构成。BeanDefinitiion其实是spring中的顶级接口,我们在阅读源码之前必须要先搞懂BeanDefinition的作用,以及成员变量的含义和其不同的实现类在spring中所扮演的角色。本文会详细解释spring初始化阶段所用到的BeanDefinition的实现类以及相应成员变量的含义。

BeanDefiniton与对象的关系

众所周知spring是一个管理bean的容器,那么在此有必要解释java中的对象和spring中的bean这两者的联系;我们的java文件在编译以后会生成class文件,jvm启动的时候会将classpath目录下的class文件加载到方法区,当项目中需要实例化某个类时就会根据类的符号引用找到方法区中的类文件元数据,也就是如下图中的User类中我们定义的id、name等等字段的类文件,此时可以将符号引用转换成实际引用,创建对象以后保存在堆上(当然从操作系统的角度来看,其实还是虚拟地址而不是真实的物理地址)。

java中所谓的对象,其实是对我们业务过程的一种抽象,而有了对象的存在也能够更方便的管理我们的业务过程,那么既然spring是用来管理java中的对象的,bean的含义其实也不言而喻,就是对我们对象的一种抽象,试想一下脱离spring框架,如果需要我们自己实现一个对于对象管理的工具要如何实现?首先肯定是需要一个类能够描述所有的业务对象,比如我们可以自己设计一个类叫做GeneralObject,伪代码如下:

public class GeneralObject { private class sourceObject; private String objectName; private File classFile;}

我们可以很自然的想到,要设计一个能够描述所有业务类的通用的类,那么肯定会需要所描述的类的元数据、实例化后对象的名字、类文件的地址等等。在spring框架中BeanDefinition就充当了这个角色,只不过spring框架比较强大,除了上述这些属性还增加了许多其他的属性,比如管理bean的实例化时机的属性scope等等,下面让我们对BeanDefinition这个顶级接口具体有哪些能力来一探究竟。

BeanDefinition的重要属性

与BeanDefinition的接口

BeanDefinition接口

package org.springframework.beans.factory.config;import org.springframework.beans.BeanMetadataElement;import org.springframework.beans.MutablePropertyValues;import org.springframework.core.AttributeAccessor;import org.springframework.lang.Nullable;public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; int ROLE_APPLICATION = 0; int ROLE_SUPPORT = 1; int ROLE_INFRASTRUCTURE = 2; void setParentName(@Nullable String parentName); @Nullable String getParentName; void setBeanClassName(@Nullable String beanClassName); @Nullable String getBeanClassName; void setScope(@Nullable String scope); @Nullable String getScope; void setLazyInit(boolean lazyInit); boolean isLazyInit; void setDependsOn(@Nullable String... dependsOn); @Nullable String[] getDependsOn; void setAutowireCandidate(boolean autowireCandidate); boolean isAutowireCandidate; boolean isPrimary; void setFactoryBeanName(@Nullable String factoryBeanName); @Nullable String getFactoryBeanName; void setFactoryMethodName(@Nullable String factoryMethodName); @Nullable String getFactoryMethodName; ConstructorArgumentValues getConstructorArgumentValues; default boolean hasConstructorArgumentValues { return !getConstructorArgumentValues.isEmpty; } MutablePropertyValues getPropertyValues; default boolean hasPropertyValues { return !getPropertyValues.isEmpty; } void setInitMethodName(@Nullable String initMethodName); @Nullable String getInitMethodName; void setDestroyMethodName(@Nullable String destroyMethodName); @Nullable String getDestroyMethodName; void setRole(int role); int getRole; void setDescription(@Nullable String description); @Nullable String getDescription; boolean isSingleton; boolean isPrototype; boolean isAbstract; @Nullable String getResourceDescription; @Nullable BeanDefinition getOriginatingBeanDefinition;}

与Spring中bean的生命周期有关的属性和方法

String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;//这两个很好理解,就是在Spring的bean工厂中定义的两个字符串常量//SCOPE_SINGLETON的值为singleton,SCOPE_PROTOTYPE的值为prototypeboolean isSingleton;boolean isPrototype;

以上两个常量很好理解,这里说明一下singleton与prototype的区别。

? singleton:单例bean,如果一个bean是单例的,那么在它被实例化以后就会被存放在DefaultListableBeanFactory类的singletonObjects上的(这也是很多博客中提到的,spring的单例池)map中。

? prototype:原型,每次需要bean的时候都会重新实例化一个,也就是每次都需要经过完整的bean生命周期。

单例和原型的处理方式有所不同,在spring源码中几乎处处都有体现,因为针对原型,如果每次都要解析需要注入的属性、构造这个类的对象所需要使用哪一个构造方法等,那么操作效率会很低,因此spring通过缓存的机制对此做了优化,这个等分析到具体代码的时候再详细阐述。

void setScope(@Nullable String scope);@NullableString getScope;//与scope相关的api

1. 懒加载

void setLazyInit(boolean lazyInit);boolean isLazyInit;

配置懒加载的bean,不会在Spring初始化的时候实例化bean,而是要等到使用这个bean的时候才会去初始化。

2. DependsOn

void setDependsOn(@Nullable String... dependsOn);@NullableString[] getDependsOn;

如果有一个UserService的bean,这个bean的初始化需要依赖于PowerService、RoleService。那么在UserService上声明@DependsOn标签,参数是String数组,传入PowerService、RoleService的BeanName即可。Spring在初始化UserService的时候会去判断所依赖的bean是否已经实例化完成了。

3. 生命周期回调方法

void setInitMethodName(@Nullable String initMethodName);@NullableString getInitMethodName;void setDestroyMethodName(@Nullable String destroyMethodName);@NullableString getDestroyMethodName;

实现生命周期回调方法,可以有三种方式:

1) 在xml中配置init-method="xxx"或者在某个方法上声明@PostConstruct,但是这两者Spring在处理的时候有些区别。通过xml配置底层是通过BeanDefinition的setInitMethodName方法,在某个后置处理器调用的时候会调用方法;而@PostConstruct是注解声明的方法,并不会去调用setInitMethodName方法,是在某个后置处理器解析到方法的时候就会直接调用。

2)直接为bean的BeanDefinition赋值:

GenericBeanDefinition testBean = (GenericBeanDefinition)beanFactory.getBeanDefinition("testBean");testBean.setDestoryMethodName("myDestory");

3)实现InitializingBean,重写afterPropertiesSet方法

在createBean的initializeBean方法中会执行生命周期回调方法。

exposedObject = initializeBean(beanName,exposedObject,mbd); ...invokeInitMethods(beanName,wrappedBean,mbd); ...((InitializingBean)bean).afterPropertiesSet; ...invokeCustomInitMethod(beanName,bean.mbd);

以上只是简单的列举调用回调方法的时机,具体代码等讲Bean生命周期的时候再来详细阐述。

4. 接口的多个实现的处理

void setAutowireCandidate(boolean autowireCandidate);boolean isAutowireCandidate;boolean isPrimary;

假设我们有一个UserService接口,其中有两个它的实现类,比如有UserServiceImpl与UserServiceWrapperImpl,我们有一个UserController,需要注入UserService

public class UserController { @Autowired private UserService userService;}

在这种情况下如果UserController的自动装配模型是byType,那么首先会根据UserService类型去Spring单例池中找对应的BeanName,这个时候会找出多个,再根据寻找出的beanName再去获取bean,这个时候如果beanName没有与注入属性的属性名一致,就无法正常注入,但通过Primary与AutowireCandidate注解后就可以实现注入。前者是将注解声明在属性上,参数是beanName,语义是该属性注入遇到多个实现类的时候,会选择注解中的beanName对应的bean作为要注入的bean。而后者是配置在实现类中,语义是当前这个bean不作为注入的候选bean参与注入。

5. 构造方法参数与成员变量属性集合

ConstructorArgumentValues getConstructorArgumentValues;default boolean hasConstructorArgumentValues { return !getConstructorArgumentValues.isEmpty;}MutablePropertyValues getPropertyValues;default boolean hasPropertyValues { return !getPropertyValues.isEmpty;}

这两个属性在xml中都有对应的配置,但是在注解方式中笔者翻阅了spring的doc文档,并没有找到相应的注解方式赋值(可能是xml注入bean元数据的这种方式是时代的淘汰物),这里给举个通过BeanDefinition注入的方式。

beanDefinition1.getPropertyValues.add("name","tom");beanDefinition.getConstructorArgumentValues.addGenericArgumentValue(new Test1,Test1.class.getName);

6. BeanClassName

void setBeanClassName(@Nullable String beanClassName);@NullableString getBeanClassName;

理解了bean与BeanDefinition与我们pojo实体类的关系,这两个方法就很容易理解了,保存的是类的全路径名。

7. ParentBeanDefinition

void setParentName(@Nullable String parentName);@NullableString getParentName;boolean isAbstract;

BeanDefinition可以设置一个父BeanDefinition,子BeanDefinition可以继承父BeanDefinition的所有属性。

RootBeanDefinition rootBeanDefinition = new RootBeanDefinition;rootBeanDefinition.setBeanClass(ParentBean.class);rootBeanDefinition.getPropertyValues.add("name","child");rootBeanDefinition.getPropertyValues.add("type","parent-type");//注册BeanDefinitionac.registerBeanDefinition("parent",rootBeanDefinition);GenericBeanDefinition beanDefinition = new GenericBeanDefinition;beanDefinition.setBeanClass(ChildBean.class);beanDefinition.getPropertyValues..add("name","child-type");beanDefinition.setParentName("parent");ac.registerBeanDefinition("child",beanDefinition);ac.register(AppCOnfig.class);ac.refresh;System.out.println(ac.getBean(ChildBean.class).getType);

可以看到最后打印出来的type属性值是parent-type,关于这里还需要说明的一点是isAbstract。因为前面我们说了,BeanDefinition的作用就是描述一个Bean,最后要实例化一个bean,那么为什么还要有是不是抽象的这么一个属性呢?原因就在于父子BeanDefinition的存在。试想一个场景,如果说某一类BeanDefinition的属性值很多都是相同的,那么我们就可以抽取一个抽象的父BeanDefinition,这个父BeanDefinition不需要被实例化,只是将属性值赋值给每个子BeanDefinition,当然这种用法目前应该是比较少见了。Spring在实例化之前会调用merge方法,会将子bd的父bd属性值合并下来(如果存在父bd)

8. 工厂方法

void setFactoryBeanName(@Nullable String factoryBeanName);@NullableString getFactoryBeanName;void setFactoryMethodName(@Nullable String factoryMethodName);@NullableString getFactoryMethodName;

Spring设计的这个FactoryBeanName与FactoryBeanMethod属性是很容易理解的,因为我们说过BeanDefinition的目的就是描述Bean、实例化Bean的,那么有工厂方法也是很正常的,如果某个bean的实例化过程比较复杂,通过工厂方法会使代码健壮性大大提高,比如实例化DataSource的时候。

9. 其他

//可以在一个类上声明Description注解,里面可以传入对这个bean的描述,用处不大void setDescription(@Nullable String description);@NullableString getDescription;//用来描述当前bean的class文件的路径@NullableString getResourceDescription;

resourceDescription的打印信息

file[G:spring-frameworkspring-demooutproductionclassesaspectTestBean1.class]

当然在AbstractBeanDefinition中还新增加了一些属性,这些等到源码分析到的时候再聊。除了这些成员变量和方法,BeanDefinition接口还继承了AttributeAccessor和BeanMetadataElement这两个接口。

BeanDefinition继承的接口

public interface AttributeAccessor { void setAttribute(String name, @Nullable Object value); @Nullable Object getAttribute(String name); @Nullable Object removeAttribute(String name); boolean hasAttribute(String name); String[] attributeNames;}

要说明AttributeAccessor接口的使用,要先举一个老生常谈的例子,spring的配置类如何正确使用?

@Configuration@ComponentScan("com")public class AopConfig { @Bean public CycleA cycleA { return new CycleA; } @Bean public CycleB cycleB { CycleA cycleA = cycleA; //do something with cycleA ... return new CycleB; }}@Component@ComponentScan("com")public class AopConfig { @Bean public CycleA cycleA { return new CycleA; } @Bean public CycleB cycleB { CycleA cycleA = cycleA; //do something with cycleA ... return new CycleB; }}

以上两段代码,在spring中作为配置类都可以正常启动,不会报错,但是这里有两个问题,为什么我们一个配置类要加@Configuration注解?为什么@Component在spring中也可以正常使用?其次,假设生成CycleB对象的时候需要使用到CycleA对象,上述这种方式是否破坏了spring Bean单例的原则?因为我们分明手动调用了实例化CycleA对象的方法。笔者在前工作中遇到的具有多年经验的开发工程师也对这个问题感到不解,那么我们可以看一下这两个不同的注解spring是如何处理的。

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); System.out.println(context.getBean(AppConfig.class));

打印配置类的元数据可以发现,第一种方式打印的是

com.config.AppConfig$$EnhancerBySpringCGLIB$$33f14ba8@588df31b

而第二种方式打印的是

com.config.AppConfig@6ee52dcd

这里可能你会有个疑问,对于一个加了@Configuration注解的配置类,spring为什么要去通过cglib进行代理,而不同的注解生成的配置类对象不同又是如何做到的?

其中的奥秘就在于上述的AttributeAccessor接口中,针对@Configuration注解的类,spring在解析的时候会在attributs属性中(attributes最后的实现其实就是LinkedHashMap,用来存放属性名-属性值的键值对)增加一个描述,标识该类是一个配置类。这个map的作用也是用来描述一个bean的,它所描述的是除了BeanDefinition以外的信息,比如一个类是不是配置类。

beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE,Boolean.TRUE);

至于为什么要对配置类进行代理,以及是否破坏spring单例原则的问题,笔者在后续的spring源码解读中会对此展开重点讲解,在此先跳过这个问题。

BeanMetadataElement实现类比较简单,只额外提供了一种方法,可以获取到当前bean的class文件对象。与BeanDefinition的getResourceDescription不同,这不是一个字符串而是一个FileSystemResource对象,提供了方法可以去读写这个class文件。

然而这两个接口有一个共同的实现类BeanMetadataAttributeAccessor

public interface BeanMetadataElement { Object getSource;}

最后列举一下BeanDefinition接口的关系图:

BeanDefinition的相关实现类

1. RootBeanDefinition与ChildBeanDefinition

RootBeanDefinition有两个作用,一个是用来描述某个Bean注册到BeanDefinitionMap当中,另一个作用就是当作父bd,用来做合并,合并的时候会实例化一个新的RootBeanDefinition,父bd属性赋值给新实例化的rdb,父bd调用set方法,将子bd的属性赋值给自己。

mbd = new RootBeanDefinition(pbd);mbd.overrideFrom(bd);

现在RootBeanDefinition主要是在调用合并方法的时候会用到。

2. GenericBeanDefinition

这个BeanDefinition已经可以完全取代ChildBeanDefinition

GenericBeanDefinition is a one-stop shop for standard bean definition purposes.

Like any bean definition, it allows for specifying a class plus optionally

constructor argument values and property values. Additionally, deriving from a parent bean definition can be flexibly configured through the "parentName" property.

以上的java doc是在GenericBeanDefinition类中作者给出的注释,GenericBeanDefinition并没有实现特别的功能。作者也说了这个BeanDefinition是一个用来定义标准Bean的,而且可以设置父bd,也就是说它本身可以将自己这个类的实例对象作为父bd,那么也就可以完全取代ChildBeanDefinition。

package org.springframework.beans.factory.support;import org.springframework.beans.factory.config.BeanDefinition;import org.springframework.lang.Nullable;import org.springframework.util.ObjectUtils;@SuppressWarnings("serial")public class GenericBeanDefinition extends AbstractBeanDefinition { @Nullable private String parentName; public GenericBeanDefinition { super; } public GenericBeanDefinition(BeanDefinition original) { super(original); } @Override public void setParentName(@Nullable String parentName) { this.parentName = parentName; } @Override @Nullable public String getParentName { return this.parentName; } @Override public AbstractBeanDefinition cloneBeanDefinition { return new GenericBeanDefinition(this); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof GenericBeanDefinition)) { return false; } GenericBeanDefinition that = (GenericBeanDefinition) other; return (ObjectUtils.nullSafeEquals(this.parentName, that.parentName) && super.equals(other)); } @Override public String toString { StringBuilder sb = new StringBuilder("Generic bean"); if (this.parentName != null) { sb.append(" with parent '").append(this.parentName).append("'"); } sb.append(": ").append(super.toString); return sb.toString; }}

GenericBeanDefinition继承自AbstractBeanDefinition,我们可以看到该类并没有实现别的功能。

3. ConfigurationClassBeanDefinition

在java-config配置类中,通过@Bean注入方式加入到spring中的bean都是以这个类来定义属性的,这种bean内部没有存beanClasss属性,而是保存了beanMetho。

@Beanpublic BeanTest beanTest { return new BeanTest;}BeanDefinition definition = ac.getBeanDefinition("beanTest");//打印结果为class org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader$ConfigurationClassBeanDefinitionSystem.out.println(definition.getClass);//打印结果为 nullSystem.out.println(definition.getBeanClassName);

说明ConfigurationClassBeanDefinition并没有报错beanClass。可以看到ConfigurationClassBeanDefinition是一个内部类,ConfigurationClassBeanDefinitionReader是Spring中特别重要的一个类,ConfigurationClassBeanDefinition比父类多了两个属性。

private final AnnotationMetadata annotationMetadata;private final MethodMetadata factoryMethodMetadata;

可以看到ConfigurationClassBeanDefinitio中保存的两个属性,第一个是保存这个bean的方法所在的那个类,以及该类的注解等信息,第二属性是生成该类的方法。看到这里我们也可以想到为什么不需要保存beanClass,因为这个bean是由一个声明了@Bean的方法所实例化的。

4. AnnotatedGenericBeanDefinition

通过register方法注册的类都是通过这个类型的bean来描述的

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext;ac.register(AppConfig.class);AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);

5. ScannedGenericBeanDefinition

如果一个类是通过@Component注解方式扫描进Spring的,那么会为每一个class实例化ScannedGenericBeanDefinition来描述bean。

以上就是spring中BeanDefinition以及实现类的相关介绍,在后续讲解spring初始化流程、spring扩展点、第三方如何集成spring的源码中会大量出现以上关于BeanDefinition的相关知识。

佳成,来自缦图互联网中心中台团队。

转载此文是出于传递更多信息目的。若来源标注错误或侵犯了您的合法权益,请与本站联系,我们将及时更正、删除、谢谢。
https://www.414w.com/read/423981.html
0
随机主题
奢华无界 劳斯莱斯Black Badge库里南系列II诠释当代颠覆精神大S回应“张兰称孩子退学”:请停止造谣,赔礼道歉0-3! 奥预赛黑马惨败, NO.2被横扫, 亚洲首败, 史诗级决胜局诞生决胜局终极绝杀,希金斯再现巅峰时刻,翻袋助力单局逆转惹众怒! 南通支云转争议文章, 内涵泰山申花球迷, 遭球迷集体抵制保时捷纯电第二弹,保时捷纯电Macan《法外枭雄: 滚石城》Steam版6月18日发售第一视角试驾视频 2023款 捷尼赛思 G90 1/5阿为特30.0%涨停, 总市值19.06亿元几千块钱的练手代步车吉利全球鹰,可惜好多人不会开手动挡了美共和党籍议员急喊制裁ICC: 今天是以色列, 下一个就会是我们!千元档王炸, vivo Y200 GT: 旗舰同款大电池, 重新定义Y系列~斗罗大陆: 92%神性, 唐三成为封号斗罗, 99级以下没人能将他打败女子为了帮衬娘家, 婚内转移财产300万, 丈夫得知后诉讼离婚赵燕菁: 房地产新政本质是救债务端, 这关乎中美博弈的走势国外网友看我们的四线城市,怎么显得有点激动贪财又贪色! 德不配位的4位老戏骨, “晚节不保”真的一点都不冤《庆余年2》范闲的底牌上线, 原著中最强卧底!踩单车也能玩出新花样!9种变化动作 让你成为足球高手据《华尔街日报》报道, 挪威在天然气管道破裂后追随新北极熊郭德纲唱戏水平真的差吗?众多业内大佬出面力挺
最新回复(0)