zoukankan      html  css  js  c++  java
  • Spring源码阅读 之 配置的加载(希望有喜欢源码的朋友一起交流)

    想写Spring的源码方面的东西想了好久了,之前花了一段时间学习了SpringCloud,现在总算对SpringCloud有了一个大概的了解,从今天开始好好读一篇Spring的源码,结合书本跟网上的一些资料,希望能坚持下去,完成这一系列的文章,给自己加油!!!!

    版本是基于5.1.X的,大家在阅读的时候请注意,不过因为是Spring的一些基础流程,估计版本间差异也不会太大

    关于配置的加载主要可以分为两种:

    1. 注解式配置的加载
    2. XML配置加载

    注解配置相对于XML更加简单,其过程主要在于注解的解析,而XML配置是用xml文件格式保存配置,其中就涉及到了文件的读取,读取后要进行标签的解析,稍显复杂。我们这篇文章主要就是来讲清楚Spring读取配置文件的过程,对于后面的XML标签的解析或者说是注解的解析其实都是差不多的,在后面的文章中,我会慢慢介绍

    我们先看一段代码:

    /**
     * @author dmz
     * @date Create in 20:49 2019/7/20
     */
    public class Spring {
        public static void main(String[] args) {
            
            // 构建一个基于XML的Spring应用的上下文
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
            
        }
    }
    

    很简单,目的就是跟踪一个基于XML的Spring应用是如何被加载的,我们一步步点进去看源码可以发现:

    	public ClassPathXmlApplicationContext(
    			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
    			throws BeansException {
    		// 这个构造函数暂时不说
    		super(parent);
            // 这个方法主要是为了设置这个应用需要使用的Spring的配置文件
            // 主要是对配置文件的路径进行一些处理
    		setConfigLocations(configLocations);
    		if (refresh) {
                // 这是Spring的核心方法
    			refresh();
    		}
    	}
    

    Spring的源码很大,我们需要一点点啃,这篇文章目的只是分析配置的读取,所以只分析跟配置相关的代码,在这里也提醒大家,阅读源码时不要被细枝末节牵绊的太深,抓住自己需要分析的主线,然后不断探究,能做到这样就是极好的!!!

    我们接着说代码:

    public void refresh() throws BeansException, IllegalStateException {
    		synchronized (this.startupShutdownMonitor) {
    			// 为容器的刷新做准备
    			prepareRefresh();
    			// 主要分析这个代码
    			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    			// 省略后面的代码
    			.......
    	}
    
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        // 刷新BeanFactory
       refreshBeanFactory();
       return getBeanFactory();
    }
        
    protected final void refreshBeanFactory() throws BeansException {
        // 如果当前容器的BeanFactory已经经过初始化,进行清空
          if (hasBeanFactory()) {
                destroyBeans();
                closeBeanFactory();
          }
         try {
                DefaultListableBeanFactory beanFactory = createBeanFactory();
                beanFactory.setSerializationId(getId());
                customizeBeanFactory(beanFactory);
             // 我们主要需要关注的方法
                loadBeanDefinitions(beanFactory);
                synchronized (this.beanFactoryMonitor) {
                    this.beanFactory = beanFactory;
        }
      }
        catch (IOException ex) {
          throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
            }
    }
        
        protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
            // 我们可以看到,在这一步,创建了一个XmlBeanDefinitionReader,构造参数是一个DefaultListableBeanFactory
            XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    		// 对这个reader的成员变量进行赋值
            beanDefinitionReader.setEnvironment(this.getEnvironment());
            beanDefinitionReader.setResourceLoader(this);
            beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    		// 提供给子类使用,让子类能自定义BeanDefinitionReader
            initBeanDefinitionReader(beanDefinitionReader);
            // 最后调用loadBeanDefinitions
            loadBeanDefinitions(beanDefinitionReader);
        }
    

    我们将注意力放到loadBeanDefinitions这个方法上,从方法名称上我们可以知道,这个方法一定读取了配置文件,跟踪其代码最后会到org.springframework.context.support.AbstractXmlApplicationContextloadBeanDefinitions方法,我们看下这个方法的具体实现:

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        // 我们之前给定的是一个application.xml,所以会进这个方法
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }
    

    继续跟踪,进入org.springframework.beans.factory.support.AbstractBeanDefinitionReaderloadBeanDefinitions方法

    	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
            // 获取一个资源加载器
    		ResourceLoader resourceLoader = getResourceLoader();
    		if (resourceLoader == null) {
    			throw new BeanDefinitionStoreException(
    					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    		}
    		// 这里获取到的资源加载器是一个org.springframework.context.support.ClassPathXmlApplicationContext,而所有的applicationContext都实现了ResourcePatternResolver接口
    		if (resourceLoader instanceof ResourcePatternResolver) {
    			try {
                    // 呼~,终于到这个加载资源的方法了,这个方法会根据不同location,选择对应的resourceLoader加载配置文件
    				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    				int count = loadBeanDefinitions(resources);
    		......省略部分代码......
    

    在我们的示例中,最终会调用org.springframework.core.io.DefaultResourceLoadergetResource方法

    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
    
        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }
    
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }
    

    我们可以看到,这个方法会根据一些匹配规则,比如说localtion是否以“/”开头,是否是一个FileURL等等分别创建

    ClassPathResource,FileUrlResource,UrlResource等,我们可以看一下它的类图:

    在这里插入图片描述

    ​ 在java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStramHandler)来处理不同来源的资源的读取逻辑,一般hanlder使用不同前缀(协议,Protocol)来识别,如“file”,"http:"等,然而URL没有默认定义相对Classpath或者ServletContext等资源的hanlder,虽然可以注册自己的URLStream来解析特定的URL前缀,比如“classpath:”,然后这需要了解URL的实现机制,而且URL也没有提供基本的方法,如检查当前资源是否存在,检查当前资源是否可读等,因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源

    public interface InputStreamSource {
    
        /**
         * 顶层接口,返回资源对应的输入流
      	 */
        InputStream getInputStream() throws IOException;
    
    }
    
    public interface Resource extends InputStreamSource {
    
    	/**
    	 * 是否存在
    	 */
    	boolean exists();
    
    	/**
    	 * 是否可读
    	 */
    	default boolean isReadable() {
    		return exists();
    	}
    
    	/**
    	 * 是否打开
    	 */
    	default boolean isOpen() {
    		return false;
    	}
    
    	/**
         * 是否是一个file,如果是的话,getFile()一定能正常返回
    	 */
    	default boolean isFile() {
    		return false;
    	}
    
    	/**
         * 返回资源对应的URL
    	 */
    	URL getURL() throws IOException;
    
    	/**
         * 返回资源对应的URI
    	 */
    	URI getURI() throws IOException;
    
    	/**
         * 返回资源对应的文件
    	 */
    	File getFile() throws IOException;
    
    	/**
         * 返回一个根据当前资源转变的可读的nio中的ReadableByteChannel
    	 */
    	default ReadableByteChannel readableChannel() throws IOException {
    		return Channels.newChannel(getInputStream());
    	}
    
    	/**
         * 返回资源的字节长度
    	 */
    	long contentLength() throws IOException;
    
    	/**
    	 * 返回上一次修改的时间戳
    	 */
    	long lastModified() throws IOException;
    
    	/**
    	 * 基于当前资源,创建一个相对资源
    	 */
    	Resource createRelative(String relativePath) throws IOException;
    
    	/**
    	 * 返回资源的文件名(不带路径),没有文件名则返回null
    	 */
    	@Nullable
    	String getFilename();
    
    	/**
    	 * 返回资源描述
    	 */
    	String getDescription();
    
    }
    

    绝大部分情况下,我们都是直接在resoures直接创建配置文件,所以大部分情况,我们使用的都是ClassPathResource,我们不妨看一下它对这些方法的实现,加深我们对这些接口的理解

    @Override
    // 可以发现,就是通过类加载器获取对应资源,最终的实现还是会依赖于我们jdk中的URL类
    // 最后通过URL返回一个流
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }
    // 判断这个ClassPathResource对应的路径能否被解析成一个java中的URL,如果能则存在
    public boolean exists() {
        return (resolveURL() != null);
    }
    .......
    

    经过上面的分析我们已经知道了Spring怎么加载一个配置文件,我们可以总结如下:

    1. 创建一个XmlBeanDefinitionReader
    2. 获取对应的ResourceLoader资源加载器
    3. 根据location的不同匹配模式,采用不同的ResourceLoader进行资源的加载
    4. Spring封装了java中的URL类,自定义了一套资源加载策略
    5. 最后返回一个Spring中的Resource对象

    码字不易,希望能找几个同学一起学习源码,后续会一直更新这个系列,有喜欢的朋友加个收藏,点个赞吧!!!谢啦~

  • 相关阅读:
    poj 3613(经过N条边的最短路)
    poj 3328(多起点多终点的最短路)
    poj 3311(floyd+状态压缩)
    新CCIE笔记-IP网络基础
    新CCIE笔记-IP网络基础
    算法之【冒泡排序法】
    算法之【冒泡排序法】
    算法之【冒泡排序法】
    算法之【辗转相除法】
    算法之【辗转相除法】
  • 原文地址:https://www.cnblogs.com/daimzh/p/12854417.html
Copyright © 2011-2022 走看看