您的位置:首页 > 编程语言 > Java开发

Spring IoC学习笔记(一)

2016-09-13 11:29 330 查看

一、IoC基本介绍

   大多数应用都是由两个或多个类通过彼此的合作来实现业务逻辑的,这使得每个对象都需要与之合作对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么如你所见,这将导致代码高度耦合并且难以测试。所以引入了IOC(控制反转)的概念,将合作对象的引用或依赖关系的管理由具体对象交给框架或者IOC容器来完成。

   应用控制反转后,当对象被创建时,由一个调控系统内的所有对象的外界实体将其所依赖的对象的引用传递给它,即依赖被注入到对象中。所以,控制反转是关于一个对象如何获取它所依赖的对象的引用,在这里反转指的是责任的反转。

二、IoC容器

   在Spring中,IoC容器是实现依赖控制反转模式的载体,它可以在对象生成或初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖。

   IoC容器的注入功能的实现首先要处理几个问题:

1. 对象和对象之间的关系怎么表示?
可以用XML,.properties等语义化配置文件表示
2. 描述对象文件存放在哪里?
可能是classPathm,filesystem,或者是URL网络资源,servletContext等
3. 配置文件的解析问题
1. 不同配置文件对对象关系的描述不一样,如标准的,自定义的怎么统一?
在内部需要有一个统一的对对象的定义,所有的外部描述都必须转化为统一的描述定义,
在spring中定义BeanDefinition来管理各种对象以及他们之间的依赖关系
2. 如何对不同的配置文件进行解析?
对不同的配置文件语法,采用不同的解析器


三、Spring IoC容器系列结构与设计

下图为IoC容器的接口设计图,这张图描述了IoC容器的主要接口设计



简要分析此图,其中有两条接口设计总线:

从接口BeanFactory到HierarchicalBeanFactory(分层的),再到ConfigurableBeanFactory(可配置的),是一条主要的BeanFactory设计路径。在这条接口设计路径中,BeanFactory接口定义了基本的IoC容器的规范。在这个接口定义中,包括了getBean()这样的IoC容器的基本方法(从容器中取得bean).而HirerarchicalBeanFactory接口在继承了BeanFactory的基本接口后,增加了getParentBeanFactory()的接口功能。使得BeanFactory具备了双亲IoC容器的管理功能。在接下来的ConfigurableBeanFactory接口中,主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory()设置双亲容器,通过addBeanPostProcessor()配置Bean后置处理器,等等。通过这些接口设计的叠加,定义了BeanFactory也就是基本IoC容器的基本功能。

以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory到ListableBeanFactory,再到ApplicationContext,再到我们常用的WebApplicationContext或者ConfigurableApplicationContext接口。我们常用的应用上下文基本都是ConfigurableApplicationContext或者WebApplicationContext的实现。在这个接口体系中,ListableBeanFactory和HierarchicalBeanFactory连接BeanFactory接口定义ApplicationContext接口定义。在ListableBeanFactory接口中,细化了许多BeanFactory的接口功能,比如定义了getBeanDefinitionNames()接口方法;对于HierarchicalBeanFactory,1中已经提到过了;对于ApplicationContext接口,它通过继承MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单IoC容器的基础上添加了许多对高级容器的特性的支持。

==ListableBeanFactory== : Extension of the {@link BeanFactory} interface to be implemented by bean factories that can enumerate all their bean instances, rather than attempting bean lookup by name one by one as requested by clients. BeanFactory implementations that preload all their bean definitions (such as XML-based factories) may implement this interface.

1. 基本IOC容器:BeanFactory

BeanFactory定义了IoC容器的最基本的形式,并且提供了IoC容器所应该遵守的最基本的服务契约,同时这也是我们使用IoC容器所应遵守的最底层和最基本的编程规范。我们可以通过查看源码的方式来了解BeanFactory

public interface BeanFactory {

//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,
//如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";

//根据bean的名字,获取在IOC容器中得到bean实例
Object getBean(String name) throws BeansException;

//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。
Object getBean(String name, Class requiredType) throws BeansException;

//提供对bean的检索,看看是否在IOC容器有这个名字的bean
boolean containsBean(String name);

//根据bean名字得到bean实例,并同时判断这个bean是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

//得到bean实例的Class类型
Class getType(String name) throws NoSuchBeanDefinitionException;

//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);

}


