您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

Spring 源码第一篇开整!配置文件是怎么加载的?

时间:2020-06-17 09:40:10  来源:  作者:

上周把话撂出来,看起来小伙伴们都挺期待的,其实松哥也迫不及待想要开启一个全新的系列。

但是目前的 Spring Security 系列还在连载中,还没写完。连载这事,一鼓作气,再而衰三而竭,一定要一次搞定,Spring Security 如果这次放下来,以后就很难再拾起来了。

所以目前的更新还是 Spring Security 为主,同时 Spring 源码解读每周至少更新一篇,等 Spring Security 系列更新完毕后,就开足马力更新 Spring 源码。其实 Spring Security 中也有很多和 Spring 相通的地方,Spring Security 大家文章认真看,松哥不会让大家失望的!

1.从何说起

Spring 要从何说起呢?这个问题我考虑了很长时间。

因为 Spring 源码太繁杂了,一定要选择一个合适的切入点,否则一上来就把各位小伙伴整懵了,那剩下的文章估计就不想看了。

想了很久之后,我决定就先从配置文件加载讲起,在逐步展开,配置文件加载也是我们在使用 Spring 时遇到的第一个问题,今天就先来说说这个话题。

2.简单的案例

先来一个简单的案例,大家感受一下,然后我们顺着案例讲起。

首先我们创建一个普通的 Maven 项目,引入 spring-beans 依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

然后我们创建一个实体类,再添加一个简单的配置文件:

public class User {
    private String username;
    private String address;
    //省略 getter/setter
}

resources 目录下创建配置文件:

<?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 class="org.JAVAboy.loadxml.User" id="user"/>
</beans>

然后去加载这个配置文件:

public static void main(String[] args) {
    XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
    User user = factory.getBean(User.class);
    System.out.println("user = " + user);
}

这里为了展示数据的读取过程,我就先用这个已经过期的 XmlBeanFactory 来加载,这并不影响我们阅读源码。

上面这个是一个非常简单的 Spring 入门案例,相信很多小伙伴在第一次接触 Spring 的时候,写出来的可能都是这个 Demo。

在上面这段代码执行过程中,首先要做的事情就是先把 XML 配置文件加载到内存中,再去解析它,再去。。。。。

一步一步来吧,先来看 XML 文件如何被加入到内存中去。

3.文件读取

文件读取在 Spring 中很常见,也算是一个比较基本的功能,而且 Spring 提供的文件加载方式,不仅仅在 Spring 框架中可以使用,我们在项目中有其他文件加载需求也可以使用。

首先,Spring 中使用 Resource 接口来封装底层资源,Resource 接口本身实现自 InputStreamSource 接口:

Spring 源码第一篇开整!配置文件是怎么加载的?

 

我们来看下这两个接口的定义:

public interface InputStreamSource {
 InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
 boolean exists();
 default boolean isReadable() {
  return exists();
 }
 default boolean isOpen() {
  return false;
 }
 default boolean isFile() {
  return false;
 }
 URL getURL() throws IOException;
 URI getURI() throws IOException;
 File getFile() throws IOException;
 default ReadableByteChannel readableChannel() throws IOException {
  return Channels.newChannel(getInputStream());
 }
 long contentLength() throws IOException;
 long lastModified() throws IOException;
 Resource createRelative(String relativePath) throws IOException;
 @Nullable
 String getFilename();
 String getDescription();

}

代码倒不难,我来稍微解释下:

  1. InputStreamSource 类只提供了一个 getInputStream 方法,该方法返回一个 InputStream,也就是说,InputStreamSource 会将传入的 File 等资源,封装成一个 InputStream 再重新返回。
  2. Resource 接口实现了 InputStreamSource 接口,并且封装了 Spring 内部可能会用到的底层资源,如 File、URL 以及 classpath 等。
  3. exists 方法用来判断资源是否存在。
  4. isReadable 方法用来判断资源是否可读。
  5. isOpen 方法用来判断资源是否打开。
  6. isFile 方法用来判断资源是否是一个文件。
  7. getURL/getURI/getFile/readableChannel 分别表示获取资源对应的 URL/URI/File 以及将资源转为 ReadableByteChannel 通道。
  8. contentLength 表示获取资源的大小。
  9. lastModified 表示获取资源的最后修改时间。
  10. createRelative 表示根据当前资源创建一个相对资源。
  11. getFilename 表示获取文件名。
  12. getDescription 表示在资源出错时,详细打印出出错的文件。

