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只是第一步,后面探究的就是怎么读取配置文件的了。

相关推荐
Miketutu5 小时前
Spring MVC消息转换器
java·spring
小小虫码7 小时前
项目中用的网关Gateway及SpringCloud
spring·spring cloud·gateway
带刺的坐椅12 小时前
无耳科技 Solon v3.0.7 发布(2025农历新年版)
java·spring·mvc·solon·aop
精通HelloWorld!15 小时前
使用HttpClient和HttpRequest发送HTTP请求
java·spring boot·网络协议·spring·http
LUCIAZZZ16 小时前
基于Docker以KRaft模式快速部署Kafka
java·运维·spring·docker·容器·kafka
拾忆,想起16 小时前
如何选择Spring AOP的动态代理?JDK与CGLIB的适用场景
spring boot·后端·spring·spring cloud·微服务
鱼骨不是鱼翅17 小时前
Spring Web MVC基础第一篇
前端·spring·mvc
hong_zc19 小时前
Spring MVC (三) —— 实战演练
java·spring·mvc
Future_yzx20 小时前
Spring AOP 入门教程:基础概念与实现
java·开发语言·spring
安清h21 小时前
【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户注册
数据库·后端·mysql·spring·mybatis