1.前言
本文为对tiny-spring的学习解读,代码参考自tiny-spring。一个手写的Spring简易版框架。
前面已经提到了Spring IOC容器的创建大致分为3个步骤。但是这个三个步骤是有一个演进的过程的,Spring容器创建方式前后有6种,从最基本的实例化创建创建到后来的自动化,这些过程的学习对我们学习理解IOC有很大的帮助。
2.容器创建需要的代码
HelloWorldService:
public interface HelloWorldService {
void helloWorld();
}
HelloWorldServiceImpl:
public class HelloWorldServiceImpl implements HelloWorldService {
private String text;
private OutputService outputService;
@Override
public void helloWorld(){
outputService.output(text);
}
public void setText(String text) {
this.text = text;
}
public void setOutputService(OutputService outputService) {
this.outputService = outputService;
}
}
OutputService:
public interface OutputService {
void output(String text);
}
OutputServiceImpl:
public class OutputServiceImpl implements OutputService {
@Override
public void output(String text){
System.out.println(text);
}
}
tinyioc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id="outputService" class="us.codecraft.tinyioc.OutputServiceImpl">
<property name="helloWorldService" ref="helloWorldService"></property>
</bean>
<bean id="helloWorldService" class="us.codecraft.tinyioc.HelloWorldServiceImpl">
<property name="text" value="Hello World!"></property>
<property name="outputService" ref="outputService"></property>
</bean>
<bean id="beanInitializeLogger" class="us.codecraft.tinyioc.BeanInitializeLogger">
</bean>
</beans>
3.创建方式演变
1.最基本的容器
IoC最基本的角色有两个:容器(BeanFactory
)和Bean本身。这里使用BeanDefinition
来封装了bean对象,这样可以保存一些额外的元信息。
// 1.初始化beanfactory
BeanFactory beanFactory = new BeanFactory();
// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);
// 3.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
输出结果为:“Hello World!”
分析:
Spring 通过xml中对Bean类(HelloWorldService)属性的赋值给予了HelloWorldService中的 String text =“Hello World!”,这样OutputServiceImpl调用output()方法时可以输出text的值。
另外这个容器创建的过程:
1.手动实例化最基本的BeanFatory容器接口。
BeanFactory beanFactory = new BeanFactory();
从上一篇博客可以知道BeanFatory有很多实现类,不同的实现类代表的不通过规格的容器,可以实现不同的功能。BanFatory系列可以实现双亲IOC的配置以及Bean的自动配置功能,ApplicaitonContext系列更是增加了面向框架的一些特性。
2.指定BeanDefinition(容器中的数据结构)
BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
BeanDefinition是容器中的抽象Bean结构,是容器为调用方依赖注入时映射的模板,也就是我们在注入原型时就是参考这个抽象的Bean结构。
3.注册BeanDefinition到容器中
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);
这里调用了beanFatory的registerBeanDefinition()方法,这个注册方法的底层是HashMap实现的,结构上是将我们解析的BeanDefinition以HashMap(key,BeanDefinition)的形式放到BeanFatory中,告诉它都有哪些类Bean在容器里。另外可以通过getBean(key)的形式得到BeanDefinition。
2.将bean创建放入工程且为Bean注入属性
第一个方法中的Bean(HelloWorldService)是初始化好之后再set进去的,实际使用中,我们希望容器来管理bean的创建。于是我们将bean的初始化放入BeanFactory中。为了保证扩展性,我们使用Extract Interface的方法,将BeanFactory
替换成接口,而使用AbstractBeanFactory
和AutowireCapableBeanFactory
作为其实现。”AutowireCapable”的意思是“可自动装配的”,为我们后面注入属性做准备。
// 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();
// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);
// 3.设置属性
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("text", "Hello World!"));
beanDefinition.setPropertyValues(propertyValues);
// 4.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();
输出结果:“Hello World!”
分析改进点:
1.容器的选择从BeanFatory()改成它的实现类AutowireCapableBeanFactory()可以参考上一篇博客的类图。它拥有装配属性的作用。
2.在载入BeanDefinition时,不再是创建一个Bean类放入其中,这本身就违背了IOC的理念。而是直接指定了BeanDefinition的数据结构模型。
3.调用AutowireCapableBeanFactory()容器的setPropertyValues方法可以为HelloWorldService类指定的属性text手动赋值,而不需要xml中的配置。
3.读取xml配置来初始化Bean
上面为Bean属性注入的部分比较麻烦,如果属性一多,代码会非常麻烦,这里的BeanDefinition
只是一些配置,我们还是用xml来初始化吧。我们定义了BeanDefinitionReader
初始化bean,它有一个实现是XmlBeanDefinitionReader
。
// 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.读取配置的部分封装的东西比较多。核心问题是如何将xml的Bean配置解析成Spring中的Bean类,可以充当BeanDinfition放到容器中充当数据结构模板。首先是使用xmlBeanDefinitionReader类来读取xml,需要调用Resource接口进行I/O操作,另外在loadBeanDefinitions方法中是先将xm转换成document对象,再放到DefinitionBeanReader中解析,后Spring能识别的Bean形式被BeanDefinitionHolder持有,而BeanDefinitionHolder中就有BeanDefinition。这样就可以注册的容器中。
2.注册的过程有两点,其一,注册其实就将BeanDefinitionHolder中的BeanDefinition放到BeanFatory中。其二,就是map的循环,map的底层Entry实现的。
4.使用高级容器:ApplicationContext
现在BeanFactory的功能齐全了,但是使用起来有点麻烦。于是我们引入熟悉的ApplicationContext
接口,并在AbstractApplicationContext
的refresh()
方法中进行bean的初始化工作。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();
这种方式是我们最常用也是最好的一种方法了,在项目中经常这样写。一是ApplicationContext 除了实现了BeanFatory接口外,还实现了其他高级接口,提供了更多的特性。二是使用ClassPathXmlApplicationContext指定了Resource接口进行IO操作。将所有的解析过程都封装在了底层,而使用者无需手工操作。