当我们加载不同资源时,对应了 Resource 的不同实现类,来看下 Resource 的继承关系:

Spring 源码第一篇开整!配置文件是怎么加载的?

 

可以看到,针对不同类型的数据源,都有各自的实现,我们这里来重点看下 ClassPathResource 的实现方式。

ClassPathResource 源码比较长,我这里挑一些关键部分来和大家分享:

public class ClassPathResource extends AbstractFileResolvingResource {

 private final String path;

 @Nullable
 private ClassLoader classLoader;

 @Nullable
 private Class<?> clazz;

 public ClassPathResource(String path) {
  this(path, (ClassLoader) null);
 }
 public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
  Assert.notNull(path, "Path must not be null");
  String pathToUse = StringUtils.cleanPath(path);
  if (pathToUse.startsWith("/")) {
   pathToUse = pathToUse.substring(1);
  }
  this.path = pathToUse;
  this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
 }
 public ClassPathResource(String path, @Nullable Class<?> clazz) {
  Assert.notNull(path, "Path must not be null");
  this.path = StringUtils.cleanPath(path);
  this.clazz = clazz;
 }
 public final String getPath() {
  return this.path;
 }
 @Nullable
 public final ClassLoader getClassLoader() {
  return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
 }
 @Override
 public boolean exists() {
  return (resolveURL() != null);
 }
 @Nullable
 protected URL resolveURL() {
  if (this.clazz != null) {
   return this.clazz.getResource(this.path);
  }
  else if (this.classLoader != null) {
   return this.classLoader.getResource(this.path);
  }
  else {
   return ClassLoader.getSystemResource(this.path);
  }
 }
 @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;
 }
 @Override
 public URL getURL() throws IOException {
  URL url = resolveURL();
  if (url == null) {
   throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
  }
  return url;
 }
 @Override
 public Resource createRelative(String relativePath) {
  String pathToUse = StringUtils.ApplyRelativePath(this.path, relativePath);
  return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
    new ClassPathResource(pathToUse, this.classLoader));
 }
 @Override
 @Nullable
 public String getFilename() {
  return StringUtils.getFilename(this.path);
 }
 @Override
 public String getDescription() {
  StringBuilder builder = new StringBuilder("class path resource [");
  String pathToUse = this.path;
  if (this.clazz != null && !pathToUse.startsWith("/")) {
   builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
   builder.append('/');
  }
  if (pathToUse.startsWith("/")) {
   pathToUse = pathToUse.substring(1);
  }
  builder.append(pathToUse);
  builder.append(']');
  return builder.toString();
 }
}
  1. 首先,ClassPathResource 的构造方法有四个,一个已经过期的方法我这里没有列出来。另外三个,我们一般调用一个参数的即可,也就是传入文件路径即可,它内部会调用另外一个重载的方法,给 classloader 赋上值(因为在后面要通过 classloader 去读取文件)。
  2. 在 ClassPathResource 初始化的过程中,会先调用 StringUtils.cleanPath 方法对传入的路径进行清理,所谓的路径清理,就是处理路径中的相对地址、windows 系统下的 \ 变为 / 等。
  3. getPath 方法用来返回文件路径,这是一个相对路径,不包含 classpath。
  4. resolveURL 方法表示返回资源的 URL,返回的时候优先用 Class.getResource 加载,然后才会用 ClassLoader.getResource 加载,关于 Class.getResource 和 ClassLoader.getResource 的区别,又能写一篇文章出来,我这里就大概说下,Class.getResource 最终还是会调用 ClassLoader.getResource,只不过 Class.getResource 会先对路径进行处理。
  5. getInputStream 读取资源,并返回 InputStream 对象。
  6. createRelative 方法是根据当前的资源,再创建一个相对资源。

