Spring源码学习之IOC(1)

[align=center][size=x-large][b]IOC初始化[/b][/size][/align]

最近再看Spring技术内幕。发现里面写的挺不错,就是个人觉得有点乱。这里按照程序执行顺序重新整理一遍,方便理解。
再次声明:仅是个人意见!!!

[align=center][img]http://dl.iteye.com/upload/attachment/459679/f98c8d17-1fbb-3ac6-97e0-7d14ff98a7fc.png[/img]
[size=small][b]Figure1[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459681/ef5c6064-a31b-3abe-a9cf-ef7026cdcb37.png[/img]
[size=small][b]Figure2[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459689/0d582314-74f8-3e9e-8cb7-338ea60f01e2.png[/img]
[size=small][b]Figure3[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459685/ac157b5f-dce6-39c0-9740-b938aecfdc2c.png[/img]
[size=small][b]Figure4[/b][/size]

[img]http://dl.iteye.com/upload/attachment/459677/ef2fe1d9-7fcb-31de-8aaf-8c3671ed7976.png[/img][/align]
[size=small][b]Figure5[/b][/size]

以FileSystemXmlApplicationContext为例,初始化的三个部分:
1. 配置文件资源(Resource interface)的定位
2. 配置文件资源的载入(载入Document对象并且解析成BeanDefinition的格式)
3. BeanDefinition在IOC容器中的注册(这一部分包括将BeanDefinition注册入容器)
代码1:

ClassPathResource res = new ClassPathResource(“beans.xml”);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
Reader.loadBeanDefinitions(res);


上面代码是一个简单的初始化一个IOC容器。其中DefaultListableBeanFactory是一个BeanFactory的实现类。其只是个IOC容器,而ApplicationContext是对上述进行了一个封装。
首先BeanDefinition是Xml中Bean标签的抽象,FileSystemXmlApplicationContext底层还是使用的DefaultListableBeanFactory。由Figure3可以看出AbstractApplicationContext实现了ResourceLoader,所以资源的定位这一部分是由AbstractApplicationContext完成的,而FileSystemXmlApplicationContext重写了getResourceByPath(String path)方法,所以具体定位是由FileSystemXmlApplicationContext完成的
第二部分这里使用了两个类DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate,前者是读取BeanDefinition的信息,后者是解析BeanDefinition的信息完成对Xml中Bean标签的抽象
由figure5可以看出DefaultListableBeanFactory继承了BeanDefinitionRegistry。所以第三部分BeanDefinition是由DefaultListableBeanFactory完成的。

[size=large][b]1.启动初始化[/b][/size]
在FileSystemXmlApplicationContext的构造方法FileSystemXmlApplicationContext(String[] configLocationions,boolean refresh,Application parent)中调用了方法refresh(),该方法定义了整个初始化IOC的流程,也是IOC初始化的入口.具体实现在其父类AbstractApplicationContext中实现。改方法中有句代码如ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这句主要就是刷新(刷新的过程其实就是销毁所有的bean再关闭BeanFactory然后再新建)当前的bean facotry也就是IOC容器并且返回,当然如当前没有IOC那就新建一个返回。在obtainFreshBeanFactory()方法中调用refreshBeanFactory()方法,该方法是由其子类AbstractRefreshableApplicatioContext实现的。改方法中就会判断是否已经存在了IOC容器。判断完之后紧接着就是创建一个beanFactory代码如下:
[b]代码2[/b]
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}


从这里可以看出其底层依旧是使用DefaultListableBeanFactory.从这里主要开始看loadBeanDefinitions这个方法了。这个loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法是个抽象方法具体实现需要在其子类AbstractXmlApplicationContext中查找代码如下:
[b]代码3:[/b]

protected void loadBeanDefinitions(DefaultListableBeanFactory
beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new
XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new
ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}


这段代码看起来和之前的代码一有相似之处了。但还有不同之处,由于代码1中我们直接定义了一个Resource换而言之就是我们手动的定位了Resource。而这里我们能获取Resource的唯一途径就在我们构造FileSystemXmlApplication的时候传入的configLocatio-ns这个文件路径数组。所以我们需要使用setResourceLoader(ResourceLoader resourceLoader)这个方法将一个Resource加载器给当前的BeanDefinition的读取器XmlBeanDefinitionReader,让它内部能够自动完成Resource的定位和创建。然后就是将这个XmlBeanDefinitionReader作为参数传递给loadBeanDefinitions的一个重载方法loadBeanDefinitions(XmlBeanDefinitionReader reader)中。在这个loadBeanDefinitions的方法中。从这里开始将进入配置文件资源Resource定位的部分了。

[size=large][b]2.配置文件资源Resource定位[/b][/size]
上面讲到进入重载方法loadBeanDefinitions(XmlBeanDefinitionReader reader)。在这个方法中代码如下:
[b]代码4:[/b]

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}


从代码能够发现,它在这里不仅会检测配置文件路径configLocations还会检测已经定位好的Resource通过getConfigResources()方法。但是在该文件的源代码中的getConfigResources()方法中只有个return null的语句。所以if (configResources != null)始终不通过,换言之。这里只检测配置文件路径,这里可能是为了后续的拓展。至于getConfigLocations(),改方法继承自其父类AbstractRefreshableConfigApplicationContext中的。具体实现语句就是判断configLocations是否为null,如果不是null返回当前值,是null返回默认值。由于之前构造FileSystemXmlApplicationContext的时候已经将configLoactions传入进去了。所以这里直接进入if语句。调用loadBeanDef-initions(String[] locations)方法,该是继承自XmlBeanDefinitionReader父类AbstractBeanDefinitionReader中的。具体代码如下:
[b]代码5:[/b]

public int loadBeanDefinitions(String[] locations) throws
BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be
null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}



