概述
对于大多数第一次看spring源码的人来说,都会感觉不知从哪开始看起,因为spring项目源码由多个子项目组成,如spring-beans,spring-context,spring-core,spring-aop,spring-web,spring-webmvc等,整个项目结构如图:

可能有人会觉得,既然spring是一个IOC容器或者说是一个bean的容器,那么应该从spring-beans看起,先了解spring是如何从xml文件配置获取需要创建的bean的信息,但是这里有个问题就是虽然知道怎么遍历初始化,但是不知道哪里用到或者说哪里让这些初始化开始,而且像BeanFactory,FactoryBean,Environment,PropertySource等接口还是比较抽象的,比较难看懂,所以很容易让人感觉枯燥,然后就放弃了。
我们可以换个思路,从能接触到的角度开始,即我们通常会使用spring-mvc来进行web开发,如@Controller,@RequestMapping都是再熟悉不过的了。如果搭过spring-mvc项目都知道,通常需要在web.xml文件中,配置一个ContextLoaderListener,contextConfigLocation,DispatcherServlet,可能很多人都是从网上copy了一份配置过来或者知道contextConfigLocation是指定spring配置文件的位置,DispatcherServlet是接收所有请求的前端控制器,需要指定拦截路由:“/”,从而拦截所有URL中带“/”的请求,但是在spring源码中是怎么使用这些组件的呢?以及怎么配置了一个@Controller,@RequestMapping中指定了一个url,就可以访问了呢?还有就是通常我们的web项目都会部署在web容器,如tomcat当中,那么tomcat和spring有啥关系呢?所以我们可以带着这些问题去查看spring源码找到答案。
所以我推荐是从spring-mvc开始看spring源码,因为这个是我们使用得比较多,比较容易理解的一个模块,然后一层一层往上剥,找到与spring-context,spring-beans,spring-aop等的关系。如果真的对JavaWeb开发,Java EE很感兴趣,或者更容易读懂spring的源码,可以先看servlet规范和Tomcat的设计与Tomcat的请求处理工作流。我目前也在结合这两个方面看,也可以看下我的Tomcat源码分析系列。

Servlet规范
在servlet的规范当中,servlet容器或者叫web容器,如tomcat,中运行的每个应用都由一个ServletContext表示,在web容器中可以包含多个ServletContext,即可以有多个web应用在web容器中运行。如在tomcat的webapp目录下,每个war包都对应一个web应用,tomcat启动时会解压war包,并启动相关的应用。
在web容器启动的时候,会初始化web应用,即创建ServletContext对象,加载解析web.xml文件,获取该应用的Filters,Listener,Servlet等组件的配置并创建对象实例,作为ServletContext的属性,保存在ServletContext当中。之后web容器接收到客户端请求时,则会根据请求信息,匹配到处理这个请求的Servlet,同时在交给servlet处理之前,会先使用应用配置的Filters对这个请求先进行过滤,最后才交给servlet处理。
了解web容器启动,之后接受客户端请求这些知识有啥用处呢?这里我们需要回过头来看我们的spring项目。我们在日常开发中,直接接触的是spring相关的组件,然后打成war包,放到web容器中,如拷贝到tomcat的webapp目录,并不会直接和web容器打交道。经过以上的分析,其实一个spring项目就是对应web容器里的一个ServletContext,所以在ServletContext对象的创建和初始化的时候,就需要一种机制来触发spring相关组件的创建和初始化,如包含@Controller和@RequestMapping注解的类和方法,这样才能处理请求。
Listener监听器机制:ContextLoaderListener
servlet规范当中,使用了Listener监听器机制来进行web容器相关组件的生命周期管理以及Event事件监听器来实现组件之间的交互。
其中一个重要的生命周期监听器是ServletContextListener。web容器在创建和初始化ServletContext的时候,会产生一个ServletContextEvent事件,其中ServletContextEvent包含该ServletContext的引用。然后交给在web.xml中配置的,注册到这个ServletContext的监听器ServletContextListener。ServletContextListener在其contextInitialized方法中定义处理逻辑,接口定义如下:
/**
* Implementations of this interface receive notifications about changes to the
* servlet context of the web application they are part of. To receive
* notification events, the implementation class must be configured in the
* deployment descriptor for the web application.
*
* @see ServletContextEvent
* @since v 2.3
*/
public interface ServletContextListener extends EventListener { /**
** Notification that the web application initialization process is starting.
* All ServletContextListeners are notified of context initialization before
* any filter or servlet in the web application is initialized.
* @param sce Information about the ServletContext that was initialized
*/
public void contextInitialized(ServletContextEvent sce); /**
** Notification that the servlet context is about to be shut down. All
* servlets and filters have been destroy()ed before any
* ServletContextListeners are notified of context destruction.
* @param sce Information about the ServletContext that was destroyed
*/
public void contextDestroyed(ServletContextEvent sce);
}

