目录

1.引言

2.spring5架构

2.1    核心容器(Core Container)

2.2    AOP和设备支持

2.3    数据访问及集成

2.4    Web

2.5    Messaging

2.6    Test

3.分析过程

3.1    ServletContext

3.2 root WebApplicationContext(可称为spring容器或者IOC容器)

3.3    子上下文(可称为mvc容器)

4.总结


1.引言

只要做过java开发的IT人,基本都听过或者用过spring框架,为了实现快速开发,方法有很多,比如从网上下载一个ssm框架或者自己搭建一个ssm框架,再者使用当下非常流行的springboot框架。对于现在的java开发人员,spring对开发过程提供了很多方便。
        但是,你真的了解spring吗?在使用spring相关方法时,可能你会根据自己的需求,到搜索引擎直接搜索如何使用该方法,会出现很多你想要的答案,而你只需要选择最合适的,以此来完成功能的开发。但是,这些答案你有深入剖析其中的原理吗?
        Ok,为了解决这些问题,可能一些道听途说的“二手”资料可以解决一些疑惑,但是都不如分析源码得到的一手资料来的更为直接。那么,问题又来了,spring源码的模块分的比较细,模块较多,导致无从下手啊!所以,本次分享将从实际项目代码的角度,来引导大家分析spring源码,这样理解起来更为容易些。Spring5源码的目录结构如下图,

2.spring5架构

Spring5模块比较多,有大约20多个,都是相互独立的,在使用上,可以联合多个模块使用。Spring5的核心模块架构图如下,

可以划分为六大块,即核心容器(Core Container)、AOP(Aspect Oriented Programming)和设备支持(Instrmentation)、数据访问及集成(Data Access/Integeration)、Web、报文发送(Messaging)、Test。

2.1    核心容器(Core Container)

包括Beans、Core、Context 和 Expression模块,具体介绍如下。
Beans模块:提供了BeanFactory,springBeanFactory来生产和管理bean,是工厂模式的经典实现。
Core模块:提供了spring框架的基本组成部分,包括IOC和DI功能。
Context模块:建立在Beans和Code模块的基础上,它是访问定义和配置任何对象的媒介。ApplicationContext接口是上下文模块的焦点。
Expression模块:是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。

2.2    AOP和设备支持

包括AOP、Aspects和Instrumentation模块,具体介绍如下。
AOP模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
Aspects模块:模块集成自 AspectJ 框架,主要是为 Spring AOP 提供多种 AOP 实现方法。
Instrumentation模块:模块是基于 JAVA SE 中的”java.lang.instrument”进行设计的,应该算是
AOP 的一个支援模块,主要作用是在 JVM 启用时,生成一个代理类,程序员通过代理类在运行时修改类
的字节,从而改变一个类的功能,实现 AOP 的功能。在分类里,我把他分在了 AOP 模块下,在 Spring 官
方文档里对这个地方也有点含糊不清,这里是纯个人观点。

2.3    数据访问及集成

包括 JDBC、ORM、OXM、JMS 和 Transactions 模块,具体介绍如下。
JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
ORM 模块:对流行的对象关系映射 API,包括 JPA、JDO、Hibernate 和 iBatis 提供了的集成层。
OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。
JMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息,自 Spring Framework 4.1以后,他还提供了对 spring-messaging 模块的支撑。
Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO。

2.4    Web

包括 WebSocket、WebMVC、Web、WebFlux模块,具体介绍如下。
WebSocket模块:主要是与 Web 前端的全双工通讯的协议。
WebMVC模块:众所周知是一个的Web-Servlet模块 ,实现了Spring MVC(model-view-Controller)的Web应用。
Web模块:为 Spring 提供了最基础 Web 支持,主要建立于核心容器之上,通过 Servlet 或者 Listeners 来初始化 IOC 容器,也包含一些与 Web 相关的支持。
WebFlux模块:是一个新的非堵塞函数式 Reactive Web 框架,可以用来建立异步的,非阻塞,事件驱动的服务,并且扩展性非常好。

2.5    Messaging

Messaging模块:从 Spring4 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

2.6    Test

Test模块:主要为测试提供支持的。

3.分析过程

spring的三大核心思想之一就是控制反转(IOC),相比IOC,DI和AOP都依赖IOC,所以创建Spring IOC容器(可以理解为ApplicationContext接口)是核心,在web应用中,实现ApplicationContext的接口方法用的是WebApplicationContext,结构图如下。

