ZB-039-02手写SpringIoc容器

仓库地址

手写精简版Ico容器

在上篇文章的基础上

001 新建 src/main/resources/ioc.properties

1
2
orderDao=com.github.hcsp.OrderDao
orderService=com.github.hcsp.OrderService

002 bean的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class MyIocContainer {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(MyIocContainer.class.getResourceAsStream("/ioc.properties"));

Map<String,Object> beans = new HashMap<>();

// bean的初始化
properties.forEach((beanName,beanClass)->{
try {
Class<?> klass = Class.forName((String) beanClass);
Object beanInstance = klass.getConstructor().newInstance();
beans.put((String)beanName,beanInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
});

// 装配依赖
beans.forEach((beanName,beanInstance)-> dependencyInject(beanName,beanInstance,beans));

OrderService orderService = (OrderService) beans.get("orderService");
OrderDao orderDao = (OrderDao) beans.get("orderDao");
System.out.println();
}

private static void dependencyInject(String beanName, Object beanInstance, Map<String, Object> beans) {
// 把所有的 field扫描一遍
List<Field> fieldsToBeAutowried = Stream.of(beanInstance.getClass().getDeclaredFields())
.filter(field -> field.getAnnotation(Autowired.class) != null)
.collect(Collectors.toList());
// 过滤带有 @Autowired 注解的过滤出来

fieldsToBeAutowried.forEach(field -> {
try {
String fieldName = field.getName();
Object dependencyBeanInstance = beans.get(fieldName);
field.setAccessible(true);
field.set(beanInstance,dependencyBeanInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
}

最难能可贵的是,还能兼容循环引用

  • OrderService 依赖 OrderDao
  • OrderDao 还依赖 OrderService
  • 这个依赖的还是单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OrderService {
@Autowired
private OrderDao orderDao;

// ...
}


public class OrderDao {
@Autowired
private OrderService orderService;
// ...
}

// 循环引用,不会重复产生对象。

题外话 stream非常重要

在 junit 里 stream 无处不在

循环依赖的问题怎么解决,它是为什呢出现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 循环依赖出现的问题是因为 ,构造器注入
public class OrderDao {
private OrderService orderService;

@Autowired
public OrderDao(OrderService orderService) {
this.orderService = orderService;
}
// ...
}


public class OrderService {
private OrderDao orderDao;

public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
// ...
}


这是一个在初始化过程中的循环依赖,是无法解决的

什么循环依赖是好解决的

是它们创建出来之后,在分别对他们的依赖进行设值

无状态和有状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrderDao {
// 这个值 随着运行的时间而改变叫做“有状态”
private int count;
@Autowired
private OrderService orderService;

public void select(){
count++;
System.out.println("select");
}
}


// 无状态代表。在整个生命周期里,它里面的某种状态 比如 orderService的引用 始终是一个

无状态的好处是:线程安全的,无状态结果更加容易预测

而如果是有状态的。排查的过程中非常繁杂

@Autowired 的推荐用法

推荐写在构造器上注入

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OrderService {
private OrderDao orderDao;

@Autowired
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}

public void doSomething(){
System.out.println(orderDao);
orderDao.select();
}
}

不推荐这样,因为这样意味着你从来不写单元测试

1
2
3
4
5
6
7
8
9
public class OrderService {
@Autowired
private OrderDao orderDao;

public void doSomething(){
System.out.println(orderDao);
orderDao.select();
}
}

为什么不推荐在Field上 注解,而是在 构造器上

因为有个非常要命的问题,因为它是 private OrderDao orderDao; private成员

spring是通过启动的时候,通过反射把它的依赖注入进去的。

但是如果你要进行单元测试

此时你就会碰到问题,因为它是private的你必须要用反射才能把它注入进去

而如果你用构造器注入的好处是你可以 mock它

Spring启动过程

  • 在xml里定义Bean
  • BeanDefinition的载入和解析
  • Bean的实例化和依赖注入
  • 对外提供服务