从contextInitialized的注释可知:通知所有的ServletContextListeners,当前的web应用正在启动,而且这些ServletContextListeners是在Filters和Servlets创建之前接收到通知的。所以在这个时候,web应用还不能接收请求,故可以在这里完成底层处理请求的组件的加载,这样等之后接收请求的Filters和Servlets创建时,则可以使用这些创建好的组件了。spring相关的bean就是这里所说的底层处理请求的组件,如数据库连接池,数据库事务管理器等。
ContextLoaderListener:spring-web包的ContextLoaderListener就是一个ServletContextListener的实现类。ContextLoaderListener主要用来获取spring项目的整体配置信息,并创建对应的WebApplicationContext来保存bean的信息,以及创建这些bean的对象实例。默认去WEB-INF下加载applicationContext.xml配置,如果applicationContext.xml放在其他位置,或者使用其他不同的名称,或者使用多个xml文件,则与指定contextConfigLocation。具体spring源码的实现过程后续文章详细分析。

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 修改配置文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
DispatcherServlet:前端控制器
在web容器中,web.xml中的加载顺序:context-param -> listener -> filter -> servlet。其中ContextLoaderListener是属于listener阶段。我们通常需要在项目的web.xml中配置一个DispatcherServlet,并配置拦截包含“/”路径的请求,即拦截所有请求。这样在web容器启动应用时,在servlet阶段会创建这个servlet,由Servlet规范中servlet的生命周期方法可知:
public interface Servlet {
void init(ServletConfig var1) throws ServletException; ServletConfig getServletConfig(); void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; String getServletInfo(); void destroy();
}
web容器在创建这个servlet的时候,会调用其init方法,故可以在DispatcherServlet的init方法中定义初始化逻辑,核心实现了创建DispatcherServlet自身的一个WebApplicationContext,注意在spring中每个servlet可以包含一个独立的WebApplicationContext来维护自身的组件,而上面通过ContextLoaderListener创建的WebApplicationContext为共有的,通常也是最顶层,即root WebApplicationContext,servlet的WebApplicationContext可以通过setParent方法设值到自身的一个属性。DispatcherServlet默认是加载WEB-INF下面的“servletName”-servlet.xml,来获取配置信息的,也可以与ContextLoaderListener一样通过contextLoaderConfig来指定位置。DispatcherServlet具体的源码设计在之后文章详细分析。
总结
从上面的分析,可知spring相关配置解析和组件创建其实是在web容器中,启动一个web应用的时候,即在其ServletContext组件创建的时候,首先解析web.xml获取该应用配置的listeners列表和servlet列表,然后保存在自身的一个属性中,然后通过分发生命周期事件ServletContextEvent给这些listeners,从而在listeners感知到应用在启动了,然后自定义自身的处理逻辑,如spring的ContextLoaderListener就是解析spring的配置文件并创建相关的bean,这样其实也是实现了一种代码的解耦;其次是创建配置的servlet列表,调用servlet的init方法,这样servlet可以自定义初始化逻辑,DispatcherServlet就是其中一个servlet。
所以在ContextLoaderListener和DispatcherServlet的创建时,都会进行WebApplicationContext的创建,这里其实就是IOC容器的创建了,即会交给spring-context,spring-beans包相关的类进行处理了,故可以从这里作为一个入口,一层一层地剥spring的源码了。
 
获取更多学习资料,可以加群:473984645或者扫描下方二维码