我们通过web应用的三个上下文来看spring源码,web项目中用的最多的就是spring-mvc,    而spring-mvc项目中web.xml配置是必须的,配置比较多的如ContextLoaderListener,contextConfigLocation,filter,DispatcherServlet等。

3.1    ServletContext

在servlet的规范当中,servlet容器或者叫web容器,如tomcat、jboss等中运行的每个应用都由一个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处理。

3.2 root WebApplicationContext(可称为spring容器或者IOC容器)

上面提到的ContextLoaderListener,这个方法的作用就是创建root WebApplicationContext,这个root是相对的,下面会提到的。
        servlet规范当中,使用了Listener监听器机制来进行web容器相关组件的生命周期管理以及Event事件监听器来实现组件之间的交互。
        其中一个重要的生命周期监听器是ServletContextListener。web容器在创建和初始化ServletContext的时候,会产生一个ServletContextEvent事件,其中ServletContextEvent包含该ServletContext的引用。然后交给在web.xml中配置的,注册到这个ServletContext的监听器ServletContextListener。ServletContextListener在其contextInitialized方法中定义处理逻辑,相关方法如下,篇幅有限,省略了部分代码:

public interface ServletContextListener extends EventListener {void contextInitialized(ServletContextEvent var1);void contextDestroyed(ServletContextEvent var1);
}public class ContextLoaderListener extends ContextLoader implements ServletContextListener {.../*** Initialize the root web application context.*/@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}...
}/*** Initialize Spring's web application context for the given servlet context,* using the application context provided at construction time, or creating a new one* according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and* "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.* @param servletContext current servlet context* @return the new WebApplicationContext* @see #ContextLoader(WebApplicationContext)* @see #CONTEXT_CLASS_PARAM* @see #CONFIG_LOCATION_PARAM*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " +"check whether you have multiple ContextLoader* definitions in your web.xml!");}servletContext.log("Initializing Spring root WebApplicationContext");Log logger = LogFactory.getLog(ContextLoader.class);if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}configureAndRefreshWebApplicationContext(cwac, servletContext);}}servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException | Error ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}
}

从contextInitialized代码可知,在创建root WebApplicationContext前会先检查是否已存在,对于不存在的情况才会创建。
        需注意,ServletContextListeners是在Filters和Servlets创建之前接收到通知的。所以在这个时候,web应用还不能接收请求,故可以在这里完成底层处理请求的组件的加载,这样等之后接收请求的Filters和Servlets创建时,则可以使用这些创建好的组件了。spring相关的bean就是这里所说的底层处理请求的组件,如数据库连接池,数据库事务管理器等,所以root WebApplicationContext通常都用来加载service层、dao层等业务核心代码。
Web.xml中配置如下。

<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>

3.3    子上下文(可称为mvc容器)

第三个上下文是由DispatcherServlet创建的,是root WebApplicationContext的子上下文,DispatcherServlet也叫前端控制器,通常我们说到的spring里的父子容器,指的就是这两个上下文,对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext中的内容,而反过来不行。
       在web容器中,web.xml中的加载顺序:context-param -> listener -> filter -> servlet。其中ContextLoaderListener是属于listener阶段。我们通常需要在项目的web.xml中配置一个DispatcherServlet,并配置拦截包含“/”路径的请求,即拦截所有请求。配置如下。

<servlet><servlet-name>test-web</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:conf/spring/spring-servlet.xml</param-value></init-param><load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping><servlet-name>test-web</servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>

DispatcherServlet的结构图如下,顶层是Servlet类。

这样在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();
}
public final void init() throws ServletException {...// Let subclasses do whatever initialization they like.initServletBean();
}
protected final void initServletBean() throws ServletException {...try {this.webApplicationContext = initWebApplicationContext();initFrameworkServlet();}...
}
protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac = null;if (this.webApplicationContext != null) {// A context instance was injected at construction time -> use itwac = this.webApplicationContext;if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// The context instance was injected without an explicit parent -> set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac == null) {// No context instance was injected at construction time -> see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac = findWebApplicationContext();}if (wac == null) {// No context instance is defined for this servlet -> create a local onewac = createWebApplicationContext(rootContext);}if (!this.refreshEventReceived) {// Either the context is not a ConfigurableApplicationContext with refresh// support or the context injected at construction time had already been// refreshed -> trigger initial onRefresh manually here.synchronized (this.onRefreshMonitor) {onRefresh(wac);}}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName = getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac;
}

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来指定位置。
        注意上面的onRefresh方法里执行的是mvc初始化,做些HandlerMapping检查、HandlerAdapter检查、支持文件上传等功能,所以子上下文通常用来加载controller,filter等方法。

4.总结

从上面的分析,可知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的其他模块,基本都是通过配置bean的方式加载到IOC容器里,供项目使用,各个模块是故可以从这里作为一个入口,一层一层地剥spring的源码了。

主要参考:
1)https://blog.csdn.net/lj1314ailj/article/details/80118372
2)http://blog.ibyte.vip/2019/12/12/Spring5%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E4%B8%80-%E4%BB%8E%E5%93%AA%E9%87%8C%E5%BC%80%E5%A7%8B%E7%9C%8BSpring%E6%BA%90%E7%A0%81/
3)http://blog.ibyte.vip/2019/12/20/Spring5%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90[%E4%BA%8C]-Spring-webmvc%20%E5%AE%B9%E5%99%A8%E5%88%9D%E5%A7%8B%E5%8C%96/
4)http://blog.ibyte.vip/2019/12/24/Spring5%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90[%E4%BA%8C]-Spring-webmvc%20MVC%E5%88%9D%E5%A7%8B%E5%8C%96/
5)https://www.jianshu.com/p/2854d8984dfc

spring源码分析之分析入口相关推荐

  1. Spring源码总结与分析

    前言 Spring是什么?它是一个应用程序框架,为应用程序的开发提供强大的支持,例如对事务处理和持久化的支持等:它也是一个bean容器,管理bean对象的整个生命周期,维护bean的各种存在状态,例如 ...

  2. Spring源码分析-如何获取Bean对象

    导语   在上篇博客中 介绍了关于BeanFactory和FactoryBean相关的操作,并且查看了在两个操作中他们具体的代码有那些,这篇博客主要就是顺着上篇博客思路继续来分析Bean对象的获取.下 ...

  3. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  4. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  5. spring源码分析第一天------源码分析知识储备

    spring源码分析第一天------源码分析知识储备 Spring源码分析怎么学? 1.环境准备: 2.思路    看:是什么? 能干啥    想:为什么?     实践:怎么做?         ...

  6. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  7. Spring源码分析:Bean加载流程概览及配置文件读取

    很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...

  8. Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构

    2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 架构         先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现.这是AOP实现的三个步 ...

  9. spring源码阅读--aop实现原理分析

    aop实现原理简介 首先我们都知道aop的基本原理就是动态代理思想,在设计模式之代理模式中有介绍过这两种动态代理的使用与基本原理,再次不再叙述. 这里分析的是,在spring中是如何基于动态代理的思想 ...

最新文章

  1. 有哪些好用的远程办公软件推荐?
  2. 世界首富贝佐斯将“退休”
  3. 面试:说说 HTTPS 的工作原理?
  4. Jquery背景图片的预加载
  5. 现代密码学1.2--Kerckhoffs原则
  6. 十年编程经验输给新晋AI工程师,6个月我们带你绝地反击
  7. 探索 .NET Core 依赖注入的 IServiceProvider
  8. VS2012 发布网站步骤
  9. python 桌面应用 启动缓慢_如何加快Python 应用的启动时间
  10. python类的定义和创建_Python类对象的创建和使用
  11. SSIS [大容量插入任务] 找不到文件错误
  12. Amadeus Pro for Mac(多轨音频编辑器)
  13. 什么年代了,买硬盘不论T?
  14. 基于jpress二次开发的H5商城(已开源)
  15. 给定一个邻接矩阵,求可达矩阵及强连通、单向连通、弱连通、不连通的判断
  16. android H5页面跳转APP,H5唤醒app并跳转到指定页面
  17. SiT3808:1 -80MHz 单端压控振荡器VCXO
  18. 云出阿里见月明(上)
  19. PHP的OpenSSL加密扩展学习(三):证书操作
  20. 2023在家赚钱怎么做,有什么适合在家做的副业项目

热门文章

  1. js跨域解决方案php,详解js跨域原理以及2种解决方案_javascript技巧
  2. 变频器的工作原理及其电路分析
  3. OA系统授权、删除权限
  4. 可信区块链推进计划互操作项目组成立,微众银行担任副组长单位
  5. 使用FFmpeg添加、删除、替换和提取视频中的音频
  6. 我什么要坚持学技术?
  7. 论文速递:一种用于视觉定位的基于NLP思路的直线特征匹配算法
  8. 什么是VMOS功率场效应管,工作原理是什么
  9. java 美发管理系统_基于安卓Android潮流美发系统APP设计(MySQL)
  10. 1-Wire总线上挂载多个DS18B20温度传感器驱动程序