前言
上篇《照虎畫貓寫自己的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
XmlReaderUtil將核心代碼改為 即添加了對于屬性標簽的解析和存儲,詳細代碼可進入GitHub項目查看。 在FairyApplicationContext中添加實現依賴注入功能的函數,主要思路就是對某個需要依賴注入的主體(這里的FairyService),找到要依賴注入的類(這里的FairyDao),借助反射機制,通過setter方法將FairyDao注入到FairyService中。 injectObject() 編寫測試代碼 得到結果 其中第一行打印結果是在通過反射執行 至此,我們為Fairy實現了依賴注入的功能,項目地址 如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,并和您一起分享我日常閱讀過的優質文章。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();
}
實現依賴注入函數
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);
}
}
}
}
測試
/**
* 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);
時觸發打印的。
https://github.com/DMinerJackie/fairy項目結構
Fairy項目改動盤點
FairyApplicationContext(String configLocation)
構造函數,默認加載的配置文件是xml格式
文章列表