框架原理

[精]Spring源码深度解析系列(一) IOC容器的初始化详解

2018-12-11  本文已影响124人  lhsjohn

SpringFrameWork的核心是IOC容器的实现

1. IOC容器和依赖反转模式

依赖反转模式是为了解耦对象之间的依赖关系而诞生的一种模式或者说是一种思想,矫情来说,你甚至可以把它当成是一种情怀。

传统来说,合作对象的引用以及依赖关系由具体对象来完成,会造成高度耦合和代码可测试性降低,而在面向对象的系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上.而这个时候如果将这些对象的依赖注入交给框架或者IOC容器来完成,就相当于将注入依赖的责任反转交给了外界,而不再是由具体对象去操心这些事情了.

1.1 依赖控制反转的实现方式

依赖控制反转的实现方式有很多种,在Spring中,IOC容器便是实现这个模式的载体。它可以在对象生成或者是初始化时直接将数据注册到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖。这种依赖注入是可以递归的,对象可以逐层注入。

2.1 Spring的IOC容器系列

BeanFactory和ApplicationContext都可以看作是容器的具体表现形式。

BeanFactory接口类型定义了IOC容是对器的基本功能规范,其余各式各样的容器都是以这个接口定义的功能规范为基础,
进行扩展以增加功能的。

我们可以看一下BeanFactory在继承体系中的地位:

Spring的ioc容器系列.png

在这些Spring提供的基本ioc容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间的相互依赖关系.

BeanDefinition是我们对Bean定义的一种抽象,是对依赖反转模式中管的对象依赖关系的一种抽象,是容器实现依赖反转功能的核心数据结构。

2. IOC容器的实现:BeanFactory和ApplicationContext

2.21 BeanFactory对Ioc容器的功能定义

正如我们上面所说,BeanFactory定义了容器功能的基本规范,这个基本规范中包括了诸如getBean(),containsBean(),isSingleton(),isPrototype(),getType(),getAliases()此类的方法。

下面的代码展示了BeanFactory接口供我们参考

public interface BeanFactory {

