Spring技术内幕——Spring Framework的IOC容器实现(二)

三、IOC容器的初始化过程
IOC容器的初始化时由前面介绍的refresh方法来启动的,这个方法标志着IOC容器的正式启动。这个启动包括BeanDefinition的Resource定位、载入和注册。下面我们将详细分析这三个实现过程,Spring把这三个过程分开,并使用不同的模块来完成,通过这样的设计让用户更加灵活的这三个过程进行剪裁和扩展,定义出最适合自己的IOC容器的初始化过程。
第一个过程:
Resource定位过程,是指BeanDefinition的资源定位,他由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一的接口。比如,在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象,在类路径中的Bean定义信息可以使用前面提到的ClassPathResource来使用,这个定位过程类似于容器寻找数据的过程,就像水桶装水先要找到水。
第二个过程:
BeanDefinition的载入,这个过程是把用户定义好的Bean表示成IOC容器内部数据结构,而这个容器内部的数据结构就是BeanDefinition。具体来说,这个BeanDefinition实际上就是POJO对象在IOC容器中的抽象,通过这个BeanDefinition定义的数据结构,使IOC容器能够方便的对POJO对象也就是Bean进行管理。
第三个过程:
向IOC容器注册这些BeanDefinition的过程。通过调用BeanDefinitionRegistry接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IOC容器进行注册。通过分析,在IOC容器内部将BeanDefinition注入到一个HashMap中去,IOC容器就是通过IOC容器的初始化过程来持有这些BeanDefinition数据的。
说明:这里说的IOC容器初始化过程,一般不包含Bean依赖注入的实现。在Spring的IOC设计中,Bean的定义载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但有一个例外值得注意,在使用IOC容器设计时有一个预实例化的配置,通过这个配置(为bean定义信息中的lazyinit属性),用户可以对容器初始化过程作一个微笑的控制,从而改变这个被设置了lazyinit属性的Bean的依赖注入。举例来说,如果我们对某个Bean设置了lazyinit属性,那个这个Bean的依赖注入在IOC容器初始化时就预先完成了,而不需要等到整个初始化完成之后。
1、BeanDefinition的Resource定位
首先看下面一行代码:

ClassPathResource res = new ClassPathResource("benas.xml");

这里定义的Resource并不能直接由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在这里我们可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处。在ApplicationContext中,Spring已经为我们提供了一系列的加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IOC容器,需要为他配置特定的读取器才能完成这些功能。当然,有利有弊,使用DefaultListableBeanFactory这种更底层的容器能提高定制IOC容器的灵活性。
回到ApplicationContext上来,例如FileSystemXMLApplicationContext、ClassPathXMLApplicationContext以及XMLWebApplicationContext等。简单的从这些类名字就看到他们可以提供哪些不同的Resource读入功能。
下面以FileSystemXMLApplicationContext为例,分析ApplicationContext的实现怎样完成Resource的定位过程
这里写图片描述
下面让我看下FileSystemXMLApplicationContext的具体实现:

