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

相关推荐
山海上的风2 小时前
Spring Batch终极指南:原理、实战与性能优化
spring·性能优化·batch·springbatch
找不到、了4 小时前
Spring的Bean原型模式下的使用
java·spring·原型模式
超级小忍4 小时前
Spring AI ETL Pipeline使用指南
人工智能·spring
Boilermaker19927 小时前
【Java EE】SpringIoC
前端·数据库·spring
写不出来就跑路7 小时前
Spring Security架构与实战全解析
java·spring·架构
sleepcattt8 小时前
Spring中Bean的实例化(xml)
xml·java·spring
小七mod8 小时前
【Spring】Java SPI机制及Spring Boot使用实例
java·spring boot·spring·spi·双亲委派
ruan1145149 小时前
Java Lambda 类型推断详解:filter() 方法与 Predicate<? super T>
java·开发语言·spring·stream
paopaokaka_luck10 小时前
基于SpringBoot+Vue的非遗文化传承管理系统(websocket即时通讯、协同过滤算法、支付宝沙盒支付、可分享链接、功能量非常大)
java·数据库·vue.js·spring boot·后端·spring·小程序
邓不利东14 小时前
Spring中过滤器和拦截器的区别及具体实现
java·后端·spring