前言
接上篇介绍,本次我们实现的是setter注入。
说明
这是学习刘欣老师《从零开始造Spring》课程的学习笔记
数据结构的表达
xml中的配置
<bean id="petStoreService"
class="com.jay.spring.service.v2.PetStoreService">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<property name="owner" value="xiangwei"/>
<property name="version" value="2"/>
</bean>
<bean id="accountDao" class="com.jay.spring.dao.v2.AccountDao"></bean>
<bean id="itemDao" class="com.jay.spring.dao.v2.ItemDao"/>
思考:我们使用BeanDefinition表达了<bean>
标签中的id,class。 那么对于property 怎么表达?ref 怎么表达? value 怎么表达?
我们新建一个PropertyValue类用于存放<property>
标签中的name和 ref 或者value。
类图如下:
如果是ref 类型的我们还需要将其实例名保存到RuntimeBeanReference
中,如果是value 类型的我们需要将其值保存到TypedStringValue
中
关键代码实例
- GenericBeanDefinition 类,存放property的数据结构
public class GenericBeanDefinition implements BeanDefinition{
private String beanClassName;
private List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
public String getBeanClassName() {
return this.beanClassName;
}
@Override
public List<PropertyValue> getPropertyValues() {
return this.propertyValueList;
}
}
- XmlBeanDefinitionReader 类,主要用于解析xml,读取其中的配置
public class XmlBeanDefinitionReader {
public static final String PROPERTY_ELEMENT = "property";
public static final String REF_ATTRIBUTE = "ref";
public static final String VALUE_ATTRIBUTE = "value";
public static final String NAME_ATTRIBUTE = "name";
public void parsePropertyElement(Element beanElem, BeanDefinition bd) {
Iterator iterator = beanElem.elementIterator(PROPERTY_ELEMENT);
while (iterator.hasNext()) {
Element propElem = (Element) iterator.next();
// 取出name 元素
String propertyName = propElem.attributeValue(NAME_ATTRIBUTE);
// 元素如果为空直接返回
if (!StringUtils.hasLength(propertyName)) {
logger.fatal("Tag 'property' must have a 'name' attribute");
return;
}
Object val = parsePropertyValue(propElem, bd, propertyName);
PropertyValue pv = new PropertyValue(propertyName, val);
bd.getPropertyValues().add(pv);
}
}
public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
//分别取出ref属性和value属性。
boolean hasRefAttribute = (ele.attribute(REF_ATTRIBUTE) != null);
boolean hasValueAttribute = (ele.attribute(VALUE_ATTRIBUTE) != null);
if (hasRefAttribute) {
String refName = ele.attributeValue(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
logger.error(elementName + " contains empty 'ref' attribute");
}
RuntimeBeanReference ref = new RuntimeBeanReference(refName);
return ref;
} else if (hasValueAttribute) {
TypedStringValue typedStringValue = new TypedStringValue(ele.attributeValue(VALUE_ATTRIBUTE));
return typedStringValue;
} else {
throw new RuntimeException(elementName + " must specify a ref or value");
}
}
}
读取出来ref 或者value之后,我们如何将其实例化呢?对此我们新建了一个类BeanDefinitionValueResolve
来处理value 的值。
代码如下:
public class BeanDefinitionValueResolve {
private final BeanFactory beanFactory;
public BeanDefinitionValueResolve(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public Object resolveValueIfNecessary(Object value) {
if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String refName = ref.getBeanName();
Object bean = this.beanFactory.getBean(refName);
return bean;
} else if (value instanceof TypedStringValue) {
return ((TypedStringValue) value).getValue();
} else {
throw new RuntimeException("the value " + value + " has not implemented");
}
}
}
那么如果获取bean,并且设置属性呢:
关键代码如下:(DefaultBeanFactory类中)
public Object createBean(BeanDefinition bd) {
// 创建实例
Object bean = instantiateBean(bd);
//设置属性
populateBean(bd, bean);
// populateBeanUseCommonBeanUtils(bd,bean);
return bean;
}
private Object instantiateBean(BeanDefinition bd) {
ClassLoader beanClassLoader = this.getBeanClassLoader();
String beanClassName = bd.getBeanClassName();
try {
Class<?> clz = beanClassLoader.loadClass(beanClassName);
return clz.newInstance();
} catch (Exception e) {
throw new BeanCreationException("create bean for "+ beanClassName +" failed",e);
}
}
/**
* 调用set方法进行setter注入
* @param bd
* @param bean
*/
protected void populateBean(BeanDefinition bd, Object bean) {
// 获取一个bean下所有的PropertyValue
List<PropertyValue> pvs = bd.getPropertyValues();
if (pvs == null || pvs.isEmpty()) {
return;
}
// 实例化BeanDefinitionValueResolve,并传入当前的DefaultBeanFactory
BeanDefinitionValueResolve valueResolve = new BeanDefinitionValueResolve(this);
SimpleTypeCoverter coverter = new SimpleTypeCoverter();
try {
for (PropertyValue pv : pvs) {
String propertyName = pv.getName();
//对于ref来说就是beanName,对于value 来说就是value
Object originalValue = pv.getValue();
Object resolvedValue = valueResolve.resolveValueIfNecessary(originalValue);
// 假设现在originalValue表示的是ref=accountDao,已经通过resolve得到了accountDao对象,接下来
// 如何调用petStoreService的setAccountDao方法?
// 注释:使用到了java.beans 中的Introspector类拿到bean的相关信息,包括其属性,方法
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (pd.getName().equals(propertyName)) {
Object convertedValue = coverter.convertIfNecessary(resolvedValue, pd.getPropertyType());
//通过反射的方式调用set方法
pd.getWriteMethod().invoke(bean, convertedValue);
break;
}
}
}
} catch (Exception e) {
throw new BeanCreationException("Failed to obtain BeanInfo for class["+bd.getBeanClassName()+"]",e);
}
}
调试如下:
类型转化
类图如下:
解析说明:
TypeConverter 类型转化接口,将传入的值转化为其需要的类型。
SimpleTypeCoverter 是TypeConverter接口的一个实现。其依赖于java.beans中的PropertyEditor,其类似于java GUI中的编程,例如:拖拽一个button, 然后,设置其颜色,长度,宽度,这些都属于button的属性,在java.beans中将这些抽象成了一个PropertyEditor 接口。 setAsText(), 例如button 的高度,值是什么跟属性的类型密切相关。
代码示例
public class CustomNumberEditor extends PropertyEditorSupport {
private final Class<? extends Number> numberClass;
private final boolean allowEmpty;
private final NumberFormat numberFormat;
public CustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty) {
this(numberClass, allowEmpty,null);
}
public CustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty, NumberFormat numberFormat) {
if (numberClass == null || !Number.class.isAssignableFrom(numberClass)) {
throw new IllegalArgumentException("Property class must be a subclass of Number");
}
this.numberClass = numberClass;
this.allowEmpty = allowEmpty;
this.numberFormat = numberFormat;
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (this.allowEmpty && !StringUtils.hasText(text)) {
setValue(null);
} else if (this.numberFormat != null) {
setValue(NumberUtils.parseNumber(text, this.numberClass, this.numberFormat));
} else {
setValue(NumberUtils.parseNumber(text, this.numberClass));
}
}
public void setValue(Object value) {
if (value instanceof Number) {
super.setValue(NumberUtils.convertNumberToTargetClass((Number) value, this.numberClass));
} else {
super.setValue(value);
}
}
public String getAsText() {
Object value = getValue();
if (value == null) {
return "";
} else if (this.numberFormat != null) {
return this.numberFormat.format(value);
} else {
return value.toString();
}
}
}
SimpleTypeConverter 类
/**
* 值的类型转化
* @param value 3
* @param requiredType Integer.class
* @param <T>
* @return
* @throws TypeMismatchException
*/
@Override
public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException {
// 值能不能直接赋值呢,能,直接返回
if (ClassUtils.isAssignableValue(requiredType, value)) {
return (T) value;
} else {
// 只支持字符串
if (value instanceof String) {
PropertyEditor defaultEditor = findDefaultEditor(requiredType);
try {
defaultEditor.setAsText((String) value);
} catch (IllegalArgumentException e) {
//非法参数异常
throw new TypeMismatchException(value, requiredType);
}
return (T) defaultEditor.getValue();
} else {
throw new RuntimeException("Todo : can't convert value for "+value +" class:"+requiredType);
}
}
}
private PropertyEditor findDefaultEditor(Class<?> requiredType) {
// 查找DefaultEditor
PropertyEditor defaultEditor = this.getDefaultEditor(requiredType);
if (defaultEditor == null) {
throw new RuntimeException("Editor for" + requiredType + "has not been implemented");
}
return defaultEditor;
}
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
if (defaultEditors == null) {
createDefaultEditors();
}
return defaultEditors.get(requiredType);
}
private void createDefaultEditors() {
this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
}
———————————-分割线——————————————————–
使用common-beanutil设置bean的属性
说明: Commons BeanUtils 类的
BeanUtils.setProperty(bean, propertyName,propertyValue)
bean : java对象,例如petStoreService
propertyName,例如: “version”,”accountDao”
propertyValue,例如:3, accountDao对象
代码示例
private void populateBeanUseCommonBeanUtils(BeanDefinition bd, Object bean) {
List<PropertyValue> pvs = bd.getPropertyValues();
if (pvs == null || pvs.isEmpty()) {
return;
}
BeanDefinitionValueResolve valueResolve = new BeanDefinitionValueResolve(this);
try {
for (PropertyValue pv : pvs) {
String propertyName = pv.getName();
Object originalValue = pv.getValue();
Object resolve = valueResolve.resolveValueIfNecessary(originalValue);
BeanUtils.copyProperty(bean, propertyName, resolve);
}
} catch (Exception e) {
throw new BeanDefinitionException("Populate bean property failed for["+bd.getBeanClassName()+"");
}
}
———————-答疑总结—————————-
- 职责分离的问题:解析的时候只做解析的事,不要做额外的工作。
- Open Close 对修改封闭,对扩展开放,例如:
AbstractApplicationContext
。流程定下来了。通过
public abstract Resource getResourceByPath(String configFile);
进行扩展。
3. 单一职责
4. 对接口编程,不对实现编程
5. 优先使用组合。
6. 现在PropertyValue中value有两种类型,是否可以封装成两种PropertyValue
RuntimeBeanReferencePropertyValue和TypedStringValuePropertyValue呢?
答:
1. PropertyValue类中新增一个resolve的抽象方法
public abstract Object resolve(BeanFactory beanFactory);
- RuntimeBeanReferencePropertyValue类
public class RuntimeBeanReferencePropertyValue extends PropertyValue {
public RuntimeBeanReferencePropertyValue(String name,String value) {
this.name = name;
this.value = value;
}
@Override
public Object resolve(BeanFactory factory) {
return factory.getBean(name);
}
}
- TypedStringValuePropertyValue类
public class TypedStringValuePropertyValue extends PropertyValue {
public TypedStringValuePropertyValue(String name,String value) {
this.name = name;
this.value = value;
}
@Override
public Object resolve(BeanFactory factory) {
return value;
}
}
- XmlBeanDefinitionReader中做的修改
public PropertyValue parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
String elementName = (propertyName != null) ?
"<property> element for property '" + propertyName + "'" :
"<constructor-arg> element";
boolean hasRefAttribute = (ele.attribute(REF_ATTRIBUTE) != null);
boolean hasValueAttribute = (ele.attribute(VALUE_ATTRIBUTE) != null);
if (hasRefAttribute) {
String refName = ele.attributeValue(REF_ATTRIBUTE);
if (!StringUtils.hasText(refName)) {
logger.error(elementName + " contains empty 'ref' attribute");
}
PropertyValue ref = new RuntimeBeanReferencePropertyValue(ele.attributeValue(NAME_ATTRIBUTE),refName);
return ref;
} else if (hasValueAttribute) {
PropertyValue typedStringValue = new TypedStringValuePropertyValue(ele.attributeValue(NAME_ATTRIBUTE),ele.attributeValue(VALUE_ATTRIBUTE));
return typedStringValue;
} else {
throw new RuntimeException(elementName + " must specify a ref or value");
}
}
参考源代码
https://github.com/XWxiaowei/spring-learn/releases/tag/testcase-3