【视频来源于:B站up主孙帅suns Spring源码视频】【微信号:suns45】
学习总结
MargedBeanDefinition合并bean的定义
核心是createBeanInstance创建Bean实例,最后给我们返回了一个instanceWrapper,这个Wrapper在上面声明了,那它的类型是什么呢?BeanWrapper,这里其实是有两个问题是需要关注的。第一个问题就是 研究一个新框架的源码时候,研究起来非常乱,抓不住重点,我不能够像老师一样在很多代码中 挑出主干,为什么我不能呢?要想确定核心代码,我要告诉各位的是,确定核心代码是有两个非常重要的技巧的,一个技巧是望文知义 通过方法名字来猜,通过注释来猜,这只是针对Sprng的时候,当确定完是主干的时候可以通过debug的方式去验证这个问题,需要去看Debug窗口
经过这样的分析doCreateBean里面最核心的方法就是createBeanInstance帮我们完成对象的创建
这也就是说我们通过createBeanInstance完成了对象的创建但是同时对对象进行了一层包装,看BeanWrapper叫做包装,它肯定是包装了User,User的功能它肯定是具备的,同时我们再包一层的目的是什么?显然是进行了功能性的扩展。那这个功能性的扩展是什么呢?
接下来分析,找到BeanWrapper的定义,看看它的实现类BeanWrapperImpl
这个rootInstance和rootClass代表了什么呢?
rootInstance是User类,rootClass是User类型,还有一个
propertyValues【Map】propertyValues【PropertyValues】
考虑一个问题,日后在实战当中假定有个类叫User,User中有两个属性 一个是id 一个是name,如果想使用的话应该怎么办?
先获取User对象,通过user的相关方法获取对应的属性值,比如说通过user.getId(),通过user.getName(),来获取。这样调用起来是比较繁琐的,对于wrapper的封装思想就是把User对象存储到Wrapper中,同时把id和name也存储到Wrapper中,这样为了方便程序直接操作对象的属性。
spring要使用这些属性的目的是什么呢? 一方面是用这个属性 进行属性填充,另外一方面一定会设计到一个问题就是类型转化器。
作为类型转换器来讲他的核心作用是什么呢?把spring当中配置文件中的字符串信息,转换成我们所说的对应对象属性的实际类型。
我有一个User对应的可能会有一个xml 里面配置了bean标签有id和name值,此时配置文件中id的value值是字符串类型,此时在User当中持有的属性是什么类型呢?User中的id是Integer类型,我们应该在注入的过程当中把 integer的1填入进去而不是字符串的1填入进去,显然这是不行的,在spring中这么写了它也是没有问题的,它仍然能从string类型转换成integer类型,这里设计到了类型转换器,实际上我们说的wrapper进行对象的包装,很重要的一部分原因就是为了把这个属性对应的类型转换器也放置在Wrapper中。
这个Wrapper实际上是为了既存有对象本身,又存有对象的属性所需要的类型转换器。怎么证明这一点?
在 BeanWrapper中有这么一个东西
conversionService作用就是做自定义类型转换器,typeConverterDelegate 类型转换的一个委托对象。
整个包装beanWrapper的一个核心就是既把对象存起来又把它的属性获取存起来 同时还要匹配类型转换器。
有些同学会问,你老给我讲配置文件,那我要是基于注解该怎么办呢?基于注解为id赋值,我应该怎么赋值?如果要基于注解的话必须要加一个@Value注解它会读取对应properties相关配置文件的内容。
app.properties
user.id=1
@Value(${user.id})
public Integer id;
还需要注意的是加@PropertySource(配置文件名称)这个注解
采用注解跟配置文件是一样的,你配置文件存的是字符串,你也需要转成integer类型。
封装wrapper的目的就是把类型与之匹配的类型转换器进行配对儿式的封装便于后续的操作。
既然提到类型转换器,必须让大家有一个意识
有两种类型转换器
Stirng->Integer
String->List
String->数组类型
这些都是内置类型转换器可以解决的问题,spring内置好了。
string->Date 日期类型
因为有了这个对象之后它才可以进行封装,那么是怎么创建的呢?显然需要对象的描述信息,也就是我们说的BeanDefinition,这个方法一定是需要BeanDefinition,作为创建对象的参数传进去的,只有有了BeanDefinition的描述信息之后它才能按照这个描述信息把对象给我们创建出来,有了BeanDefintion之后,我们大概能推断出来 它肯定是通过反射的方式来创建对象。
进入到instantiateBean这个方法中,spring容器当中很少做安全校验,核心是getInstantiationStrategy().instantiate(mbd,beanName,parent)来创建实例然后就进行了 BeanWrapperImpl的封装 最终返回给客户。
进入getInstantiationStrategy().instantiate()这个方法中找到instantiateClass再进去找到ctor.newInstance(args)把你的构造参数给我,我调用构造方法来创建对象,这是核心。找到层层包装,底层还是调用了反射,这个代码我们自己写行不行,我们自己写肯定是可以的,测试获取反射对象。
//无参构造
Clazz clazz = Class.forName("")
Constcutor ctor = clazz.getDeclaredConstructor();
User user = (User)ctor.newInstance()
查看被包装的BeanWrapper,查看rootObject就是封装的User对象属性都是null目前只是完成了对象的创建,还没有进行填充。
查看typeConverterDelegate、conversionService是一种转换器,
customEditors、customEditorsForPath、customEditorCache是旧的类型转换器。
还有默认的类型转换器 defaultEditors
整个这么多属性除了这个与被包装的wrappedObject对象相关的一些属性之外,剩下的这些属性就都是以类型转换器相关的。
当讨论完创建对象这块的话
属性填充的核心是 populate() 关注点应该放在哪呢?
属性填充说的通俗一点就是set注入。
我们整个的注入其实上是分为 三种的,
一种 是set注入,一种是自动注入,一种是构造注入。
而构造注入显示是调用构造方法 也就是createBeanInstance在创建对象的同事完成构造注入。
填充属性这块不需要讨论构造注入,自动注入是在populateBean中是有所体现的,但是这部分源码我们要选择性的忽略
为什么要忽略,是因为我们现在已经没有人在使autowired/default-autowired,这部分我们永远都不可能用,可以选择性忽略不看。
最后筛来筛去,无外乎整个的注入实际上主要是set注入。
那这个时候在看一下set注入这块,它应该是怎么来做的?
首先从形式角度它分为两种
从形式上来讲分为xml类型和注解类型
从属性的类型上来讲又会分为 JDK所提供的,还有一种是用户自定义的类型/自建的类型
@Value等同于JDK类型
@Autowired 等同于自建类型的注入或者就是property ref的那种方式
既要包括 xml的解析,xml的解析包括JDK的也要包括自建的。同时也要兼顾注解 也要考虑JDK和用户自定义
第二个在属性填充过程中要注意的问题是什么呢?类型转换,刚才在BeanWrapper里面封装好了这个bean,实际上这个数据和对象的数据是分开的,本身的数据和对象的数据是分开的,它没有填充,一旦发生了填充,一定会涉及到类型转化,再回顾一下刚才的BeanWrapper,重新dubug再过一遍 查看instanceBeanWrapper查看rootObject,此时发现对象是空的说明还没有赋值,那么属性数据都存在哪了呢?找到它的描述符,就是BeanDefinition mdb,打开这个mbd,是没有一个成型的User对象,但是它里面会有User对象的描述信息,包括对象有什么样的属性,有什么样的数据,查看它的propertyValues里面有propertyValueList 里面有 name 和password这是它的属性,还有它的值在哪呢?name值在propertyValue里面在value里面,value是一个TypedStringValue类型的,password的value也是TypedStringValue类型。添加一个 Interger类型的id 值为1,发现这个id的value类型也是TypedStringValue,值是字符串1。
而最终这个值,我们肯定要赋给Integer id,显然会涉及到类型转化,在这个环节里面没有类型转化,mbd是在这拿的还没有到类型转化的工作,只是把对象给我们了,属性的数据还在mbd里面,那么在哪发生了转化?是在populateBean()发生了转化,所以此时发现在填充属性的过程当中,为什么需要这个instanceWrapper对象,我想为user填充数据,当然会需要user对象,有这个instanceWrapper就够了,但是user成员变量的数据是在mbd MergeBeanDefine中存着呢,所以它也必须给populateBean当做参数,刚才查看的id是字符串1,也就是说populateBean里面一定会涉及到字符串1变成Integer1 给user赋值,在填充属性的过程当中,永远不可能规避的一个核心是什么?涉及到类型转换,这个类型转换一定会涉及到 内置类型转换器,如果 配置了自定义类型转换器的时候 也会在这个populateBean中进行调用。
以上就是我们所说的在属性填充中要整体考虑的内容。
了解到populateBean的整个作用以及核心细节之后,然后进入方法中查看细节
1、先获取到mbd中的propertyValues
2、自动注入的处理,现在很少处理自动注入相关的,基本上没人使用 autowire=“byType/byName”,即使退1w步,注解没有成为主流,也没有人会使用。
3、判断注入形式是注解的还是property标签
4、基于注解的形式为你的成员变量完成注入一大块逻辑,通过beanPostProcessor的方式完成了基于注解的注入@Value @Autowired,我们当时在讲spring的BeanPostProcessor的时候不管是外部扩展还是内部扩展都是非常重要的。
用的是beanPostProcessor的形式,但是需要关注的是beanPostProcessor在spring的内部有很多,通过AutowiredAnnotationBeanPostProcessor完成了@Value @Autowired的赋值操作,你会发现它整个代码是需要循环的,但是只有在AutowiredAnnotationBeanPostProcessor处理的时候才会进行赋值,而赋值的核心是什么?ibp.postProcessProperties()
5、校验
6、pvs就是我们的属性值信息 applyPropertyValues(beanName,mbd,bw,pvs)非注解的属性填充
先关注非注解的属性填充
1、安全校验
2、获取PropertyValues赋值给original
3、有没有自定义类型转换器,没有就是用BeanWrapper
4、创建BeanDefinitionValueResolver valueResolver负责类型转换,自定义转换器是null,那它解决的一定是内置类型转换。
5、遍历PropertyValue 发生类型转换,拿属性name【id】,拿类型转换器value【TypeStringValue】,从类型转换器value中把value提出来【“1”】,拿转化好的value【把字符串1转换成integer 1】,判断是不是final 是否可写, convertForProperty()负责转换工作【把字符串 1 变成Integer 1】
6、把处理好的值 赋值给PropertyValue,循环结束标记转换完成
7、把转换好的数据交给bw,此时再看 发现User的属性 就有值了。内部肯定是通过反射完成的,根据name和value进行反射赋值。