Spring原理-IoC容器初始化过程

来源:互联网 发布:英语口语跟读软件 编辑:程序博客网 时间:2024/04/28 19:04

IoC容器初始化过程

IoC容器的两个核心接口BeanFactory和ApplicationContext大概功能都讲解了一些,接下来我们讲解一下IoC容器的初始化过程,让大家有一个深一点的理解。讲解还是以FileSystemXmlApplicationContext类作为入口进行讲解。

首先我们看一段FileSystemXmlApplicationContext类中的源码:

    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

这一段是它的一个构造函数主要有两个方法

    setConfigLocations(configLocations):这个方法它的主要功能是,把Resource路径参数解析成当前Context需要的路径。例如:如果是FileSystemXmlApplicationContex的话,就会把路径解析成为文件系统的路径,如果是ClassPathXmlApplicationContext的话,就会把路径解析成为类路径。todo(确认是否正确)

    refresh():IoC初始化是由refresh方法来启动的,这个方法标志着IoC容器的正式启动。所以,我们接下来重点要讲的就是refresh这个方法。

IoC容器的启动包括BeanDefinition的定位,载入,注册3个基本过程。Spring把这3个过程分开,并使用不同的模块来完成,例如:ResourceLoader,BeanDefinitionReader等。在前面介绍XmlBeanFactory的实现时,我们也简单介绍了如何自己实现这个过程。在我们自己实现的过程中,就包括了定位(ClassPathResource res = new ClassPathResource("beans.xml"))和载入(reader.loadBeanDefinitions(res))这两个过程。


下面简单介绍3个过程:

第一个过程,是Resource定位过程。这个Resources定位是指BeanDefinition的资源定位,它是由ResourceLoader通过统一的Resources接口来完成,这个Resources对各种形式的BeanDefinition的使用都使用了统一接口。比如:文件系统中的Bean定义信息可以用FileSystemResource来定位;类路径中的Bean定义可以使用前面提到的ClassPathResource来使用。
第二个过程,是BeanDefinition的载入和解析。这个载入过程中是把用户定义好的Bean表示成IoC窗口内部的数据结构,而这个窗口内部的数据结构就是BeanDefinition。
第三个过程,是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC窗口就是通过这个HashMap来持有这些BeanDefinition数据的。值得注意的是这里谈的是IoC容器初始化过程,在这个过程中一般不包含Bean依赖注入的实现,Bean的定义和依赖注入是两个独立的过程


