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操作的条件,但是具体的数据还没有开始读入。

  • 相关阅读:
    windows系统切换jdk,修改java_home无效情况
    Cannot instantiate interface org.springframework.context.ApplicationListener
    MySQL分组查询获取每个学生前n条分数记录(分组查询前n条记录)
    ASP.NET Web API 使用Swagger生成在线帮助测试文档,支持多个GET
    EF TO MYSQL 无法查询中文的解决方法
    HttpWebRequest post请求获取webservice void数据信息
    This implementation is not part of the Windows Platform FIPS validated cryptographic algorithms. 此实现不是 Windows 平台 FIPS 验证的加密算法的一部分 解决方案
    MySQL 5.7.13解压版安装记录 mysql无法启动教程
    C# udpclient 发送数据断网后自动连接的方法
    汽车XX网站秒杀抢购代码
  • 原文地址:https://www.cnblogs.com/xiaoblog/p/4269873.html
Copyright © 2011-2022 走看看