Spring: 依賴注入的實現

上一篇中已經實現了經過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)
```
複製代碼
相關文章
相關標籤/搜索