整理:Spring IOC 实现原理

Posted 阿征new

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了整理:Spring IOC 实现原理相关的知识,希望对你有一定的参考价值。

Spring IOC 实现原理

目录

Spring IOC 实现原理

BeanFactory

ApplicationContext

BeanDefinition

IOC容器的初始化

XmlBeanFactroy

FileSystemXmlApplicationContext 

IOC容器的初始化过程

BeanDefinition在IOC容器中的注册

依赖注入

doGetBean():

有以下问题:

doCreateBean():


 

 

 

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容器的使用步骤:

  1. 创建IOC抽象资源,这个抽象资源包含了BeanDefinition的定义信息
  2. 创建一个BeanFactory
  3. 创建一个BeanDefinition的读取器,通过一个回调配置给BeanFactory
  4. 从定义好的抽象资源中读取配置信息。完成载入注册的定义后,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'")

具体实现的方式不在展开,感兴趣的可以去看一下源码实现

我们来看以上,程序执行到此处后,都做了哪些操作:

  1. AbstractApplicationContext中初始化了resourcePatternResolver(多资源文件的载入),用于获取Resource,关于何时使用后面再解释
  2. 将资源的定义路径保存在了configLocations数组

到此,IOC容器根据资源定义路径获取Resouce的准备工作便完成了。

IOC容器的初始化过程

在开始分析初始化过程之前,先从宏观上对初始化的过程做一个简单的介绍,

有一个大体框架的初始化模型,方便后面的理解,初始化过程分成了三个过程

  1. Resource定位
  2. BeanDefinition的载入
  3. 向IOC容器注册这些BeanDefinition信息。
    1. 这个过程是通过 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一共做了三件事

    1. 调用了父类的构造器,进行了初始化
    2. 设置了BeanDefinition的定义路径
    3. 执行了Refresh()方法
  • refresh()方法来启动整个BeanDefinition的载入过程

    • 创建容器 DefaultListableBeanFactory
    • 创建了XmlBeanDefinitionReader
    • 开始准备通过reader来加载资源

AbstractBeanDefinitionReader 读取Bean定义资源,AbstractBeanDefinitionReaderloadBeanDefinitions方法源码如下:

 

//方法重载
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进行依赖注入

依赖注入在以下两种情况下发生(应该不对??):

  1. 用户第一次通过getBean()方法想容器索取时,进行依赖注入
  2. 当用户在Bean的定义中为<bean>配置了lazy-init属性,让容器在解析注册时进行欲初始化,触发依赖注入

 

Bean初始化时机:

  1. 默认scope=singleton IOC启动完毕,就初始化Bean(实例化对象)
  2. scopte=singleton,并且配置lazy-init=true 开启懒加载,在第一次getBean()的时候,进行初始化(实例化对象)
  3. scope=prototype,不会预先初始化,每次getBean(),都会实例化一个新的对象

 

doGetBean():

AbstactBeanFactory 通过getBean()函数获取被管理的Bean

但是因为lazyinit默认为false,所以spring会提前进行部分实例的初始化操作

让我们回退到 AbstactApplicationContext 类的  finishBeanFactoryInitialization(beanFactory) 方法:

该方法进行了所有单例类的初始化操作,初始化的动作包装在了getBean()方法中,这个方法是获取Bean的地方,也是依赖注入发生的地方,对genBean的调用会转入到doGetBean()方法来执行,来看一下该方法的定义:

有以下问题:

  1. FactoryBean 和 BeanFactory的区别
  2. 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 实现原理的主要内容,如果未能解决你的问题,请参考以下文章

Spring读取xml配置文件的原理与实现

Spring Ioc底层实现

Spring之Ioc原理

Spring基础:IOC底层原理

Spring:源码解读Spring IOC原理

Spring:源码解读Spring IOC原理