第一个过程:Resources定位

    以编程的方式使用DefaultListableBeanFactory时,首先要定义一个Resources来定位容器使用的BeanDefinition。例如:ClassPathResource,FileSystemResource等。但这里定义的Resources并不是由DefaultListableBeanFactory直接使用,而是通过BeanDefinitionReader来对这些信息进行处理,通过这点,我们可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处。我们用一个例子来看一下读取过程进如何进行的。
    我们使用的例子是ClassPathXmlApplicationContext, FileSystemXmlApplicationContext和他们的一些基类。AbstractXmlApplicationContext类是这两个类的基类,因为这两个类都是针对XML的操作,有很多相同的操作,所以这里用了模版模式把这两个类的共通部分给包装起来了。
    那是在哪定义并使用Reader的呢?我们一步步来看,先看一下上面说的refresh方法的实现。我们为什么要看这个方法的实现呢?因为它详细地描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessageSource和PostProcessor的注册等等。

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            // 这里是子类启动refreshBeanFactory的地方,refreshBeanFactory是个抽象方法,需要子类去实现。
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // 设置BeanFactory的后置处理
                postProcessBeanFactory(beanFactory);
                // 调用BeanFactory的后处理器,这些后处理器是在Bean定义中向窗口注册的。
                invokeBeanFactoryPostProcessors(beanFactory);
                // 注册Bean的后处理器,在Bean创建过程中调用
                registerBeanPostProcessors(beanFactory);
                // 对上面文的消息源进行初始化
                initMessageSource();
                // 初始化上下文的事件机制
                initApplicationEventMulticaster();
                // 初始化其它特殊Bean
                onRefresh();
                // 检查“监听Bean”,并将这些Bean向容器注册
                registerListeners();
                // 实例化所有的(non-lazy-init)单例(singleton)Bean
                finishBeanFactoryInitialization(beanFactory);
                // 发布容器事件结束refresh过程
                finishRefresh();
            }
            catch (BeansException ex) {

            其它代码省略,不介绍了。

    }

   介绍了上面这段源码,我们对IoC容器(ApplicationContext)的初始化过程有了一个了解,接下来我们继续看Resource定位的相关代码。我们进入到上面的obtainFreshBeanFactory()这个方法中去。这个方法中有一个refreshBeanFactory()方法,这个方法是个抽象方法,具体的实现在AbstractRefreshableApplicationContext类中。代码如下:

    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {

            // 创建IoC窗口,这里使用的是生成DefaultListableBeanFactory

            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重写了。

(从方法的名字看是“载入”的意思,但继续住下看你会发现,BeanDefinition的定位和载入都在这个方法所调用的重载方法里进行了。)
进入到AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 方法中,就可以看到使用的是XmlBeanDefinitionReader这个Reader进行读取的。在这个方法中还调用了这loadBeanDefinitions(beanDefinitionReader);方法,这个方法的作用就是,让Reader读取“String类型“的路径,并从路径中读取相对应XML类型的BeanDefinition定义。
    上面说了,不是通过Resource定位的吗,怎么这里没出现Resource呢?这个稍微有点复杂,原来是这样的:
    首先我们先进入AbstractXmlApplicationContext的loadBeanDefinitions(beanDefinitionReader)方法中,在这个方法中的Reader调用的loadBeanDefinitions方法是XmlBeanDefinitionReader的基类AbstractBeanDefinitionReader的loadBeanDefinitions(String... locations)方法,在这个方法中对于每个路径又调用了loadBeanDefinitions(String location)方法。这个方法中只有一句话,调用loadBeanDefinitions(location, null);方法。在loadBeanDefinitions(location, null)方法里有两行代码进行定位:

    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

    Resource resource = resourceLoader.getResource(location);

    (下面要说的“载入”的调用语句也在这个方法中:int loadCount = loadBeanDefinitions(resources);)

而这个resourceLoader,是在Reader创建的时候设置的。这一点,可以看下面的类中的代码可以验证。

AbstractXmlApplicationContext#loadBeanDefinitions (DefaultListableBeanFactory beanFactory) ->

  beanDefinitionReader.setResourceLoader(this);

整个的调用如下:

FileSystemXmlApplicationContext#FileSystemXmlApplicationContext->

  AbstractApplicationContext#refresh->obtainFreshBeanFactory() ->

    AbstractRefreshableApplicationContext#refreshBeanFactory() ->

      AbstractXmlApplicationContext#loadBeanDefinitions(beanDefinitionReader) ->

        AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations) -> loadBeanDefinitions(String location) -> loadBeanDefinitions(location, null) ->

            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

            Resource resource = resourceLoader.getResource(location);



第二个过程:Bean的载入和解析

Bean的载入和解析的大概内容:

  • 读入XML文件,变成Java的Document对象
  • 把Document对象按照Spring的BeanDefinition的规则,解析成BeanDefinition对象

Bean的载入和解析的处理位置:

    因为Bean的定位和载入是在同一个方法中调用的,所以在上面介绍Resource定位的时候,我们顺便也说了一下Bean载入的语句的位置,就是AbstractBeanDefinitionReader#loadBeanDefinitions(location, null)方法里面的int loadCount = loadBeanDefinitions(resources)方法,它是在上面介绍的定位语句后被调用的。

    当你进入loadBeanDefinitions(resources)方法的实现后,看到里面有一个For循环,里现还有一条语句counter += loadBeanDefinitions(resource)。当你进入这个loadBeanDefinitions(resource)方后后,你会发现这个方法是个抽象方法并没有实现,这个方法的实现是在哪里实现的呢?其实是在XmlBeanDefinitionReader里实现的,XmlBeanDefinitionReader重写了loadBeanDefinitions(Resource resource)方法。

    在这个方法中,一层层深入后,会发现loadBeanDefinitions(EncodedResource encodedResource)这个方法有很多内容,在这里取得了Resource的输入流(InputStream),然后在doLoadBeanDefinitions方法中,进行更进一步的读取,这里是从特定的XML文件中实际载入BeanDefinition的地方。下面是一部分源码:

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
        }
    现在我们已经找到了BeanDefinition的载入和解析的比较具体的位置,

  • Document doc = doLoadDocument(inputSource, resource):载入的代码
  • return registerBeanDefinitions(doc, resource):解析的代码