/**
 * Standalone XML application context, taking the context definition files
 * from the file system or from URLs, interpreting plain paths as relative
 * file system locations (e.g. "mydir/myfile.txt"). Useful for test harnesses
 * as well as for standalone environments.
 * 独立的XML应用程序上下文,以上下文定义文件
 * <p><b>NOTE:</b> Plain paths will always be interpreted as relative
 * to the current VM working directory, even if they start with a slash.
 * (This is consistent with the semantics in a Servlet container.)
 * <b>Use an explicit "file:" prefix to enforce an absolute file path.</b>
 *
 * <p>The config location defaults can be overridden via {@link #getConfigLocations},
 * Config locations can either denote concrete files like "/myfiles/context.xml"
 * or Ant-style patterns like "/myfiles/*-context.xml" (see the
 * {@link org.springframework.util.AntPathMatcher} javadoc for pattern details).
 *
 * <p>Note: In case of multiple config locations, later bean definitions will
 * override ones defined in earlier loaded files. This can be leveraged to
 * deliberately override certain bean definitions via an extra XML file.
 *
 * <p><b>This is a simple, one-stop shop convenience ApplicationContext.
 * Consider using the {@link GenericApplicationContext} class in combination
 * with an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}
 * for more flexible context setup.</b>
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see #getResource
 * @see #getResourceByPath
 * @see GenericApplicationContext
 */
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
    /**
     * Create a new FileSystemXmlApplicationContext for bean-style configuration.
     * @see #setConfigLocation
     * @see #setConfigLocations
     * @see #afterPropertiesSet()
     */
    public FileSystemXmlApplicationContext() {
    }
    /**
     * Create a new FileSystemXmlApplicationContext for bean-style configuration.
     * @param parent the parent context
     * @see #setConfigLocation
     * @see #setConfigLocations
     * @see #afterPropertiesSet()
     */
    public FileSystemXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }
    /**
     * Create a new FileSystemXmlApplicationContext, loading the definitions
     * from the given XML file and automatically refreshing the context.
     * @param configLocation file path
     * @throws BeansException if context creation failed
     * 这个构造函数的configLocation包含的是BeanDefinition所在的文件路径
     */
    public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }
    /**
     * Create a new FileSystemXmlApplicationContext, loading the definitions
     * from the given XML files and automatically refreshing the context.
     * @param configLocations array of file paths
     * @throws BeansException if context creation failed
     * 这个构造函数允许configLocation包含多核BeanDefinition的文件路径
     */
    public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }
    /**
     * Create a new FileSystemXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files and automatically
     * refreshing the context.
     * @param configLocations array of file paths
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * 这个构造函数在允许configLocation包含多个BeanDefinition的文件路径同时,还允许指定自己的双亲IOC容器
     */
    public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
        this(configLocations, true, parent);
    }
    /**
     * Create a new FileSystemXmlApplicationContext, loading the definitions
     * from the given XML files.
     * @param configLocations array of file paths
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }
    /**
     * Create a new FileSystemXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of file paths
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     * 在对象初始化过程中,调用refresh函数载入BeanDefinition,这个refresh启动了BeanDefinition的载入过程
     */
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }
    /**
     * Resolve resource paths as file system paths.
     * <p>Note: Even if a given path starts with a slash, it will get
     * interpreted as relative to the current VM working directory.
     * This is consistent with the semantics in a Servlet container.
     * @param path path to the resource
     * @return Resource handle
     * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
     * 这是应用于文件系统中Resource的实现,通过构造一个FileSystemResource来得到一个在文件系统中定位的BeanDefinition
     * 这个getResourceByPath是在BeanDefinitionReader的loadBeanDefintion中被调用的
     * loadBeanDefintion采用了模板模式,具体的定位实现实际上是由各个子类来完成的
     */
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }
}

getResourceByPath方法是一个模板方法,是为读取Resource服务的。对于IOC容器的功能实现这里没有涉及,因为他继承了AbstractXMLApplicationContext,关于IOC容器的功能相关实现都是在FileSystemXMLApplicationContext中完成的,但是在构造函数中通过refresh来东IOC容器的初始化,这个refresh方法非常重要,这也是以后分析容器初始化过程实现的一个重要入口。
在IOC容器的初始化过程中,BeanDefinition资源的定位、读入和注册过程是分开进行的,这也是一个解耦的体现。关于读入的配置,可以到FileSystemXMLApplicationContext的基类AbstractRefreshableApplicationContext中看看怎么实现:

/**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    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);
        }
    }
/**
     * Create an internal bean factory for this context.
     * Called for each {@link #refresh()} attempt.
     * <p>The default implementation creates a
     * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
     * with the {@linkplain #getInternalParentBeanFactory() internal bean factory} of this
     * context's parent as parent bean factory. Can be overridden in subclasses,
     * for example to customize DefaultListableBeanFactory's settings.
     * @return the bean factory for this context
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
     * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
     * 这就是在上下文中创建DefaultListableBeanFactory地方,而getInsternalParentBeanFactory(),具体实现可以参见
     * AbstractApplicationContext中的实现,会根据容器已有的双亲IOC容器信息来生成DefaultListableBeanFactory
     * 的双亲IOC容器
     */
    protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }
/**
     * Load bean definitions into the given bean factory, typically through
     * delegating to one or more bean definition readers.
     * @param beanFactory the bean factory to load bean definitions into
     * @throws BeansException if parsing of the bean definitions failed
     * @throws IOException if loading of bean definition files failed
     * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     * 这就是使用BeanDefinitionReader载入Bean定义的地方,因为允许有多种载入方式,虽然用的最多的是XML
     * 通过一个抽象函数把具体的实现委托给子类来完成
     */
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;
/**
     * Load bean definitions from the specified resource location.
     * <p>The location can also be a location pattern, provided that the
     * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
     * @param location the resource location, to be loaded with the ResourceLoader
     * (or ResourcePatternResolver) of this bean definition reader
     * @param actualResources a Set to be filled with the actual Resource objects
     * that have been resolved during the loading process. May be {@code null}
     * to indicate that the caller is not interested in those Resource objects.
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #getResourceLoader()
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
     */
    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        // 这里取得ResourceLoader,使用的是DefaultResourceLoader
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }
        // 这里对Resource的路径模式进行解析,比如我们设定的各种Ant格式的路径定义,得到需要的Resource集合
        // 这些Resource集合指向我们已经定义好的BeanDefinition信息,可以是多个文件
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                // 调用DefaultResourceLoader的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.
            // 调用DefaultResourceLoader的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;
        }
    }

