在上一小节中,我们已经分析了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;
}