从代码中可以看出,这里只是定义的只是一系列的接口方法,通过这一系列的BeanFactory接口,可以使用不同的Bean的检索方法,很方便的从IoC容器中得到需要的Bean,从而忽略具体的IoC实现。

2. IOC容器实现

BeanFactory接口提供了使用IoC容器的规范。在这个基础上,Spring还提供了符合IoC容器接口的一系列容器的实现,比如XmlBeanFactory,ClassPathXmlApplicationContext等。其中的XmlBeanFactory是针对最基本IoC容器的实现。

XmlBeanFactory

如下图为XmlBeanFactory设计的类继承关系:



同样我们也通过代码来学习XmlBeanFactory:

public class XmlBeanFactory extends DefaultListableBeanFactory {

private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

/**
* 根据给定来源,创建一个XmlBeanFactory
* @param resource  Spring中对与外部资源的抽象,最常见的是对文件的抽象,特别是XML文件。而且Resource里面通常
* 是保存了Spring使用者的Bean定义,比如applicationContext.xml在被加载时,就会被抽象为Resource来处理。
* @throws BeansException 载入或者解析中发生错误
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}

/**
* 根据给定来源和BeanFactory,创建一个XmlBeanFactory
* @param resource  Spring中对与外部资源的抽象,最常见的是对文件的抽象,特别是XML文件。而且Resource里面通常
* 是保存了Spring使用者的Bean定义,比如applicationContext.xml在被加载时,就会被抽象为Resource来处理。
* @param parentBeanFactory 父类的BeanFactory
* @throws BeansException 载入或者解析中发生错误
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}


从上述代码中我们可以发现,XmlBeanFactory仅仅只有两个构造方法,那么它怎么实现BeanFactory这么多功能呢?很简单,因为它大多数功能都建立在DefaultListaleBeanFactory这个容器的基础上,它只是在这个基础上实现其他诸如XML读取的附加功能。事实上,DefaultListableBeanFactory是很重要的一个IoC实现,在其他IoC容器中,比如ApplicationContext,其实现的基本原理类似,也是通过持有或者扩展DefaultListableBeanFactory来获得基本的IoC容器的功能的。

再来看具体代码,为了创建XmlBeanFactory,我们需要指定BeanDefinition(对象之间的依赖关系的统一描述)的信息来源,而这个信息来源需要封装成Spring中的Resource类来给出,比如我们的BeanDefinition信息是以XML文件形式存在的,那么可以使用像

“ClassPath-Resource res=new ClassPathResource(“bean.xml”);”

这样具体的ClassPathResource来构造需要的Resource,然后将Resource作为构造参数传递给XmlBeanFactory来对Bean完成容器的初始化和依赖注入过程。而Xml形式的Beandefinition信息读取的工作交给了XmlBeanDefinitionReader来完成。这样的过程可以通过编程模拟使用IoC容器:

public class ProgramBeanFactory {
public static void main(String args[]){
ClassPathResource resource=new ClassPathResource("com/test/spring/MyXml.xml");
DefaultListableBeanFactory factory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
Message message=factory.getBean("message",Message.class);
message.printMessage();
}
}
结果:
信息: Loading XML bean definitions from class path resource [com/test/spring/MyXml.xml]
message.class


这样我们就可以通过factory对象来使用DefaultListableBeanFactory这个IoC容器,在使用IoC容器时,需要如下几个步骤:

1. 创建IoC配置文件的抽象资源,这个资源包含了BeanDefinition的定义信息

2. 创建一个BeanFactory,这里使用DefaultListableBeanFactory.

3. 创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置个BeanFactory

4. 从定义好的资源位置读人配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义后,需要的IoC框架就建立起来了。这个时候就可以直接使用IoC容器了。

ApplicationContext

ApplicationContext是Spring提供的高级IoC容器,在上面IoC接口的UML图中我们可以看到ApplicationContext实现了MessageSource,ApplicationEventPublisher等接口这也为它添加BeanFactory不具备的新特性:

1. 支持信息源,可以实现国际化。(实现MessageSource接口)

访问资源。(实现ResourcePatternResolver接口)

支持应用事件。(实现ApplicationEventPublisher接口)

正是因为这些提供的附加服务,使得ApplicationContext与BeanFactory相比,对它的使用是一种面向框架的使用风格,所以一般建议在开发应用时使用ApplicationContext作为IoC容器的基本形式

与BeanFactory容器的实现XmlBeanFactroy相比,在ApplicationContext容器中,我们以常用的FileSystemXmlApplicationContext
17e6d
作为容器的实现例子。

继续上代码:

package org.springframework.context.support;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

/**
* 在FileSystemXmlApplicationContext的设计中,我们看到ApplicationContext的主要功能其实已经在AbstractXmlApplicationContext
* 中完成了,而在FileSystemXmlApplicationContext只需要完成它自身的两个功能。
*
* 一个就是启动Ioc容器的refresh()过程。这个会在下一章进行重点论述。
* 另一个就是加载XML的Bean定义资源,主要是getResourceByPath方法来完成。
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {

/**
* 创建FileSystemXmlApplicationContext类
*/
public FileSystemXmlApplicationContext() {
}

/**
* 根据父类,创建FileSystemXmlApplicationContext类
* @param parent 父类的接口
*/
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}

