整理:Spring IOC 实现原理
Posted 阿征new
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了整理:Spring IOC 实现原理相关的知识,希望对你有一定的参考价值。
Spring IOC 实现原理
目录
FileSystemXmlApplicationContext
IOC: Inversion of Control ,即 "控制反转" , 不是什么技术,而是一种思想。原先需要自行实例化的对象, 交给IOC容器去实现。那么控制反转,谁被控制? 谁被反转 ?
在传统的JavaSE程序中,直接在类的内部通过new关键字来创建对象的实例,是程序主动去创建依赖对象,而IOC有一个专门的容器,来创建这些对象,由IOC容器来控制对象的创建,依赖对象也是容器帮忙查找创建并进行注入,对象只是被动的接受,依赖对象的获取被反转了。它还有一个更加形象的名字叫 依赖注入。
注入方式:
- 接口注入
- setter
- 构造器注入
IOC容器的设计与实现有两种:BeanFactory和ApplicationContext
IOC容器的两个容器设计系列:。
- 实现了BeanFactory接口的简单容器系列,只是实现了容器最基本的功能
- Applic ationContext应用上下文,作为容器的高级形态存在。除了具有基本的功能外,还增加了许多面向框架的特性,同时对应用环境做了许多适配。
BeanFactory
BeanFactory为IOC容器具体实现指定了基本的规范,换句话说BeanFactory是最顶层的类,它有三个子类,ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory接口。但是它们最终生成了一个默认的实现类DefaultListableBeanFactory。类的继承关系如下:
上图中DefaultListableBeanFactory的继承关系并不完整,删除了AliasRegistry接口的相关信息。
下面看一下BeanFactory接口中关于方法的定义:
public interface BeanFactory
//通过使用转义符“&”,来区分BeanFactory产生的对象和BeanFactory本身
String FACTORY_BEAN_PREFIX = "&";
//根据Bean的名字获取Bean的实例
Object getBean(String var1) throws BeansException;
//根据Bean的名字和Class类型来获取实例,增加了类型安全验证
<T> T getBean(String var1, Class<T> var2) throws BeansException;
//根据Class类型来获取实例
<T> T getBean(Class<T> var1) throws BeansException;
//返回实例
Object getBean(String var1, Object... var2) throws BeansException;
//是否包含指定的实例
boolean containsBean(String var1);
//是否单例
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
//是否是原型
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
//类型是否匹配
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
//返回指定实例名的类型
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
//返回别名
String[] getAliases(String var1);
通过对上面方法的分析,可以得出。BeanFactory并不关心bean是如何定义,怎样被加载的,当我们需要bean的时候,直接来取就可以了。对于工厂来说,我只需要关心有没有产品,对于产品是怎么产生,怎么制作的,那是工人干的事,工厂并不关心。定义的都是最核心的接口,如getBean(),从容器中获取实例。
ApplicationContext
ApplicationContext是Spring提供的一个高级的IOC容器,除了提供基本IOC容器的功能外,还能为用户提供:
- 支持信息源,可以实现国际化
- 访问资源
- 支持应用事件
- ApplicationContext中提供的附加服务
看一下ApplicationContext的继承关系。
通过对上图的分析,可以看出ApplicationContext除了BeanFactory的功能,还具有ResourceLoader接口的功能,具有了高级容器特性的支持。
下面对接口的继承关系做一个简要的分析:
- 在BeanFactory中,从BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一个条主要的设计路径。 BeanFactory中定义了基本的IOC容器规范,在HierarchicalBeanFactory接口中增加了getparantBeanFactory()的接口功能,使BeanFactory具备了双亲IOC容器的管理功能,在ConfigurableBeanFactory主要定义了一些对BeanFactory的配置功能
- 在ApplicationContext中,从BeanFactory到ListableBeanFactory,再到ApplicationContext,
- 再到常用的WebApplicationContext或者ConfigurableApplicationContext接口。在BeanFactory简单接口的基础上增加了对高级容器的特性支持。
BeanDefinition
SpringIOC容器帮我们管理了各种bean对象及其相互依赖的关系,Bean对象在Spring实现中是以BeanDefinition来描述的。*换句话说 依赖关系是使用BeanDefinition来保存的。 *其继承体系如下:
Spring关于Bean的装配,有下面三种方式:
- 在XML中进行显示配置
- 在java中进行显示配置
- 隐式的bean发现机制和自动装配
Bean的解析非常的复杂,功能被分的很细,需要扩展的地方也很多,必须保证有足够的灵活性,下面看一下基于xml配置文件解析的继承体系:
XmlBeanDefinitionReader的继承关系图.png
IOC容器的初始化
IOC容器的初始化包括:BeanDefinition的Resouce定位,BeanDefinition的载入和注册三个基本的过程。
需要注意的是Spring把上面过程进行了分离,并使用了不同的模块来完成,
定位使用了 ResourceLoader,
解析使用了BeanDefinitionReader等。
这样设计的目的可以让用户对这三个过程进行裁剪和扩展,定义出适合自己的IOC容器初始化过程。
下面来看一下两种IOC容器的创建过程:
BeanFactory 以XmlBeanFactory为例,ApplicationContext 以 FileSystemXmlApplicationContext为例
XmlBeanFactroy
看一下XmlBeanFactory的源码定义:
public class XmlBeanFactory extends DefaultListableBeanFactory
//定义一个BeanDefinitionReader阅读器
private final XmlBeanDefinitionReader reader;
//构造方法,需要传入Resource,资源的定位
public XmlBeanFactory(Resource resource) throws BeansException
this(resource, (BeanFactory)null);
//构造方法重载,需要传入Resource,和父容器,此方法完成了读取器的初始化操作
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
通过阅读XmlBeanFactory的源码可以看出。
XmlBeanFactory继承自 DefaultListableBeanFactory 类。
而DefaultListableBeanFactory类包含了基本IOC容器所具有的重要功能。
那BeanDefinition的信息来源自哪,如何定位? 定位之后又如何读取解析呢??
对于信息来源的定位封装成Spring中的Resource类来给出,解析是它的内部定义了一个XmlBeanDefinitionReader对象,有了这个对象,就有了处理xml文件的能力。
参考XmlBeanFactory的实现,我们手动来模拟一下XmlBeanFactory的实现过程
//首先进行BeanDefinition资源文件的定位,封装为Source的子类
ClassPathResource res = new ClassPathResource("beans.xml");
//创建基本的IOC容器
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//创建文件读取器,并进行回调设置。
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//资源加载解析
reader.loadBeanDefinitions(res);
通过上面的代码,总结一下IOC容器的使用步骤:
- 创建IOC抽象资源,这个抽象资源包含了BeanDefinition的定义信息
- 创建一个BeanFactory
- 创建一个BeanDefinition的读取器,通过一个回调配置给BeanFactory
- 从定义好的抽象资源中读取配置信息。完成载入和注册的定义后,IOC容器就建立了起来了。
FileSystemXmlApplicationContext
以此举例说明:applicationContext
先来看一下FileSystemXmlApplicationContext的继承关系:
通过上面的继承关系,可以发现它的基类AbstractXmlApplicationContext中已经实现了主要的功能。
在看一下这个类的源码实现,关于构造方法部分,我做了删减:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext
//构造方法重载,省略其他的构造方法
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException
super(parent);
this.setConfigLocations(configLocations);
if(refresh)
this.refresh();
//通过一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition
protected Resource getResourceByPath(String path)
if(path != null && path.startsWith("/"))
path = path.substring(1);
return new FileSystemResource(path);
除了构造方法和getResourceByPath外,并没有其他的方法。也就说它只是实现了和它自身设计相关的两个功能。
看一下该类的调用方式:
ApplicationContext context =new FileSystemXmlApplicationContext(xmlPath);
上面调用了参数是字符串的构造函数,但是,对构造函数的调用最终都会转到下面的方法来执行,
refresh方法启动了整个容器的初始化流程:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException
super(parent);
this.setConfigLocations(configLocations);
if(refresh)
this.refresh();
来看一下上面的构造方法中,最终都执行了那些操作,具体的操作都是由那些类执行的。
通过代码跟随,可以发现,super(parent)这个方法最终是由其基类(AbstractApplicationContext)来执行,执行了AbstractApplicationContext的无参构造方法和setParent()方法。其代码如下,省略了静态代码块的定义:
//FileSystemXmlApplicationContext调用父类构造方法就是该方法
public AbstractApplicationContext(ApplicationContext parent)
this();
setParent(parent);
//具体需要执行的方法
public AbstractApplicationContext()
this.resourcePatternResolver = getResourcePatternResolver();
//获取一个Source的加载器用于读入Spring Bean 定义资源文件
protected ResourcePatternResolver getResourcePatternResolver()
return new PathMatchingResourcePatternResolver(this);
//设置双亲ioc容器
public void setParent(ApplicationContext parent)
this.parent = parent;
if (parent != null)
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment)
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
调用父类的构造方法执行完成后,返回FileSystemXmlApplicationContext类,执行setConfigLocations(configLocations)方法,该方法的定义在类AbstractRefreshableConfigApplicationContext中:
//处理资源定义的数组,解析Bean文件的定义路径
public void setConfigLocations(String[] locations)
if (locations != null)
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++)
//解析路径
this.configLocations[i] = resolvePath(locations[i]).trim();
else
this.configLocations = null;
在资源定位的时候,支持下面两种方式:
- ClasspathResource res = new ClasspathResource("a.xml,b.xml");
- ClasspathResource res = new ClasspathResource("new String()'a.xml' , 'b.xml'")
具体实现的方式不在展开,感兴趣的可以去看一下源码实现
我们来看以上,程序执行到此处后,都做了哪些操作:
- 在AbstractApplicationContext中初始化了resourcePatternResolver(多资源文件的载入),用于获取Resource,关于何时使用后面再解释
- 将资源的定义路径保存在了configLocations数组中
到此,IOC容器根据资源定义路径获取Resouce的准备工作便完成了。
IOC容器的初始化过程
在开始分析初始化过程之前,先从宏观上对初始化的过程做一个简单的介绍,
有一个大体框架的初始化模型,方便后面的理解,初始化过程分成了三个过程:
- Resource定位
- BeanDefinition的载入
- 向IOC容器注册这些BeanDefinition信息。
- 这个过程是通过 BeanDefinitionRegistry 接口的实现来完成的。最终注入到HashMap中去,这个hashmap就是持有beandefinition数据的。
下面来看一下FileSystemXmlApplicationContext中的关于refresh()方法的调用,实际调用的是 AbstractApplicationContext 中的refresh()方法,该方法定义如下:
public void refresh() throws BeansException, IllegalStateException
synchronized (this.startupShutdownMonitor)
//环境准备,获取容器启动的时间,设置活动标志,以及属性的初始化
prepareRefresh();
//在子类中启动resreshBeanfactory()方法
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器属性
prepareBeanFactory(beanFactory);
try
//设置BeanFactory的后置处理
postProcessBeanFactory(beanFactory);
//调用后置处理,为这些后置处理器在Bean的定义中向容器注册
invokeBeanFactoryPostProcessors(beanFactory);
//注册Bean的后续处理,在Bean创建过程中调用
registerBeanPostProcessors(beanFactory);
//初始化上下文消息机制
initMessageSource();
//初始化上下文中事件机制
initApplicationEventMulticaster();
//初始化其他特殊的Bean
onRefresh();
//检查监听Bean并且将这些Bean向容器注册
registerListeners();
//初始化所有的singleton beans , 设置lazy-init = true 的bean除外
finishBeanFactoryInitialization(beanFactory);
//发布容器事件,结束Refresh过程
finishRefresh();
catch (BeansException ex)
// 为防止Bean资源占用,在异常处理中,销毁已经生成的单件Bean
destroyBeans();
//重置Rest标志
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
refresh() 方法主要为IOC容器Bean的生命周期提供管理条件??(如何利用)。
Spring IOC容器的生成是从refreshBeanFactory()方法开始的,也就是执行了下面的代码:
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
protected ConfigurableListableBeanFactory obtainFreshBeanFactory()
//使用委派模式,父类只是定义了方法,具体实现交给子类
refreshBeanFactory();//--就是通过该方法初始化ioc-容器
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled())
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
return beanFactory;
在 AbstractApplicationContext 抽象类中,只是进行了 refreshBeanFactory() 方法的定义,方法的实现是在其子类 AbstractRefreshableApplicationContext 中实现的,在子类的定义如下:
protected final void refreshBeanFactory() throws BeansException
if (hasBeanFactory()) //如果已经有容器,则销毁并关闭容器
destroyBeans();
closeBeanFactory();
try
//创建IOC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//对容器进行定制化,如设置启动参数,开启注解自动装配等
customizeBeanFactory(beanFactory);
//委派模式,资源的加载交给子类实现,启动对BeanDefinition的载入操作
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor)
this.beanFactory = beanFactory;
catch (IOException ex)
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
上面代码 loadBeanDefinitions(beanFactory); 我们说是一个委派模式,只是进行了方法的定义,具体实现则是由AbstractXmlApplicationContext类实现,在该方法中创建了 读取器 XmlBeanDefinitionReader 的实例,
然后把这个读取器在IOC容器中设置好,最后是启动读取器来完成对BeanDefinition在IOC容器中的载入,定义如下:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException
//创建XmlBeanDefinitionReader
//即创建Bean读取器,并通过回调设置到容器中去,容器使用该读取器,读取Bean定义资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//为Bean读取器设置资源加载器
beanDefinitionReader.setResourceLoader(this);
//设置一个Sax实体用于解析
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//当Bean读取器读取Bean定义的xml资源文件时,启用xml校验机制
initBeanDefinitionReader(beanDefinitionReader);
//Bean 读取器真正的实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
在 XmlBeanDefinitionReader 的初始化过程中,还进行了一些其他的操作,具体如下:
//调用父类的构造方法
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry)
super(registry);
//父类构造方法
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry)
//赋值
this.registry = registry;
//判断register是否是ResourceLoader 类型
if (this.registry instanceof ResourceLoader)
this.resourceLoader = (ResourceLoader) this.registry;
else
//为resourceLoader 新建一个资源加载器
this.resourceLoader = new PathMatchingResourcePatternResolver();
//判断register 是否是 EnvironmentCapable类型
if (this.registry instanceof EnvironmentCapable)
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
else
//初始化一个environment
this.environment = new StandardEnvironment();
通过上面代码发现,在创建 XmlBeanDefinitionReader的过程中,完成了resourceLoader和environment的赋值操作。
首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader来完成的。
因为使用的FileSystemXmlApplicationContext, getConfigResources()方法返回的是null,所以程序会走第二个分支
//Xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException
//获取资源定位
Resource[] configResources = getConfigResources();
if (configResources != null)
//Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
reader.loadBeanDefinitions(configResources);
//如果子类获取的Bean定义为空,则获取FileSystemXmlApplication构造方法中setConfigLocations方法设置的资源。
String[] configLocations = getConfigLocations();
if (configLocations != null)
reader.loadBeanDefinition(configLocations);
程序分析到这,来梳理一下上面的执行流程:
-
在FileSystemXmlApplicationContext一共做了三件事
- 调用了父类的构造器,进行了初始化
- 设置了BeanDefinition的定义路径
- 执行了Refresh()方法
-
refresh()方法来启动整个BeanDefinition的载入过程
- 创建容器 DefaultListableBeanFactory
- 创建了XmlBeanDefinitionReader
- 开始准备通过reader来加载资源
AbstractBeanDefinitionReader 读取Bean定义资源,AbstractBeanDefinitionReader 的 loadBeanDefinitions方法源码如下:
//方法重载
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5)
String location = var3[var5];
counter += this.loadBeanDefinitions(location);
return counter;
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException
return this.loadBeanDefinitions(location, (Set)null);
//重载的最终执行方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException
//获取资源加载器
//上面提到过,XmlBeanDefinitionReader初始化时,在其父类中执行了加载器的初始化操作
//resourceLoader的类型为PathMatchingResourcePatternResolver
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null)
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
//判断类型
if (resourceLoader instanceof ResourcePatternResolver)
try
//将指定位置的Bean定义资源文件解析转化为Resource
//加载多个指定位置的资源定义文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//读取Resource
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null)
for (Resource resource : resources)
actualResources.add(resource);
if (logger.isDebugEnabled())
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
return loadCount;
catch (IOException ex)
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
else
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null)
actualResources.add(resource);
if (logger.isDebugEnabled())
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
return loadCount;
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
Resource[] var3 = resources;
int var4 = resources.length;
for(int var5 = 0; var5 < var4; ++var5)
Resource resource = var3[var5];
counter += this.loadBeanDefinitions((Resource)resource);
return counter;
上面的方法主要进行了两件事:
- 调用资源加载器获取资源 resourceLoader.getResource(location)
- 真正执行加载功能的是子类 XmlBeanDefinitionReader的loadBeanDefinitions方法
loadBeanDefinitions()方法在AbstractBeanDefinitionReader中并没有具体的实现,它会转到XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)中运行:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException
//对xml资源进行编码处理
return this.loadBeanDefinitions(new EncodedResource(resource));
//方法重载,转入此方法执行
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException
Assert.notNull(encodedResource, "EncodedResource must not be null");
if(this.logger.isInfoEnabled())
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
Object currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if(currentResources == null)
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
if(!((Set)currentResources).add(encodedResource))
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
else
int var5;
try
//将资源文件转换为类型为InputStream的I/O流
InputStream ex = encodedResource.getResource().getInputStream();
try
//从InputStream中得到xML的解析源
InputSource inputSource = new InputSource(ex);
//编码如果不为null, 则设置inputSource的编码
if(encodedResource.getEncoding() != null)
inputSource.setEncoding(encodedResource.getEncoding());
//读取数据
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
finally
ex.close();
catch (IOException var15)
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
finally
((Set)currentResources).remove(encodedResource);
if(((Set)currentResources).isEmpty())
this.resourcesCurrentlyBeingLoaded.remove();
return var5;
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException
try
//转化为Document 对象
Document doc = doLoadDocument(inputSource, resource);
//启动对Bean定义解析的详细过程,会用到Spring Bean的配置规则
return registerBeanDefinitions(doc, resource);
//删除了部分catch语句
catch (....)
throw ex;
将XML文件转换成Document对象,解析过程由 documentLoader实现,getValidationModeForResource(resource)验证xml文件的模式 DTD还是XSD,此方法不在展开。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception
return this.documentLoader.loadDocument(inputSource,getEntityResolver(), this.errorHandler,getValidationModeForResource(resource),isNamespaceAware());
创建Document 的过程:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception
//创建文件解析工厂
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled())
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
//创建文档解析器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析Spring bean定义资源 ,并返回Document对象
return builder.parse(inputSource);
spring并没有对XML进行特殊的处理,使用了SAX对xml文档进行解析,操作分为三步:
- 创建DocumentBuilderFactory
- 创建DocumentBuilder
- 解析inputSource对象,返回Document 对象
Document对象代表了一个XML文档的模型树,所有的其他Node都以一定的顺序包含在Document对象之内,排列成一个树状结构,以后对XML文档的所有操作都与解析器无关,直接在这个对象上操作即可。NodeList代表了包含一个或者多个Node的列表,操作上可以看作数组,使用getLength()获得列表中的节点数,item(int index ) 返回集合中第index个项。Node对象很少使用,会使用它的自对象Element,Attr 等。
至此,Spring IOC容器根据定位的Bean定义资源文件,并将其加载读入转换为document对象的过程完成。
上面略微呢有一点跑题,document对象的创建过程可能不是我们最关心的,
我们关心就是Spring的BeanDefinition是怎样按照Spring的Bean语义要求进行解析并转化为容器内部数据结构的。
这个过程是在下面的方法中执行的,我们来看一下,具体的操作过程:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException
//创建DocumentReader来对XML格式的BeanDefinition进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获得容器中已经存在的Bean数量
int countBefore = getRegistry().getBeanDefinitionCount();
//具体的解析过程在这个方法中执行
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
Bean定义资源的解析分为以下两个过程:
- 通过调用xml解析器,将资源定义文件转换为Document对象,document对应并没有按照spring bean的规则进行解析。
- 在完成通用xml解析之后,按照Spring的Bean规则对Document对象进行解析,这个过程是在documentReader中实现的。具体的操作是由 DefaultBeanDefinitionDocumentReader 完成的。
处理的结果由 BeanDefinitionHolder 对象持有。解析过程由 BeanDefinitionParserDelegate 来实现。
来看下面的流程:
//根据Spring DTD 对bean定义的规则解析Bean定义Document对象
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
//获取xml描述符
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获得Document的根对象
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
protected void doRegisterBeanDefinitions(Element root)
BeanDefinitionParserDelegate parent = this.delegate;
//BeanDefinitionParserDelegate中定义了Spring Bean定义的XML文件的各种元素
//默认BeanDefinitionParserDelegate会处理”http://www.springframework.org/schema/beans“命名空间下元素及其属性
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root))
//对于默认的命名空间,首先开始的是对profile属性解析
//profile用得最多的是DataSource在不同环境下使用不同的bean
//spring使用StringTokenizer来进行字符串的分割,但是jdk为了兼容性是推荐使用String.split()方法的:
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec))
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles))
if (logger.isInfoEnabled())
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());
return;
//接下来的解析使用了模板模式
//进行自定义的解析,增强解析过程的可扩展性 ,空实现
preProcessXml(root);
//从Document的根元素开始进行Bean定义的document对象
parseBeanDefinitions(root, this.delegate);
//在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性。空实现
postProcessXml(root);
this.delegate = parent;
protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate)
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root, parentDelegate);
return delegate;
解析document文件,不同的命名的空间采用不同的方法处理
protected void parseBeanDefinitions(Element root,BeanDefinitionParserDelegate delegate)
//Bean定义的Document对象使用了Spring默认的XML命名空间
if (delegate.isDefaultNamespace(root))
//获取Document对象的所有子节点,NodeList的含义上面进行了介绍
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++)
Node node = nl.item(i); //获取Node节点
//判断Node是否是Element类型
if (node instanceof Element)
Element ele = (Element) node;
//判断该元素是否属于Spring定义Bean的默认命名空间
if (delegate.isDefaultNamespace(ele))
//使用Spring的Bean规则解析元素节点
parseDefaultElement(ele, delegate);
else
//使用用户自定义的规则进行解析
delegate.parseCustomElement(ele);
else
//没有使用spring默认的命名空间,则使用用户自定义的解析规则解析
delegate.parseCustomElement(root);
使用spring的Bean规则解析Document元素节点,
有些元素节点是<import>、<bean>、<beans> 等,则分别进行解析
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
//如果元素节点是<Import>导入元素,进行导入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT))
importBeanDefinitionResource(ele);
//如果元素节点是<Alias>别名元素,进行别名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT))
processAliasRegistration(ele);
//如果是<bean>转入此流程处理
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT))
processBeanDefinition(ele, delegate);
//如果是<beans>转入此流程处理
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT))
// recurse
doRegisterBeanDefinitions(ele);
主要看一下对bean标签的解析过程(重点):
protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate)
//BeanDefinitionHolder是对BeanDefinition的封装,delegate解析完成后使用holder封装,
//bdHolder 包含了id ,别名和BeanDefinition的信息
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null)
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try
//--核心:向容器注册封装后的实例
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
catch (BeanDefinitionStoreException ex)
....
//在Beandefinition向IoC容器注册完成以后,发送消息
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
下面看一下 parseBeanDefinitionElement() 方法的具体实现,
对于 BeanDefinition的注册时存放在ConcurrentHashMap中的,beanName变为存放的健key,BeanDefinition是value:
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)
//获取id的值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取name的值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//分割name属性
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr))
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
//将id赋值给beanName
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty())
beanName = aliases.remove(0);
if (logger.isDebugEnabled())
logger.debug("....");
if (containingBean == null)
checkNameUniqueness(beanName, aliases, ele);
//该方法引发对Bean元素的详细解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
//程序执行到此处,整个<bean>标签的解析就算结束了。一个beanDefinition就创建好了
if (beanDefinition != null)
if (!StringUtils.hasText(beanName))
try
if (containingBean != null)
//如果不存在beanName,那么根据Spring中的提供的命名规则为当前bean生成对应的beanName
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
else
beanName = this.readerContext.generateBeanName(beanDefinition);
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName))
aliases.add(beanClassName);
if (logger.isDebugEnabled())
logger.debug("....");
catch (Exception ex)
error(ex.getMessage(), ele);
return null;
String[] aliasesArray = StringUtils.toStringArray(aliases);
//将信息封装到BeanDefinitionHolder中
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
return null;
BeanDefinition可以看成是对<bean>定义的抽象,
这个数据对象中封装的数据大多都是与<bean>定义相关的,也有很多就是我们在定义Bean时看到的那些Spring标记。
这个BeanDefinition数据类型是非常重要的,它封装了很多基本数据,这些都是Ioc容器需要的,上面代码最后我们返回了一个BeanDefinitionHolder实例,这个实例封装了beanDefinition,beanName, aliase三个信息,beanDefinition中也包含了beanName,aliase信息。
对Bean元素的详细解析 parseBeanDefinitionElement:
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean)
this.parseState.push(new BeanEntry(beanName));
//这里只读取定义的<bean>中设置的class名字,然后载入到BeanDefinition中去,
//只是做个记录,并不涉及对象的实例化过程,对象的实例化过程实际是在依赖注入时完成的
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE))
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
try
//解析parent属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE))
parent = ele.getAttribute(PARENT_ATTRIBUTE);
//创建用于承载属性的AbstractBeanDefinition类型的GenereicBeanDefinition
//这里生成需要的BeanDefinition对象,为Bean定义的信息做载入做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//这里对当前Bean元素进行属性解析,并设置description信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析元数据
parseMetaElements(ele, bd);
//解析lookup-method属性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replaced-method属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析构造函数参数
parseConstructorArgElements(ele, bd);
//解析property子元素
parsePropertyElements(ele, bd);
//解析qualifier子元素
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
//返回BeanDefinition对象
return bd;
catch (ClassNotFoundException ex)
...
catch (NoClassDefFoundError err)
...
catch (Throwable ex)
...
finally
this.parseState.pop();
return null;
从上面的代码可以看出,要解析属性首先要创建用于承载属性的实例,也就是创建 GenericBeanDefinition类型的实例,
而代码 createBeanDefinition(className,parent) 的作用就是实现此功能。创建完承接的实例后,便可以进行各种属性的解析了,首先进行解析的是在<bean></bean>标签中定义的各种属性,如scope, singleton,abstract,lazy-init等,然后再解析子标签中的属性,如:lookup-method ,constructor-arg等。解析完成之后关联到实例上,之所以能进行关联,是因为xml中所有的配置都能在GenericBeanDefinition的实例类中找到对应的配置。
此时容器还没有起作用,要想起作用,需要向容器进行注册。
分析到这,已经完成了xml文件向BeanDefinition的转化,每个一个<bean>标签都会转化成一个BeanDefinition对应的实体。实体中包含了<bean>中定义的所有属性。
BeanDefinition在IOC容器中的注册
前面只是进行了BeanDefinition在IOC容器中的载入和解析过程,这些动作完成后,已经完成了定义的数据转化为BeanDefinition的过程。下面代码继续执行,看一下注册的过程:也就是processBeanDefinition函数中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry()) 的代码。
//registerBeanDefinition()方法的具体实现
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException
//获取beanName
String beanName = definitionHolder.getBeanName();
//使用beanName作为唯一标识注册
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
//如果解析的BeanDefinition有别名,也要根据别名在注册一遍,不然根据别名找不到相应的信息
String[] aliases = definitionHolder.getAliases();
if (aliases != null)
for (String alias : aliases)
//beanName -> alias ,先将alias转换为beanName,在查找
registry.registerAlias(beanName, alias);
最终承接注册任务方法是在 DefaultListableBeanFactory 类中定义的,
为了更好的排版,我对下面的代码进行了删减,主要删除了抛出异常信息和日志信息,并不影响代码的逻辑,
对于beanDefinition的注册,做了一些验证之后,直接将beanDefinition放入了Map中:
//向IoC容器注册解析的BeanDefiniton
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException
if (beanDefinition instanceof AbstractBeanDefinition)
try
//注册前的最后一次校验
//主要是对AbstractBeanDefinition属性中的methodOverrides校验
((AbstractBeanDefinition) beanDefinition).validate();
catch (BeanDefinitionValidationException ex)
....
BeanDefinition oldBeanDefinition;
//检查是否有同名的beanName存在,判断allowBeanDefinitionOveriding属性
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null)
if (!isAllowBeanDefinitionOverriding())
throw ...
else if (oldBeanDefinition.getRole() < beanDefinition.getRole())
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled())
this.logger.warn("....");
else if (!beanDefinition.equals(oldBeanDefinition))
if (this.logger.isInfoEnabled())
this.logger.info("....");
else
if (this.logger.isDebugEnabled())
this.logger.debug("...");
//如果允许覆盖则执行覆盖操作
this.beanDefinitionMap.put(beanName, beanDefinition);
else
//判断是否已经有其他的Bean开始初始化
//在容器启动的最后会预初始化所有的singleton beans
if (hasBeanCreationStarted())
//注册的过程中需要线程同步,以保证数据的一致性
synchronized (this.beanDefinitionMap)
//注册
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName))
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
else
//正常情况下应该走此分支
//将BeanDefinition放入Map中,此map中保存了所有的BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
//这个ArrayList中会按照bean配置的顺序保存每一个注册的Bean的名字
this.beanDefinitionNames.add(beanName);
//手动注册singleton bean
this.manualSingletonNames.remove(beanName);
this.frozenBeanDefinitionNames = null;
if (oldBeanDefinition != null || containsSingleton(beanName))
resetBeanDefinition(beanName);
至此,
Bean定义资源文件中配置的Bean被解析过后,已经注册到IoC容器中,被容器管理起来,真正完成了IoC容器初始化所做的全部工作。
现在,IoC容器中已经建立了整个Bean的配置信息,这些BeanDefinition信息已经可以使用,并且可以被检索,
IoC容器的作用就是对这些注册的Bean定义信息进行处理和维护。
这些注册的Bean定义信息是IoC容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。
依赖注入
执行完上面的操作后,IOC容器已经实现了对Bean管理定义的相关数据,但是此时IOC容器还没有对所管理的Bean进行依赖注入
依赖注入在以下两种情况下发生(应该不对??):
- 用户第一次通过getBean()方法想容器索取时,进行依赖注入
- 当用户在Bean的定义中为<bean>配置了lazy-init属性,让容器在解析注册时进行欲初始化,触发依赖注入
Bean初始化时机:
- 默认scope=singleton IOC启动完毕,就初始化Bean(实例化对象)
- scopte=singleton,并且配置lazy-init=true 开启懒加载,在第一次getBean()的时候,进行初始化(实例化对象)
- scope=prototype,不会预先初始化,每次getBean(),都会实例化一个新的对象
doGetBean():
AbstactBeanFactory 通过getBean()函数获取被管理的Bean
但是因为lazyinit默认为false,所以spring会提前进行部分实例的初始化操作
让我们回退到 AbstactApplicationContext 类的 finishBeanFactoryInitialization(beanFactory) 方法:
该方法进行了所有单例类的初始化操作,初始化的动作包装在了getBean()方法中,这个方法是获取Bean的地方,也是依赖注入发生的地方,对genBean的调用会转入到doGetBean()方法来执行,来看一下该方法的定义:
有以下问题:
- FactoryBean 和 BeanFactory的区别
- spring是如何解决循环依赖的
//根据执行的名称获取容器管理的Bean,已经初始化就直接返回,否则就先初始化再返回
protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)throws BeansException
//提取定义的beanName,如果是别名,则将别名转换成规范的BeanName
final String beanName = transformedBeanName(name);
//定义Bean
Object bean;
//检查缓存中或者实例工厂中是否有对应的实例,单例模式的bean只创建一次
Object sharedInstance = getSingleton(beanName);
//如果实例已经创建,进入if分支 ,简单介绍一下参数args,一般情况下args为null,如果args不为null,则不是为了得到bean,而是为了创建bean
if (sharedInstance != null && args == null)
if (logger.isDebugEnabled())
if (isSingletonCurrentlyInCreation(beanName))
logger.debug("...");
else
logger.debug("...");
//返回对应的实例,getOb以上是关于整理:Spring IOC 实现原理的主要内容,如果未能解决你的问题,请参考以下文章