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:负责资源的加载

  • 相关阅读:
    iOS中Zbar二维码扫描的使用
    SOJ 1135. 飞跃原野
    SOJ 1048.Inverso
    SOJ 1219. 新红黑树
    SOJ 1171. The Game of Efil
    SOJ 1180. Pasting Strings
    1215. 脱离地牢
    1317. Sudoku
    SOJ 1119. Factstone Benchmark
    soj 1099. Packing Passengers
  • 原文地址:https://www.cnblogs.com/hanjiehu/p/8603393.html
Copyright © 2011-2022 走看看