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

相关推荐
huangjiazhi_8 小时前
纯C++实现ini文件操作
java·后端·spring
卓怡学长10 小时前
w272基于springboot便民医疗服务小程序
java·spring boot·spring·小程序·intellij-idea
霸道流氓气质10 小时前
Harness Engineering 从理论到实战:基于 Spring AI Alibaba 的完整实现指南
数据库·人工智能·spring·harness
AI人工智能+电脑小能手1 天前
【大白话说Java面试题 第155题】【06_Spring篇】第15题:Spring 如何解决循环依赖问题?
java·spring·循环依赖·三级缓存·aop代理
AI人工智能+电脑小能手1 天前
【大白话说Java面试题 第156题】【06_Spring篇】第16题:Spring 用到了哪些设计模式?
java·spring·设计模式·代理模式·工厂模式
辰海Coding1 天前
MiniSpring框架学习笔记-动态代理:如何在运行时插入逻辑?
java·笔记·学习·spring·动态代理
一路向北North1 天前
Spring Security OAuth2.0(16):密码模式
java·后端·spring
卓怡学长1 天前
w273基于springboot的智能笔记的开发与应用
java·spring boot·spring·intellij-idea
无心水2 天前
【全域智能营销实战】2、Spring AI 模块化架构深度解读:从 1.0 到 2.0 的演进与最佳实践
人工智能·spring·架构·harness·顶尖架构师·全域智能营销·harmess
livemetee2 天前
【关于Spring声明式事务】
java·后端·spring