#### 每篇一句
> 具备了技术深度,遇到问题可以快速定位并从根本上解决。有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕
#### 相关阅读
[【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor](https://blog.csdn.net/f641385712/article/details/90702928)

[【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用](https://blog.csdn.net/f641385712/article/details/95481552)
[【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor](https://blog.csdn.net/f641385712/article/details/95907073)

---
对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码)

---
#### 前言
书写此篇博文的缘由是出自一道面试题:面试题目大概如标题所述。
我个人认为这道面试题问得是非常有水平的,因为它涉及到的知识点既有深度,又有广度,可谓一举两得~~~因此在这里分享给大家。

为了给此文做铺垫,前面已经有两篇文章分别叙述了Java内省和`BeanWrapper`,而且还分析了底层接口:属性访问器(`PropertyAccessor`)。若对此部分还不是很了解的话,建议可以先出门左拐或者单击【相关阅读】里的链接~

#### Spring IoC和Java内省的依赖关系说明
Spring需要依赖注入就需要使用`BeanWrapper`,上章节说了`BeanWrapperImpl`的实现大都委托给了`CachedIntrospectionResults`去完成,而`CachedIntrospectionResults`它的核心说法就是**Java内省机制**。

从层层委托的依赖关系可以看出,`Spring IoC`的依赖注入(给属性赋值)是层层委托的最终给了Java内省机制,这是Spring框架设计精妙处之一。这也符合我上文所诉:**`BeanWrapper`这个接口并不建议应用自己去直接使用**~~~
那么本文就着眼于此,结合源码去分析Spring IoC容器它使用`BeanWrapper`完成属性赋值(依赖注入)之精华~

## Spring IoC中使用`BeanWrapper`源码分析
`Spring IoC`我相信小伙伴并不陌生了,但`IoC`的细节不是本文的重点。为了便于分析,我把这个过程画一个时序图描述如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190715215305226.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70)
有了这个简略的时序图,接下来就一步一步的分析吧
#### doCreateBean() 创建Bean
任何创建Bean的过程,都得经历`doCreateBean()`。这句代码我们已经非常熟悉了,它在`AbstractAutowireCapableBeanFactory`里:
```java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
...
// 这一步简单的说:通过构造函数实例化Bean后,new BeanWrapperImpl(beanInstance)包装起来
// 并且:initBeanWrapper(bw); 作用是注册ConversionService和registerCustomEditors() ...
instanceWrapper = createBeanInstance(beanName, mbd, args);
...
// 给属性赋值:此处会实施BeanWrapper的真正实力~~~~
// 注意:此处第三个参数传入的是BeanWrapper,而不是源生beanduixiang~~~
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
...
}
```
`doCreateBean`这个方法完成整个Bean的实例化、初始化。而这里面我们最为关注的自然就是`populateBean()`这个方法,它的作用是完成给属性赋值,从时序图中也可以看出这是一个入口
#### populateBean():给Bean的属性赋值~
```java
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
...
// 从Bean定义里面把准备好的值都拿出来~~~
// 它是个MutablePropertyValues,持有N多个属性的值~~~
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
...
for (BeanPostProcessor bp : getBeanPostProcessors()) {
...
// 此处会从后置处理,从里面把依赖的属性,值都拿到。比如大名鼎鼎的AutowiredAnnotationBeanPostProcessor就是在此处拿出值的~~~
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
...
pvs = pvsToUse;
}
...
// 若存在属性pvs ,那就做赋值操作吧~~~(本处才是今天关心的重点~~~)
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
```
深入到方法内部,它完成了k-v值的准备工作,很多重要的`BeanPostProcessor `也在此处得到执行。对于最终给属性赋值的步骤,是交给了本类的`applyPropertyValues()`方法去完成~~~
> 其实到了此处,理论上小伙伴就应该就能猜到接下来的**核心下文**了~

#### `applyPropertyValues()`:完成属性赋值
这个方法的处理内容才是本文**最应该关注的核心**,它在处理数据解析、转换这一块还是存在不小的复杂度的~
```java
// 本方法传入了beanName和bean定义信息,以及它对应的BeanWrapper和value值们~
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
if (pvs.isEmpty()) {
return;
}
...
MutablePropertyValues mpvs = null;
List original;

// 说明一下:为何这里还是要判断一下,虽然Spring对PropertyValues的内建实现只有MutablePropertyValues
// 但是这个是调用者自己也可以实现逻辑的~~~so判断一下最佳~~~~
if (pvs instanceof MutablePropertyValues) {
mpvs = (MutablePropertyValues) pvs;
// 此处有个短路处理:
// 若该mpvs中的所有属性值都已经转换为对应的类型,则把mpvs设置到BeanWrapper中,返回
if (mpvs.isConverted()) {
// Shortcut: use the pre-converted values as-is.
try {
bw.setPropertyValues(mpvs);
return;
} catch (BeansException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
// 否则,拿到里面的属性值们~~~
original = mpvs.getPropertyValueList();
} else {
original = Arrays.asList(pvs.getPropertyValues());
}

// 显然,若调用者没有自定义转换器,那就使用BeanWrapper本身~~~(因为BeanWrapper实现了TypeConverter 接口~~)
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}

// 获取BeanDefinitionValueResolver,该Bean用于将bean定义对象中包含的值解析为应用于目标bean实例的实际值。
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

// Create a deep copy, resolving any references for values.
// 此处翻译成深度拷贝不合适,倒不如翻译成深度解析更为合理~~~~
List deepCopy = new ArrayList(original.size());
boolean resolveNecessary = false;

// 遍历没有被解析的original属性值们~~~~
for (PropertyValue pv : original) {
if (pv.isConverted()) {
deepCopy.add(pv);
} else { // 那种还没被解析过的PropertyValue此处会一步步解析~~~~
String propertyName = pv.getName(); // 属性名称
Object originalValue = pv.getValue(); // 未经类型转换的值(注意:是未经转换的,可能还只是个字符串或者表达式而已~~~~)

// 最为复杂的解析逻辑~~~
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;

// 属性可写 并且 不是嵌套(如foo.bar,java中用getFoo().getBar()表示)或者索引(如person.addresses[0])属性
boolean convertible = bw.isWritableProperty(propertyName) && !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
// 用类型转换器进行转换
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}
if (resolvedValue == originalValue) {
if (convertible) {
pv.setConvertedValue(convertedValue);
}
deepCopy.add(pv);
} else if (convertible && originalValue instanceof TypedStringValue &&
!((TypedStringValue) originalValue).isDynamic() &&
!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
pv.setConvertedValue(convertedValue);
deepCopy.add(pv);
}
else {
resolveNecessary = true;
deepCopy.add(new PropertyValue(pv, convertedValue));
}
}
}

// 标记mpvs已经转换
if (mpvs != null && !resolveNecessary) {
mpvs.setConverted();
}

// Set our (possibly massaged) deep copy.
// 使用转换后的值进行填充~~~~~~~~~~
try {
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
} catch (BeansException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}

}

// 属性值的转换
@Nullable
private Object convertForProperty(@Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {
// 需要特别注意的是:convertForProperty方法是BeanWrapperImpl的实例方法,并非接口方法
// 这个方法内部就用到了CachedIntrospectionResults,从何就和Java内省搭上了关系~~~
if (converter instanceof BeanWrapperImpl) {
return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
} else { // 自定义转换器
PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
}
}
```
说明:`BeanDefinitionValueResolver`是Spring一个内建的非`public`类,它在上述步骤中承担了非常多的**任务**,具体可参考此处:[BeanDefinitionValueResolver和PropertyValues](https://blog.csdn.net/zhuqiuhui/article/details/82391851)

从命名中就能看出,它处理`BeanDefinition`的各式各样的情况,它主要是在`xml`配置时代起到了非常大的作用,形如这样:
```xml

```
因为我们知道在`xml`时代配置Bean非常的灵活:引用Bean、Map、List甚至支持SpEL等等,这一切权得益于`BeanDefinitionValueResolver`这个类来处理各种case~

其实在现在注解大行其道的今天,配置Bean我们大都使用`@Bean`来配置,它是一种工厂方法的实现,因此这个处理类的作用就被弱化了很多。但是,但是,但是,它仍旧是我们实施**定制化BeanDefinition**的一个有力武器~

`applyPropertyValues()`这一步完成之后,就彻底完成了对Bean实例属性的赋值。从中可以看到最终的赋值操作,核心依赖的就是这么一句话:
```java
bw.setPropertyValues(new MutablePropertyValues(deepCopy))
```
并且从转换的逻辑我们也需要知道的是:IoC并不是100%得使用`BeanWrapper`的,若我们是自定义了一个转换器,其实是**可以不经过Java内省机制**,而是直接通过反射来实现的,当然并不建议这么去做~
#### 总结
`BeanWrapper`体系相比于 Spring 中其他体系是比较简单的,它作为`BeanDefinition`向 `Bean`转换过程中的**中间产物**,承载了 bean 实例的**包装、类型转换、属性的设置**以及**访问**等重要作用(请不要落了访问这个重要能力)。

关于此面试题怎么去回答,如果是我主考我会这么评价回答:

1. 能答到`populateBean()`这里算是对这块知识入门了
2. 能答到`applyPropertyValues()`这里,那基本对此回答就比较满意了
3. 当然若能答到:通过自定义实现一个转换器+反射实现作为实现,而绕过Java内省机制。那势必就可以加分了~
1. 若达到自定义、个性化定义`BeanDefinition`这块(虽然与本问题没有必然关联),也是可以加分的(毕竟是面试而非考试~)
#### 知识交流
> **`若文章格式混乱,可点击`**:[原文链接-原文链接-原文链接-原文链接-原文链接](https://blog.csdn.net/f641385712/article/details/95933977)

==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被`作者本人许可的~`==

**若对技术内容感兴趣可以加入wx群交流:`Java高工、架构师3群`。
若群二维码失效,请加wx号:`fsx641385712`(或者扫描下方wx二维码)。并且备注:`"java入群"` 字样,会手动邀请入群**
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190703175020107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70,pic_center =300x)

【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的的更多相关文章

  1. 【小家Spring】老项目迁移问题:@ImportResource导入的xml配置里的Bean能够使用@PropertySource导入的属性值吗?

    #### 每篇一句 > 大师都是偏执的,偏执才能产生力量,妥协是没有力量的.你对全世界妥协了你就是空气.所以若没有偏见,哪来的大师呢 #### 相关阅读 [[小家Spring]详解Propert ...

  2. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...

  3. spring框架--IOC容器,依赖注入

    思考: 1. 对象创建创建能否写死? 2. 对象创建细节 对象数量 action  多个   [维护成员变量] service 一个   [不需要维护公共变量] dao     一个   [不需要维护 ...

  4. spring 中IOC实验(一)

    软件151  王帅 1.三个类,Human(人类)是接口,Chinese(中国人)是一个子类,American(美国人)是另外一个子类. 代码如下: package cn.com.chengang.s ...

  5. Spring之IOC原理及代码详解

    一.什么是IOC 引用 Spring 官方原文:This chapter covers the Spring Framework implementation of the Inversion of ...

  6. 初识Spring——Spring核心容器

    一. IOC和DI基础 IOC-Inversion of Control,译为控制反转,是一种遵循依赖倒置原则的代码设计思想. 所谓依赖倒置,就是把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑 ...

  7. 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor

    #### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...

  8. Spring笔记 #01# 一个小而生动的IOC例子代码

    索引 Spring容器的最小可用依赖 用XML定义元数据 实例化容器&使用容器 例子中仅包含两种类:英雄类Hero和武器类Weapon. 演示DI:给Hero初始化Weapon 演示AOP:法 ...

  9. 挖坟之Spring.NET IOC容器初始化

    因查找ht项目中一个久未解决spring内部异常,翻了一段时间源码.以此文总结springIOC,容器初始化过程. 语言背景是C#.网上有一些基于java的spring源码分析文档,大而乱,乱而不全, ...

随机推荐

  1. Django+Tastypie作后端,Backbone作前端的TodoMVC

    TodoMVC是各种js框架入门的比较经典的例子,详细可查看github地址https://github.com/tastejs/todomvc 接着上篇文章, 1,先在github上把backbon ...

  2. Visual Studio安装及单元测试

    一.安装环境 操作系统版本:Win10中文版64位 CPU:i5-4200M  2.50GHz 硬盘内存:500G 二.软件版本 Visual Studio 2013 三.安装过程 1.首先开始安装, ...

  3. 洛谷U2641 木板面积(area)——S.B.S.

    题目背景 一年一次的夏令营又要开始了,卡卡西和小伙伴们早就做好了准备,满心期 待着这趟快乐之旅.在一个阳光明媚的清晨,卡卡西在老师的带领下来到了这次 夏令营的首站——“神奇木材加工厂” . 题目描述 ...

  4. 分享Kali Linux 2016.2第45周镜像

    分享Kali Linux 2016.2第45周镜像Kali Linux官方于11月6日发布Kali Linux 2016.2的第45周的镜像.此次镜像维持了以往11个镜像文件的规模,包括32位.64位 ...

  5. CentOS安装+配置+远程

    这篇博客我之前写在了csdn,转了过来,这篇是自己认为写的比较有技术含量的文章^_^ 最近和CentOS打了交到,其中遇到了很多问题,于是看了一些博客,解决了一些问题,但是都不是特别全面,所以想来一篇 ...

  6. Windows 产品激活状态、密钥等信息查看

    目前,大多PC都是预装了微软家的桌面级系统 Windows ,这也算是微软一大得意之作.可是 Windows 产品可不是免费的,是要 $ 的.可能在中国,大多数系统可能是盗版过来的,像当年的雨林木风( ...

  7. js利用闭包封装自定义模块的几种方法

    1.自定义模块: 具有特定功能的js文件 将所有的数据和功能都封装在一个函数的内部 只向外暴露一个包含有n个方法的对象或者函数 模块使用者只需要通过模块暴露的对象调用方法来实现相对应的功能 1.利用函 ...

  8. dummy_backend_queue.go

    ) } func (d *dummyBackendQueue) Empty() error {     return nil }

  9. Redis源码 - 事件管理

    Redis 的事件分类 分类 描述 定时器 线程内定时响应,更新缓存时间.关闭非活动的客户端连接等等 pipe 线程间通信,用于其他线程通知主线程退出aeApiPoll() unixsocket 本地 ...

  10. PHP content-type为"application/json"的post过来的数据$_POST接受不到的问题

    ajax默认是以application/x-www-form-urlencoded方式提交.也就是常见的表单提交方式.在PHP中使用$_POST方式可以轻松获取. 但如果将ajax的请求头强制指定为a ...