24--Spring建立Bean的過程(六),bean屬性填充解析屬性值
在上一小節中,我們已經分析了Spring建立bean的時候,對bean屬性填充的簡要過程,這一過程是相當複雜的。
- 對於bean中的屬性,可能有String,int,甚至陣列,List,Map,Set等等,那麼Spring是如何通過解析beanDefinition從而將其轉換為合適的型別呢?
- 我們之前講過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;
}