• Spring源码解析(二)BeanDefinition的Resource定位


       IOC容器的初始化过程主要包括BeanDefinition的Resource定位、载入和注册。在实际项目中我们基本上操作的都是ApplicationContex的实现,我们比较熟悉的ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebapplicationContext等。ApplicationContext的具体继承体系如下图所示:

      

       其实,不管是XmlWebApplicationContext还是ClasspathXmlApplicationContext 他们的区别只是Bean的资源信息来源不一样而已,最终都会解析为统一数据结构BeanDefinition。

    下面我们源码的解析就从高富帅的ClassPathXmlApplicationContext开始。

            ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath*:test.xml");

    构造方法:

    /**
         * Create a new ClassPathXmlApplicationContext, loading the definitions
         * from the given XML file and automatically refreshing the context.
         * @param configLocation resource location
         * @throws BeansException if context creation failed
         */
        public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
            this(new String[] {configLocation}, true, null);
        }

    最终调用构造方法:

    /**
         * Create a new ClassPathXmlApplicationContext with the given parent,
         * loading the definitions from the given XML files.
         * @param configLocations array of resource locations
         * @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 ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
                throws BeansException {
    
            super(parent);
            setConfigLocations(configLocations);
            if (refresh) {
                refresh();
            }
        }

    1.设置父级上下文,最终是给AbstractApplicationContext的parent属性赋值,AbstractApplicationContext是ApplicationContext最顶层的实现类。

    2.设置XML文件的位置,调用了AbstractRefreshableConfigApplicationContext的setConfigLocations方法

    /**
         * Set the config locations for this application context.
         * <p>If not set, the implementation may use a default as appropriate.
         */
        public void setConfigLocations(String[] locations) {
            if (locations != null) {
                Assert.noNullElements(locations, "Config locations must not be null");
                this.configLocations = new String[locations.length];
                for (int i = 0; i < locations.length; i++) {
                    this.configLocations[i] = resolvePath(locations[i]).trim();
                }
            }
            else {
                this.configLocations = null;
            }
        }

    3.刷新容器,调用了AbstractApplicationContext的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.
                //由子类实现容器的刷新(重启)
                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.
                    //甚至beanFacotry的后置处理
                    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
                    registerListeners();
    
                    // Instantiate all remaining (non-lazy-init) singletons.
                    //实例化所有非延迟加载的Bean
                    finishBeanFactoryInitialization(beanFactory);
    
                    // Last step: publish corresponding event.
                    //发布容器事件,结束refresh过程
                    finishRefresh();
                }
    
                catch (BeansException ex) {
                    logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
    
                    // Destroy already created singletons to avoid dangling resources.
                    destroyBeans();
    
                    // Reset 'active' flag.
                    cancelRefresh(ex);
    
                    // Propagate exception to caller.
                    throw ex;
                }
            }
        }
    prepareRefresh():主要是设置启动时间、状态等等;
    我们着重看一下刷新容器的obtainFreshBeanFactory()方法:
    /**
         * Tell the subclass to refresh the internal bean factory.
         * @return the fresh BeanFactory instance
         * @see #refreshBeanFactory()
         * @see #getBeanFactory()
         */
        protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
            //销毁已有容器,重新创建容器并加载Bean
            refreshBeanFactory();
            ConfigurableListableBeanFactory beanFactory = getBeanFactory();
            if (logger.isDebugEnabled()) {
                logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
            }
            return beanFactory;
        }
    AbstractApplicationConetxt的refreshBeanFactory()方法是一个抽象方法,是由它的子类AbstractRefreshableApplicationContext实现的,从类的命名上可以看出这个类主要就是进行容器Refresh用的。
    /**
         * 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 {
            //如果容器已经存在则销毁容器中的bean并关闭容器
            if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
            }
            try {
                //创建beanFacotry
                DefaultListableBeanFactory beanFactory = createBeanFactory();
                beanFactory.setSerializationId(getId());
                customizeBeanFactory(beanFactory);
                //根据bean定义的方式(XML、注解等)不同,由子类选择相应的BeanDefinitionReader去解析
                loadBeanDefinitions(beanFactory);
                synchronized (this.beanFactoryMonitor) {
                    this.beanFactory = beanFactory;
                }
            }
            catch (IOException ex) {
                throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
            }
        }

    第一步: 这个方法会判断如果已存在容器,则先销毁所有的Bean并且关闭容器,这也是为了保证容器的唯一性。

    第二步:createBeanFactory()创建了一个DefaultListableBeanFactory,这个类是BeanFacotry最高级的实现,有了它就有个容器最基本的功能了。

    /**
         * 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
         */
        protected DefaultListableBeanFactory createBeanFactory() {
            //新建一个DefaultListableBeanFactory
            return new DefaultListableBeanFactory(getInternalParentBeanFactory());
        }

     DefaultListableBeanFactory的继承关系:

    我们看到DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,也就是说最终BeanDefinition的注册工作是由它和它的子类来完成的。

    第三步:loadBeanDefinitions(beanFactory),这个方法也是一个抽象方法。因为Bean定义方式不同(XML、注解等),会有多个子类分别去实现具体的解析。

    此处,调用的是AbstractXmlApplicationContext的实现:

    /**
         * Loads the bean definitions via an XmlBeanDefinitionReader.
         * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
         * @see #initBeanDefinitionReader
         * @see #loadBeanDefinitions
         */
        @Override
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // Create a new XmlBeanDefinitionReader for the given BeanFactory.
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
            // Configure the bean definition reader with this context's
            // resource loading environment.
            beanDefinitionReader.setEnvironment(this.getEnvironment());
            //ApplicationContext继承了ResourceLoader接口,所以this是可以直接使用的
            beanDefinitionReader.setResourceLoader(this);
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    
            // Allow a subclass to provide custom initialization of the reader,
            // then proceed with actually loading the bean definitions.
            initBeanDefinitionReader(beanDefinitionReader);
            //委派模式,具体事情委派给beanDefinitionReader去做
            loadBeanDefinitions(beanDefinitionReader);
        }

     进入loadBeanDefinitions(XmlBeanDefinitionReader reader):

    /**
         * Load the bean definitions with the given XmlBeanDefinitionReader.
         * <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
         * method; hence this method is just supposed to load and/or register bean definitions.
         * @param reader the XmlBeanDefinitionReader to use
         * @throws BeansException in case of bean registration errors
         * @throws IOException if the required XML document isn't found
         * @see #refreshBeanFactory
         * @see #getConfigLocations
         * @see #getResources
         * @see #getResourcePatternResolver
         */
        protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
            Resource[] configResources = getConfigResources();
            if (configResources != null) {
                reader.loadBeanDefinitions(configResources);
            }
            String[] configLocations = getConfigLocations();
            if (configLocations != null) {
                reader.loadBeanDefinitions(configLocations);
            }
        }

    该方法调用了XmlBeanDefinitionReader父类AbstractBeanDefinitionReader的loadBeanDefinitions方法:

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
            Assert.notNull(locations, "Location array must not be null");
            int counter = 0;
            for (String location : locations) {
                counter += loadBeanDefinitions(location);
            }
            return counter;
        }

    l循环加载location并返回加载个数,最终调用了本类的loadBeanDefinitions(String location, Set<Resource> actualResources)方法,actualResources为null:

    /**
         * 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 resourceLoader = getResourceLoader();
            if (resourceLoader == null) {
                throw new BeanDefinitionStoreException(
                        "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
            }
            //resourceLoader是ClasspathXmlApplicationContext,ApplicationContext接口本身继承了ResourcePatternResolver接口
            if (resourceLoader instanceof ResourcePatternResolver) {
                // Resource pattern matching available.
                try {
                    //location转为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[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location)将location转成了Resource[],这一步完成了资源的定位工作。
    它调用了PathMatchingResourcePatternResolver的getResources方法:
     1 public Resource[] getResources(String locationPattern) throws IOException {
     2         Assert.notNull(locationPattern, "Location pattern must not be null");
     3         //是否以classpath*:开头
     4         if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
     5             // a class path resource (multiple resources for same name possible)
     6             //是否为Ant-style路径
     7             //? 匹配任何单字符
     8             //* 匹配0或者任意数量的字符
     9             //** 匹配0或者更多的目录
    10             if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
    11                 // a class path resource pattern
    12                 return findPathMatchingResources(locationPattern);
    13             }
    14             else {
    15                 // all class path resources with the given name
    16                 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
    17             }
    18         }
    19         else {
    20             // Only look for a pattern after a prefix here
    21             // (to not get fooled by a pattern symbol in a strange prefix).
    22             int prefixEnd = locationPattern.indexOf(":") + 1;
    23             if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
    24                 // a file pattern
    25                 return findPathMatchingResources(locationPattern);
    26             }
    27             else {
    28                 // a single resource with the given name
    29                 return new Resource[] {getResourceLoader().getResource(locationPattern)};
    30             }
    31         }
    32     }

     根据location写法,解析方式也不同:

    1、前缀为classpath*

      1)文件路径路径中包含*和?

        调用findPathMatchingResources方法

      2)文件路径中不含*和?

        调用findAllClassPathResources方法

    2.、前缀为classpath

      1)文件路径路径中包含*和?

      调用findPathMatchingResources方法
     2)文件路径中不含*和?
      调用DefaultResourceLoader的getResource方法new一个ClasspathResource并返回,如果资源文件根本就不存在,此处也不会校验。
    findPathMatchingResources和findAllClassPathResources具体都干了什么呢?
    先看一下findAllClassPathResources:
    /**
         * Find all class location resources with the given location via the ClassLoader.
         * @param location the absolute path within the classpath
         * @return the result as Resource array
         * @throws IOException in case of I/O errors
         * @see java.lang.ClassLoader#getResources
         * @see #convertClassLoaderURL
         */
        protected Resource[] findAllClassPathResources(String location) throws IOException {
            String path = location;
            if (path.startsWith("/")) {
                path = path.substring(1);
            }
            ClassLoader cl = getClassLoader();
            Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
            Set<Resource> result = new LinkedHashSet<Resource>(16);
            while (resourceUrls.hasMoreElements()) {
                URL url = resourceUrls.nextElement();
                result.add(convertClassLoaderURL(url));
            }
            return result.toArray(new Resource[result.size()]);
        }

       protected Resource convertClassLoaderURL(URL url) {
    return new UrlResource(url);
    }
    这个方法很简单,根据具体的location通过classLoader的getResources方法返回RUL集合,根据URL创建UrlResource并返回UrlResource的集合。
    再来看一下findPathMatchingResources方法:
    /**
         * Find all resources that match the given location pattern via the
         * Ant-style PathMatcher. Supports resources in jar files and zip files
         * and in the file system.
         * @param locationPattern the location pattern to match
         * @return the result as Resource array
         * @throws IOException in case of I/O errors
         * @see #doFindPathMatchingJarResources
         * @see #doFindPathMatchingFileResources
         * @see org.springframework.util.PathMatcher
         */
        protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
            String rootDirPath = determineRootDir(locationPattern);
            String subPattern = locationPattern.substring(rootDirPath.length());
            Resource[] rootDirResources = getResources(rootDirPath);
            Set<Resource> result = new LinkedHashSet<Resource>(16);
            for (Resource rootDirResource : rootDirResources) {
                rootDirResource = resolveRootDirResource(rootDirResource);
                if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                    result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
                }
                else if (isJarResource(rootDirResource)) {
                    result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
                }
                else {
                    result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
            }
            return result.toArray(new Resource[result.size()]);
        }
    1.String rootDirPath = determineRootDir(locationPattern),获取location前缀classpath*/classpath
    2.
    Resource[] rootDirResources = getResources(rootDirPath),调用的上面讲到的getResources方法,返回classpath根路径的Resource[],如果是classpath会返回一个Resource,
    如果是classpath*会放回所有的classpath路径。
    3.遍历根路径Resource[],doFindPathMatchingFileResources方法就是获取给定路径下的所有文件,根据指定的文件名test*.xml去模糊匹配,返回的是FileSystemResource。所以location为classpath:test*.xml可能会找不到文件。

     

     



  • 相关阅读:
    sql查询重复记录、删除重复记录方法大全
    查询字段所在的表/视图
    查询某张表被哪些存储过程或者视图用到的sql语句
    SQL中char、varchar、nvarchar的区别
    JS快速获取图片宽高的方法
    Git代码冲突常见解决方法
    HTML__图片轮播ion-slide-box
    oracle列出两个日期间所有日期
    myeclipse 8.0 注册码
    网页中图片旋转的几种实现方式
  • 原文地址:https://www.cnblogs.com/monkey0307/p/8436134.html
Copyright © 2020-2023  润新知