前言:

为什么要学习Spring源码?我个人觉得,学习源码的应该分为3个阶段,

第一阶段,知道Spring框架设计的理念和初衷,以及其中流程的理解;

第二阶段,能够分析出来Spring框架在设计的时候,为什么会这么设计,其中运用的设计模式和设计思想;

第三阶段,可以根据开源框架的设计理念,自己在实际开发过程中,运用到开发中,并能写出一些创造性的中间件。

 

对于Spring的源码解析,我准备从三方面来讲述,

第一方面,解析存储Bean

第二方面,实例化Bean

第三方面,AOP是怎么实现的

 

这篇文章我们首先来看下Spring源码是怎么解决对Bean的解析存储的。

public static void main(String[] args) {

        /**
         * XML 解析用户
         */
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = (User) applicationContext.getBean("user");
        System.out.println(user.getName());
    }
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd"
    default-lazy-init="false">

    <!--自定义标签-->
    <context:component-scan base-package="com.monco"/>

    <!--传统标签-->
    <bean class="com.monco.entity.User" id="user"/>

</beans>
package com.monco.entity;

/**
 * @author monco
 * @data 2020/9/15 18:33
 * @description :
 */
public class User {

    private String username;

    private String password;

    private String name = "monco";

    private int age;

    // 省略get set 方法
}

 

一段最简单的创建 applicationContext 并通过 applicationContext 得到Bean的例子,我们debug进去,就大概可以知道流程了。

1、我们这边是创建了一个 ClassPathXmlApplicationContext 的容器,这个容器的作用就是根据XML文件读取的一个上下文的容器,然后根据调用容器的 getBean(String beanName) 方法来获取Bean。

2、ClassPathXmlApplicationContext 在实例化的时候,读取 spring.xml 文件,然后调用 ClassPathXmlApplicationContext 的实例化方法,然后调用 refresh() 方法,然后这个 refresh() 方法是在 AbstractApplicationContext 中具体实现的,这里是一个

很经典的模板设计模式,在 AbstractApplicationContext 父类中可以进行 refresh() 的流程定义,其他子类继承于父类,可以根据自己的不同情况去实现父类模板方法中的方法,自定义实现的方法,我们称之为“钩子方法”。为了 spring 读取文件 refresh()

在多线程下的一个并发安全,所以在 refresh() 方法中加了synchronized 关键字,这里是一个典型的对象锁,可以参考我之前的博客,synchronized 关键字的解析。

3、refresh() 方法中,我们重点看到 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 拿到了 ConfigurableListableBeanFactory 之后,那么这个对象是干嘛的呢?我们来看张图。

spring spel 解析位置_spring spel 解析位置

 

 ConfigurableListableBeanFactory 同时继承了3个接口,ListableBeanFactory、AutowireCapableBeanFactory 和 ConfigurableBeanFactory,扩展之后,加上自有的这8个方法,这个工厂接口总共有83个方法,实在是巨大到不行了。这个工厂接口的自

有方法总体上只是对父类接口功能的补充,包含了BeanFactory体系目前的所有方法。因此,ConfigurableBeanFactory 应用上下文环境可以通过 bean 的 name 或者 clazz 获取指定的 bean;也就是说,我们的bean是存储到了一个工厂中,使用的时候,再

从这个工厂中去拿。之后,我们主要注意解析,这些 xml 中定义的元素 是怎样存储到这个一个工厂中的。我们发现 这个工厂可不简单,他里面有成员变量 beanDefinitionMap 存储了一些以 beanName 为key ,beanDefinition 为 value 的 Map。这也就解

释了我们解析的元素放到哪里去了。

4、我们解决了 bean 放到了哪,并且从哪里去取的,所以接下来我们需要解决的问题是怎样放进去?又是怎样取的?跟源码之后,我们发现,要想放到这样一个 map 中,首先解决的首要问题是将 xml 的元素解析为 beanDefinition ,至于读取 xml ,解析

xml,应用了一系列的 xml 解析器啊 之类的,我们可以不去了解,主要了解的方向是怎么转化的,根据不同的类型的元素又是怎样的处理方案。

5、解析标签,spring 将 xml 的标签分为两类,一类自定义标签,一类默认标签

spring spel 解析位置_父类_02

 

 我们首先来看下 默认标签的解析 默认标签的类型主要分为以下四种:import 、 alias 、bean 、 beans

spring spel 解析位置_父类_03

 

这里我们重点分析以下 bean 标签的解析,也就是 processBeanDefinition() 方法

spring spel 解析位置_xml_04

 

我们发现在这里我们得到了一个 BeanDefinitionHolder 对象,之后的 BeanDefinitionReaderUtils.registerBeanDefinition 的作用就是将 BeanDefinitionHolder 注册到了 工厂(DefaultListableBeanFactory)的 beanDefinitionMap 中。

