Spring源码系列——依赖注入(三)-属性注入

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Spring源码系列——依赖注入(三)-属性注入

前面文章中对依赖注入的触发和bean的创建做了学习记录,本文将来记录一下bean的属性注入过程。Bean的属性注入发生在 BeanDefinitionValueResolver这个类中, BeanDefinitionValueResolver这类是用于bean工厂实现的Helper类,职责就是将bean定义对象中包含的值解析为应用于目标bean实例的实际值。

BeanDefinitionValueResolver类中的 resolveValueIfNecessary()方法包含了对所有注入类型的处理。所以本文主要围绕这个方法展开来说。

resolveValueIfNecessary方法

resolveValueIfNecessary():给定一个PropertyValue,返回一个value,解析对工厂中其他bean的引用。 value可能是:

  • RuntimeBeanReference : 在解析到依赖的Bean的时侯,解析器会依据依赖bean的name创建一个RuntimeBeanReference对像,将这个对像放入BeanDefinition的MutablePropertyValues中。
  • ManagedList:用来保存它所管理的List元素,它可以包含运行时期的bean引用(将被解析为bean对象).
  • ManagedSet :用来保存它所管理的set值,它可以包含运行时期的bean引用(将被解析为bean对象)
  • ManagedMap :用来保存它所管理的map值,它可以包含运行时期的bean引用(将被解析为bean对象)
  • 1、方法申明

    argName :为其定义的参数的名称

    value   :要解析的值对象

    
    public Object resolveValueIfNecessary(Object argName, Object value)
    

    2、RuntimeBeanReference

    当在beanfactory中作为另外一个bean的引用时,作为属性值对象,将在运行时进行解析。 RuntimeBeanReference是在对BeanDefinition进行解析时生成的数据对象。

    
    if (value instanceof RuntimeBeanReference) {
        RuntimeBeanReference ref = (RuntimeBeanReference) value;
        return resolveReference(argName, ref);
    }
    

    3、RuntimeBeanNameReference

    当在beanfactory中作为另外一个bean名称的引用时,作为属性值对象,将在运行时进行解析。

    
    else if (value instanceof RuntimeBeanNameReference) {
        String refName = ((RuntimeBeanNameReference) value).getBeanName();
        refName = String.valueOf(doEvaluate(refName));
        if (!this.beanFactory.containsBean(refName)) {
            //异常:Invalid bean name '" + refName + "' in bean reference for " + argName
        }
        return refName;
    }
    

    4、BeanDefinitionHolder

    解析BeanDefinitionHolder:包含具有名称和别名的BeanDefinition。BeanDefinitionHolder就是使用名称或者别名来保存BeanDefinition的。

    
    else if (value instanceof BeanDefinitionHolder) {
        BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;
        return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
    }
    

    5、BeanDefinition

    解析纯粹的BeanDefinition

    
    else if (value instanceof BeanDefinition) {
        // Resolve plain BeanDefinition, without contained name: use dummy name.
        BeanDefinition bd = (BeanDefinition) value;
        String innerBeanName = "(inner bean)" + BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR +
                ObjectUtils.getIdentityHexString(bd);
        return resolveInnerBean(argName, innerBeanName, bd);
        }
    

    6、ManagedArray

    包含运行时期的bean引用(将被解析为bean对象)

    
    else if (value instanceof ManagedArray) {
        // May need to resolve contained runtime references.
        ManagedArray array = (ManagedArray) value;
        Class? elementType = array.resolvedElementType;
        if (elementType == null) {
            String elementTypeName = array.getElementTypeName();
            if (StringUtils.hasText(elementTypeName)) {
                try {
                    elementType = ClassUtils.forName(elementTypeName,
                    this.beanFactory.getBeanClassLoader());
                    array.resolvedElementType = elementType;
                }
                catch (Throwable ex) {
                    // Improve the message by showing the context.
                    //异常:Error resolving array type for " + argName
                }
            }
            else {
                elementType = Object.class;
            }
        }
        return resolveManagedArray(argName, (List?) value, elementType);
    }
    

    7、ManagedList,ManagedSet,ManagedMap

    包含运行时期的bean引用(将被解析为bean对象)

    
    //对ManagedList进行解析
    else if (value instanceof ManagedList) {
        return resolveManagedList(argName, (List?) value);
    }
    //对ManagedSet进行解析
    else if (value instanceof ManagedSet) {
        return resolveManagedSet(argName, (Set?) value);
    }
    //对ManagedMap进行解析
    else if (value instanceof ManagedMap) {
        return resolveManagedMap(argName, (Map?, ?) value);
    }
    

    8、ManagedProperties

    ManagedProperties表示的是一个spring管理的属性实例,它支持父/子 definition的合并。

    
    //对ManagedProperties进行解析
    else if (value instanceof ManagedProperties) {
        Properties original = (Properties) value;
        Properties copy = new Properties();
        for (Map.EntryObject, Object propEntry : original.entrySet()) {
            Object propKey = propEntry.getKey();
            Object propValue = propEntry.getValue();
            if (propKey instanceof TypedStringValue) {
                propKey = evaluate((TypedStringValue) propKey);
            }
            if (propValue instanceof TypedStringValue) {
                propValue = evaluate((TypedStringValue) propValue);
            }
            copy.put(propKey, propValue);
        }
        return copy;
    }
    

    9、TypedStringValue

    TypedStringValue保存的是一个类型的属性值。

    
    //对TypedStringValue进行解析
    else if (value instanceof TypedStringValue) {
        // Convert value to target type here.
        TypedStringValue typedStringValue = (TypedStringValue) value;
        Object valueObject = evaluate(typedStringValue);
        try {
            Class? resolvedTargetType = resolveTargetType(typedStringValue);
            if (resolvedTargetType != null) {
                return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
            }
            else {
                return valueObject;
            }
        }
        catch (Throwable ex) {
            // Improve the message by showing the context.
            throw new BeanCreationException(
            //异常:Error converting typed String value for " + argName
        }
    }
    

    10、作为表达式进行评估

    将给定的值作为表达式进行评估。

    
    else {
        return evaluate(value);
    }
    

    在完成上述解析之后,已经为我们的依赖注入做好了准备。这是真正把Bean对象设置到它所依赖的另一个Bean的属性中去的地方,可以看到,处理的属性也是各式各样的。具体属性的注入是在之前提到的BeanWrapper接口的实现类BeanWrapperImpl的setPropertyValue方法来完成。

    setPropertyValue方法

    a、方法声明

    这个方法是私有的,是BeanWrapperImpl实际处理的方法,其对外也提供了setPropertyValue的其它重载方法来提供服务。

    
    private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv)
        throws BeansException
    

    b、PropertyTokenHolder是BeanWrapperImpl的内部类

    
    private static class PropertyTokenHolder {
        public String canonicalName;
        public String actualName;
        public String[] keys;
    }
    

    在setPropertyValue方法中会根据tokens变量是否为null,有两个不同的分支。其中当tokens为null时,则会对属性名进行递归调用分析处理,返回分析处理后的BeanWrapImpl对象nestedBw。如果nestedBw==this,则会设置pv的resolvedTokens属性值,最后将调用nestedBw对象的设置属性值方法设置属性。来具体看看:

    c、其中当tokens为null时,即对集合类的域进行注入

    
    // 设置tokens的索引和keys
    PropertyTokenHolder getterTokens = new PropertyTokenHolder();
    getterTokens.canonicalName = tokens.canonicalName;
    getterTokens.actualName = tokens.actualName;
    getterTokens.keys = new String[tokens.keys.length - 1];
    System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
    Object propValue;
    //getPropertyValue用来获取Bean中对对象注入的引用;
    try {
        propValue = getPropertyValue(getterTokens);
    }
    catch (NotReadablePropertyException ex) {
    //异常:Cannot access indexed value in property referenced 
    }
    

    1、propValue为null

    propValue为null

    
    if (propValue == null) {
        // 空值映射的情况
        if (this.autoGrowNestedPaths) {
            // TODO: cleanup, this is pretty hacky
            int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
            getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
            propValue = setDefaultValue(getterTokens);
        }
        else {
        //异常:Cannot access indexed value in property referenced " +
        "in indexed property path '" + propertyName + "': returned null"
        }
    }
    

    2、对array进行注入

    对array进行注入

    
    if (propValue.getClass().isArray()) {
        PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
        Class requiredType = propValue.getClass().getComponentType();
        int arrayIndex = Integer.parseInt(key);
        Object oldValue = null;
        try {
            if (isExtractOldValueForEditor() && arrayIndex  Array.getLength(propValue)) {
                oldValue = Array.get(propValue, arrayIndex);
            }
            Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
                    requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
            Array.set(propValue, arrayIndex, convertedValue);
        }
        catch (IndexOutOfBoundsException ex) {
        //异常:Invalid array index in property path '" + propertyName
        }
    }
    

    2、对list进行注入

    对list进行注入

    
    else if (propValue instanceof List) {
        PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
        Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType(
                pd.getReadMethod(), tokens.keys.length);
        List list = (List) propValue;
        int index = Integer.parseInt(key);
        Object oldValue = null;
        if (isExtractOldValueForEditor() && index  list.size()) {
            oldValue = list.get(index);
        }
        Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
                requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
        int size = list.size();
        if (index = size && index  this.autoGrowCollectionLimit) {
            for (int i = size; i  index; i++) {
                try {
                    list.add(null);
                }
                catch (NullPointerException ex) {
                    //异常:InvalidPropertyException
                }
            }
            list.add(convertedValue);
        }
        else {
            try {
                list.set(index, convertedValue);
            }
            catch (IndexOutOfBoundsException ex) {
                //异常:Invalid list index in property path '" + propertyName + "'"
            }
        }
    }
    

    2、对map进行注入

    对map进行注入

    
    else if (propValue instanceof Map) {
        PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName);
        Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType(
                pd.getReadMethod(), tokens.keys.length);
        Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType(
                pd.getReadMethod(), tokens.keys.length);
        Map map = (Map) propValue;
        //重要提示:不要在这里传递完整的属性名称
        TypeDescriptor typeDescriptor = (mapKeyType != null ?
                TypeDescriptor.valueOf(mapKeyType) : TypeDescriptor.valueOf(Object.class));
        Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);
        Object oldValue = null;
        if (isExtractOldValueForEditor()) {
            oldValue = map.get(convertedMapKey);
        }
        // 在这里传递完整的属性名称和旧值,因为希望对map值有完整的转换能力。
        Object convertedMapValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
                mapValueType, TypeDescriptor.nested(property(pd), tokens.keys.length));
        map.put(convertedMapKey, convertedMapValue);
    }
    

    其中当tokens不为null时,即对非集合类的域进行注入

    这里是核心的地方,取得注入属性的set方法,通过反射机制,把对象注入进去。

    
    final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ?
        ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() :
        pd.getWriteMethod());
    

    总结

    通过上面的几篇分析我们大概的熟悉了Bean创建和对象依赖注入的一个过程,在这个过程中,spring需要根据Beandefinition中的信息来递归完成依赖注入。并且这些递归的入口都是getBean这个方法。

    一个递归是在上下文体系中查找需要的Bean和创建Bean的递归调用;

    另一个递归是在依赖注入时通过递归调用容器的getBean方法,得到当前Bean的依赖Bean,同时也触发对依赖Bean的创建和注入。

    在对Bean的属性进行依赖注入时解析的过程也是一个递归的过程。这样就可以根据依赖关系,一层一层的完成Bean的创建和注入,直到最后完成当前Bean的创建。

    参考

  • 《Spring技术内幕》
  • https://www.cnblogs.com/davidwang456/p/4213652.html
  • https://www.cnblogs.com/davidwang456/p/4213652.html

    原文始发于微信公众号(glmapper工作室):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> Spring源码系列——依赖注入(三)-属性注入


     上一篇
    Spring源码系列——依赖注入(二)createBean Spring源码系列——依赖注入(二)createBean
    在Spring源码系列:依赖注入(一)(AbstractBeanFactory-getBean)最后说道getBean是依赖注入的起点,bean的创建都是通过createBean来完成具体的创建的。createBean的具体实现是在Abst
    2021-04-05
    下一篇 
    Spring源码系列——依赖注入(四)-总结 Spring源码系列——依赖注入(四)-总结
    Spring源码系列:依赖注入(一)getBean Spring源码系列:依赖注入(二)createBean Spring源码系列:依赖注入(三)-属性注入 在上面三篇文章中对依赖注入做了一个大致的梳理;里面都是大量代码的分析
    2021-04-05