/**
* 根据XML文件名,创建FileSystemXmlApplicationContext类
* @param configLocation BeanDefinition所在的文件路径
* @throws BeansException 如果创建失败就抛出异常
*/
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}

/**
* 根据XML文件数组名,创建FileSystemXmlApplicationContext类
* @param configLocations XML文件名,可以指定多个BeanDefinition资源路径
* @throws BeansException 如果创建失败就抛出异常
*/
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}

/**
* 根据载入的父类接口,以及XML文件名自动刷新环境以及创建FileSystemXmlApplicationContext类
* @param configLocations XML文件名
* @param parent 父类接口,同时指定自己的双亲容器
* @throws BeansException 如果创建失败就抛出异常
*/
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}

/**
* 根据XML文件名自动刷新环境以及创建FileSystemXmlApplicationContext类
* @param configLocations XML文件名
* @param refresh 是否自动刷新环境
* @throws BeansException 如果创建失败就抛出异常
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}

/**
* 根据XML文件名、父类接口、自动刷新环境以及创建FileSystemXmlApplicationContext类
* 调用父类AbstractRefreshableConfigApplicationContext的方法,设置BeanDefinition定义的资源文件,完成IoC容器Bean定义资源的定位
* FileSystemXmlApplicationContext中最重要的实现方法,其他构建大部分都是基于它,类似的设计也在其他实现类中得以运用
*
* @param configLocations XML文件名
* @param refresh 是否自动刷新环境
* @param parent 父类接口,同时指定自己的双亲容器
* @throws BeansException 如果创建失败就抛出异常
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {

super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();      //Ioc容器的refresh()过程,是个非常复杂的过程,但不同的容器实现这里都是相似的,因此基类中就将他们封装好了
}
}

/**
* 加载XML Bean的给定资源
* 在文件应用中读取XML中的BeanDefinition以应对不同的BeanDefinition读取方式。
*
* @param string 具体路径
* @return Resource 返回一个具体资源
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}


可以看到,FileSystemXmlApplicationContext与XmlBeanFactory类似,本身并无太多代码,ApplicationContext的主要功能已经在其基类AbstractXmlApplicationContext中实现。作为一个具体上下文,FileSystemApplicationContext只需要实现与它自身设计有关的两个功能:

1. 如果应用直接使用FileSystemXmlApplicationContext,对于实例化这个应用上下文的支持,同时启动IoC容器的refresh()过程,代码如下

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {

super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}


Ioc容器的refresh()过程,是个非常复杂的过程,但不同的容器实现这里都是相似的,因此基类中就将他们封装好了,这个refresh过程再之后的IoC初始化我们再继续解析。

2. 与怎样从文件系统中加载XML的bean定义资源有关,通过这个过程,可以为在文件系统中读取以XML形式存在的BeanDefinition做准备,在不同的应用上下文实现对应不同的对去BeanDefinition 的方式,在FileSystemXmlApplicationContext中的实现代码如下:

protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}


通过调用这个方法,我们可以得到Resource形式的资源定位。

至此我们将最重要的两种IoC容器的创建和功能概括性的介绍了一下,每种容器的创建流程我们也大致了解了一下,由于ApplicationContext相比更为丰富和更为重要,后面我们将针对FileSystemXmlApplicationContext创建流程具体分析。

四、IoC容器的初始化

IoC容器的初始化是由refresh()方法来启动的,这个方法标志着IoC容器的正式启动。具体地说,这个启动包括BeanDefinition的Resource定位、载入和注册三个基本过程。这三个过程涉及到的重要类如下:

1. Resource

Resource对各种形式的BeanDefinition的使用都提供了统一的接口。对于这些BeanDefinition的存在形式,相信大家都不会陌生。比如,在文件系统中,Bean的定义信息可以使用FileSystemResource来抽象;在类路径中的Bean定义信息可以使用ClassPathResource来使用,等的。这个定位过程类似于容器寻找数据的过程,就像使用水桶装水先把水找到一样。Resource体系结构如下:



1. Resourse资源定位

Resoource定位,也就是Resource资源的读入,在FileSystemXmlAppplicationContext中,顾名思义也就是从文件系统中载入Resource,我们先看下他的继承体系:



在FileSystemXmlApplicationContext中,资源的定位是由方法getResourceByPath()得到的,可以从之前的代码看出,这是个模板方法,具体的定位实现是由各个子类完成的。我们可以通过查看它的调用关系来分析:





我们可以清楚的看到这个对BeanDefinition资源定位的过程,最初是由refresh()方法触发的,这个refresh调用是在FileStystemXmlApplicationContext的构造函数中启动的。逐步查看代码分析:

@class AbstractApplicationContext
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
------------方法分割线-------------
@class AbstractRefreshableApplicationContext
protected final void refreshBeanFactory() throws BeansException {
//判断,如果建立了BeanFactory则销毁并关闭该BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
//这里是创建并持有DefaultListableBeanFactory的地方,并且同时调用loadBeanDefinition载入BeanDefinition的信息
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
------------方法分割线-------------
@class XmlApplicationContext
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resou`rce loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
------------方法分割线-------------
@class  AbstractBeanDefinitionReader
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}

if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//调用DefaultResouceLoader的getResource完成具体的Resource定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
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.
//调用DefaultResouceLoader的getResource完成具体的Resource定位
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;
}
}
------------方法分割线-------------
@class DefaultResourceLoader
//对于取得Resource具体过程,我们可以看看DefaultResourceLoader是怎样完成的
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");

for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}

if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
//如果既不是classpath也不是URL标识的Resource定位,则把getResource的重任交给getResourceByPath
//这个方法是个protected方法,默认实现是得到一个ClassPathContextResource,这个方法常常会用子类来实现
return getResourceByPath(location);
}
}
}
------------方法分割线-------------
@class FlieSystemXmlApplicationContext
// getResourceByPath被子类FileSystemXmlApplicationContext实现,这个方法返回的是一个FileSystemResource对象,
//通过这个对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}


到这里,Resource的定位已经完成,也就是说水源已经找到,下面的过程就是如何把水源装到桶里。

2. 资源载入与解析

上一步我们完成了Resource资源的定位,得到Resource代表的资源。下面来了解 整个BeanDefinition载入的过程,也就是把上一步得到的Resource资源转换为spring内部数据结构BeanDefinition的过程。再回到IoC容器的初始化入口refresh


public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {

super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}


对于容器的实现来说,refresh是一个很重要的方法,在1中我们可以看到,该方法在AbstractApplicationContext类中实现。它详细的描述了整个ApplicationContext的初始化过程。refresh过程如下:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}

catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// Destroy already created singletons to avoid dangling resources.
destroyBeans();

// Reset 'active' flag.
cancelRefresh(ex);

// Propagate exception to caller.
throw ex;
}

finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}


和Resource资源的定位一样,资源的载入分析也是在IoC容器的创建中完成,具体交互过程如下:



和1中一样,逐步沿调用过程分析。在1中我们分析到AbstractBeanDefinitionReader的loadBeanDefinition方法,资源定位我们是分析到:

//调用DefaultResouceLoader的getResource完成具体的Resource定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);


下一行代码:

int loadCount = loadBeanDefinitions(resource);


就是我们的资源载入与分析,它在AbstractBeanDefinitionReader的代码如下:

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;
}


这里调用的是loadBeanDefinitions(Resource)方法,但这个方法在AbstractbeanDefinitionReader类里是没有实现的,它是一个接口方法,具体实现在XmlbeanDefinitionReader中。在读取器中,需要得到代表XML文件的Resource,因为这个Resource对象封装了对XML文件的I/O操作,所以读取器可以在打开I/O流后得到XML文件对象。有了这个文件对象后,就可以按照Spring的Bean定义规则来对这个XML文档树进行解析,而这个解析是交个BeanDefinitonParserDelegate来完成的。代码清单如下:

//这里是调用的入口
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
//这里是载入XML形式的BeanDefinition的地方
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!");
}
//这里得到XML文件,并得到IO的InputSource准备进行读取
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();
}
}
}

//具体的读取过程可以在doLoadeBeanDefinitions方法中找到
//这是从特定的XML文件中实际载入DeanDefinition的地方
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//这里是取得XML文件的Document对象,具体怎么做我们不做讨论
Document doc = doLoadDocument(inputSource, resource);
//这里启动的是对BeanDefinition解析的详细过程,这个解析会用到Spring的Bean配置规则
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);
}
}


我们对如何得到Document对象不做解析,我们关心的是如何按照Spring的Bean语义要求进行解析并转换为容器内部数据结构:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//从这里得到BeanDefinitionDocumentReader来对XML的BeanDefinition进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//具体的解析过程在这个registerBeanDefinition中完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}


BeanDefinition的载入分成两个部分,首先调用XML解析器得到Document对象,但这些Document对象并没有按照Spring的bean规则进行解析。在完成通用的XML解析后,才是按照Spring的bean解析的地方。这个过程再documentReader中实现,这里使用的是默认设置好的DefaultBeanDefinitionReader

“`

//得到默认设置好的DefaultBeanDefinitionReader

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {

return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));

}

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);

if (this.delegate.isDefaultNamespace(root)) {
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);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);

this.delegate = parent;
}
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);
}
}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//这里是处理Bean解析的地方
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//BeanDefinitionHolder是BeanDefinition对象的封装类,封装了BeanDefinition,Bean的名字和别名,用它来完成IoC的注册
//得到这个V=BEANDefinitionHolder意味着beanDefinition是通过BeanDefinitionParserDelegate对XML元素信息按照Spring的Bean规则解析得到的
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//这里是向IoC容器注册解析得到的BeanDefinition的地方
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}


“`

具体的Spring解析是在BeanDefinitionParserDelegate中完成的。这个类包含了对各种Bean定义规则的处理,也就是把bean定义中的ia,name,aliase等属性值从XML文件相应的元素的属性中读取出来,并设置到生成的BeanDefinitionHolder中。还有各种Bean

属性的配置,通过一个较为复杂的解析过程,这个过程由parseBeanDefinitionElement来完成。完成解析后,会把解析结果放到BeanDefinition对象中并设置到BeanDefinitonHolder中,代码如下:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}

String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}

if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//这个方法会引发对Bean元素的详细解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
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("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}

return null;
}

public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {

this.parseState.push(new BeanEntry(beanName));

String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}

try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//生成需要的BeanDefinition对象,为Bean定义信息的载入做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//这里对当前的Bean元素进行属性解析,并设置description信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//对各种<bean>元素解析的地方
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);

bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));

return bd;
}
//下面这些异常是在配置bean出现问题时常常看到的
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}

return null;
}


具体的bean元素解析我们这里不做介绍。到这里为止,我们从XML文件地址开始,得到资源定位Resource,在对Resource资源进行载入和分析,得到BeanDefinition并设置到生成的BeanDefinitionHolder对象中。最终我们得到的是解析并设置了各种Bean属性的BeanDefinitionHolder。在之前的代码我们可以看到,我们通过调用函数BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());向IoC容器中注册我们的Bean信息。下面我们就要分析如何注册到IoC容器中。

3. BeanDefinition资源在IOC容器的注册

前面我们得到的BeanDefinition还不能直接在IoC容器中使用,需要在IoC中对这些数据进行注册,这个注册为IoC提供了更为友好的使用方式。在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在类中可以看到:

/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);


在2中我们得到了BeanDefinition包装类BeanDefinitionHolder,在这里我们将通过这个类来进行注册:

@class BeanDefinitionReaderUtils
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {

// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
//这个registry一般是DefaultListableBeanFactory
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

@class DefaultListableBeanFactory
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {

Assert.hasText(beanName, "Bean name 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(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}

BeanDefinition oldBeanDefinition;

oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
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("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
//注册过程需要synchronized,保证数据的一致性
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 {
// Still in startup registration phase
//这里正常注册BeanDefinition的过程,把Bean的名字存入到beanDefinitionNames的同时,把beanName作为HashMap的key,把beanDefinition作为value存入到IoC容器持有的beanDefinitionMap中
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}

if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}


完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableBeanFactory中已经建立了整个bean的配置信息。而且他们可以在beanDefinitionMap中被检索和使用。容器的作用就是对这些信息进行维护和处理。这些信息是容器建立依赖反转的基础。有了这些基础数据,我们才能进行下一步的依赖注入。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring ioc 对象