1. 程式人生 > >24--Spring建立Bean的過程(六),bean屬性填充解析屬性值

24--Spring建立Bean的過程(六),bean屬性填充解析屬性值

在上一小節中,我們已經分析了Spring建立bean的時候,對bean屬性填充的簡要過程,這一過程是相當複雜的。

  1. 對於bean中的屬性,可能有String,int,甚至陣列,List,Map,Set等等,那麼Spring是如何通過解析beanDefinition從而將其轉換為合適的型別呢?
  2. 我們之前講過Spring可以通過Setter方法注入屬性,那麼這個加入bean中有name屬性,name屬性存在setName方法,這個方法何時會被呼叫呢?

答案就在bean屬性填充的過程中,本過程是比較複雜枯燥的,例如對bean屬性的型別轉換,String,int,陣列,List,Map,Set等等,都需要單獨去進行判斷,接下來我們通過原始碼來分析其是如何解析,如何被注入到bean例項中的。

#####1.新建測試

  • bean
package com.lyc.cn.day12;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:36
 */
public interface Animal {
	void sayHello();
}
package com.lyc.cn.day12;

import org.springframework.util.CollectionUtils;

import java.util.*;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:36
 */
public class Cat implements Animal {
	/** 姓名 **/
	private String name;

	/** 年齡 **/
	private int age;

	/** 注入List集合 **/
	private List<String> listNames;

	/***注入Set集合*/
	private Set<String> setNames;

	/** 注入Properties **/
	private Properties propertiesNames;

	/** 注入Map集合 **/
	private Map<String, String> mapNames;

	/** 注入陣列 **/
	private String[] arrayNames;

	/** 無參建構函式 **/
	public Cat() {

	}

	/**
	 * 建構函式
	 * @param name 姓名
	 * @param age  年齡
	 */
	public Cat(String name, int age) {
		this.name = name;
		this.age = age;
	}

	/**
	 * 列印陣列集合
	 */
	public void sayArrayNames() {
		System.out.println("注入陣列-->");
		if (arrayNames != null && arrayNames.length > 0) {
			for (int i = 0; i < arrayNames.length; i++) {
				System.out.println(arrayNames[i]);
			}
		}
		System.out.println();
	}

	/**
	 * 列印Map集合
	 */
	public void sayMapNames() {
		if (null != mapNames && mapNames.size() > 0) {
			System.out.println("注入Map集合-->");
			for (Map.Entry<String, String> entry : mapNames.entrySet()) {
				System.out.println("key= " + entry.getKey() + " value= " + entry.getValue());
			}
			System.out.println();
		}
	}

	/**
	 * 列印Properties屬性
	 */
	public void sayPropertiesNames() {
		if (null != propertiesNames) {
			System.out.println("注入properties屬性-->");
			System.out.println(propertiesNames.get("name"));
			System.out.println(propertiesNames.get("age"));
			System.out.println();
		}
	}

	/**
	 * 列印List集合
	 */
	public void sayListNames() {
		if (!CollectionUtils.isEmpty(listNames)) {
			System.out.println("注入List集合-->");
			for (String string : listNames) {
				System.out.println(string);
			}
			System.out.println();
		}
	}

	/**
	 * 列印Set集合
	 */
	public void saySetNames() {
		if (!CollectionUtils.isEmpty(setNames)) {
			System.out.println("注入Set集合-->");
			Iterator<String> iterator = setNames.iterator();
			while (iterator.hasNext()) {
				System.out.println(iterator.next());
			}
			System.out.println();
		}
	}

	@Override
	public void sayHello() {
		System.out.println("大家好, 我叫" + getName() + ", 我今年" + getAge() + "歲了\n");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public List<String> getListNames() {
		return listNames;
	}

	public void setListNames(List<String> listNames) {
		this.listNames = listNames;
	}

	public Set<String> getSetNames() {
		return setNames;
	}

	public void setSetNames(Set<String> setNames) {
		this.setNames = setNames;
	}

	public Properties getPropertiesNames() {
		return propertiesNames;
	}

	public void setPropertiesNames(Properties propertiesNames) {
		this.propertiesNames = propertiesNames;
	}

	public Map<String, String> getMapNames() {
		return mapNames;
	}

	public void setMapNames(Map<String, String> mapNames) {
		this.mapNames = mapNames;
	}

	public String[] getArrayNames() {
		return arrayNames;
	}

	public void setArrayNames(String[] arrayNames) {
		this.arrayNames = arrayNames;
	}
}
  • 配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="cat" class="com.lyc.cn.day12.Cat">

