文章出處

前言

上篇《照虎畫貓寫自己的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,因為屬性值是依附在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

項目結構

Fairy項目改動盤點

  • 添加FairyApplicationContext(String configLocation)構造函數,默認加載的配置文件是xml格式
  • 添加Json配置文件解析器,可以解析Json格式的配置文件并加載bean
  • 重構測試Bean,將接口FairyBean改為FairyDao,并新增FairyService接口及實現類,方便本文的用例測試
  • 升級XmlReaderUtil,支持Bean的自標簽Property的解析
  • 添加依賴注入函數,用戶實現依賴注入功能
  • 添加PropertyDefinition模型,用于存儲property屬性值

如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,并和您一起分享我日常閱讀過的優質文章。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()