Spring: 依赖注入的实现

栏目: Java · 发布时间: 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)
```
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

复杂网络理论及其应用

复杂网络理论及其应用

汪小帆、李翔、陈关荣 / 清华大学出版社 / 2006 / 45.00元

国内首部复杂网络专著 【图书目录】 第1章 引论 1.1 引言 1.2 复杂网络研究简史 1.3 基本概念 1.4 本书内容简介 参考文献 第2章 网络拓扑基本模型及其性质 2.1 引言 2.2 规则网络 2.3 随机图 2.4 小世界网络模型 2.5 无标度网络模型 ......一起来看看 《复杂网络理论及其应用》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具