Spring: 依赖注入的实现

757 阅读6分钟
上一篇中已经实现了通过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)
```