zoukankan      html  css  js  c++  java
  • IOC容器初始化——BeanDefinition的Resource定位

    以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。这是使用的是ClassPathResource,意味着Spring会在类路径中去寻找以文件形式存在的BeanDefinition的信息。

    ClassPathResource res =new ClassPathResource('beans.xml');

    这里定义的Resource不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在这里,我们也可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处。在ApplicationContext中,提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IOC容器,需要为它配置特定的读取器来完成功能。但是使用DefaultListableBeanFactory这种底层的容器,能提高IOC容器的灵活性。

    我们经常使用的ApplicationContext,比如FileSystemXmlApplicationContext、ClassPathXmlSystemXmlApplicationContext以及XmlWebApplicationContext。从类的名字可以看出它们提供哪些不同的Resource读入功能,依次比如为从文件系统,从class path,从web容器载入Resource等。

    我们以FileSystemXmlApplicationContext为例,继承关系如下图:

    因为基类是DefaultResourceLoader(它实现了ResourceLoader接口),这个FileSystemXmlApplicationContext已经具备ResourceLoader的读入功能。

    先看下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的载入过程,待会看refresh详细分析
        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
         */
        @Override
        //这是应用于文件系统中的Resource的实现,通过构造一个FileSystemResource来得到一个文件在
        //系统中定位的BeanDefinition
        //这个getResourceByPath是在BeanDefinitionReader的loadBeanDefinition中被调用
        //loadBeanDefinition采用模板模式,具体的定位实现是由各个子类来完成
        protected Resource getResourceByPath(String path) {
            if (path != null && path.startsWith("/")) {
                path = path.substring(1);
            }
            return new FileSystemResource(path);
        }
    
    }

    refresh方法非常重要,容器初始化过程的一个重要入口。refresh()在AbstractApplicationContext实现,看下部分代码:

        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.
                    //重置active标志
                    cancelRefresh(ex);
    
                    // Propagate exception to caller.
                    throw ex;
                }
            }
        }
        protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            refreshBeanFactory();
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            if (logger.isDebugEnabled()) {
                logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
            }
            return beanFactory;
        }


    在IOC容器的初始化过程中,BeanDefinition的定位,读入和载入过程是分开进行的,这是解耦的一个体现。关于读入器的配置,要先看下FileSystemXmlApplicationContext的基类AbstractRefreshableApplicationContext实现。需要重点看你下AbstractRefreshableApplicationContext的refreshBeanFactory方法,这个refreshBeanFactory被FileSystemXmlApplicationContext构造函数中的refresh方法调用(refresh方法调用了obtainFreshBeanFactory()上面代码可以看出)。

    下面看下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);
            }
        }

    在这个方法中,通过createBeanFactory方法构建了一个IOC容器供Application使用。这个IOC容器就是前面提到的DefaultListableBeanFactory,同时它启动了loadBeanDefinitions来载入BeanDefinition。

    //这就是在上下文中创建DefaultListableBeanFactory的地方,getInternalParentBeanFactory()的具体实现在
        //AbstractApplicationContext,会根据已有的双亲IOC容器信息来完成DefaultListableBeanFactory的双亲IOC容器
    
        protected DefaultListableBeanFactory createBeanFactory() {
            return new DefaultListableBeanFactory(getInternalParentBeanFactory());
        }
        /**
         * 
         *     protected BeanFactory getInternalParentBeanFactory() {
                  return (getParent() instanceof ConfigurableApplicationContext) ?
                         ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent();
        }
         */
    /**
         * 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;
        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路径解析,Resource集合可以是多个文件
            if (resourceLoader instanceof ResourcePatternResolver) {
                // Resource pattern matching available.
                try {
                    //取得具体的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.
                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");
            if (location.startsWith(CLASSPATH_URL_PREFIX)) { //     public static final String CLASSPATH_URL_PREFIX = "classpath:";
                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);
                }
            }
        }
        protected Resource getResourceByPath(String path) {
            return new ClassPathContextResource(path, getClassLoader());
        }

    对于上面的FileSystemXmlApplicationContext对getResourceByPath方法的实现就是下面方法

        @Override
        //这是应用于文件系统中的Resource的实现,通过构造一个FileSystemResource来得到一个文件在
        //系统中定位的BeanDefinition
        //这个getResourceByPath是在BeanDefinitionReader的loadBeanDefinition中被调用
        //loadBeanDefinition采用模板模式,具体的定位实现是由各个子类来完成
        protected Resource getResourceByPath(String path) {
            if (path != null && path.startsWith("/")) {
                path = path.substring(1);
            }
            return new FileSystemResource(path);
        }


    返回的FileSystemResource对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位。

    如果是其他的ApplicationContext,那么会对应生成其他种类的Resource。比如ClassPathResource、ServletContextResource等。关于Resource种类,可以看下继承关系图

    作为接口的Resource定义了许多与I/O相关的操作。

    通过前面的实现原理的分析,我们以FileSystemXmlApplicationContext的实现原理为例,了解了Resource的定位问题,既是FileSystem方式存在的Resource的定位实现。在在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象来进行载入了。在定位过程完成以后,为BeanDefinition的载入创造了I/O操作的条件,但是具体的数据还没有开始读入。

  • 相关阅读:
    input不可编辑
    span width无效
    react配置rem解决移动端适配问题
    iframe 根据内容自适应高度-终极解决方案
    页面导入样式时,使用link和@import有什么区别?
    怎么让Chrome支持小于12px 的文字?
    React Hook 父子组件相互调用方法
    CSS3实现毛玻璃效果
    React阻止组件渲染
    JSX 中内联条件渲染的方法
  • 原文地址:https://www.cnblogs.com/xiaoblog/p/4269873.html
Copyright © 2011-2022 走看看