上一篇中已经实现了通过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)
```
复制代码
原文
https://juejin.im/post/5ce406f2e51d4556d86c7a1c