[b]代码6:[/b]

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(…);
}
if (resourceLoader instanceof ResourcePatternResolver) {

}
else {
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}

return loadCount;
}
}

这里首先会判断ResourceLoader是否为空。由于之前我们在AbstractXmlApplicationContext里填入了该值beanDefinitionReader.setResourceLoader(this)。所以继续执行下面的if。由于我们填入的ResourceLoader并不是ResourcePatternResolver类型所以进入else中。else中执行了Resource resource = resourceLoader.getRes-ource(location).由于之前在AbstractXmlApplicationContext里调用了beanDefinitionReader.setResourceLoader(this)。而AbstractXmlApplicationContext是DefaultResourceLoader的子类。DefaultResourceLoader又是ResouceLoader的实现类,所以这里应该调用的是DefaultResourceLoader的getResouce(String location)方法。具体代码如下:
[b]代码7:[/b]
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
return getResourceByPath(location);
}
}
}

由于这里并不是URL方式表示的配置文件路径。所以直接跳入cathc语句块。执行getResourceByPath(location)。由于这个getResource方法是其子类FileSystemXmlApplicationContext调用的,所以调用的是FileSystemXmlApplicationContext重写的getResourceByPath(location)并且返回一个Resource方法代码如下:
[b]代码8:[/b]
	protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}


由于至此Resource的定位部分完成了。

[size=large][b]3.BeanDefinition的加载和解析[/b][/size]
之前已经介绍如何定位Resource.也就是已经将配置文件的IO封装成Resource并且返回了。让我们继续回到代码6中AbstractBeanDefinitionReader这个类。在获取了Resource之后继续执行了loadBeanDefinitions (Resource resource).这里的resource正是刚才获取的。执行的loadBeanDefinitions是BeanDefinitionReader的接口方法。具体的实现实在其实现类AbstractBeanDefinitionReader的子类中。当然loadBeanDefinitions(Resource resource)这个方法也不是直接使用resource去解析。而是将resource进一步封装成EncodeResource并将其作为参数传递给loadBeanDefinitions(EncodeResource encodeResource)这个重载的方法进行处理.主要代码如下:
[b]代码9:[/b]
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(…);
if (logger.isInfoEnabled()) {

}
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(…);
}
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(…);
}finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.set(null);
}
}
}

如代码所示在这里会对Resource处理,将IO流从Resource中读取出来,然后封装成InputSource并将其传入doLoadBeanDefinitions。doLoadBeanDefinitions方法依旧是XmlBeanDefinitionReader中的方法.然后doLoadBeanDefinitions将根据inputSource产生出Document对象这个对象就是Xml文件的一个抽象。并且将这个Document传入XmlBeanDefinitionReader中的registerBeanDefinition方法中去代码如下
[b]代码10:[/b]
protected int doLoadBeanDefinitions(InputSource inputSource, Resource 
resource) throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(),
this.errorHandler, validationMode,
isNamespaceAware());
return registerBeanDefinitions(doc, resource);
}

}


配置文件加载完成,现在开始解析部分,将Xml形式解析成BeanDefinition这种形式。首先进入XmlBeanDefinitionReader这个的registerBeandDefinitions这个方法。这个方法内部会新建立一个BeanDefinitionDocumentReader这个对象。该对象是专门用于读取文档的一个API。然后再将上面所说的Document对象作为参数传递给BeanDefinitionDocumentReader对象的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法中并且调用,该方法中将完成解析的过程代码如下:
[b]代码11:[/b]

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { SPI.
BeanDefinitionDocumentReader documentReader =
createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc,
createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}


这里调用的registerBeanDefinitions这个方法是个接口方法,具体实现在其子类DefaultBeanDefinitionDocumentReader中实现。该子类实现的是仅仅是对Document的读取功能,具体解析的功能还需要一个类registerBeanDefinitions.所以在registerBeanDefinitions方法中会产生个registerBeanDefinitions类帮助解析。具体如何解析这里不做说明。自己也能看懂
至此,BeanDefinition的加载和解析也完成了。接下来就是如何注册了。

[size=large][b]4.BeanDefinition在IOC容器中的注册[/b][/size]
经过之前的分析已经能够了解到注册这部分的功能的实现是通过DefaultListableBeanFactory这个Class完成的。而实际上BeanDefinition的注册这一功能的实现是通过一个名为beanDefinitionMap的HashMap这样一个数据结构来维护的。那么具体是从说明地方开始调用和触发注册部分的功能的呢。通过Eclipse发现,如图所示

[align=center][img]http://dl.iteye.com/upload/attachment/459683/39d775fd-cc75-39ab-b534-024ee3feb700.png[/img]
[b]Figure6[/b][/align]

当在DefaultBeanDefinitionDocumentReader调用processBeanDefinition的时候。当我们解析完BeanDefinition之后通过BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())。这么一条语句调用的。最后会获取我们bean的Name和BeanDefinition对象然后作为参数传入registerBeanDefinition这个方法完成注册。注册代码如下:
[b]代码12:[/b]

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
Assert.hasText(beanName, "'beanName' must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(…);
}
}
synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition =
this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(…);
}else {
if (this.logger.isInfoEnabled()) {
this.logger.info(…);
}
}
}else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
resetBeanDefinition(beanName);
}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章