IOC,即"依赖倒置原理",是spring框架的核心, 即由容器提供一个对象所依赖的所有引用。 在应用了基于控制反转之后,对象之间的互相引用全部由IOC容器进行注入。这种依赖方式减轻了对象之间的耦合,使得开发变得更加简洁。本文主要对IOC容器的实现进行一系列的探讨。(源码版本为spring4.3.8)
在Spring IOC容器的实现中,主要包含两个容器系列,一个是实现BeanFactory接口的简单容器系列,一个是ApplicationContext应用上下文。 实现BeanFactory接口的容器更多只是包含了IOC容器的基本功能,而实现ApplicationContext接口的容器则是在IOC容器的基础上,增加了许多面向框架的特性与应用环境的适配。
一,BeanFacory接口
通过BeanFactory接口实现IOC容器的设计主线主要包含 BeanFactory 到HierarchicalBeanFactory 再到ConfigurableBeanFactory接口的设计路径。在这条接口设计线路中,主要定义了基本的IOC容器规范。 BeanFactory接口代码如下:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
可以看到BeanFactory接口中主要定义了getBean()方法,getBean()方法主要用于从IOC容器中获取Bean。
public interface HierarchicalBeanFactory extends BeanFactory {
BeanFactory getParentBeanFactory();
boolean containsLocalBean(String name);
}
随后,在继承了BeanFactory接口的HierarchicalBeanFactory中,可以看到该接口增加了getParentBeanFactory()的定义,这意味着实现该接口便可以获取对于双亲IOC容器的管理,以及双亲IOC容器中Bean的获取权利。接下来在ConfigurableBeanFactory的代码中,则主要添加了一些对于BeanFactory的配置功能,比如通过setParentBeanFactory()方法配置双亲BeanFactory。通过对这些接口进行叠加便可以定义出IOC容器的基本功能。
从上文可知,BeanFactory接口定义了getBean方法,这个方法是IOC容器管理Bean所用到的主要方法。在BeanFactory中:containsBean方法可以用来判断容器中是否还有指定名字的Bean, isSingleton与isPrototype方法用于查询指定名字的Bean是否为Singleton或Prototype类型的Bean, 这两个属性均可在BeanDefination中指定。isTypeMatch方法用于指定名字的Bean类型是否为特定的class类型。 getAliase方法则用来查询指定名字的Bean的所有别名。
可以看出BeanFactory主要定义的是在容器中对于Bean的各类基本的检索方法, 代表了最基本的IOC容器入口。
二,BeanFactory接口的实现
spring提供了许多对于BeanFactory系列接口的实现,很多教程中常常以XmlBeanFactory的实现为例子说明简单IOC容器的实现。然而在当前版本中XmlBeanFactory类已被标记为过时。可取代的解决方案一是使用XmlBeanDefinationReader+ DefaultListableBeanFactory取代,二是使用applicationContext取代。由于applicationContext实际上应划分为IOC容器的更高级实现,因此此处主要对于XmlBeanFactory以及XmlBeanDefinationReader进行一定的讨论。
XmlBeanFactory继承自DefaultListableBeanFactory,DefaultListableBeanFactory基本包含了IOC容器所具备的重要功能,通常作为默认的IOC容器进行使用。XmlBeanFactory在继承了DefaultListableBeanFactory的基础上,添加了从Xml文件中读取BeanDefination的过程。XmlBeanFactory源码如下,
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
可以看出在XmlBeanFactory中,主要包含三个流程
1,通过对XmlBeanDefinationReader的回调进行读取器的初始化。
2,通过构造函数实现的Resource的定位。
3,调用XmlBeanDefinationReader对Resource进行BeanDefination的载入。
XmlBeanFactory的初始化操作如果手动使用XmlBeanDefinationReader以及其父类DefaultListableBeanFactory进行替代的话。实现流程大致如下:
ClassPathResource resource = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
通过上述代码便可以手动实现XmlBeanFactory中对于BeanDefination的载入过程。 在XmlBeanFactory的注释中,作者提到了通过使用XmlBeanDefinationReader与DefaultListableBeanFactory可以实现多Xml文件的载入以及更多高级配置。 这些配置的实现主要依赖于XmlBeanDefinationReader所继承的AbstractBeanDefinationReader。XmlBeanDefinationReader类的继承关系如下图:
在AbstractBeanDefinitionReader中,定义了BeanDefinitionReader接口的默认实现。这里以实现从多个Resource加载xml配置为例,展示BeanDefinitionReader接口在Spring中的实现方式 。首先在AbstractBeanDefinitionReader中loadBeanDefinition方法定义了从多个Resource中加载配置的方法:
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
for (Resource resource : resources) {
counter += loadBeanDefinitions(resource);
}
return counter;
}
可以看到该方法中对参数中的所有Resource进行遍历,调用BeanDefinition接口中定义的loadBeanDefinitions(resource)方法对各个xml配置文件进行读取,随后只要调用在XmlBeanDefinitionReader中重写的loadBeanDefinitions(Resource resource)方法,便可以实现针对Xml文件的多文件读取,值得注意的是这是一系列调用中最后一个对BeanDefinitionReader接口中进行重写的方法。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
在XmlBeanDefinitionReader实现的载入过程中,在载入目标文件流之后,首先进行线程相关处理,将正在解析的resource存入threadlocal变量中,随后调用doLoadBeanDefinitions方法对输入流进行载入。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
在doLoadBeanDefinitions方法中,首先在doLoadDocument方法中从输入中提取Document,随后在registerBeanDefinitions方法中对document中的元素进行注册,将其注册为BeanDefinition。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
在registerBeanDefinitions方法中,在当前IOC容器即BeanFactory创建XmlReaderContext并将其作为参数传入BeanDefinitionDocumentReader中的registerBeanDefinitions方法,使得解析出的BeanDefinition可以直接存入当前IOC容器。具体从xml文件中读取BeanDefinition的过程主要在DefaultBeanDefinitionDocumentReader中的parseBeanDefinitions方法中。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在parseBeanDefinition方法中,对Document中的元素进行遍历,在parseDefaultElement方法中根据类型分别进行处理(Alias, Bean , Import, Beans ),将对应的BeanDefinition存入DefaultListableBeanFactory中的beanDefinitionMap映射中。从而完成整个从Xml读取BeanDefinition的整个流程。
通过XmlBeanDefinitionReader+ DefaultListableBeanFactory完全可以实现XmlFactoryBean中所包含的方法, 且可以方便的进行更高级的拓展。另一方面,通过FactoryBean实现IOC容器的直接使用后面也逐渐被ApplicationContext所取代,这可能也是XmlFactoryBean被弃用的原因之一。
可以看到,在Spring对接口实现的过程中,使用的便是一种"单实现,多继承"的方式,即首先通过多接口拼接,完成满足功能需求的单个默认实现类,随后在继承默认实现的多个不同子类中对接口中对应方法进行重写,从而完成功能的拓展。理解这种实现方式,对于之后阅读Spring源码和自己代码的编写都会很有帮助。