#### 每篇一句
> 具备了技术深度,遇到问题可以快速定位并从根本上解决。有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕
#### 相关阅读
[【小家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之IoC篇 - 深入了解Spring IoC(面试题)

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

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

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

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

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

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

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

随机推荐

  1. epplus输出成thml

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  2. 微信小程序把玩(二十二)action-sheet组件

    原文:微信小程序把玩(二十二)action-sheet组件 action-sheet组件是从底部弹出可选菜单项,估计也是借鉴IOS的设计添加的,action-sheet有两个子组件, action-s ...

  3. Quartz Cron 生成工具

    /** * 每周期 */ function everyTime(dom) { var item = $("input[name=v_" + dom.name + "]&q ...

  4. Linux 桌面玩家指南:19. 深入理解 JavaScript,及其开发调试工具

    特别说明:要在我的随笔后写评论的小伙伴们请注意了,我的博客开启了 MathJax 数学公式支持,MathJax 使用$标记数学公式的开始和结束.如果某条评论中出现了两个$,MathJax 会将两个$之 ...

  5. VS2013设置release版本可调试

    http://blog.csdn.net/caoshangpa/article/details/76575640

  6. 条款16:成对使用new和delete时要使用相同的形式

    请牢记: 如果在new表达式中使用[],必须在相应的delete表达式中也使用[]. new[]  对应  delete[] 如歌在new表达式中不适用[],一定不要在相应的delete表达式中使用[ ...

  7. 304902阿里巴巴Java开发手册1.4.0

    转自官网 前言 <阿里巴巴Java开发手册>是阿里巴巴集团技术团队的集体智慧结晶和经验总结,经历了多次大规模一线实战的检验及不断完善,系统化地整理成册,回馈给广大开发者.现代软件行业的高速 ...

  8. wed前端html/css简单理解

    开发工具: txt文本 / dreamwave:DW(cs6/cc) / Hbuilder / webstorm / sublime / vscode 前端: 知识架构: 3层: 结构 / 表现 / ...

  9. 我整理的一份来自于线上的Nginx配置(Nginx.conf),希望对学习Nginx的有帮助

    我整理了一份Nginx的配置文件说明,是真正经历过正式线上考验过.如果有优化的地方,也请朋友们指点一二,整理出一份比较全而实用的配置. 主要包含配置:负载均衡配置,页面重定向,转发,HTTPS和HTT ...

  10. python trojan development 2nd —— use python to send mail and listen to the key board then combine them

    请勿用于非法用途!!!!!本人概不负责!!!原创作品,转载说明出处!!!!! from pynput.keyboard import Key,Listener import logging impor ...