载入的代码说明:

    可以看到,源码中有一个Document接口,这个接口就是用来处理XML的接口。一步步进入,你会发现读取处理是在DefaultDocumentLoader#loadDocument()方法中处理的,有兴趣的可以自己看看。首先通过调用XML解析器取得Document对象,就像上面的Document doc = doLoadDocument(inputSource, resource)语句。但是它只是把XML文件读取成Document对象,并没有按照Spring了Bean规则进行解析。按照Bean规则进行解析的过程processBeanDefinition方法里进行的,调用过程如下:

DefaultBeanDefinitionDocumentReader#registerBeanDefinitions -> doRegisterBeanDefinitions -> parseBeanDefinitions -> parseDefaultElement -> processBeanDefinition

    processBeanDefinition的源码如下:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                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));
        }
    }

解析的代码说明:

    我们看processBeanDefinition的源码会看到,解析BeanDefinition的操作是由BeanDefinitionParserDelegate来做的。它负责把Document对象(也就是读到Java中的XML文件),按照Spring的Bean规则来解析。解析完后,解析结果由BeanDefinitionHolder来持有。这个BeanDefinitionHolder除了持有BeanDefinition对象外,还有与BeanDefinition的使用相关的信息。比如Bean的名字,和别名集合等。(BeanDefinitionParserDelegate的解析过程,有兴趣的可以看一看,你会看到熟悉的定义,比如:id,name等)

    再介绍一下,到现在为止大家可能都会一直想,BeanDefinition到底是在哪里生成的呢?它是在BeanDefinitionParserDelegate# parseBeanDefinitionElement里生成的,代码如下:

    AbstractBeanDefinition bd = createBeanDefinition(className, parent);

    这个方法的调用过程如下:

    DefaultBeanDefinitionDocumentReader#processBeanDefinition ->

        BeanDefinitionParserDelegate#parseBeanDefinitionElement -> parseBeanDefinitionElement -> parseBeanDefinitionElement


第三个过程:向IoC容器注册BeanDefinition

    在经过载入和解析的过程后,用户定义的BeanDefinition信息已经在IoC容器内建立起了数据结构等信息。但此时还不能供IoC容器使用,需要在IoC容器内对这些BeanDefinition进行注册。在DefaultListableBeanFactory中,注册完的BeanDefinition是保存到一个Map中:

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

    实现注册的接口为BeanDefinitionRegistry,看一下DefaultListableBeanFactory的实现,可以看到它已经实现了这个接口。

    注册的源码不写出来了,大概做了几个操作:

  • BeanDefinition如果是AbstractBeanDefinition的实例的话,就进行一些验证。
  • 查看保存BeanDefinition的Map里,有没有和要注册的Bean相同名字的Bean。如果有的话,进行可覆盖等检查。
  • 没有问题,进行注册的时候,要进行Synchronized同步,保持数据一致性。
  • 保存时,保存BeanDefinition到Map里,并把BeanDefinition的名字保存到一个名字List中,最后从SingletonNameList中删除当前BeanDefinition的名字。

完成了注册BeanDefinition的过程,就完成了IoC容器初始化的过程。在使用的IoC容器DefaultListableBeanFactory中已经建立了所有Bean的配置信息,而且这些BeanDefinition可以被容器使用了。这些信息都是建立依赖反转的基础,有了这些数据,下面我们可以看依赖注入是如何完成的。



0 0