zoukankan      html  css  js  c++  java
  • spring源码阅读(1)-- 容器启动之资源定位

      在工作中,基本上所有的项目都需要用到spring,但本人至今一直没有深入研读spring的源码。鉴于此,本人决定对其展开深入研究,并将所思所想记录下来,若理解出现偏差,还望大家不吝赐教。

      关于spring的基本使用,这里不便详细展开,关于阅读源码的方式方法,本人认为带着疑问通过debug阅读会更显轻松,亦更为容易达到“知其所以然”的目的。

      本文要解决的问题是:spring是如何找到指定的配置文件的?

      OK,首先搭建一个maven项目,pom配置如下。本系列文章使用的spring版本是4.3.2.RELEASE

      

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.zksite</groupId>
        <artifactId>spring-framework-test</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    
        <properties>
            <spring.version>4.3.2.RELEASE </spring.version>
            <slf4j.version>1.7.13</slf4j.version>
            <logback.version>1.1.3</logback.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId> org.springframework</groupId>
                <artifactId> spring-context</artifactId>
                <version> ${spring.version}</version>
            </dependency>
            <dependency>
                <groupId> org.springframework</groupId>
                <artifactId> spring-core</artifactId>
                <version> ${spring.version}</version>
            </dependency>
            <dependency>
                <groupId> org.springframework</groupId>
                <artifactId> spring-beans</artifactId>
                <version> ${spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4j.version}</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>jcl-over-slf4j</artifactId>
                <version>${slf4j.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>${logback.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
            </dependency>
        </dependencies>
    </project>
    View Code

      项目结构如下

      

      spring-context-test.xml文件内容:

      

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        
        <bean id="springtest" class="com.zksite.spring.test.SpringBeanTest" scope="singleton"/>
    </beans>
    View Code

      当项目中使用到了spring之后,我们需要在项目启动时启动spring容器,将需要的bean托管给spring管理。

        public static void main(String[] args) {
    		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context-test.xml");
    		SpringBeanTest springBeanTest = applicationContext.getBean(SpringBeanTest.class);
    		springBeanTest.sayHello();
    	}
    

      这里使用ClassPathXmlApplicationContext启动容器,ClassPathXmlApplicationContext是ApplicationContext其中的一个实现,spring提供了针对不同使用场景ApplicationContext实现如下

      

        ClassPathXmlApplicationContext:从classpath获取指定配置文件,可以使用通配符

        FileSystemXmlApplicationContext:通过指定文件,可以使用url指定配置文件

        XmlWebApplicationContext:web环境时使用,通过web.xml指定配置文件由org.springframework.web.context.ContextLoader或org.springframework.web.servlet.FrameworkServlet启动,关于更多的实现可以参考spring doc

        这里选用ClassPathXmlApplicationContext启动spring。本节主要目的深入spring对资源的定位,通过debug进入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()方法。AbstractRefreshableApplicationContext只实现了refreshBeanFactory方法而且还是final的,说明spring规范了BeanFactory的刷新流程。

        

        protected final void refreshBeanFactory() throws BeansException {
    		if (hasBeanFactory()) {
    			destroyBeans();
    			closeBeanFactory();
    		}
    		try {
    			DefaultListableBeanFactory beanFactory = createBeanFactory();//创建bean工厂
    			beanFactory.setSerializationId(getId());
    			customizeBeanFactory(beanFactory);
    			loadBeanDefinitions(beanFactory);//资源定位和加载BeanDefinition
    			synchronized (this.beanFactoryMonitor) {
    				this.beanFactory = beanFactory;
    			}
    		}
    		catch (IOException ex) {
    			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    		}
    	}
    

      这里首先判断当前上下文是否已经存在beanFactory,如果存在,首先销毁所有bean和关闭beanFactory,然后创建beanFactory然后进入loadBeanDefinitions方法。loadBeanDefinitions方法实现了资源的定位和创建BeanDefinition。loadBeanDefinitions方法源码如下

      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());
            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);
            loadBeanDefinitions(beanDefinitionReader);
        }

      因为当前使用的是xml配置spring,所以loadBeanDefinitions的实现由AbstractXmlApplicationContext提供。方法里分别做了以下处理:

        1.通过指定的BeanDefinitionRegistry创建XmlBeanDefinitionReader实例

        2.设置环境,用于在读取配置文件时使用

        3.设置ResourceLoader,为接下来的定位资源做准备(ResourceLoader下面解释)

        4.设置SAX解析器

        5.加载BeanDefinition

      XmlBeanDefinitionReader是BeanDefinitionReader的其中一个实现。BeanDefinitionReader抽象了将配置文件转化为BeanDefinition的过程,分别有:获取bean注册中心、获取资源加载器、获取ClassLoader、获取bean名称生成器、指定资源加载BeanDefinition、指定配置文件加载BeanDefinition

    public interface BeanDefinitionReader {
        
        /**
         * 获取BeanDefinition注册中心
         * Return the bean factory to register the bean definitions with.
         * <p>The factory is exposed through the BeanDefinitionRegistry interface,
         * encapsulating the methods that are relevant for bean definition handling.
         */
        BeanDefinitionRegistry getRegistry();
    
        /**
         * 获取资源加载器
         * Return the resource loader to use for resource locations.
         * Can be checked for the <b>ResourcePatternResolver</b> interface and cast
         * accordingly, for loading multiple resources for a given resource pattern.
         * <p>Null suggests that absolute resource loading is not available
         * for this bean definition reader.
         * <p>This is mainly meant to be used for importing further resources
         * from within a bean definition resource, for example via the "import"
         * tag in XML bean definitions. It is recommended, however, to apply
         * such imports relative to the defining resource; only explicit full
         * resource locations will trigger absolute resource loading.
         * <p>There is also a {@code loadBeanDefinitions(String)} method available,
         * for loading bean definitions from a resource location (or location pattern).
         * This is a convenience to avoid explicit ResourceLoader handling.
         * @see #loadBeanDefinitions(String)
         * @see org.springframework.core.io.support.ResourcePatternResolver
         */
        ResourceLoader getResourceLoader();
    
        /**
         * 获取ClassLoader
         * Return the class loader to use for bean classes.
         * <p>{@code null} suggests to not load bean classes eagerly
         * but rather to just register bean definitions with class names,
         * with the corresponding Classes to be resolved later (or never).
         */
        ClassLoader getBeanClassLoader();
    
        /**
         * 获取bean名字生成器
         * Return the BeanNameGenerator to use for anonymous beans
         * (without explicit bean name specified).
         */
        BeanNameGenerator getBeanNameGenerator();
    
    
        /**
         * 指定资源加载BeanDefinition
         * Load bean definitions from the specified resource.
         * @param resource the resource descriptor
         * @return the number of bean definitions found
         * @throws BeanDefinitionStoreException in case of loading or parsing errors
         */
        int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    
        /**
         * 指定多个资源加载BeanDefinition
         * Load bean definitions from the specified resources.
         * @param resources the resource descriptors
         * @return the number of bean definitions found
         * @throws BeanDefinitionStoreException in case of loading or parsing errors
         */
        int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
    
        /**
         * 指定配置文件加载BeanDefinition
         * 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
         * @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[])
         */
        int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
    
        /**
         * 指定多个配置文件加载BeanDefinition
         * Load bean definitions from the specified resource locations.
         * @param locations the resource locations, to be loaded with the ResourceLoader
         * (or ResourcePatternResolver) of this bean definition reader
         * @return the number of bean definitions found
         * @throws BeanDefinitionStoreException in case of loading or parsing errors
         */
        int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
    
    }
    View Code

       通过BeanDefinitionReader里的方法定义看出接口依赖Resource和ResourceLoader这两个组件。spring将配置抽象为Resource,并针对不同使用场景提供了不同实现,Resource的实现如下

      

      ClassPathResource:classpath路径资源,通过指定的ClassLoader加载资源

      FileSystemResource:文件系统资源

      UrlResource:URL资源

      而另一个组件ResourceLoader负责资源的加载,ResourceLoader的实现如下

      从结构可以发现,ClassPathXmlApplicationContext是ResourceLoader的其中一个实现,但ClassPathXmlApplicationContext并没有真正实现了怎么去加载一个资源,而是通过持有PathMatchingResourcePatternResolver实例,通过委派给PathMatchingResourcePatternResolver去加载资源,具体后面会介绍到,接着进入的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法

      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);
            }
        }

      方法里首先调用getConfigResources(),这是一个预留给子类扩展的一个方法这方法,在ClassPathXmlApplicationContext是一个空实现(估计是因为ClassPathXmlApplicationContext不需要加载自定以的配置)。然后调用getConfigLocations()方法获取用户配置或默认配置,然后调用XmlBeanDefinitionReader.loadBeanDefinitions()方法进行BeanDefinition加载

      然后一直debug执行到AbstractBeanDefinitionReader.loadBeanDefinitions(String location, Set<Resource> actualResources)

     1   public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
     2         ResourceLoader resourceLoader = getResourceLoader();
     3         if (resourceLoader == null) {
     4             throw new BeanDefinitionStoreException(
     5                     "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
     6         }
     7 
     8         if (resourceLoader instanceof ResourcePatternResolver) {
     9             // Resource pattern matching available.
    10             try {
    11                 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    12                 int loadCount = loadBeanDefinitions(resources);
    13                 if (actualResources != null) {
    14                     for (Resource resource : resources) {
    15                         actualResources.add(resource);
    16                     }
    17                 }
    18                 if (logger.isDebugEnabled()) {
    19                     logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
    20                 }
    21                 return loadCount;
    22             }
    23             catch (IOException ex) {
    24                 throw new BeanDefinitionStoreException(
    25                         "Could not resolve bean definition resource pattern [" + location + "]", ex);
    26             }
    27         }
    28         else {
    29             // Can only load single resources by absolute URL.
    30             Resource resource = resourceLoader.getResource(location);
    31             int loadCount = loadBeanDefinitions(resource);
    32             if (actualResources != null) {
    33                 actualResources.add(resource);
    34             }
    35             if (logger.isDebugEnabled()) {
    36                 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
    37             }
    38             return loadCount;
    39         }
    40     }

       方法里首先获取ResourceLoader,获取到的是刚才在new XmlBeanDefinitionReader时设置的ClassPathXmlApplicationContext(ClassPathXmlApplicationContext实现了ResourceLoader)。然后判断是否是一个ResourcePatternResolver实例(我们在使用spring时,当有多个配置文件时,是可以通过模糊匹配配置,例如当有如下配置文件时:spring-context-db.xml、spring-context-cache.xml,在设置配置文件路径时可以写成spring-context-*.xml),ApplicationContext接口继承了ResourcePatternResolver,所以这里直接进入到if里面,然后通过resourceLoader.getResources()方法,执行到这里,才开始资源的定位。代码如下:

    1   public Resource[] getResources(String locationPattern) throws IOException {
    2         return this.resourcePatternResolver.getResources(locationPattern);
    3     }

        可以看到,最终获取资源是通过ClassPathXmlApplicationContex里的resourcePatternResolver去实现。resourcePatternResolver是PathMatchingResourcePatternResolver的一个实例,resourcePatternResolver的初始化是在创建ClassPathXmlApplicationContex是发生。进入PathMatchingResourcePatternResolver.getResources()方法:

     1 public Resource[] getResources(String locationPattern) throws IOException {
     2         Assert.notNull(locationPattern, "Location pattern must not be null");
     3         if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
     4             // a class path resource (multiple resources for same name possible)
     5             if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
     6                 // a class path resource pattern
     7                 return findPathMatchingResources(locationPattern);
     8             }
     9             else {
    10                 // all class path resources with the given name
    11                 return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
    12             }
    13         }
    14         else {
    15             // Only look for a pattern after a prefix here
    16             // (to not get fooled by a pattern symbol in a strange prefix).
    17             int prefixEnd = locationPattern.indexOf(":") + 1;
    18             if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
    19                 // a file pattern
    20                 return findPathMatchingResources(locationPattern);
    21             }
    22             else {
    23                 // a single resource with the given name
    24                 return new Resource[] {getResourceLoader().getResource(locationPattern)};
    25             }
    26         }
    27     }

      方法里首先判断一下指定的配置文件路径是否是以“classpath*:”开头,如果不是再判断一下是否已“:”开头,如果这两者都不是,则调用DefaultResourceLoader提供的getResource()方法返回单个资源,里面的实现会根据指定的配置确定返回的是UrlResource或ClassPathContextResource。

      如果指定的配置文件路径是以“classpath*:”或“:”开头,将使用 ant path匹配规则去匹配(Ant path匹配规则)。当配置文件路径为“classpath*:spring-context-*.xml”时,进去findPathMatchingResources(),在classpath模糊匹配多个资源。当配置文件为“classpath*:spring-context.xml”时,在classpath路径下获取单个文件。其实当配置文件是“classpath*:spring-context-*.xml”时,也会调用doFindAllClassPathResources方法,因为需要会找出classpath的所有文件包括jar,如果是jar包也会在jar包里面查找。

      至此,已经加载完指定的所有资源,下面给出加载资源的时序图,从时序图可以看得出,资源的最终定位是由PathMatchingResourcePatternResolver实现。

      最后总结一下,spring容器启动时资源定位涉及到的组件:

        Application:spring应用上下文

        BeanDefinitionReader:负责从配置文件加载BeanDefinition

        ResourceLoader:负责资源的加载

  • 相关阅读:
    【Gerrit】Gerrit与Jenkins/Hudson CI服务器搭建
    【Gerrit】Gerrit cmd query (gerrit命令行查询change信息)
    【python】jiraAPI使用教程 自动创建jira问题单并置状态为OPEN
    【Jenkins】jenkins简单搭建并执行任务
    【python】Redis介绍及简单使用
    【python】PIL 批量绘制图片矩形框工具
    【Flask】Flask快速玩框架
    C# split 几种使用方法
    40个有用的jQuery技术和教程
    jQuery性能优化
  • 原文地址:https://www.cnblogs.com/hanjiehu/p/8603393.html
Copyright © 2011-2022 走看看