    /**
     * Used to dereference a {@link FactoryBean} instance and distinguish it from
     * beans <i>created</i> by the FactoryBean. For example, if the bean named
     * {@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
     * will return the factory, not the instance returned by the factory.
     */
    String FACTORY_BEAN_PREFIX = "&";


    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>This method allows a Spring BeanFactory to be used as a replacement for the
     * Singleton or Prototype design pattern. Callers may retain references to
     * returned objects in the case of Singleton beans.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to retrieve
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no bean definition
     * with the specified name
     * @throws BeansException if the bean could not be obtained
     */
    Object getBean(String name) throws BeansException;

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type
     * safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the
     * required type. This means that ClassCastException can't be thrown on casting
     * the result correctly, as can happen with {@link #getBean(String)}.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to retrieve
     * @param requiredType type the bean must match. Can be an interface or superclass
     * of the actual class, or {@code null} for any match. For example, if the value
     * is {@code Object.class}, this method will succeed whatever the class of the
     * returned instance.
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanNotOfRequiredTypeException if the bean is not of the required type
     * @throws BeansException if the bean could not be created
     */
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>Allows for specifying explicit constructor arguments / factory method arguments,
     * overriding the specified default arguments (if any) in the bean definition.
     * @param name the name of the bean to retrieve
     * @param args arguments to use when creating a bean instance using explicit arguments
     * (only applied when creating a new instance as opposed to retrieving an existing one)
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanDefinitionStoreException if arguments have been given but
     * the affected bean isn't a prototype
     * @throws BeansException if the bean could not be created
     * @since 2.5
     */
    Object getBean(String name, Object... args) throws BeansException;

    /**
     * Return the bean instance that uniquely matches the given object type, if any.
     * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
     * but may also be translated into a conventional by-name lookup based on the name
     * of the given type. For more extensive retrieval operations across sets of beans,
     * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
     * @param requiredType type the bean must match; can be an interface or superclass.
     * {@code null} is disallowed.
     * @return an instance of the single bean matching the required type
     * @throws NoSuchBeanDefinitionException if no bean of the given type was found
     * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
     * @throws BeansException if the bean could not be created
     * @since 3.0
     * @see ListableBeanFactory
     */
    <T> T getBean(Class<T> requiredType) throws BeansException;

    /**
     * Return an instance, which may be shared or independent, of the specified bean.
     * <p>Allows for specifying explicit constructor arguments / factory method arguments,
     * overriding the specified default arguments (if any) in the bean definition.
     * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
     * but may also be translated into a conventional by-name lookup based on the name
     * of the given type. For more extensive retrieval operations across sets of beans,
     * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
     * @param requiredType type the bean must match; can be an interface or superclass.
     * {@code null} is disallowed.
     * @param args arguments to use when creating a bean instance using explicit arguments
     * (only applied when creating a new instance as opposed to retrieving an existing one)
     * @return an instance of the bean
     * @throws NoSuchBeanDefinitionException if there is no such bean definition
     * @throws BeanDefinitionStoreException if arguments have been given but
     * the affected bean isn't a prototype
     * @throws BeansException if the bean could not be created
     * @since 4.1
     */
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;


    /**
     * Does this bean factory contain a bean definition or externally registered singleton
     * instance with the given name?
     * <p>If the given name is an alias, it will be translated back to the corresponding
     * canonical bean name.
     * <p>If this factory is hierarchical, will ask any parent factory if the bean cannot
     * be found in this factory instance.
     * <p>If a bean definition or singleton instance matching the given name is found,
     * this method will return {@code true} whether the named bean definition is concrete
     * or abstract, lazy or eager, in scope or not. Therefore, note that a {@code true}
     * return value from this method does not necessarily indicate that {@link #getBean}
     * will be able to obtain an instance for the same name.
     * @param name the name of the bean to query
     * @return whether a bean with the given name is present
     */
    boolean containsBean(String name);

    /**
     * Is this bean a shared singleton? That is, will {@link #getBean} always
     * return the same instance?
     * <p>Note: This method returning {@code false} does not clearly indicate
     * independent instances. It indicates non-singleton instances, which may correspond
     * to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly
     * check for independent instances.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to query
     * @return whether this bean corresponds to a singleton instance
     * @throws NoSuchBeanDefinitionException if there is no bean with the given name
     * @see #getBean
     * @see #isPrototype
     */
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    /**
     * Is this bean a prototype? That is, will {@link #getBean} always return
     * independent instances?
     * <p>Note: This method returning {@code false} does not clearly indicate
     * a singleton object. It indicates non-independent instances, which may correspond
     * to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly
     * check for a shared singleton instance.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to query
     * @return whether this bean will always deliver independent instances
     * @throws NoSuchBeanDefinitionException if there is no bean with the given name
     * @since 2.0.3
     * @see #getBean
     * @see #isSingleton
     */
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    /**
     * Check whether the bean with the given name matches the specified type.
     * More specifically, check whether a {@link #getBean} call for the given name
     * would return an object that is assignable to the specified target type.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to query
     * @param typeToMatch the type to match against (as a {@code ResolvableType})
     * @return {@code true} if the bean type matches,
     * {@code false} if it doesn't match or cannot be determined yet
     * @throws NoSuchBeanDefinitionException if there is no bean with the given name
     * @since 4.2
     * @see #getBean
     * @see #getType
     */
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

    /**
     * Check whether the bean with the given name matches the specified type.
     * More specifically, check whether a {@link #getBean} call for the given name
     * would return an object that is assignable to the specified target type.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to query
     * @param typeToMatch the type to match against (as a {@code Class})
     * @return {@code true} if the bean type matches,
     * {@code false} if it doesn't match or cannot be determined yet
     * @throws NoSuchBeanDefinitionException if there is no bean with the given name
     * @since 2.0.1
     * @see #getBean
     * @see #getType
     */
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    /**
     * Determine the type of the bean with the given name. More specifically,
     * determine the type of object that {@link #getBean} would return for the given name.
     * <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates,
     * as exposed by {@link FactoryBean#getObjectType()}.
     * <p>Translates aliases back to the corresponding canonical bean name.
     * Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the name of the bean to query
     * @return the type of the bean, or {@code null} if not determinable
     * @throws NoSuchBeanDefinitionException if there is no bean with the given name
     * @since 1.1.2
     * @see #getBean
     * @see #isTypeMatch
     */
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    /**
     * Return the aliases for the given bean name, if any.
     * All of those aliases point to the same bean when used in a {@link #getBean} call.
     * <p>If the given name is an alias, the corresponding original bean name
     * and other aliases (if any) will be returned, with the original bean name
     * being the first element in the array.
     * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
     * @param name the bean name to check for aliases
     * @return the aliases, or an empty array if none
     * @see #getBean
     */
    String[] getAliases(String name);

}

2.2.2 IOC容器XmlBeanFactory的实现原理

首先让我们看一下XmlBeanFactory的继承体系结构

XmlBeanFactory的继承结构.png

BeanFactory接口提供了使用ioc容器的规范,在这个基础上,Spring提供了符合这个ioc容器接口的一系列容器的实现供开发人员使用。

现在我们从这个容器系列的最底层实现XmlBeanFactory开始,这个容器实现只提供了最基本的IOC容器的功能,从名字可以看得出来,这个容器可以读取以Xml形式定义的BeanDefinition.

XmlBeanFactory继承自DefaultListableBeanFactory这个类,DefaultListableBeanFactory包含了ioc容器的重要
功能,XmlBeanFactory在继承了DefaultListableBeanFactory容器的功能的同时,所增加的额外功能我们从名字就能看得出来,
它可以读取以xml形式定义的BeanDefinition。

那么它实现xml读取的功能是怎么实现的呢?
对xml文件定义的信息处理并不是由XmlBeanFactory这个类本身进行处理的。在XmlBeanFactory中,初始化了一个XmlBeanDefinitionReader对象,该对象用来进行xml文件信息的处理.

XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,在这个基本容器的基础上实现了其他例如xml读取的附加功能.在XmlBeanFactory的构造方法中需要用到Resource对象,对XmlBeanDefinitionReader 对象的初始化以及使用这个对象来完成loadBeanDefinition的调用,就是从这个调用启动了从Resource载入BeanDefinitions的过程,这个过程同时也是ioc容器初始化的一个重要的部分.

我们可以看一下XmlBeanFactory的代码实现

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);


    /**
     * Create a new XmlBeanFactory with the given resource,
     * which must be parsable using DOM.
     * @param resource XML resource to load bean definitions from
     * @throws BeansException in case of loading or parsing errors
     */
    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }

    /**
     * Create a new XmlBeanFactory with the given input stream,
     * which must be parsable using DOM.
     * @param resource XML resource to load bean definitions from
     * @param parentBeanFactory parent bean factory
     * @throws BeansException in case of loading or parsing errors
     */
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }

}

