• Spring 属性注入(一)JavaBean 内省机制在 BeanWrapper 中的应用


    Spring 属性注入(一)JavaBean 内省机制在 BeanWrapper 中的应用

    Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

    Spring 中的属性注入也是基于 JDK 的 JavaBean 的内省,详见《JDK 之 JavaBean 内省机制》:https://www.cnblogs.com/binarylei/p/10204208.html

    一、BeanWrapper 的使用

    @Test
    public void test() {
        // 1.1 beanWrapper
        BeanWrapper beanWrapper = new BeanWrapperImpl(new Company());
        //BeanWrapper beanWrapper = new BeanWrapperImpl(Company.class);
    
        // 2.1 属性注入
        beanWrapper.setPropertyValue("name", "company");
        // 2.2 也可以这样,自动转 int
        PropertyValue pv = new PropertyValue("total", "20");
        beanWrapper.setPropertyValue(pv);
    
        // 2.3 嵌套注入,autoGrowNestedPaths=true 时当属性为 null 时自动创建对象
        beanWrapper.setAutoGrowNestedPaths(true);
        beanWrapper.setPropertyValue("director.name", "director");
        beanWrapper.setPropertyValue("employees[0].name", "binarylei");
    
        // 3.1 获取实例
        Company company = (Company) beanWrapper.getWrappedInstance();
        // 3.2 获取属性
        int total = (int) beanWrapper.getPropertyValue("total");
    }
    
    // JavaBean 如下,省略 get/set 方法
    public class Company {
        private String name;
        private int total;
    
        private Employee director;
        private Employee[] employees;
    
        public static class Employee{
            private String name;
            private double salary;
        }
    }
    

    那 Spring 是如何将一个字符串转化为 int 类型的呢?

    二、BeanWrapper 属性注入

    跟踪 setPropertyValue 代码到 AbstractNestablePropertyAccessor#processLocalProperty 方法

    // 简单属性注入,而 Array, Collection, Map 则走 processKeyedProperty 方法
    private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
        PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
        Object originalValue = pv.getValue();
        Object valueToApply = originalValue;
        // 1. 类型转换
        alueToApply = convertForProperty(
                        tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
        // 2. 利用 JavaBean 的内省机制设置属性值
        ph.setValue(valueToApply);
    }
    

    processLocalProperty 主要完成了两件事:一是类型转换;二是设置属性值。convertForProperty 利用 JDK 的 PropertyEditorSupport 进行类型转换,Spring 中内置了一批转换器,当然也可以自定义。而 setValue 则是使用反射进行赋值,关键代码如下:(BeanWrapperImpl#BeanPropertyHandler#setValue)

    writeMethod.invoke(getWrappedInstance(), value)
    

    我们再看一下 BeanPropertyHandler 是什么,其实 BeanPropertyHandler 只是对 PropertyDescriptor 的简单封装。代码如下:

    @Override
    protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
        PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
        return (pd != null ? new BeanPropertyHandler(pd) : null);
    }
    

    三、PropertyEditor 在 BeanWrapper 中的应用

    Spring 提供了两种类型的转换方式:一是 JDK 的 PropertyEditor;二是 Spring 提供的 ConversionService。

    既然 JDK 已经提供了 PropertyEditor,Spirng 为什么还要自己造轮子 ConversionService?其实是 JDK 的 PropertyEditor 只能从 String 类型转换为其他类型,而 ConversionService 支持从任何类型的转化。这里只关注 PropertyEditor 方式。

    BeanWrapper 将 JavaBean 类型转换都委托给了 TypeConverterDelegate 组件,这个组件有一个重要的属性 propertyEditorRegistry,可以通过这个注册器获取对应的属性编辑器 PropertyEditor。

    private final PropertyEditorRegistrySupport propertyEditorRegistry;
    

    跟踪 AbstractNestablePropertyAccessor#convertForProperty 到 TypeConverterDelegate#convertIfNecessary 方法中。

    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
                @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
        // 1. 用户自定义属性编辑器
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
    
        // 2. Spring 默认属性编辑器
        if (editor == null) {
            editor = findDefaultEditor(requiredType);
        }
    
        // 3. 执行类型转换
        convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
    }
    

    convertIfNecessary 方法将只是匹配可用的 PropertyEditor 而执行则交给 doConvertValue 完成,很显然 doConvertValue 会调用 PropertyEditor#setAsText 进行类型转换,每个方法只做一件事。

    // 判断是否要进行类型转换
    private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
            @Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {
        // 省略...
        Object convertedValue = newValue;
        if (convertedValue instanceof String) {
            if (editor != null) {
                // Use PropertyEditor's setAsText in case of a String value.
                String newTextValue = (String) convertedValue;
                return doConvertTextValue(oldValue, newTextValue, editor);
            } else if (String.class == requiredType) {
                returnValue = convertedValue;
            }
        }
        return returnValue;
    }
    
    // 调用 PropertyEditor 的 setAsText 进行类型转换 
    private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
        try {
            editor.setValue(oldValue);
        } catch (Exception ex) {
        }
        editor.setAsText(newTextValue);
        return editor.getValue();
    }
    

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    [leetCode]09.用两个栈实现队列
    ubuntu:无法获得锁;无法锁定管理目录
    [leetCode]07.重建二叉树
    [leetCode]剑指 Offer 06. 从尾到头打印链表
    [leetCode]剑指 Offer 05. 替换空格
    [leetCode]1330.翻转子数组得到最大的数组值
    [leetCode]312.戳气球
    UVALive
    CodeChef
    CodeChef
  • 原文地址:https://www.cnblogs.com/binarylei/p/10253428.html
Copyright © 2020-2023  润新知