对于Resource的具体过程,我们可以看看DefaultResourceLoader是怎样完成的

    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        // 这里处理带有classpath标识的Resource
        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标识的Resource定位
                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);
            }
        }
    }

下面我们来看看getResourceByPath的实现

/**
     * Resolve resource paths as file system paths.
     * <p>Note: Even if a given path starts with a slash, it will get
     * interpreted as relative to the current VM working directory.
     * This is consistent with the semantics in a Servlet container.
     * @param path path to the resource
     * @return Resource handle
     * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
     */
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

如果是其他的ApplicationContext,那么会对应生成其他种类的Resource,比如ClassPathResource、ServletContextResource。
在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象来进行BeanDefinition的载入了。在定位过程完成以后,为BeanDefinition的载入创造了I/O操作的条件,但是具体的数据还没有开始读入。这些数据的读入将在下面介绍BeanDefinition的载入和解析中来完成。仍然以水桶为例子,这里就到了就像用水桶去打水,要先找到水源。这里完成对Resource定位,就类似于水源已经找到了,下面就是打水的过程了,类似于把找到的水装到水桶里面。找水不简单,但是与打水相比,打水更需要技巧。
2、BeanDefinition的载入和解析
在IOC容器来说,载入过程相当于把定义的BeanDefinition在IOC容器中转化为一个Spring内部表示的数据结构的过程。IOC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的。zhexieBeanDefinition的数据在IOC容器中通过一个HashMap来保持好和维护。当然这只是一种比较简单的维护方式,如果需要提高IOC容器的性能和容量,完全可以自己做一些扩展。
下面从DefaultListableBeanFactory的设计入手,看看IOC容器是怎么样完成BeanDefinition载入的。在开始分析之前,先回到IOC容器的初始化入口,也就是看一下refresh方法,这个方法的最初是在FileSystemXMLApplicationContext的构造函数中被调用的,他的调用标识着容器初始化的开始,这些初始化对象就是BeanDefinition数据,初始化入口如下:

/**
     * Create a new FileSystemXmlApplicationContext with the given parent,
     * loading the definitions from the given XML files.
     * @param configLocations array of file paths
     * @param refresh whether to automatically refresh the context,
     * loading all bean definitions and creating all singletons.
     * Alternatively, call refresh manually after further configuring the context.
     * @param parent the parent context
     * @throws BeansException if context creation failed
     * @see #refresh()
     */
    public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

对容器的启动来说,refresh是一个很重要的方法。该方法在AbstractApplicationContext类(FileSystemXMLApplicationContext的基类)。他详细的描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessageSource和PostProcessor的注册等。
这个IOC容器的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.
            // 这里是在子类中启动refreshBeanFactory的地方
            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.
                // 设置BeanFactory的后置处理
                postProcessBeanFactory(beanFactory);
                // Invoke factory processors registered as beans in the context.
                // 调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的
                invokeBeanFactoryPostProcessors(beanFactory);
                // Register bean processors that intercept bean creation.
                // 注册Bean的后处理器,在Bean创建过程中调用
                registerBeanPostProcessors(beanFactory);
                // Initialize message source for this context.
                // 对上下文中的消息源进行初始化
                initMessageSource();
                // Initialize event multicaster for this context.
                // 初始化上下文中的事件机制
                initApplicationEventMulticaster();
                // Initialize other special beans in specific context subclasses.
                // 初始化其他的特殊Bean
                onRefresh();
                // Check for listener beans and register them.
                // 检查监听Bean,并且将这些Bean向容器注册
                registerListeners();
                // Instantiate all remaining (non-lazy-init) singletons.
                // 实例化所有的non-lazy-init事件
                finishBeanFactoryInitialization(beanFactory);
                // Last step: publish corresponding event.
                // 发布容器事件,结束Refresh过程
                finishRefresh();
            }
            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                // 为防止Bean资源占用,在异常处理中,销毁已经在前面生成的单件Bean
                destroyBeans();
                // Reset 'active' flag.
                cancelRefresh(ex);
                // Propagate exception to caller.
                throw ex;
            }
        }
    }

进入到AbstractRefreshableApplicationContext的refreshBeanFactory方法中,在这个方法中创建了BeanFactory。在创建IOC容器前,如果已经有容器存在,那么需要把已有的容器销毁和关闭,保证在refresh以后使用的是新建立的IOC容器。这么看来,refresh非常像重启容器,就想重启计算机那样。在建立好当前的IOC容器以后,开始了对容器的初始化过程,比如BeanDefinition的载入。
下面看下refreshBeanFactory的代码:

/**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    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);
        }
    }

这里写图片描述
未完待续……

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值