下面我们来分析下这个 BeanDefinitionHolder 对象 到底有哪些属性,从 xml 中是怎么转化成这个对象的。

BeanDefinitionHolder 对象中包含三个成员变量,分别是 BeanDefinition 对象,一个是 beanName 属性,还有一个是别名属性

spring spel 解析位置_spring spel 解析位置_05

 

 BeanDefinition 是一个接口,而 BeanDefinitionHolder  实际在 存储 User Bean的时候  实际上是将 AbstractBeanDefinition 对象放入了 BeanDefinition 对象中,那我们可以针对 AbstractBeanDefinition 的属性进行一波分析,返回 AbstractBeanDefinition 的内部,实际上是 new 了一个 GenericBeanDefinition ,这个类是非常重要的一个类,这个类里面的属性如下:我们可以再做进一步分析。

 

spring spel 解析位置_spring_06

 

 这些属性,我们来解释一下下。

id : Bean 的唯一标识名。它必须是合法的 XMLID,在整个 XML 文档中唯一。 

 

name : 用来为 id 创建一个或多个别名。它可以是任意的字母符合。多个别名之间用逗号或空格分开。

 

class:用来定义类的全限定名(包名+类名)。只有子类 Bean 不用定义该属性。

 

parent:子类 Bean 定义它所引用它的父类 Bean。这时前面的 class 属性失效。子类 Bean 会继承父类 Bean 的所有属性,子类 Bean 也可以覆盖父类 Bean 的属性。注意:子类 Bean 和父类 Bean 是同一个 Java 类。

 

abstract(默认为”false”):用来定义 Bean 是否为抽象 Bean。它表示这个 Bean 将不会被实例化,一般用于父类 Bean,因为父类 Bean 主要是供子类 Bean 继承使用。

 

lazy-init(默认为“default”):用来定义这个 Bean 是否实现懒初始化。如果为“true”,它将在 BeanFactory 启动时初始化所有的 SingletonBean。反之,如果为“false”,它只在 Bean 请求时才开始创建 SingletonBean。 

 

autowire(自动装配,默认为“default”):它定义了 Bean 的自动装载方式。

  “no”:不使用自动装配功能。

  “byName”:通过 Bean 的属性名实现自动装配。

  “byType”:通过 Bean 的类型实现自动装配。

  “constructor”:类似于 byType,但它是用于构造函数的参数的自动组装。 

  “autodetect”:通过 Bean 类的反省机制(introspection)决定是使用“constructor”还是使用“byType”。 

 

depends-on(依赖对象):这个 Bean 在初始化时依赖的对象,这个对象会在这个 Bean 初始化之前创建。

 

init-method:用来定义 Bean 的初始化方法,它会在 Bean 组装之后调用。它必须是一个无参数的方法。

 

destroy-method:用来定义 Bean 的销毁方法,它在 BeanFactory 关闭时调用。同样,它也必须是一个无参数的方法。它只能应用于 singletonBean。

 

factory-method:定义创建该 Bean 对象的工厂方法。它用于下面的“factory-bean”,表示这个 Bean 是通过工厂方法创建。此时,“class”属性失效。

 

factory-bean:定义创建该 Bean 对象的工厂类。如果使用了“factory-bean”则“class”属性失效。 

 

autowire-candidate:采用 xml 格式配置 bean 时,将<bean/>元素的 autowire-candidate属性设置为 false,这样容器在查找自动装配对象时,将不考虑该 bean,即它不会被考虑作为其它 bean自动装配的候选者,但是该 bean 本身还是可以使用自动装配来注入其它 bean 的。 

 

MutablePropertyValues:用于封装<property>标签的信息,其实类里面就是有一个 list,list里面是 PropertyValue 对象,PropertyValue 就是一个 name 和 value 属性,用于封装<property>标签的名称和值信息。

 

ConstructorArgumentValues:用于封装<constructor-arg>标签的信息,其实类里面就是有一个 map,map 中用构造函数的参数顺序作为 key,值作为 value 存储到 map 中。

 

MethodOverrides:用于封装 lookup-method 和 replaced-method 标签的信息,同样的类里面有一个 Set 对象添加 LookupOverride 对象和 ReplaceOverride 对象。

 

这些字段的解释,我这边也是大概写一下,具体使用还要参考其他的资料。我这边只是解释到这里,这篇博客就暂时写到这里,主要解决的问题,是将 xml 解析到 BeanDefinition 对象中,然后将 BeanDefinition 对象保存到 BeanFactory 中,这里暂时只是

 

解析到了默认标签的解析,之后文章我会讲解下 自定义标签的解析 这里面有个 SPI 的思想。这个会找个专题具体介绍下。

 

在此谢谢大家阅读,如有错误,欢迎指正。

 

方法总比困难多。 思想重于实现。