Spring是怎么找到resources下面的配置文件的?

前言

Spring是怎么找到resources下面的配置文件的?先说一个大概,后面再完善。

在Spring中对resources下面的配置文件加载是通过new ClassPathReource("XXX")来封装成ClassPathResource,而ClassPathResource中是通过class或者classLoader底层的双亲委派机制方法进行加载,即AppClassLoader在父类加载器中类找到的时候,就会自己查找,在java.class.path路径下查找,找到就返回,没找到就让之类找,如果没有之类就会抛出异常。

问题场景

less 复制代码
    @Test
    @SuppressWarnings(value = "deprecation")
    public void testSimpleLoad() {
       XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
       MyTestBean myTestBean = (MyTestBean) xmlBeanFactory.getBean("myTestBean");
    }

beanFactoryTest.xml

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="myTestBean" class="MyTestBean"/>

</beans>

MyTestBean.java

typescript 复制代码
public class MyTestBean {

    private String testStr = "testStr";

    public String getTestStr() {

       return testStr;

    }



    public void setTestStr(String testStr) {

       this.testStr = testStr;

    }

}

这段代码实现了如下功能:

  1. 加载资源文件

  2. 读取配置文件

  3. 实例化Bean

  4. 然后获取实例化的Bean

这里我们探究第一个功能,即加载资源文件

探究原理

源码流程

This.reader.loadBeanDefinition(resource)才是资源加载的真正实现

java 复制代码
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {

    super(parentBeanFactory);

    this.reader.loadBeanDefinitions(resource);

}

封装资源文件,对参数使用EncodedResource进行封装

java 复制代码
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {

    return loadBeanDefinitions(new EncodedResource(resource));

}

获取资源流inputstream。得到inpustream后,我们就可以进行解析了

csharp 复制代码
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {

    Assert.notNull(encodedResource, "EncodedResource must not be null");

    if (logger.isTraceEnabled()) {

       logger.trace("Loading XML bean definitions from " + encodedResource);

    }



    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();



    if (!currentResources.add(encodedResource)) {

       throw new BeanDefinitionStoreException(

             "Detected cyclic loading of " + encodedResource + " - check your import definitions!");

    }



    try (InputStream inputStream = encodedResource.getResource().getInputStream()) {

       InputSource inputSource = new InputSource(inputStream);

       if (encodedResource.getEncoding() != null) {

          inputSource.setEncoding(encodedResource.getEncoding());

       }

       return doLoadBeanDefinitions(inputSource, encodedResource.getResource());

    }

    catch (IOException ex) {

       throw new BeanDefinitionStoreException(

             "IOException parsing XML document from " + encodedResource.getResource(), ex);

    }

    finally {

       currentResources.remove(encodedResource);

       if (currentResources.isEmpty()) {

          this.resourcesCurrentlyBeingLoaded.remove();

       }

    }

}

然后通过classLoader。GetResourceAsStream(this.path)来获取

kotlin 复制代码
@Override

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;

}

后面就不展开了,就是通过双亲委派机制,父类加载器没找到,而是先调用AppClassLoader#findResource,然后调用BuiltinClassLoader# findResourceOnClassPath(name),然后通过URLClassPath#findResource获取对应的loader,然后最后调用相应的Loader#findResource方法来获取到配置文件的URL。

什么是ClassPathResource?在源码中,是怎么通过ClassPathResource#getInputStream获取到资源输入流呢?

Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源,我们通过ClassPathResource("XXXX")加载classpath资源。

ClassPathResource中的实现了Resource中的getInputStream方法,是通过classLoader提供的底层方法进行调用的。

ClassPathReource的classLoader是什么呢?

目前所知的就是我们在加载我们的配置文件的时候,在创建ClassPathResource实例时候,会指定其classLoader为AppClassLoader

classLoader底层是怎么找到我们要加载的配置文件的呢?

JVM在加载ClassLoaders类的时候会调用静态块文件:

通过System.getProperty("java.class.path")获取系统类路径,然后实例化一个URLClassPath,进而实例化出一个AppClassLoader出来,而这个AppClassLoader就能加载所有的class path文件,至于这个class path是怎么来的,而系统属性中的java.class.path又是怎么赋初值的,这里先不说,我也还没研究明白。

总结

Spring其实就是通过Java提过的类加载机制来加载我们的配置文件。Spring通过接口类Resource抽象了所有内部使用到的底层资源,不同的资源有不同的实现,我们加载配置文件用到的ClassPathResource就对应于Classpath资源,其实现便是通过JAVA类加载提供的底层方法进行的。

扩展

  1. System.getProperty("java.class.path")获取到的系统属性是怎么来的或者说是怎么进行赋值的呢?这一块我通过看java.class.path对应的路径发现都是我们依赖的java和resources文件夹路径,所以猜测与我们编译过程中有关或者maven有关

  2. 最后我们探究下来其实就是通过URLClassPath中的Loader有关,而通过debug发现loader其实有JarLoader,FileLoader等等,这些与我们说的类加载器是啥关系?之前一直以为类加载器就是加载.class文件的。

  3. Redource接口抽象了所有spring内部使用的底层资源:文件、Classpath、URL资源、InputStream资源,这里面用到了大量的模板方法来抽取公共方法,抽取抽象类,抽象接口。

  4. 加载配置火箭获取inputstream只是第一步,后面探究的就是怎么读取配置文件的了。

相关推荐
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭3 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
李小白664 小时前
Spring MVC(上)
java·spring·mvc
Lojarro6 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
zjw_rp7 小时前
Spring-AOP
java·后端·spring·spring-aop
撒呼呼10 小时前
# 起步专用 - 哔哩哔哩全模块超还原设计!(内含接口文档、数据库设计)
数据库·spring boot·spring·mvc·springboot
天使day11 小时前
SpringMVC
java·spring·java-ee
壹佰大多12 小时前
【spring-cloud-gateway总结】
java·spring·gateway
CodeChampion12 小时前
60.基于SSM的个人网站的设计与实现(项目 + 论文)
java·vue.js·mysql·spring·elementui·node.js·mybatis
秋意钟12 小时前
Spring框架处理时间类型格式
java·后端·spring
科马16 小时前
【Redis】缓存
数据库·redis·spring·缓存