		<!--注入String-->
		<property name="name" value="美美"/>
		<!--注入int-->
		<property name="age" value="3"/>

		<!--注入List集合-->
		<property name="listNames">
			<!-- merge 父子bean是否合併條目 -->
			<list value-type="java.lang.String" merge="false">
				<value>張三</value>
				<value>李四</value>
				<value>王五</value>
			</list>
		</property>

		<!--注入Set集合-->
		<property name="setNames">
			<set value-type="java.lang.String" merge="true">
				<value>張三</value>
				<value>李四</value>
				<value>王五</value>
			</set>
		</property>

		<!--注入Map集合-->
		<property name="mapNames">
			<map key-type="java.lang.String" value-type="java.lang.String">
				<entry key="name" value="小明"/>
				<entry key="age" value="3"/>
			</map>
		</property>

		<!--注入陣列-->
		<property name="arrayNames">
			<array value-type="java.lang.String">
				<value>張三</value>
				<value>李四</value>
				<value>王五</value>
			</array>
		</property>

		<!--注入Properties-->
		<property name="propertiesNames">
			<props value-type="java.lang.String">
				<prop key="name">小明</prop>
				<prop key="age">3</prop>
			</props>
		</property>
	</bean>
</beans>
  • 測試類
package com.lyc.cn.day12;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:43
 */
public class MyTest {
	private XmlBeanFactory xmlBeanFactory;


	@Before
	public void initXmlBeanFactory() {
		System.out.println("========測試方法開始=======\n");
		xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("day12.xml"));
	}

	@After
	public void after() {
		System.out.println("\n========測試方法結束=======");
	}

	@Test
	public void test() {
		Cat cat = xmlBeanFactory.getBean("cat",Cat.class);
		cat.sayHello();
		cat.sayArrayNames();
		cat.sayListNames();
		cat.sayMapNames();
		cat.sayPropertiesNames();
		cat.saySetNames();
	}

}
========測試方法開始=======

大家好, 我叫美美, 我今年3歲了

注入陣列-->
張三
李四
王五

注入List集合-->
張三
李四
王五

注入Map集合-->
key= name value= 小明
key= age value= 3

注入properties屬性-->
小明
3

注入Set集合-->
張三
李四
王五


========測試方法結束======

這個測試類,我們在前面的章節中已經分析過,分別注入了 String,int,List,Set,Map,Properties等屬性,接下來我們來分析其是如何被注入的,這個過程相當複雜,一個小節是講不完的,這一小節,我們先來分析屬性值的解析過程。

2.屬性填充過程簡析
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
    // ① 如果PropertyValues為空,直接返回
    if (pvs.isEmpty()) {
        return;
    }

    // ②判斷安全管理器
    if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
        ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
    }

    //MutablePropertyValues是PropertyValues介面的預設實現類
    MutablePropertyValues mpvs = null;
    List<PropertyValue> original;

    // ③ 獲取bean的屬性集合
    // 如果pvs是MutablePropertyValues的例項,MutablePropertyValues是PropertyValues的預設實現
    if (pvs instanceof MutablePropertyValues) {
        // 將pvs轉換為MutablePropertyValues物件,並判斷mpvs是否已經經過轉換
        mpvs = (MutablePropertyValues) pvs;
        if (mpvs.isConverted()) {
            // 如果pvs已經轉換過,則直接設定屬性值無需再次轉換
            try {
                bw.setPropertyValues(mpvs);
                return;
            }
            catch (BeansException ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
            }
        }
        // 否則獲取原始PropertyValue集合
        original = mpvs.getPropertyValueList();
    }
    else {
        original = Arrays.asList(pvs.getPropertyValues());
    }

    // ④ 獲取型別轉換器
    TypeConverter converter = getCustomTypeConverter();
    if (converter == null) {
        converter = bw;
    }
    BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

    // ⑤ 通過深度拷貝,解析值引用
    List<PropertyValue> deepCopy = new ArrayList<>(original.size());
    boolean resolveNecessary = false;
    // 迴圈解析PropertyValues
    for (PropertyValue pv : original) {
        if (pv.isConverted()) {
            deepCopy.add(pv);
        }
        else {
            // 獲取屬性名稱
            String propertyName = pv.getName();
            // 獲取原始屬性值
            Object originalValue = pv.getValue();
            // 解析原始屬性值
            // 當注入集合屬性時,如果指定了,value-type,如value-type="java.lang.String",
            // 那麼resolveValueIfNecessary也會執行型別的轉換操作
            Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
            Object convertedValue = resolvedValue;
            // isWritableProperty-->判斷屬性是否可寫,如果屬性不存在返回false
            // isNestedOrIndexedProperty-->判斷是否索引屬性或者巢狀屬性
            boolean convertible = bw.isWritableProperty(propertyName) && !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
            if (convertible) {
                // 型別轉換
                convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
            }
            // Possibly store converted value in merged bean definition,in order to avoid re-conversion for every created bean instance.
            // ⑥快取已經轉換過的值,避免再次轉換
            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));
            }
        }
    }

    if (mpvs != null && !resolveNecessary) {
        mpvs.setConverted();
    }

    // ⑦設定屬性值.
    try {
        bw.setPropertyValues(new MutablePropertyValues(deepCopy));
    }
    catch (BeansException ex) {
        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
    }
}

