想写Spring的源码方面的东西想了好久了,之前花了一段时间学习了SpringCloud,现在总算对SpringCloud有了一个大概的了解,从今天开始好好读一篇Spring的源码,结合书本跟网上的一些资料,希望能坚持下去,完成这一系列的文章,给自己加油!!!!

版本是基于5.1.X的,大家在阅读的时候请注意,不过因为是Spring的一些基础流程,估计版本间差异也不会太大

关于配置的加载主要可以分为两种:

  1. 注解式配置的加载
  2. XML配置加载

注解配置相对于XML更加简单,其过程主要在于注解的解析,而XML配置是用xml文件格式保存配置,其中就涉及到了文件的读取,读取后要进行标签的解析,稍显复杂。我们这篇文章主要就是来讲清楚Spring读取配置文件的过程,对于后面的XML标签的解析或者说是注解的解析其实都是差不多的,在后面的文章中,我会慢慢介绍

我们先看一段代码:

/**
* @author dmz
* @date Create in 20:49 2019/7/20
*/
public class Spring {
public static void main(String[] args) { // 构建一个基于XML的Spring应用的上下文
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); }
}

很简单,目的就是跟踪一个基于XML的Spring应用是如何被加载的,我们一步步点进去看源码可以发现:

	public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 这个构造函数暂时不说
super(parent);
// 这个方法主要是为了设置这个应用需要使用的Spring的配置文件
// 主要是对配置文件的路径进行一些处理
setConfigLocations(configLocations);
if (refresh) {
// 这是Spring的核心方法
refresh();
}
}

Spring的源码很大,我们需要一点点啃,这篇文章目的只是分析配置的读取,所以只分析跟配置相关的代码,在这里也提醒大家,阅读源码时不要被细枝末节牵绊的太深,抓住自己需要分析的主线,然后不断探究,能做到这样就是极好的!!!

我们接着说代码:

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 为容器的刷新做准备
prepareRefresh();
// 主要分析这个代码
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 省略后面的代码
.......
} protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 刷新BeanFactory
refreshBeanFactory();
return getBeanFactory();
} protected final void refreshBeanFactory() throws BeansException {
// 如果当前容器的BeanFactory已经经过初始化,进行清空
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 我们主要需要关注的方法
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
} protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 我们可以看到,在这一步,创建了一个XmlBeanDefinitionReader,构造参数是一个DefaultListableBeanFactory
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 对这个reader的成员变量进行赋值
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 提供给子类使用,让子类能自定义BeanDefinitionReader
initBeanDefinitionReader(beanDefinitionReader);
// 最后调用loadBeanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}

我们将注意力放到loadBeanDefinitions这个方法上,从方法名称上我们可以知道,这个方法一定读取了配置文件,跟踪其代码最后会到org.springframework.context.support.AbstractXmlApplicationContextloadBeanDefinitions方法,我们看下这个方法的具体实现:

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 我们之前给定的是一个application.xml,所以会进这个方法
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

继续跟踪,进入org.springframework.beans.factory.support.AbstractBeanDefinitionReaderloadBeanDefinitions方法

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// 获取一个资源加载器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 这里获取到的资源加载器是一个org.springframework.context.support.ClassPathXmlApplicationContext,而所有的applicationContext都实现了ResourcePatternResolver接口
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 呼~,终于到这个加载资源的方法了,这个方法会根据不同location,选择对应的resourceLoader加载配置文件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
......省略部分代码......

在我们的示例中,最终会调用org.springframework.core.io.DefaultResourceLoadergetResource方法

public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null"); for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
} if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}

我们可以看到,这个方法会根据一些匹配规则,比如说localtion是否以“/”开头,是否是一个FileURL等等分别创建

ClassPathResource,FileUrlResource,UrlResource等,我们可以看一下它的类图:

​ 在java中,将不同来源的资源抽象成URL,通过注册不同的handler(URLStramHandler)来处理不同来源的资源的读取逻辑,一般hanlder使用不同前缀(协议,Protocol)来识别,如“file”,"http:"等,然而URL没有默认定义相对Classpath或者ServletContext等资源的hanlder,虽然可以注册自己的URLStream来解析特定的URL前缀,比如“classpath:”,然后这需要了解URL的实现机制,而且URL也没有提供基本的方法,如检查当前资源是否存在,检查当前资源是否可读等,因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口封装底层资源

public interface InputStreamSource {

    /**
* 顶层接口,返回资源对应的输入流
*/
InputStream getInputStream() throws IOException; }
public interface Resource extends InputStreamSource {

	/**
* 是否存在
*/
boolean exists(); /**
* 是否可读
*/
default boolean isReadable() {
return exists();
} /**
* 是否打开
*/
default boolean isOpen() {
return false;
} /**
* 是否是一个file,如果是的话,getFile()一定能正常返回
*/
default boolean isFile() {
return false;
} /**
* 返回资源对应的URL
*/
URL getURL() throws IOException; /**
* 返回资源对应的URI
*/
URI getURI() throws IOException; /**
* 返回资源对应的文件
*/
File getFile() throws IOException; /**
* 返回一个根据当前资源转变的可读的nio中的ReadableByteChannel
*/
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
} /**
* 返回资源的字节长度
*/
long contentLength() throws IOException; /**
* 返回上一次修改的时间戳
*/
long lastModified() throws IOException; /**
* 基于当前资源,创建一个相对资源
*/
Resource createRelative(String relativePath) throws IOException; /**
* 返回资源的文件名(不带路径),没有文件名则返回null
*/
@Nullable
String getFilename(); /**
* 返回资源描述
*/
String getDescription(); }

