美文网首页程序员计算机杂谈技术干货
照虎画猫写自己的Spring——依赖注入

照虎画猫写自己的Spring——依赖注入

作者: Jackie_Zheng | 来源:发表于2017-11-25 21:39 被阅读39次

    前言

    上篇《照虎画猫写自己的Spring》从无到有讲述并实现了下面几点

    • 声明配置文件,用于声明需要加载使用的类
    • 加载配置文件,读取配置文件
    • 解析配置文件,需要将配置文件中声明的标签转换为Fairy能够识别的类
    • 初始化类,提供配置文件中声明的类的实例

    一句话概括:不借助Spring容器,实现了Bean的加载和实例化

    要想契合Fairy取名时的初衷(东西不大,但是能量无穷),只有一套加载Bean的机制是远远不够的,所以还是需要照虎画猫,完善这个小精灵。

    Spring之所以在Java企业级开发的众多框架中崭露头角光芒万丈,与他的依赖注入(又名控制反转IOC)面向切面(AOP)两大杀手锏是密不可分的。在Fairy实现了加载实例化Bean的功能后,我们再往前走一步,看看依赖注入是如何实现的。

    依赖注入

    举个例子,大概介绍下依赖注入。
    没有依赖注入之前,我们买白菜的时候,需要挎着篮子去菜市场挑选并购买;
    有了依赖注入之后,我们需要白菜的时候,菜装在篮子里,已经放在你家门口。
    这就是依赖注入。

    对于Fairy,如果要实现依赖注入的功能,需要在上一版的代码上做一些小小的改动。
    将原来的FairyBean接口和实现类FairyBeanImpl改为FairyDao接口和实现类FairyDaoImpl,除此以外,我们需要新加一个接口FairyService和实现类FairyServiceImpl。
    这么声明,相信你一定明白这是为了使用依赖注入功能。

    配置

    我们依旧采用读取配置文件的方式来初始化容器。新建一个配置文件application-context-inject.xml

    <beans>
        <bean id="fairyService" class="com.jackie.fairy.bean.impl.FairyServiceImpl">
            <property name="fairyDao" ref="fairyDao"></property>
            <property name="lightColor" value="blue"></property>
        </bean>
    
        <bean id="fairyDao" class="com.jackie.fairy.bean.impl.FairyDaoImpl">
        </bean>
    </beans>
    

    同时我们需要FairyService和FairyServiceImpl
    FairyService

    package com.jackie.fairy.bean;
    
    /**
     * Created by jackie on 17/11/25.
     */
    public interface FairyService {
        void greet();
    
        void fly();
    
        void lighting();
    }
    

    FairyServiceImpl

    
    package com.jackie.fairy.bean.impl;
    
    import com.jackie.fairy.bean.FairyDao;
    import com.jackie.fairy.bean.FairyService;
    
    /**
     * Created by jackie on 17/11/25.
     */
    public class FairyServiceImpl implements FairyService {
        private FairyDao fairyDao;
        private String lightColor;
    
        public FairyDao getFairyDao() {
            System.out.println("===getFairyDao===: " + fairyDao.toString());
            return fairyDao;
        }
    
        public void setFairyDao(FairyDao fairyDao) {
            System.out.println("===setFairyDao===: " + fairyDao.toString());
            this.fairyDao = fairyDao;
        }
    
        public String getLightColor() {
            return lightColor;
        }
    
        public void setLightColor(String lightColor) {
            this.lightColor = lightColor;
        }
    
        @Override
        public void greet() {
            fairyDao.greet();
        }
    
        @Override
        public void fly() {
            fairyDao.fly();
        }
    
        @Override
        public void lighting() {
            System.out.println("----------Hi, I am light fairy. Exactly, " + lightColor + " color light fairy----------");
        }
    }
    
    • 没有使用@Autowired注入FairyDao,这是Spring的那一套
    • 将FairyDao作为成员变量,添加setter和getter方法(后续做注入使用)
    • 添加FairyService自己的实现方法lighting,这是一个会发光的小精灵的feature,小精灵的发光属性取决于lightColor,这个属性需要注入,所以也有相应的setter和getter方法

    升级解析器类

    上篇的XmlReaderUtil解析器只能解析这样的配置结构

    <parent>
        <child>
        </child>
        ...
        <child>
        </child>
    <parent>
    

    但是我们现在需要支持的配置文件如上面的配置文件所示,所以需要升级解析器类,支持读取子标签的属性标签。
    在此之前,需要新建模型PropertyDefinition,用于存储属性值

    package com.jackie.fairy.model;
    
    /**
     * Created by jackie on 17/11/25.
     */
    public class PropertyDefinition {
        private String name;
        private String ref;
        private String value;
    
        public PropertyDefinition(String name, String ref, String value) {
            this.name = name;
            this.ref = ref;
            this.value = value;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getRef() {
            return ref;
        }
    
        public void setRef(String ref) {
            this.ref = ref;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        @Override
        public String toString() {
            return "PropertyDefinition{" +
                    "name='" + name + '\'' +
                    ", ref='" + ref + '\'' +
                    ", value='" + value + '\'' +
                    '}';
        }
    }
    

    同时,需要在BeanDefinition模型中加入List<PropertyDefinition>,因为属性值是依附在BeanDefinition下面的。

    XmlReaderUtil将核心代码改为

    for (Iterator iterator = rootElement.elementIterator(); iterator.hasNext(); ) {
        Element element = (Element)iterator.next();
        String id = element.attributeValue(Constants.BEAN_ID_NAME);
        String clazz = element.attributeValue(Constants.BEAN_CLASS_NAME);
        BeanDefinition beanDefinition = new BeanDefinition(id, clazz);
        
        // 遍历属性标签
        for (Iterator propertyIterator = element.elementIterator(); propertyIterator.hasNext();) {
            Element propertyElement = (Element) propertyIterator.next();
            String name = propertyElement.attributeValue(Constants.PROPERTY_NAME_NAME);
            String ref = propertyElement.attributeValue(Constants.PROPERTY_REF_NAME);
            String value = propertyElement.attributeValue(Constants.PROPERTY_VALUE_NAME);
            propertyDefinitions.add(new PropertyDefinition(name, ref, value));
        }
    
        beanDefinition.setPropertyDefinitions(propertyDefinitions);
        beanDefinitions.add(beanDefinition);
        // 清空propertyDefinitions集合,因为有些bean没有property标签
        propertyDefinitions = Lists.newArrayList(); 
    }
    

    即添加了对于属性标签的解析和存储,详细代码可进入GitHub项目查看。

    实现依赖注入函数

    在FairyApplicationContext中添加实现依赖注入功能的函数,主要思路就是对某个需要依赖注入的主体(这里的FairyService),找到要依赖注入的类(这里的FairyDao),借助反射机制,通过setter方法将FairyDao注入到FairyService中。

    injectObject()

    private void injectObject() {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            Object bean = instanceBeans.get(beanDefinition.getId());
            if (bean != null) {
                try {
                    BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
                    /**
                     * 通过BeanInfo来获取属性的描述器(PropertyDescriptor)
                     * 通过这个属性描述器就可以获取某个属性对应的getter/setter方法
                     * 然后我们就可以通过反射机制来调用这些方法。
                     */
                    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    
                    for (PropertyDefinition propertyDefinition : beanDefinition.getPropertyDefinitions()) {
                        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                            // 用户定义的bean属性与java内省后的bean属性名称相同时
                            if (StringUtils.equals(propertyDescriptor.getName(), propertyDefinition.getName())) {
                                // 获取setter方法
                                Method setter = propertyDescriptor.getWriteMethod();
                                if (setter != null) {
                                    Object value = null;
                                    if (StringUtils.isNotEmpty(propertyDefinition.getRef())) {
                                        // 根据bean的名称在instanceBeans中获取指定的对象值
                                        value = instanceBeans.get(propertyDefinition.getRef());
                                    } else {
                                        value = ConvertUtils.convert(propertyDefinition.getValue(), propertyDescriptor.getPropertyType());
                                    }
    
                                    // //保证setter方法可以访问私有
                                    setter.setAccessible(true);
                                    try {
                                        // 把引用对象注入到属性
                                        setter.invoke(bean, value);
                                    } catch (Exception e) {
                                        LOG.error("invoke setter.invoke failed", e);
                                    }
                                }
                                break;
                            }
                        }
                    }
                } catch (Exception e) {
                    LOG.error("invoke getBean failed", e);
                }
            }
        }
    }
    
    • 用到了Java内省获取Bean各个属性的setter和getter方法
    • 使用了反射调用setter方法,将其注入FairyService类中

    测试

    编写测试代码

    /**
     * bean依赖注入
     */
    FairyApplicationContext autowiredApplicationContext =
            new FairyApplicationContext("application-context-inject.xml");
    FairyService fairyService = (FairyService) autowiredApplicationContext.getBean("fairyService");
    fairyService.greet();
    fairyService.lighting();
    

    得到结果

    ===setFairyDao===: com.jackie.fairy.bean.impl.FairyDaoImpl@6615435c
    Hi, I am fairy
    ----------Hi, I am light fairy. Exactly, blue color light fairy----------
    

    其中第一行打印结果是在通过反射执行setter.invoke(bean, value);时触发打印的。

    至此,我们为Fairy实现了依赖注入的功能,项目地址
    https://github.com/DMinerJackie/fairy

    项目结构

    1125-fairy目录结构.png

    Fairy项目改动盘点

    • 添加FairyApplicationContext(String configLocation)构造函数,默认加载的配置文件是xml格式
    • 添加Json配置文件解析器,可以解析Json格式的配置文件并加载bean
    • 重构测试Bean,将接口FairyBean改为FairyDao,并新增FairyService接口及实现类,方便本文的用例测试
    • 升级XmlReaderUtil,支持Bean的自标签Property的解析
    • 添加依赖注入函数,用户实现依赖注入功能
    • 添加PropertyDefinition模型,用于存储property属性值

    相关文章

      网友评论

        本文标题:照虎画猫写自己的Spring——依赖注入

        本文链接:https://www.haomeiwen.com/subject/efhhbxtx.html