参考XmlBeanFactory代码的实现,我们用编程的方式来使用DefaultListableBeanFactory,从中我们可以看到ioc容器使用的一些基本的过程。

ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader (factory);
reader.loadBeanDefinitions(res);

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

1.创建ioc配置文件的抽象资源,该抽象资源包含了BeanDefinition的定义信息
2.创建一个BeanFactory,这里使用DefaultListableBeanFactory
3.创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition 通过一个回调配置给BeanFactory
4.从定义好的资源位置读取配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册的Bean定义之后,需要的ioc容器就建立起来了,这个时候IOC容器就可以使用了.

2.2.3 ApplicationContext的特点

在Spring中系统已经为用户提供了许多已经定义好的容器实现,相比于那些简单扩展BeanFactory的基本ioc容器,开发人员常用的ApplicationContext除了能够提供上述我们看到的容器的基本功能意外,还为用户提供了许多附加服务,可以让用户更加方便的使用。

ApplicationContext的接口关系.png

1.支持不同的信息源

ApplicationContext扩展了MessageSource接口,这些信息源的扩展可以支持国际化的实现,为开发多语言版本的应用提供服务.

2.访问资源

对ResourceLoader和Resource有所支持,这样我们可以从不同的IO途径来获取bean的定义信息。
但是从接口关系上我们看不出来这一特性。一般来说,具体ApplicationContext都是继承了DefaultResourceLoader的子类,这是因为DefaultResourceLoader是AbstractApplicationContext的基类。

3.支持应用事件

继承了接口ApplicationEventPublisher,在上下文引入事件机制,为bean的管理提供了便利.

2.3 IOC容器的初始化

IOC容器的初始化过程: 包括BeanDefinition 的Resoure定位 、载入和注册这三个基本的过程。

2.3.1 BeanDefinition的Resource定位

常见的相关的类有FileSystemXmlApplicationContext ...ClassPathXmlApplicationContext....以及XmlWebApplicationContext等。

FileSystemXmlApplicationContext : 从文件系统载入Resource
ClassPathXmlApplicationContext: 从Class Path载入Resource
XmlWebApplicationContext: 在Web容器中载入Resource

下面我们就以FileSystemXmlApplicationContext为例,通过分析这个ApplicationContext的实现来理解它的Resource定位的过程

FileSystemXmlApplicationContext的继承体系.png

从这里可以看得出来FileSystemXmlApplicationContext已经通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力。因为AbstractApplicationContext的基类是DefaultResourceLoader

