spring(四):set注入(上)

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有不通的返回类型:refRuntimeBeanReference类型,valueTypedStringValue类型。
其他的类例如: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等。


                                                                                                生活要多点不自量力

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容