绝大部分情况下,我们都是直接在resoures直接创建配置文件,所以大部分情况,我们使用的都是ClassPathResource,我们不妨看一下它对这些方法的实现,加深我们对这些接口的理解

@Override
// 可以发现,就是通过类加载器获取对应资源,最终的实现还是会依赖于我们jdk中的URL类
// 最后通过URL返回一个流
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;
}
// 判断这个ClassPathResource对应的路径能否被解析成一个java中的URL,如果能则存在
public boolean exists() {
return (resolveURL() != null);
}
.......

经过上面的分析我们已经知道了Spring怎么加载一个配置文件,我们可以总结如下:

  1. 创建一个XmlBeanDefinitionReader
  2. 获取对应的ResourceLoader资源加载器
  3. 根据location的不同匹配模式,采用不同的ResourceLoader进行资源的加载
  4. Spring封装了java中的URL类,自定义了一套资源加载策略
  5. 最后返回一个Spring中的Resource对象

码字不易,希望能找几个同学一起学习源码,后续会一直更新这个系列,有喜欢的朋友加个收藏,点个赞吧!!!谢啦~

Spring源码阅读 之 配置的加载(希望有喜欢源码的朋友一起交流)的更多相关文章

  1. Spring源码阅读 之 配置的读取,解析

    在上文中我们已经知道了Spring如何从我们给定的位置加载到配置文件,并将文件包装成一个Resource对象.这篇文章我们将要探讨的就是,如何从这个Resouce对象中加载到我们的容器?加载到容器后又 ...

  2. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

  3. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

  4. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

  5. Spring源码分析:非懒加载的单例Bean初始化过程(下)

    上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...

  6. spring源码学习之bean的加载(一)

    对XML文件的解析基本上已经大致的走了一遍,虽然没有能吸收多少,但是脑子中总是有些印象的,接下来看下spring中的bean的加载,这个比xml解析复杂的多.这个加载,在我们使用的时候基本上是:Bea ...

  7. wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...

  8. Android 图片加载框架Glide4.0源码完全解析(二)

    写在之前 上一篇博文写的是Android 图片加载框架Glide4.0源码完全解析(一),主要分析了Glide4.0源码中的with方法和load方法,原本打算是一起发布的,但是由于into方法复杂性 ...

  9. 自己动手实现springboot运行时执行java源码(运行时编译、加载、注册bean、调用)

    看来断点.单步调试还不够硬核,根本没多少人看,这次再来个硬核的.依然是由于apaas平台越来越流行了,如果apaas平台选择了java语言作为平台内的业务代码,那么不仅仅面临着IDE外的断点.单步调试 ...

随机推荐

  1. For循环详解

    for语句 学过c语言都对循环结构不陌生,尤其是for循环,他是C语言中最有特色的循环语句,使用最为灵活. 形式 结构:for(表达式1:表达式2:表达式3){循环体结构} 每部分的作用 表达式1:一 ...

  2. Python—一个简单搜索引擎索引库

    因为课业要求,搭建一个简单的搜索引擎,找了一些相关资料并进行了部分优化(坑有点多) 一.数据 数据是网络上爬取的旅游相关的攻略页面 这个是travels表,在索引中主要用到id和url两个字段. 页面 ...

  3. B - Raising Modulo Numbers

    People are different. Some secretly read magazines full of interesting girls' pictures, others creat ...

  4. 2.react-插件

    PC: antd(蚂蚁金服)https://ant.design/index-cn 移动: mobile-antd(蚂蚁金服)https://mobile.ant.design =========== ...

  5. 对JavaScript中的this的理解

    什么是this: 解析器(就是浏览器)在调用函数时,每次都会向函数内部传递两个隐含的参数: 这两个隐含参数其中一个就是this(还有一个是arguments,用来接收函数的实参),this指向的是一个 ...

  6. php的echo 和 return的区别

    来源:https://blog.csdn.net/ljfphp/article/details/76718635 项目中碰到的问题,本来是想在控制器直接return $xml的($xml是一段xml格 ...

  7. thinkphp 5 一些常见问题

    ##  请求缓存 request_cache        

  8. 2019-2020-1 20199308《Linux内核原理与分析》第三周作业

    <Linux内核分析> 第二章 操作系统是如何工作的 2.1 函数调用堆栈 3个关键性的方法机制(3个法宝) 存储程序计算机 函数调用堆栈机制 中断 堆栈相关的寄存器 ESP:堆栈指针(s ...

  9. CG-CTF(4)

    CG-CTF https://cgctf.nuptsast.com/challenges#Web 续上~ 第十六题:bypass again 代码分析: 当a不等于b,且a和b的md5值相同时,才会返 ...

  10. zabbix 微信告警机制

    微信告警首先得注册一个企业微信,然后才能实现微信告警.自行百度 微信: 添加一个用户到上面创建的部门里面 创建完成记住 AgentID  和 Secret 下一步:记住企业 ID 1)编辑zabbix ...