资讯专栏INFORMATION COLUMN

【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Be

waruqi / 893人阅读

摘要:从层层委托的依赖关系可以看出,的依赖注入给属性赋值是层层委托的最终给了内省机制,这是框架设计精妙处之一。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的若对技术内容感兴趣可以加入群交流高工架构师群。

每篇一句
具备了技术深度,遇到问题可以快速定位并从根本上解决。有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕
相关阅读

【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor

【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor

对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的细节不是本文的重点。为了便于分析,我把这个过程画一个时序图描述如下:

有了这个简略的时序图,接下来就一步一步的分析吧

doCreateBean() 创建Bean

任何创建Bean的过程,都得经历doCreateBean()。这句代码我们已经非常熟悉了,它在AbstractAutowireCapableBeanFactory里:

    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的属性赋值~
    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():完成属性赋值

这个方法的处理内容才是本文最应该关注的核心,它在处理数据解析、转换这一块还是存在不小的复杂度的~

    // 本方法传入了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

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


    

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

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

applyPropertyValues()这一步完成之后,就彻底完成了对Bean实例属性的赋值。从中可以看到最终的赋值操作,核心依赖的就是这么一句话:

bw.setPropertyValues(new MutablePropertyValues(deepCopy))

并且从转换的逻辑我们也需要知道的是:IoC并不是100%得使用BeanWrapper的,若我们是自定义了一个转换器,其实是可以不经过Java内省机制,而是直接通过反射来实现的,当然并不建议这么去做~

总结

BeanWrapper体系相比于 Spring 中其他体系是比较简单的,它作为BeanDefinitionBean转换过程中的中间产物,承载了 bean 实例的包装、类型转换、属性的设置以及访问等重要作用(请不要落了访问这个重要能力)。

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

能答到populateBean()这里算是对这块知识入门了

能答到applyPropertyValues()这里,那基本对此回答就比较满意了

当然若能答到:通过自定义实现一个转换器+反射实现作为实现,而绕过Java内省机制。那势必就可以加分了~

    1. 若达到自定义、个性化定义`BeanDefinition`这块(虽然与本问题没有必然关联),也是可以加分的(毕竟是面试而非考试~)

知识交流
若文章格式混乱,可点击:原文链接-原文链接-原文链接-原文链接-原文链接

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

**若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群
若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群**

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75388.html

相关文章

  • 详叙BeanWrapperPropertyDescriptor

    摘要:关于它的数据转换使用了如下两种机制隶属于规范。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进中,这种对象称为值对象,或。 每篇一句 千古以来要饭的没有要早饭的,知道为什么吗? 相关阅读 【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、Pro...

    APICloud 评论0 收藏0
  • 小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    摘要:对中的数据绑定场景,小伙伴们就再熟悉不过了。比如包下大名鼎鼎的源码分析的源码相对来说还是颇为复杂的,它提供的能力非常强大,也注定了它的方法非常多属性也非常多。并且备注入群字样,会手动邀请入群 每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccesso...

    charles_paul 评论0 收藏0
  • @ImportResource导入的xml配置里的Bean能够使用@PropertySource导入

    摘要:关于使用这种方式我还有必要再说明一点若自己设置了加载属性文件,这句代码对此种场景就没有必要了,配置的占位符也是能够读取到的。 每篇一句 大师都是偏执的,偏执才能产生力量,妥协是没有力量的。你对全世界妥协了你就是空气。所以若没有偏见,哪来的大师呢 相关阅读 【小家Spring】详解PropertyPlaceholderConfigurer、PropertyOverrideConfigur...

    fancyLuo 评论0 收藏0
  • 最全面阐述WebDataBinder理解Spring的数据绑定

    摘要:每篇一句不要总问低级的问题,这样的人要么懒,不愿意上网搜索,要么笨,一点独立思考的能力都没有相关阅读小家聊聊中的数据绑定本尊源码分析小家聊聊中的数据绑定属性访问器和实现类的使用小家聊聊中的数据绑定以及内省和对感兴趣可扫码加 每篇一句 不要总问低级的问题,这样的人要么懒,不愿意上网搜索,要么笨,一点独立思考的能力都没有 相关阅读 【小家Spring】聊聊Spring中的数据绑定 --- ...

    cgspine 评论0 收藏0
  • Spring IOC 容器源码分析 - 创建原始 bean 对象

    摘要:你也会了解到构造对象的两种策略。构造方法参数数量低于配置的参数数量,则忽略当前构造方法,并重试。通过默认构造方法创建对象看完了上面冗长的逻辑,本节来看点轻松的吧通过默认构造方法创建对象。 1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续。在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程。本篇文章,我们就从战术的层面上,详细分析doCreat...

    sutaking 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<