下面我们来看FileSystemXmlApplicationContext的实现

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的loadBeanDefinition中被调用的,采用了魔板模式,具体的定为实现由子类来完成.
     
    
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

}

在FileSystemXmlApplicationContext 中,我们可以看得出来主要实现了两个重要的功能
一个是在构造函数中,对configLocation进行处理,使得所有配置在文件系统中的以xml形式存在的BeanDefinition都能得到有效的处理,比如实现了getResourceByPath.
另一方面对于ioc容器功能的相关实现,这里没有涉及,因为它继承了AbstractXmlApplicationContxt

在这里我门最需要明白的一点是,在构造函数中通过refresh来启动了容器的初始化。

接下来我们可以分析一下整个BeanDefinition资源定位的过程,这个定位的过程是由refresh触发的

getResourceByPath的调用关系.png

看了上面的调用过程我们可能会比较好奇,这个FileSystemXmlApplicationContext 在什么地方定义了BeanDefinition的读入器BeanDefinitionReader来完成BeanDefinition信息的读入呢?
我们之前有分析过,在IOC容器的初始化过程中,BeanDefinition资源的定位读入和注册过程都是分开进行的.
关于BeanDefinitionReader的配置,我们可以到FileSystemXmlApplicationContext的基类AbstractRefreshableApplicationContext中看看它们的实现.

下面分析AbstractRefreshableApplicationContext对容器的初始化 代码部分


    @Override
    protected final void refreshBeanFactory() throws BeansException {
    //这里是判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactory
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
        //这里是创建并设置持有DefaultListableBeanFactory的地方
        //同时调用loadBeanDefinition再载入BeanDefinition的信息
            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);
        }
    }

  //这个是在上下文中创建DefaultListableBeanFactory的地方,而getInternalParentBeanFactory的具体实现可以参看   
  //AbstractApplicationContext中的实现,会根据容器中已有的双亲ioc容器生成DefaultListableBeanFactory的双亲ioc容器

    protected DefaultListableBeanFactory createBeanFactory() {
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }
 
   //这里是使用BeanDefinitionReader载入Bean定义的地方,因为允许有许多种载入方式,这里通过一个抽象函数把具体的实现委托给子类完成
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;
            
    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的路径模式进行解析
        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是怎样完成的
 
 
    @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);
        }
        //这里处理带有classpath标识的Resource定位
        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标识的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会被子类FileXmlApplicationContext实现,该方法返回的是一个FileSystemResource对象,通过这个对象Spring可以进行相关的IO操作,完成BeanDefinition的定位,它实现的就是对path进行分析,然后生成一个FileSystemResource对象返回。

   //FileXmlApplicationContext生成Resource对象
    @Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

如果是其他的ApplicationContext,那么对应会生成其他种类的Resource,比如ClassPathResource ....ServletContextResource等.
我们看一下Resource类的继承关系

Resource的定义和继承关系.png

通过对前面的分析,我们以FileSystemXmlApplicationContext的实现原理为例子,了解了Resource定位问题的解决方案,即以FileSystem方式存在的Resource的定位实现。在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象来进行BeanDefinition的载入了。在定位过程完成以后,为BeanDefinition的载入创造了IO操作的条件,但是具体的数据还没有开始读入,这些数据的读入将在我们接下来看到的BeanDefinition的载入和解析完成.

2.3.2 BeanDefinition的载入和解析

对于ioc容器来说,BeanDefinition的载入过程相当于把我们定义的BeaDefinition在ioc容器中转化成一个Spring内部表示的数据结构的过程.
IOC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关的操作来完成的.这些BeanDefinition数据在ioc容器里面通过一个HashMap来保持和维护.

我们从DefaultListableBeanFactory来入手看看IOC容器是怎样完成BeanDefinition的载入的..

为了了解这一点,我们先回到ioc容器的初始化入口refresh方法,这个方法最初是在FileSystemXmlApplicationContext的构造函数被调用的,它的调用就意味着容器的初始化或数据更新,这些初始化和更新的数据就是BeanDefinition


 //启动BeanDefinition的载入

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

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
        //这里调用容器的refresh,是载入BeanDefinition的入口
            refresh();
        }
    }




这里受到字数限制,emmmmmm所以把剩下的内容放在下一篇文章https://www.jianshu.com/p/168ae282ebd6里面了,
可以查看下一篇文章进行接下来的内容。

作者:lhsjohn 若要转载请注明出处

上一篇下一篇

猜你喜欢

热点阅读