Spring源码分析(一):从哪里开始看spring源码(系列文章基于Spring5.0)的更多相关文章

  1. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  2. STL 源码分析《2》----nth_element() 使用与源码分析

    Select 问题: 在一个无序的数组中 找到第 n 大的元素. 思路 1: 排序,O(NlgN) 思路 2: 利用快排的 RandomizedPartition(), 平均复杂度是 O(N) 思路 ...

  3. Nginx源码分析:3张图看懂启动及进程工作原理

    编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享.转载请注明来自高可用架构公众号「ArchNotes」.   导读:很多工程师及架构师都希望了解及掌握高性能服务器 ...

  4. Netty源码分析 (三)----- 服务端启动源码分析

    本文接着前两篇文章来讲,主要讲服务端类剩下的部分,我们还是来先看看服务端的代码 /** * Created by chenhao on 2019/9/4. */ public final class ...

  5. HashMap源码分析(史上最详细的源码分析)

    HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...

  6. HashMap的源码分析与实现 伸缩性角度看hashmap的不足

    本文介绍 1.hashmap的概念 2.hashmap的源码分析 3.hashmap的手写实现 4.伸缩性角度看hashmap的不足 一.HashMap的概念 HashMap可以将其拆分为Hash散列 ...

  7. 4 weekend110的hdfs下载数据源码跟踪铺垫 + hdfs下载数据源码分析-getFileSystem(值得反复推敲和打断点源码)

    Hdfs下载数据源码分析 在这里,我是接着之前的,贴下代码 package cn.itcast.hadoop.hdfs; import java.io.FileInputStream; import ...

  8. MySQL MGR源码分析2 - 从start group_replication看MGR代码框架

    此文已由作者温正湖授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 上一篇我们从方案层面讲解了MGR的成员管理和故障恢复.本篇从源码层面捋一捋,通过本篇介绍,除了能够了解如何将 ...

  9. Thinkphp6源码分析之解析,Thinkphp6路由,Thinkphp6路由源码解析,Thinkphp6请求流程解析,Thinkphp6源码

    Thinkphp6源码解析之分析 路由篇-请求流程 0x00 前言: 第一次写这么长的博客,所以可能排版啊,分析啊,什么的可能会比较乱.但是我大致的流程已经觉得是说的够清楚了.几乎是每行源码上都有注释 ...

随机推荐

  1. Linux网卡驱动(4)—DM9000网卡驱动程序完全分析

    1.硬件连接 mini2440开发板上DM9000的电气连接和mach-mini2440.c文件的关系 其中片选信号AEN使用了nGCS4,所以网卡的内存区域在BANK4,也就是从地址0x200000 ...

  2. 静态栈-------C语言

    /***************************************************** Author:Simon_Kly Version:0.1 Date: 20170520 D ...

  3. 23. Jmeter使用ServerAgent对服务器进行性能监控

    我们在做服务器性能测试的时候,往往会考虑四个点:CPU.网络.磁盘.内存.一般情况下是使用Linux命令进行监控,那么jmeter可否做到呢?答案是可以的,闲话不多说,进入正题. 环境准备 jmete ...

  4. 17、通过maven生成测试报告

    目录如下: 通过Maven 生成报告 进入testngTest根目录,运行mvn test 命令 进入 testngTest\target\surefire-reports 路径查看测试报告

  5. Django框架(九)—— 单表增删改查,在Python脚本中调用Django环境

    目录 单表增删改查,在Python脚本中调用Django环境 一.数据库连接配置 二.orm创建表和字段 三.单表增删改查 1.增加数据 2.删除数据 3.修改数据 4.查询数据 四.在Python脚 ...

  6. 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue

    GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...

  7. js小项目:显示与输入的内容相关的

    1,添加键盘抬起事件 2,获取文本框的内容,是否与数组中的内容匹配 3,创建元素 <!DOCTYPE html> <html lang="en"> < ...

  8. git stash封存分支 以及关于开发新功能的处理

    有种情况,我们要修复项目的bug时,但别的分支有修改的代码,要修复的bug可能会影响(所有分支共用一个暂存区).可以单独创建一个bug分支,用于修复和提交bug,在修改前可以先stash封存分支修改的 ...

  9. 【转】elasticsearch中字段类型默认显示{ "foo": { "type": "text", "fields": { "keyword": {"type": "keyword", "ignore_above": 256} }

    官方原文链接:https://www.elastic.co/cn/blog/strings-are-dead-long-live-strings 转载原文连接:https://segmentfault ...

  10. 矢量切片应用中geoserver与geowebcache分布式部署方案

    在进行GIS项目开发中,常使用Geoserver作为开源的地图服务器,Geoserver是一个JavaEE项目,常通过Tomcat进行部署.而GeoWebCache是一个采用Java实现用于缓存WMS ...