这是 ClassPathResource,另外一个大家可能会接触到的 FileSystemResource ,小伙伴们可以自行查看其源码,比 ClassPathResource 简单。

如果不是使用 Spring,我们仅仅想自己加载 resources 目录下的资源,也可以采用这种方式:

ClassPathResource resource = new ClassPathResource("beans.xml");
InputStream inputStream = resource.getInputStream();

拿到 IO 流之后自行解析即可。

在 Spring 框架,构造出 Resource 对象之后,接下来还会把 Resource 对象转为 EncodedResource,这里会对资源进行编码处理,编码主要体现在 getReader 方法上,在获取 Reader 对象时,如果有编码,则给出编码格式:

public Reader getReader() throws IOException {
 if (this.charset != null) {
  return new InputStreamReader(this.resource.getInputStream(), this.charset);
 }
 else if (this.encoding != null) {
  return new InputStreamReader(this.resource.getInputStream(), this.encoding);
 }
 else {
  return new InputStreamReader(this.resource.getInputStream());
 }
}

所有这一切搞定之后,接下来就是通过 XmlBeanDefinitionReader 去加载 Resource 了。

4.小结

好啦,今天主要和小伙伴们分享一下 Spring 中的资源加载问题,这是容器启动的起点,下篇文章我们来看 XML 文件的解析。

如果小伙伴们觉得有收获,记得点个在看鼓励下松哥哦~



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
想要了解Spring MVC框架的原理,探究框架是如何设计的,不错的学习方式是阅读源码,然后自己手写一个框架。本文带领大家简化的手写一个Spring MVC框架。Spring框架对于Java后端程...【详细内容】
2021-09-22  Tags: Spring  点击:(53)  评论:(0)  加入收藏
前言一个基于spring boot的JAVA开源商城系统,是前后端分离、为生产环境多实例完全准备、数据库为b2b2c商城系统设计、拥有完整下单流程和精美设计的java开源商城系统https://...【详细内容】
2021-09-17  Tags: Spring  点击:(119)  评论:(0)  加入收藏
在 Java 和 Kotlin 中, 除了使用Spring Boot创建微服务外,还有很多其他的替代方案。 名称 版本 发布时间 开发商 GitHub ...【详细内容】
2021-08-06  Tags: Spring  点击:(174)  评论:(0)  加入收藏
大家都知道,MyBatis 框架是一个持久层框架,是 Apache 下的顶级项目。Mybatis 可以让开发者的主要精力放在 sql 上,通过 Mybatis 提供的映射方式,自由灵活的生成满足需要的 sql 语句。使用简单的 XML 或注解来配置和映射原...【详细内容】
2021-07-06  Tags: Spring  点击:(96)  评论:(0)  加入收藏
首先,先看 SpringBoot 的主配置类:@SpringBootApplicationpublicclassStartEurekaApplication{publicstaticvoidmain(String[] args){SpringApplication.run(StartEurekaAppli...【详细内容】
2021-06-11  Tags: Spring  点击:(144)  评论:(0)  加入收藏
环境:Spring5.3.3Spring容器启动时,创建 DefaultListableBeanFactory 工厂实例化 AnnotationConfigApplicationContext对象public AnnotationConfigApplicationContext(String...【详细内容】
2021-06-10  Tags: Spring  点击:(152)  评论:(0)  加入收藏
现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了...【详细内容】
2021-05-27  Tags: Spring  点击:(195)  评论:(0)  加入收藏
目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitHub上的星级而言)。它是作为Spring Cloud系列中Zuul代理的继任者而创建的。...【详细内容】
2021-04-21  Tags: Spring  点击:(426)  评论:(0)  加入收藏
在使用Spring MVC的时候,标准的配置是如下这样的: 注意注意:小编整理了一份Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC,有需要的朋友可以私信“spring”免费...【详细内容】
2021-04-13  Tags: Spring  点击:(239)  评论:(0)  加入收藏
今天又要给大家介绍一个 Spring Boot 中的组件 --HandlerMethodReturnValueHandler。在前面的文章中(如何优雅的实现 Spring Boot 接口参数加密解密?),松哥已经和大家介绍过如何...【详细内容】
2021-03-24  Tags: Spring  点击:(297)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条