spring源码

基本运行图

1656321165962-bf69130a-65f4-4a05-9baa-f5d9b661e980

web容器中初始化spring

在web容器初始化过程中,便会在WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml的配置文件作为SpringMVC的配置文件,如下springMVC的配置文件就是放在WEB-INF下名为dispatcherServlet-servlet.xml的配置文件

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

或者直接在类路径下配置SpringMVC配置文件

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

在web.xml下可以看到DispatcherServlet是SpringMVC的核心,下面将重点讲解DispatcherServlet的原理以及作用

bean的实例化过程

img

IOC容器概述

ApplicationContext接口相当于负责bean的初始化、配置和组装的IoC容器. Spring为ApplicationContext提供了一些开箱即用的实现, 独立的应用可以使用 ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext,web应用在web.xml配置监听,提供xml位置和org.springframework.web.context.ContextLoaderListener即可初始化 WebApplicationContextIoC容器.

配置元数据

配置元数据配置了应用中实例的实例化、配置以及组装的规则,SpringIoC容器通过此配置进行管理 Bean. 配置元数据有以下几种方式:

1、基于XML配置: 清晰明了,简单易用 2、基于Java代码配置:无xml,通过 @Configuration 来声明配置、对象实例化与依赖关系 3、基于Java注解配置:少量的XML( context:annotation-config/ ),通过注解声明实例化类与依赖关系

后续的分析基于XML配置, 与Java代码和注解大体上的机制是一样

实例化容器

实例化容器非常简单,只需要提供本地配置路径或者根据 ApplicationContext 的构造器提供相应的资源(Spring的另一个重要抽象)即可.

ApplicationContext context = new
ClassPathXmlApplicationContext("application.xml"); 

bean创建流程

img

img

img

1、三个map结构分别存放什么对象? 一级缓存:成品 二级缓存:半成品 三级缓存:lambda

2、三个map结构的查找顺序是什么样的? 先查找一级缓存,找不到再找二级缓存,再找不到的话找三级缓存

3、如果只有一个map缓存,能解决循环依赖问题吗? 理论上是可行的,但是代码不优雅,如果一级缓存中同时放成品和半成品对象,就意味着要通过一个状态标志来区分,可以在value中设置不同的标志位,但是每次在获取对象的时候都要把value取出来,然后判断这个标志位,太麻烦了,直接用两个map就可以很轻松的解决这个问题,如果不做区分,就会存在半成品对象直接对外暴露使用,那么一定会报空指针异常

4、如果只有两个map缓存,能解决循环依赖问题吗? 可以,但是有前提条件:循环依赖的对象中没有代理对象 2(放:doCreateBean,取:getSingleton)

5、为什么必须要使用三级缓存来解决循环依赖问题?三级缓存是如何解决循环依赖问题的? 《1》同一个容器中能存在同名的不同的两个对象吗? 不能 《2》创建代理对象的时候是否需要原始对象? 需要 《3》当创建完原始对象之后,紧跟着创建了代理对象,那么对象在引用的时候用哪个? 理论上来说应该是用代理对象的,但是程序是死的,他怎么去判断哪个是代理对象哪个是原始对象呢?不能判断,所以当创建代理对象之后要将代理对象去覆盖原始对象(getEarlyBeanReference) 《4》为什么三级缓存需要使用ObjectFactory类型的三级缓存呢? **getEarlyBeanReference是在lambda表达式里进行调用的,为什么要使用lambda表达式呢? ** 因为对象的属性赋值和调用是由容器来控制的,我们没有办法判断出什么时候会进行属性的赋值操作,因为在需要对属性赋值的时候必须要唯一的确定出注入的对象是代理对象还是原始对象,正常情况下,代理对象的创建是要在populateBean方法之后完成的(BeanPostProcessor的后置处理方法里面),而属性赋值是在之前执行的,所以要将代理对象的创建工作提前,提前到对象需要被注入的时候,必须要确定好是原始对象还是代理对象,使用lambda表示式类似于一种回调机制,在需要的时候直接定性为代理对象还是原始对象

img

循环依赖的详细流程(带个人理接):

img

关键源码方法(强烈建议自己去撸一遍)

• org.springframework .context.support.AbstractApplicationContext#refresh (入口) • org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryIniti alization (初始化单例对象入口) 
• org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantia teSingletons (初始化单例对象入口) 
• org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) (万恶之源,获取并创建Bean的入口)
• org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean (实际的获取 并创建Bean的实现)
• org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String) (从缓存中尝试获取) 
• org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[]) (实例化Bean)
• org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean (实例化Bean具体实现) 
• org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance (具体实例化过程) 
• org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory (将实例化后的Bean添加到三级缓存) 
• org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean (实例化后属性注入)
• org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initiali zeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) (初始化入口)  

源码导入idea及调试

前言——万里长征的第一步

要学习Spring源码,导入Spring源码到IDE是必不可少的一步,因为Spring源码各个包、各个类之间的各种关联关系非常复杂。如果仅仅是通过Spring源码文档来看,相信没多少人能坚持学下去。因此将Spring源码包导入IDE是非常必要的。本人使用IDEA较多,所以也就将Spring源码导入至IDEA中。

准备工作

Spring源码包下载

在本地磁盘下载Spring源码包,笔者在写这篇文章时下载的为Spring5源码。下载地址:https://github.com/spring-projects/spring-framework

然后选在本地磁盘目录下,使用git命令下载Spring源码(git工具怎么安装,度娘吧)。

git clone https://github.com/spring-projects/spring-framework

待源码下载完后,本地磁盘就会生成Spring源码。

gradle工具下载

gradle是一个基于Groovy的构建工具,它使用Groovy来编写构建脚本,支持依赖管理和多项目创建,类似Maven但又比Maven更加简单便捷。

gradle下载地址:http://downloads.gradle.org/distributions/gradle-4.6-bin.zip

下载好gradle之后需要配置gradle的环境变量。

在系统属性——>环境变量——>系统变量创建两个变量

变量:GRADLE_HOME
值:X:\gradle-4.6
变量:GRADLE_USER_HOME
值:%GRADLE_HONE%\.gradle
变量:path
值:;%GRADLE_HOME%/bin

配置好gradle之后,在cmd窗口下使用gradle -v命令查看是否安装成功。 img

如上图显示gradle版本,即显示安装成功。

编译Spring源码

在Spring源码目录下,打开cmd窗口(windows系统),运行以下命令:

gradlew.bat cleanIdea :spring-oxm:compileTestJava

执行完命令,如下图所示: img

将Spring源码导入IDEA中

(1)打开IDEA,选择File->New->Project From Existing Sources…

(2)选中Spring-framework文件夹,OK->Import project from external model

(3)选中Gradle,点击Next,然后点击Finish,等待IDEA导入以及下载相关联的包即可。

img img

这样,Spring源码就导入成功了。