applyPropertyValues方法進行屬性填充,大致經歷了上面七個步驟:

① 如果PropertyValues為空,直接返回 ②判斷安全管理器 ③ 如果pvs是MutablePropertyValues的例項,則嘗試獲取已經轉換過的值,否則獲取原始屬性值 ④ 獲取型別轉換器 ⑤ 通過深度拷貝,解析值引用 ⑥快取已經轉換過的值,避免再次轉換 ⑦設定屬性值 上面的步驟中,⑤和⑦比較重要也比較複雜,其他的步驟相信大家都能看懂,我們重點分析第⑤和第⑦步。

3.解析值引用

到第⑤步的時候,我們沒能通過獲取已經轉換過的值直接設定屬性,而是拿到了原始值集合,這裡就要對原始值進行轉換了。這也是我們提到的第一個問題的答案所在之處。這裡大致分為兩個步驟,1.解析值引用 2.轉換值引用。我們先來分析解析值引用

注意:當注入如List,Set屬性,如果配置檔案中配置了value-type屬性,那麼在解析值引用的同時會將集合中的值進行轉換。例如我們上面的配置:

<!--注入List集合-->
<property name="listNames">
    <!-- merge 父子bean是否合併條目 -->
    <list value-type="java.lang.String" merge="false">
        <value>張三</value>
        <value>李四</value>
        <value>王五</value>
    </list>
</property>

因為整個過程比較複雜,所以我們說明一下,避免同學們搞不清值解析和值轉換的時機。

我們繼續來看值解析的程式碼:

public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
    // We must check each value to see whether it requires a runtime reference to another bean to be resolved.

    // ① RuntimeBeanReference->執行時引用
    //   例如BeanA依賴BeanB,那麼在配置檔案中有通過配置ref標籤進行引用的,在解析BeanDefinition的時候,是不會直接例項化BeanB的,那麼這個引用就是RuntimeBeanReference
    if (value instanceof RuntimeBeanReference) {
        RuntimeBeanReference ref = (RuntimeBeanReference) value;
        return resolveReference(argName, ref);
    }
    // ② RuntimeBeanNameReference->沒弄明白
    else if (value instanceof RuntimeBeanNameReference) {
        String refName = ((RuntimeBeanNameReference) value).getBeanName();
        refName = String.valueOf(doEvaluate(refName));
        if (!this.beanFactory.containsBean(refName)) {
            throw new BeanDefinitionStoreException("Invalid bean name '" + refName + "' in bean reference for " + argName);
        }
        return refName;
    }
    // ③ 解析innerBean
    else if (value instanceof BeanDefinitionHolder) {
        // Resolve BeanDefinitionHolder: contains BeanDefinition with name and aliases.
        BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value;
        return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition());
    }
    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);
    }
    // ④ 解析陣列
    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.
                    throw new BeanCreationException(this.beanDefinition.getResourceDescription(),this.beanName,"Error resolving array type for "+argName, ex);
                }
            }
            else {
                elementType = Object.class;
            }
        }
        return resolveManagedArray(argName, (List<?>) value, elementType);
    }
    // ⑤ 解析List集合
    else if (value instanceof ManagedList) {
        // May need to resolve contained runtime references.
        return resolveManagedList(argName, (List<?>) value);
    }
    // ⑥ 解析Set集合
    else if (value instanceof ManagedSet) {
        // May need to resolve contained runtime references.
        return resolveManagedSet(argName, (Set<?>) value);
    }
    // ⑦ 解析Map集合
    else if (value instanceof ManagedMap) {
        // May need to resolve contained runtime references.
        return resolveManagedMap(argName, (Map<?, ?>) value);
    }
    // ⑧ 解析Properties集合
    else if (value instanceof ManagedProperties) {
        Properties original = (Properties) value;
        Properties copy = new Properties();
        original.forEach((propKey, propValue) -> {
            if (propKey instanceof TypedStringValue) {
                propKey = evaluate((TypedStringValue) propKey);
            }
            if (propValue instanceof TypedStringValue) {
                propValue = evaluate((TypedStringValue) propValue);
            }
            if (propKey == null || propValue == null) {
                throw new BeanCreationException(this.beanDefinition.getResourceDescription(), this.beanName,
                        "Error converting Properties key/value pair for " + argName + ": resolved to null");
            }
            copy.put(propKey, propValue);
        });
        return copy;
    }
    // ⑨ 解析字串
    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(this.beanDefinition.getResourceDescription(), this.beanName,
                    "Error converting typed String value for " + argName, ex);
        }
    }
    // ⑩ NullBean或者表示式
    else if (value instanceof NullBean) {
        return null;
    }
    else {
        return evaluate(value);
    }
}

