上一篇中已經實現了經過IOC容器建立BEAN並管理, 在實際開發中BEAN之間的依賴是不可避免的. 例: 用戶模塊依賴於通用模塊, 訂單模塊同時依賴於用戶模塊和通用模塊等等. Spring提供了依賴注入, 自動的完成BEAN之間依賴的注入操做. 本篇中將經過代碼實現依賴注入功能.
## 設計思路
經過代碼定義了BEAN間的依賴關係時,
Spring並不知道哪些屬性須要其自動注入依賴實例, 所以須要經過配置告知Spring. 在聲明BEAN的時候添加配置便可.
### XML配置
```
<bean id="managerOne" class="com.atd681.xc.ssm.ioc.demo.ManagerOne">
<property name="maxSize" value="1024" />
<property name="serviceOne" ref="serviceOne" />
</bean>
```
`<property>`爲BEAN的屬性, name爲屬性名稱, 屬性值有兩種方式
* 固定值: 屬性值基礎數據類型, 例:
```
int size = 10
String status = "ok"
```
* 其餘BEAN: 屬性值爲依賴BEAN的實例. 例:
```
UserService service
```
所以屬性值須要增長類型來區分上述兩種方式或者使用不一樣的XML屬性. Spring採用後者.
* 固定值: 使用屬性value, 例: `<property name="maxSize" value="1024" />`
* 其餘BEAN: 使用屬性ref, 例: `<property name="serviceOne" ref="serviceOne" />`
### 註解配置
BEAN中須要被注入的屬性須要添加@AutoWired註解
```
@Component
public class ServiceTwo {
// 添加@AutoWired註解告知Spring該屬性須要自動注入
// 只有ServiceOne也經過IOC容器管理時才能注入
@AutoWired
private ServiceOne serviceOne;
// 未添加@AutoWired註解, Spring不會注入
private ServiceX serviceX;
}
```
肯定好BEAN中須要被注入的屬性後 , 在解析BEAN時將屬性保存, 建立BEAN後從IOC容器中獲取依賴的BEAN, 經過JAVA反射賦值至屬性中便可.
## 代碼實現
### 增長BEAN屬性描述類
用來保存BEAN中屬性的基本信息, 包括屬性(Field), 屬性值, 類型(直接賦值,引用對象)等.
```
// BEAN屬性描述
public class BeanProperty {
// 屬性類型: 直接賦值
public static final int TYPE_VAL = 1;
// 屬性類型: 引用其餘對象
public static final int TYPE_REF = 2;
// 屬性
private Field field;
// 屬性值
private String value;
// 屬性類型
// 1: value爲固定值, 例: int maxSize = 1024
// 2: value對應的BEAN的實例對象, 例: UserService service
private int type;
// 無參實例化
public BeanProperty() {
}
// 根據屬性字段實例化(默認值爲字段名稱的實例對象)
public BeanProperty(Field field) {
this.field = field;
this.value = BeanUtil.getName(field);
this.type = TYPE_REF;
}
// Getter & Setter
// ...
}
```
### BEAN描述類中增長屬性集合
```
// BEAN描述信息
public class BeanDefinition {
// 名稱, CLASS...
// 須要被注入的屬性集合
private List<BeanProperty> propertyList = new ArrayList<BeanProperty>();
// 添加屬性
public void addProperty(BeanProperty property) {
this.propertyList.add(property);
}
// Getter & Setter
// ...
}
```
### 節點解析器中增長解析屬性
* BeanElementParser
在解析BEAN節點時增長屬性節點解析, 封裝屬性信息添加至BEAN描述的屬性集合中.
```
// Bean節點解析器,解析XML配置文件中的<bean>節點
public class BeanElementParser implements ElementParser {
// 解析<bean>節點
@SuppressWarnings("unchecked")
@Override
public void parse(Element ele, BeanFactory factory) throws Exception {
// ...
// <bean>節點中的id和class屬性
// 封裝成類描述信息
BeanDefinition bd = new BeanDefinition(id, clazz);
// 解析屬性
// 獲取BEAN下全部屬性節點
List<Element> propEleList = ele.getChildren("property");
for (Element propEle : propEleList) {
// 根據屬性名稱BEAN中對應的屬性
String propName = propEle.getAttributeValue("name");
Field field = clazz.getDeclaredField(propName);
// 封裝成屬性描述信息
BeanProperty property = new BeanProperty(field);
// 獲取屬性值(固定值)
String propValue = propEle.getAttributeValue("value");
if (propValue != null) {
property.setValue(propValue);
property.setType(BeanProperty.TYPE_VAL);
}
// 獲取屬性引用BEAN的名稱
// 同時定義value和ref時, ref屬性將覆蓋value屬性
String propRef = propEle.getAttributeValue("ref");
if (propRef != null) {
property.setValue(propRef);
property.setType(BeanProperty.TYPE_REF);
}
// BEAN描述信息中添加屬性
bd.addProperty(property);
}
// 向BEAN工廠註冊Bean
// ...
}
}
```
* ComponentScanElementParser
在解析BEAN時增長屬性解析, 查找含有@AutoWired註解的屬性添加至BEAN描述的屬性集合中.
```
// <component-scan>節點解析器
public class ComponentScanElementParser implements ElementParser {
// 解析<component-scan>節點
@Override
public void parse(Element ele, BeanFactory factory) throws Exception {
// ...
// 獲取掃描目錄絕對路徑
String baseDir = getClass().getClassLoader().getResource(basePackage.replace('.', '/')).getPath();
// 掃描目錄,獲取目錄下的全部類文件
for (File file : new File(baseDir).listFiles()) {
// ...
// 封裝成類描述信息
BeanDefinition bd = new BeanDefinition(c.value(), clazz);
// 設置須要被注入的屬性
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
// 含有@AutoWired爲須要被注入的屬性
if (f.isAnnotationPresent(AutoWired.class)) {
bd.addProperty(new BeanProperty(f));
}
}
// 向BEAN工廠註冊Bean
// ...
}
}
}
```
### 建立BEAN時增長依賴注入
```
// BEAN工廠, 提供BEAN的建立及獲取
public class BeanFactory {
// 根據名稱獲取BEAN的實例
@SuppressWarnings("unchecked")
public <T> T getBean(String name) throws Exception {
// ...
// 存在BEAN描述時根據描述信息實例化BEAN
BeanDefinition beanDef = this.beanDefinitionMap.get(name);
bean = beanDef.getClazz().newInstance();
// 對BEAN的屬性進行諸如(依賴注入)
populateBean(beanDef, bean);
// 將BEAN實例化保存至容器
// ...
}
}
```
依賴注入時根據屬性類型獲取對應的值, 經過反射將屬性值設置到屬性中.
* 固定值: 直接獲取定義的屬性值
* BEAN引用: 從BEAN工廠獲取依賴BEAN
```
// 依賴注入
public void populateBean(BeanDefinition bd, Object bean) throws Exception {
// 獲取BEAN中須要被注入的屬性集合
List<BeanProperty> propertyList = bd.getPropertyList();
// 遍歷屬性, 根據屬性信息注入
for (BeanProperty property : propertyList) {
Object value;
Field field = property.getField();
// 屬性類型爲固定值
if (property.getType() == BeanProperty.TYPE_VAL) {
// 獲取屬性的值並轉化爲屬性對應的類型
String fieldValue = property.getValue();
Class<?> fieldType = property.getField().getType();
value = BeanUtil.getValue(fieldValue, fieldType);
}
// 屬性類型爲BEAN引用
else {
// 屬性值(引用BEAN的名稱)
String refName = property.getValue();
// 根據名稱從BEAN工廠中獲取實例對象
value = getBean(refName);
}
// 經過反射對屬性賦值, 完成依賴注入
field.setAccessible(true);
field.set(bean, value);
}
}
```
## 測試
* 建立BEAN
```
package com.atd681.xc.ssm.ioc.demo.service;
import com.atd681.xc.ssm.ioc.framework.annotation.Component;
@Component
public class ServiceOne {
public void list() {
System.out.println("ServiceOne.list start...");
}
}
```
```
package com.atd681.xc.ssm.ioc.demo;
import com.atd681.xc.ssm.ioc.demo.service.ServiceOne;
public class ManagerOne {
private ServiceOne serviceOne;
private int maxSize;
public void test() {
System.out.println("ManagerOne.test start...");
System.out.println("ManagerOne.maxSize = " + this.maxSize);
serviceOne.list();
System.out.println("ManagerOne.test end...");
}
}
```
* 建立XML配置文件
```
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 配置BEAN所在目錄, IOC容器會掃描該目錄, 加載含有@Component註解的Bean -->
<component-scan package="com.atd681.xc.ssm.ioc.demo.service" />
<!-- 配置BEAN及屬性 -->
<bean id="managerOne" class="com.atd681.xc.ssm.ioc.demo.ManagerOne">
<!-- 屬性爲固定值, 使用value -->
<property name="maxSize" value="1024" />
<!-- 屬性爲BEAN引用, 使用ref -->
<property name="serviceOne" ref="serviceOne" />
</bean>
</beans>
```
* 建立測試類
```
// IOC測試類
public class Test {
// 測試IOC容器
public static void main(String[] args) throws Exception {
// 實例化應用上下文並設置配置文件路徑
ApplicationContext context = new ApplicationContext("context.xml");
// 初始化上下文(IOC容器)
context.init();
ManagerOne managerOne = context.getBean("managerOne");
managerOne.test();
}
}
```
* 運行
從IOC容器中獲取屬性對應的BEAN引用並賦值到屬性中.
```
ManagerOne.test start...
ManagerOne.maxSize = 1024
ServiceOne.list start...
ManagerOne.test end...
```
依賴注入時若是從IOC容器未找到對應的BEAN(未配置或配置錯誤)時會拋出異常: 獲取BEAN引用出現錯誤.
```
Exception in thread "main" java.lang.RuntimeException: 未定義BEAN[serviceOne1]
at com.atd681.xc.ssm.ioc.framework.BeanFactory.getBean(BeanFactory.java:59)
at com.atd681.xc.ssm.ioc.framework.BeanFactory.populateBean(BeanFactory.java:121)
at com.atd681.xc.ssm.ioc.framework.BeanFactory.getBean(BeanFactory.java:73)
at com.atd681.xc.ssm.ioc.framework.BeanFactory.instanceBean(BeanFactory.java:89)
at com.atd681.xc.ssm.ioc.framework.ApplicationContext.init(ApplicationContext.java:63)
at com.atd681.xc.ssm.ioc.framework.ApplicationContext.init(ApplicationContext.java:49)
at com.atd681.xc.ssm.ioc.demo.Test.main(Test.java:15)
```
複製代碼