Fork me on GitHub

tiny-spring分析笔记(2)

tinySpring 分析笔记——IOC(2)

学习项目 Github 地址:code4craft/tiny-spring

上一篇博客:tiny-spring分析笔记(1)

step-4-config-beanfactory-with-xml

  • 在 step 3 中,“text”参数是直接写入 java 类中。
  • step 4 的目标就是将参数的配置信息写入 xml 文件中,然后代码通过读取 xml 文件获得参数。
  • 参数放入BeanDefinition中,然后用BeanFactory将它们一一注册(包括 setBean),最终实现 getBean。

4.1 tinyioc.xml

1
2
3
4
5
// 部分代码:
<bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
<property name="text" value="Hello World!"></property>
</bean>

4.2 BeanFactoryTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BeanFactoryTest {

@Test
public void test() throws Exception {
// 1.读取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2.初始化BeanFactory并注册bean
BeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

}
}

详细步骤:

  1. 首先 new 一个XmlBeanDefinitionReader类,调用其父类 BeanDefinitionReader 的构造方法,在构造方法中,new 一个 HashMap<String,BeanDefinition>和一个ResourceLoader,分别赋给其私有属性registryresourceLoader
  2. 调用XmlBeanDefinitionReaderloadBeanDefinitions方法并传入 xml 文件的文件名。
  3. 随后将 xml 文件转换成 URL 类型,再将 url 与资源建立联系(使用 URLConnection 类),得到InputStream对象。
  4. 随后使用DocumentBuilder(DocumentBuilder 实例也是用 DocumentbuilderFactory 的生成的工厂实例)解析inputStream,得到Document对象。
  5. document沿着根元素解析,new 一个 BeanDefinition(如果有多个 name-class 属性,将对应多个 BeanDefinition,以下以一个 name-class 属性为例),拿到 xml 所配置的namevalue值并组装成① PropertyValues,之后拿到② beanClassName③ beanClass,把它们放入beanDefinition中。
  6. 然后 new 一个BeanFactory(引用其实现类 AutowireCapableBeanFactory),生成④ bean实例,放入beanDefinition中(至此,4项凑齐),同时将 beanDefinition注册进属性beanDefinitionMap中。
  7. 后续需要调用helloWorldService.helloWorld()时,只需要从beanFactorygetBean("helloWorldService"),即可。
  8. 完毕。

特点:

  • 完成了读取 xml 文件的 I/O 操作;
  • 考虑了配置文件中多个 class 的情况;
  • 但没有考虑多个 bean 循环依赖的问题。

step-5-inject-bean-to-bean

  • step 4 的场景比较简单,如果有两个 class 类,都在 xml 文件中配置,但是如果 classA 是 classB 的属性,classB 同时也是 classA 的属性,那么该怎么处理呢?究竟先注入哪一个呢?
  • 循环依赖的后果:
    • 比如,在为 classA 进行 register 操作时,会创建 classA 的 bean,同时将其 PropertyValues 插入,插入时会对其属性(即 classB)进行bean 的注入。
    • 然而,对 classB 进行注入又需要用到 class A 的 bean 的注入。
    • (估计是因为 tiny 代码不规范的缘故,所以并没有对 classA 的属性进行 bean 的注入,不然一定会报出org.springframework.beans.factory.BeanCurrentlyInCreationException的错误。)
  • 扩展阅读:从 spring 源码角度分析循环依赖 bean 的组装原理

1

5.1 互相依赖示例: xml 文件

1
2
3
4
5
6
7
8
<bean name="outputService" class="us.codecraft.tinyioc.OutputService">
<property name="helloWorldService" ref="helloWorldService"></property>
</bean>

<bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
<property name="text" value="Hello World!"></property>
<property name="outputService" ref="outputService"></property>
</bean>

5.2 step 5 的处理办法是:

  • beanDefinition刚被 new 出来时,此时四项都没有被加入。遍历子节点时,如果是 name-value,则将PropertyValues加入beanDefinition
  • 如果是 name-ref,则 new 一个 BeanReference,将 ref 对应的字符串保留在beanReference的属性区,并把这个beanReference当作PropertyValue存入
    • 相当于,读取 xml 文件时,将引用关系初始化(记录下来),但不注入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#step 4: 此方法完成 bean 的实例化
@Override
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
Object bean = doCreateBean(beanDefinition);
beanDefinition.setBean(bean);
beanDefinitionMap.put(name, beanDefinition);
}
#step 5: bean 的实例化延后,不在此处完成
@Override
public void registerBeanDefinition(String name, BeanDefinition beanDefinition) throws Exception {
beanDefinitionMap.put(name, beanDefinition);
beanDefinitionNames.add(name);
}

  • 在后续遍历Map<String,BeanDefinition>(此处 key 为class 的 name)时,如果遍历到bean == null时,再进行 newInstance,得到最终的 bean。
  • 需要注意,因为对属性增加了beanReference,所以在注入 bean 时,会对属性增加 if 判断,然后实例化属性的 bean。
    • 情景分析:
    • 如果先对 classA 进行 setBean,classA 的原始 bean 已经生成,然后对属性(即 classB)进行 setBean。
    • classB 会生成原始 bean,并对他的属性(即 classA)进行 setBean,此时可以拿到 classA 的 bean(看似不完整,但有 bean 就可以走完 classB 的注入流程),classB 的 bean 至此完成注入。
    • 回到 classA,完成其 bean 的注入。
1
2
3
4
5
# 仅展示部分代码:
if (value instanceof BeanReference) {
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getName());
}

step-6-invite-application-context

引入applicationContext,封装读取xml配置文件,完成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
# step-5: 繁琐

// 1.读取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2.初始化BeanFactory并注册bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3.初始化bean
beanFactory.preInstantiateSingletons();

// 4.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

# step-6:封装后,精简:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();

至此 tiny-spring 的 IOC 部分学习完毕。撒花 ✿✿ヽ(°▽°)ノ✿

-------------The End-------------