針對value的不同有不同的解析方式

① RuntimeBeanReference->執行時引用,如果兩個Bean之間存在著依賴關係,在配置檔案中如果通過Setter方法注入了一方的例項屬性,就是RuntimeBeanReference了,這裡還包含了對迴圈引用的處理,我們在下面的章節詳細講解 ② RuntimeBeanNameReference->沒弄明白,從名字上看來跟RuntimeBeanReference相似,但是沒弄清楚其使用方式。 ③ 解析innerBean,BeanDefinitionHolder和BeanDefinition兩種型別用於解析內部bean,當我們希望某個bean只能服務於一個指定bean時,這時就需要用到內部bean,內部bean只能被其依賴的bean使用,在程式中無法通過getBean方法獲取其例項。 ④ 解析陣列 ⑤ 解析List集合 ⑥ 解析Set集合 ⑦ 解析Map集合 ⑧ 解析Properties集合 ⑨ 解析字串,如果通過上述解析無效的話,此時value值會被當做字串來處理,注意:當做字串來處理,並不代表value值注入到bean中的最終型別,其有可能是int,float等型別。 ⑩ NullBean或者表示式,對錶達式的解析,沒弄明白,也沒找到其使用場景。

下面我們針對開發中常用的屬性解析逐步分析

3.解析陣列

建立長度為配置檔案中指定的元素個數的陣列,迴圈元素並在此呼叫resolveValueIfNecessary解析元素。

private Object resolveManagedArray(Object argName, List<?> ml, Class<?> elementType) {
    // 建立長度為配置檔案中指定的元素個數的陣列
    Object resolved = Array.newInstance(elementType, ml.size());
    // 迴圈解析陣列中的元素,如果配置檔案中指定了value-type,resolveValueIfNecessary過程中會對元素進行型別轉換
    for (int i = 0; i < ml.size(); i++) {
        Array.set(resolved, i,resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i)));
    }
    return resolved;
}
4.解析List

與解析陣列的過程相似。

private List<?> resolveManagedList(Object argName, List<?> ml) {
    // 建立長度為配置檔案中指定的元素個數的List集合
    List<Object> resolved = new ArrayList<>(ml.size());
    for (int i = 0; i < ml.size(); i++) {
        // 迴圈解析元素,如果配置檔案中指定了value-type,resolveValueIfNecessary過程中會對元素進行型別轉換
        resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i)));
    }
    return resolved;
}
5.解析Set

與解析陣列的過程相似。

private Set<?> resolveManagedSet(Object argName, Set<?> ms) {
    // 建立長度為配置檔案中指定的元素個數的Set集合
    Set<Object> resolved = new LinkedHashSet<>(ms.size());
    int i = 0;
    for (Object m : ms) {
        // 迴圈解析元素,如果配置檔案中指定了value-type,resolveValueIfNecessary過程中會對元素進行型別轉換
        resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), m));
        i++;
    }
    return resolved;
}
6.解析Map

與解析陣列的過程相似。

private Map<?, ?> resolveManagedMap(Object argName, Map<?, ?> mm) {
    // 建立長度為配置檔案中指定的元素個數的Map集合
    Map<Object, Object> resolved = new LinkedHashMap<>(mm.size());
    // 迴圈解析元素,如果配置檔案中指定了value-type,resolveValueIfNecessary過程中會對元素進行型別轉換
    mm.forEach((key, value) -> {
        Object resolvedKey = resolveValueIfNecessary(argName, key);
        Object resolvedValue = resolveValueIfNecessary(new KeyedArgName(argName, key), value);
        resolved.put(resolvedKey, resolvedValue);
    });
    return resolved;
}