想写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. 带权值的图 BFS

    用bfs遍历最图求最短路径时通常借用优先队列即优先考虑最大的或者最小的权值 方法1 优先队列:(内置函数,优先考虑较小的权值) #include<iostream> #include< ...

  2. 一口气带你踩完五个 List 的大坑,真的是处处坑啊!

    List 可谓是我们经常使用的集合类之一,几乎所有业务代码都离不开 List.既然天天在用,那就没准就会踩中这几个 List 常见坑. 今天我们就来总结这些常见的坑在哪里,捞自己一手,防止后续同学再继 ...

  3. 不是广告--如何学Java,我说点不太一样的学习方式

    首先声明,这篇文章不是卖课程.介绍培训班的广告. 最近有不少读者通过微信问我:小白应该怎么学好 Java? 提问的人里有在校大学生.有刚参加工作的.有想转行做程序员的,还有一部分是最近找工作不顺的. ...

  4. 面试官:你对Redis缓存了解吗?面对这11道面试题你是否有很多问号?

    前言 关于Redis的知识,总结了一个脑图分享给大家 1.在项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果? 面试官心理分析 这个问题,互联网公司必问,要是一个人连缓存都不太清楚, ...

  5. IntelliJ IDEA在mac中完全删除方法

    cd /Applications/ rm -r IntelliJ\ IDEA.app/ rm -r /Users/apple/Library/Logs/IntelliJIdea2019.3/ rm - ...

  6. CG-CTF(3)

    CG-CTF https://cgctf.nuptsast.com/challenges#Web 续上~ 第十四题:GBK Injection 是一道注入题: 分析:题目提示了GBK,GBK是一种多字 ...

  7. ThinkJS前端搭配vue时的Nginx配置

    Thinkjs 作为奇舞团开源的nodejs mvc框架之一,引起了很多NodeJS程序员的亲赖.但是其关于静态文件处理部分支持不够完善,主要是体现在SPA单页应用,之前在ThinkJS 2.*版本时 ...

  8. Crash日志解析

    当应用程序崩溃时,会创建一个崩溃报告,这对于了解导致崩溃的原因非常有用.本文档包含有关如何表示,理解和解释崩溃报告的基本信息. 1.介绍 2.获取崩溃和低内存报告 3.象征性的奔溃报告 1.位码(bi ...

  9. 推荐3个Python初学者学习Python案例

    回复资料,获取最新的Python的资料.想学习Python可以加微信回复报名. 希望今天的分享3个小案例,对你学习Python有帮助 Python 九九乘法表 以下实例演示了如何实现九九乘法表: 实例 ...

  10. Codeforce 140C (贪心+优先队列)补题

    C. New Year Snowmen time limit per test2 seconds memory limit per test256 megabytes inputstandard in ...