PropertyValue
接下来我们实现spring的set注入,目标是通过xml配置属性set到具体的实体内。了解了目的,我们先来写它的测试用例:
@Test
public void testGetBeanDefinition(){
DefaultBeanFactory factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Resource resource = new ClassPathResource("petstore-v2.xml");
reader.loadBeanDefinitions(resource);
BeanDefinition bd = factory.getBeanDefinition("petStore");
List<PropertyValue> pvs = bd.getPropertyValues();
Assert.assertTrue(pvs.size() == 2 );
{
PropertyValue pv = this.getPropertyValue("accountDao",pvs);
Assert.assertNotNull(pv);
Assert.assertTrue(pv.getValue() instanceof RuntimeBeanReference);
}
{
PropertyValue pv = this.getPropertyValue("itemDao" , pvs);
Assert.assertNotNull(pv);
Assert.assertTrue(pv.getValue() instanceof RuntimeBeanReference);
}
}
private PropertyValue getPropertyValue(String name , List<PropertyValue> pvs){
for (PropertyValue pv : pvs){
if (pv.getName().equals(name))
return pv;
}
return null;
}
为了让这个测试用例通过,我们给出一个要解析的xml,和其他实体类。此处只列出xml:
<bean id="petStore" class="org.litespring.service.v2.PetStoreService">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
</bean>
给bean设置一个id,指定对应的class,这个一功能上面几篇已经实现了。下面我们就要把具体的属性(prooperty)赋予相应的值。我们引进一个类(PropertyValue),这里存放bean下面的property,每个property对应一个PropertyValue,而bean下面可能会有多个property,我们又要把xml解析成BeanDefintion,所以我们在BeanDefintion中增加一个方法:
/**
* 获取property的全部属性
* @return
*/
List<PropertyValue> getPropertyValues();
消除编译错误,在GenericBeanDefinition中实现此方法,实现方式也很简单,指定把解析出来的propertyValues返回,具体查看github上相关代码。
接下来我们来实现解析property,解析的操作是在我们的XmlBeanDefintionReader的loadBeanDefintions中进行的,我们需要在此方法中增加这一部分解析,同时对代码进行重构,提取出来两个方法,新的XmlBeanDefintionReader的代码如下:
public class XmlBeanDefinitionReader {
public static final String ID_ATTRIBUTE = "id";
public static final String CLASS_ATTRIBUTE = "class";
public static final String SCOPE_ATTRIBUTE = "scope";
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";
protected final Log logger = LogFactory.getLog(getClass());
BeanDefinitionRegistry registry;//bean的注册实体
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry){
this.registry = registry;
}
public void loadBeanDefinitions(Resource resource ){
InputStream is = null;
try {
is = resource.getInputStream();
/* //获取默认类加载器
ClassLoader cl = ClassUtils.getDefaultClassLoader();
//读取文件
is = cl.getResourceAsStream(configFile);*/
SAXReader reader = new SAXReader();
Document doc = null;
doc = reader.read(is);
// 获取<beans>
Element root = doc.getRootElement();
Iterator<Element> iterator = root.elementIterator();
//遍历所有<bean>,并把信息注册到registry中
while (iterator.hasNext()){
Element element = (Element)iterator.next();
String id = element.attributeValue(ID_ATTRIBUTE);
String beanClassName = element.attributeValue(CLASS_ATTRIBUTE);
BeanDefinition bd = new GenericBeanDefinition(id,beanClassName);
if (null != element.attribute( SCOPE_ATTRIBUTE )){
bd.setScope( element.attributeValue( SCOPE_ATTRIBUTE ) );
}
parsePropertyElement(element,bd);
this.registry.registerBeanDefinition(id,bd);
}
} catch (DocumentException | IOException e) {
throw new BeanDefinitionStoreException("IOException parsing XML document",e);
} finally {
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 解析property
* @param beanElem
* @param bd
*/
public void parsePropertyElement(Element beanElem , BeanDefinition bd){
Iterator iter = beanElem.elementIterator(PROPERTY_ELEMENT);
//为了让property无顺序性,这里采取了遍历
while (iter.hasNext()){
Element propElem = (Element) iter.next();
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);
//给beanDefintion中的propertyValues添加新的pv
bd.getPropertyValues().add(pv);
}
}
/**
* 根据ref、value返回对应的实体或值。
* @param ele
* @param bd
* @param propertyName
* @return
*/
public Object 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){
//如果是ref,则返回RuntimeBeanReference类型
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){
//如果是value则返回TypedStringValue类型
TypedStringValue valueHolder = new TypedStringValue(ele.attributeValue(VALUE_ATTRIBUTE));
return valueHolder;
}else {
throw new RuntimeException(elementName + " must specify a ref or value");
}
}
}
在parsePropertyValue这个方法中解析的是property的具体值,平时我们配置spring知道,property是既支持value又支持ref的,所以我们在这个方法中对于不同的key有不通的返回类型:ref
为RuntimeBeanReference
类型,value
为TypedStringValue
类型。
其他的类例如:RuntimeBeanReference、TypedStringValue等都是一些简单的实体类,具体的查看github即可。
现在我们离set注入属性值更近了一步。只需要把RuntimeBeanReference、TypedStringValue解析成具体的值赋值给类属性即可。我们先写一下测试用例:
public class BeanDefinitionValueResolverTest {
DefaultBeanFactory factory ;
XmlBeanDefinitionReader reader ;
BeanDefinitionValueResolver resolver ;
@Before
public void setUp(){
factory = new DefaultBeanFactory();
reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("petstore-v2.xml"));
resolver = new BeanDefinitionValueResolver(factory);
}
@Test
public void testResolveRuntimeBeanReference(){
RuntimeBeanReference reference = new RuntimeBeanReference("accountDao");
Object value = resolver.resolveValueIfNecessary(reference);
Assert.assertNotNull(value);
Assert.assertTrue(value instanceof AccountDao );
}
@Test
public void testResolveTypedStringValue(){
TypedStringValue stringValue = new TypedStringValue("test");
Object value = resolver.resolveValueIfNecessary(stringValue);
Assert.assertNotNull(value);
Assert.assertEquals("test",value);
}
}
这个测试用例是为了实现一个类(BeanDefinitionValueResolver)可以把xml转成具体的实体类或值。
public class BeanDefinitionValueResolver {
private final BeanFactory beanFactory;
public BeanDefinitionValueResolver(BeanFactory beanFactory){
this.beanFactory = beanFactory;
}
/**
* 根据propertyValue的值返回具体的内容
* @param value
* @return
*/
public Object resolveValueIfNecessary(Object value){
if (value instanceof RuntimeBeanReference){
//如果value的类型为RuntimeBeanReference,则从beanFactory中获取bean
RuntimeBeanReference ref = (RuntimeBeanReference)value;
String refName = ref.getBeanName();
Object bean = this.beanFactory.getBean(refName);
return bean;
} else if (value instanceof TypedStringValue){
//如果value的类型为TypedStringValue,则返回具体的值
return ((TypedStringValue)value).getValue();
} else {
//TODO 拓展其他类型
throw new RuntimeException("the value " + value + "has not implemented");
}
}
}
上述我们完成了PropertyValue的value转换成具体的实体或值。还需要把具体的(实体或值)赋值给实体的属性。我们来写一下这个完整功能的测试用例吧。
public class ApplicationContextTestV2 {
@Test
public void testGetBean(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("petstore-v2.xml");
PetStoreService petStore = (PetStoreService)ctx.getBean("petStore");
Assert.assertNotNull(petStore.getAccountDao());
Assert.assertNotNull(petStore.getItemDao());
Assert.assertTrue(petStore.getAccountDao() instanceof AccountDao);
Assert.assertTrue(petStore.getItemDao() instanceof ItemDao);
}
}
思考一下具体的赋值应该在创建bean的时候,那自然是要修改DefaultBeanFactory的部分代码。修改后的代码如下:
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry
implements ConfigurableBeanFactory, BeanDefinitionRegistry{
private ClassLoader beanClassLoader;
private final Map<String,BeanDefinition> beanDefinitionMap =
new ConcurrentHashMap<String, BeanDefinition>();
public void registerBeanDefinition(String id, BeanDefinition bd) {
this.beanDefinitionMap.put(id,bd);
}
public BeanDefinition getBeanDefinition(String BeanId) {
return this.beanDefinitionMap.get(BeanId);
}
/**
* 通过BeanId获取bean
* @param beanId
* @return
*/
public Object getBean(String beanId) {
//获取bean的定义
BeanDefinition bd = this.getBeanDefinition(beanId);
if (bd == null){
return null;
}
/* //获取默认类加载器
ClassLoader cl = this.getBeanClassLoader();
//获取bean的name
String beanClassName = bd.getBeanClassName();
try {
Class<?> clazz = cl.loadClass(beanClassName);
//反射出类的实体
return clazz.newInstance();
} catch (Exception e) {
throw new BeanCreationException("create bean for " + beanClassName + " fail");
}*/
//判断要创建的bean是不是单例的。
if (bd.isSingleton()){
//先从singletonObjects这个map中获取
Object bean = this.getSingleton( beanId );
//如果没有获取到则创建一个,同时放进singletonObjects中
if (null == bean){
bean = createBean(bd);
this.registerSingleton( beanId , bean );
}
return bean;
}
//直接创建一个bean
return createBean(bd);
}
/* *//**
* 通过BeanDefintion创建bean
* @param bd
* @return
*//*
private Object createBean(BeanDefinition bd) {
//获取默认类加载器
ClassLoader cl = this.getBeanClassLoader();
//获取bean的name
String beanClassName = bd.getBeanClassName();
try {
Class<?> clazz = cl.loadClass(beanClassName);
//反射出类的实体
return clazz.newInstance();
} catch (Exception e) {
throw new BeanCreationException("create bean for " + beanClassName + " fail");
}
}*/
/**
* 创建bean
* @param bd
* @return
*/
private Object createBean(BeanDefinition bd){
//创建实例
Object bean = instantiateBean(bd);
//设置属性
populateBean(bd,bean);
return bean;
}
/**
* 初始化bean
* @param bd
* @return
*/
private Object instantiateBean(BeanDefinition bd){
//获取类加载器
ClassLoader cl = this.getBeanClassLoader();
String beanClassName = bd.getBeanClassName();
try {
Class<?> clz = cl.loadClass(beanClassName);
//反射出类的实体
return clz.newInstance();
} catch (Exception e) {
throw new BeanCreationException("create bean for " + beanClassName + "failed ", e);
}
}
/**
* 给bean的属性进行赋值
* @param bd
* @param bean
*/
protected void populateBean(BeanDefinition bd,Object bean){
//获取beanDefintion的value
List<PropertyValue> pvs = bd.getPropertyValues();
if (pvs == null || pvs.isEmpty()){
return;
}
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this);
try {
for (PropertyValue pv : pvs){
String propertyName = pv.getName();
Object originalValue = pv.getValue();
//获取具体的实体或值
Object resolvedValue = valueResolver.resolveValueIfNecessary(originalValue);
//利用java自带的bean方法,对bean实体属性进行赋值。
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds){
if (pd.getName().equals(propertyName)){
pd.getWriteMethod().invoke(bean,resolvedValue);
break;
}
}
}
}catch (Exception e){
throw new BeanCreationException("Failed to obtain BeanInfo for class ["+bd.getBeanClassName()+"]");
}
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public ClassLoader getBeanClassLoader() {
return (this.beanClassLoader != null ? this.beanClassLoader : ClassUtils.getDefaultClassLoader());
}
}
至此,我们已经完成了set注入的大部分功能。后续我们还会支持set其他类型的数据。包括int、boolean等。
生活要多点不自量力