2、Spring 源码学习 ~ Spring 容器的基本实现

Spring 容器的基本实现

博客使用源码地址:https://github.com/RononoaZoro/archer-spring.git


前言

大家好,我是小罗,一个普通的码农,在我们使用 Java 进行企业后台项目开发时, Spring 可以说是绕不开的框架,为此我们有必要来学习 Spring 的用法,以及在源码层面是怎么来实现这些功能的,学习源码的目的,对于小罗我来说,大致如下:

  • a、更好的使用 Spring:熟悉 Spring 源码,可以让我们更好的应用它,包括在需要的时候可以拓展它的功能,来满足特殊的需求
  • b、建立编码的基准线:了解大师们写代码的风格,建立起我自己编码的品位
  • c、掌握学习源码的学习方法:为学习其他的框架代码(如 mybatis,dubbo等)打下基础

下面就请跟着小罗一起来学习 Spring 源码吧!

一、容器的基本用法

问题:怎么使用 Spring 呢?

一句话概括:往容器中注册 bean,再通过容器取到注册的bean来使用

0、引入依赖

ext {
   
        //spring libs
        springVersion = '5.3.23'

        //logging libs
        slf4jVersion = '1.7.25'
        logbackVersion = '1.2.3'

        // lombok jdk 17 兼容版本 最低 1.18.20
        lombokVersion = '1.18.20'

        spring = [
                context: "org.springframework:spring-context:$springVersion",
        ]

        other = [
                slf4jJcl       : "org.slf4j:jcl-over-slf4j:$slf4jVersion",
                logback        : "ch.qos.logback:logback-classic:$logbackVersion",
                lombok         : "org.projectlombok:lombok:$lombokVersion"
        ]
    }

1、Bean

package com.luo.spring.guides.helloworld.common;

import lombok.Data;

/**
 * @author : archer
 * @date : Created in 2022/9/28 17:21
 * @description :
 */
@Data
public class TestBean {
   
    private String testStr;

    public TestBean() {
   
        this.testStr = "helloworld";
    }
}

2、beanfactory-test.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="testBean" class="com.luo.spring.guides.helloworld.common.TestBean"/>

</beans>

3、测试

package helloworld;

import com.luo.spring.guides.helloworld.common.TestBean;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

import static org.junit.jupiter.api.Assertions.assertEquals;

/**
 * @author : archer
 * @date : Created in 2022/9/28 17:41
 * @description :
 */
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
   

    @Test
    public void testSimpleLoad() {
   
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanfactory-test.xml"));
        TestBean testBean = (TestBean) beanFactory.getBean("testBean");
        assertEquals("helloworld", testBean.getTestStr());
    }
}

二、功能分析

我们来分析下上面代码,探索 Spring 帮我们做了什么工作?大体流程如下:

  • 1、读取配置文件 beanfactory-test.xml
  • 2、根据配置文件找到对应类的配置,并实例化
  • 3、调用实例化后的实例

为了完成上述功能,我们至少需要 3 个类,如下

  • ConfigReader:用于读取及验证配置文件。即读取配置文件的内容,放到内存中。
  • ReflectionUtil:用于根据配置文件中的配置,通过反射技术来进行实例化。
  • App:用于完成整个逻辑的串联。

在这里插入图片描述

三、工程搭建

在 Spring 源码中,用于实现上面功能的是 org.springframework.beans.jar,完成上述功能的 Spring 依赖如图

在这里插入图片描述

可以看到除了 Spring-beans 之外,还依赖了其他的一些 Spring 的 jar 包。接下来我们来学习下 Spring 的结构,它是怎么来组织代码的呢?

四、Spring 的结构组成

1、beans 包的层级结构

小罗认为阅读源码最好的方法,是通过示例跟着操作一遍,虽然有时候或者说大多数时候会被复杂的代码逻辑绕来绕去,最后不知道自己身在何处,但若再配以 UML ,还是可以把思路完整的理出来的。下面就按照我学习的思路,配以必要的 UML 来进行分析。

先来看看整个 beans 工程的源码结构,如图

在这里插入图片描述

2、核心类介绍

1)、DefaultListableBeanFactory

DefaultListableBeanFactory 是整个 bean 加载的核心部分,是 Spring 注册和加载 bean 的默认实现,XmlBeanFactory 继承自 DefaultListableBeanFactory,对它进行了拓展,主要用于从 XML 文档中读取 BeanDefinition ,增加了 XmlBeanDefinitionReader 类型的 reader 属性,在 XmlBeanFactory 中主要使用 reader 属性对资源文件进行读取和注册。下面是 DefaultListableBeanFactory 的类关系图

在这里插入图片描述

下面我们来简述上述图中各个类的作用

  • AliasRegistry:接口,定义对 alias(别名) 的简单增删改等操作
  • SimpleAliasRegistry:实现类,主要使用 map 作为 alias 的缓存,并对接口 AliasRegistry 进行实现
  • BeanDefinitionRegistry:接口,定义对 BeanDefinition 的各种增删查改操作
  • SingletonBeanRegistry:接口,定义对单例的注册及获取
  • DefaultSingletonBeanRegistry:实现类,对接口 SingletonBeanRegistry 的实现
  • FactoryBeanRegistrySupport:实现类,在 DefaultSingletonBeanRegistry 的基础上增加对 FactoryBean 的特殊处理功能
  • BeanFactory:接口,定义获取 bean 及 bean 的各种属性
  • HierarchicalBeanFactory:接口,继承 BeanFactory ,在 BeanFactory 的基础上增加了对 parentFactory 的支持
  • ConfigurableBeanFactory:接口,提供配置 Factory 的各种方法
  • ListableBeanFactory:接口,根据各种条件获取 bean 的配置清单
  • AbstractBeanFactory:;类,综合 FactoryBeanRegistrySupport 和 ConfigurableBeanFactory 的功能
  • AutowireCapableBeanFactory:接口,提供创建 bean、自动注入、初始化以及应用 bean 的处理器
  • AbstractAutowireCapableBeanFactory:类,综合 AbstractBeanFactory 并对 接口 AutowireCapableBeanFactory 进行实现
  • ConfigurableListableBeanFactory:接口,BeanFactory 配置清单,指定忽略类型及接口等
  • DefaultListableBeanFactory:综合上面所有的功能,主要是对 bean 注册后的处理
2)、XmlBeanDefinitionReader

XML 配置文件的读取是 Spring 中重要的功能,因为 Spring 的大部分功能都是以配置作为切入点的,从 XmlBeanDefinitionReader 中,我们可以梳理出资源文件读取、解析及注册的大致脉络,先来看看各个类的功能

  • ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址,来返回对应的 Resource
  • BeanDefinitionReader:主要定义资源文件读取并转换为 BeanDefinition 的各个功能
  • EnvironmentCapable:定义获取 Environment 的方法
  • DocumentLoader:定义从资源文件加载到转换为 Document 的功能
  • AbstractBeanDefinitionReader:对 EnvironmentCapable 、BeanDefinitionReader 类定义的功能进行实现
  • BeanDefinitionDocumentReader:定义读取 Document 并注册 BeanDefinition 功能
  • BeanDefinitionParserDelegate:定义解析 Element 的各种方法

下面是配置文件读取的相关类图

在这里插入图片描述

五、容器的基础 XmlBeanFactory

接下我们来深入分析下容器初始化代码的实现:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanfactory-test.xml"));

下面是上述代码的时序图

在这里插入图片描述

1、配置文件封装

Spring 的配置文件读取,是通过 ClassPathResource 进行封装的,如 new ClassPathResource(“beanfactory-test.xml”),我们来看下具体是如何封装的,ClassPathResource 最顶级的父接口是 InputStreamSource。

InputStreamSource:InputStreamSource 封装任何能返回 InputStream 的类,比如 File、Classpath 下的资源和 ByteArray 等。它的唯一方法 getInputStream() 返回一个新的 InputStream 对象。

package org.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

public interface InputStreamSource {
   
	InputStream getInputStream() throws IOException;
}

Resource:Resource 接口抽象了所有 Spring 内部使用到的底层资源:File、Classpath、URL 等,

package org.springframework.core.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

import org.springframework.lang.Nullable;

public interface Resource extends InputStreamSource {
   

    //存在性:是否存在
	boolean exists();
    //可读性:是否可读
	default boolean isReadable() {
   
		return exists();
	}
    //打开状态:是否处于打开状态
	default boolean isOpen() {
   
		return false;
	}
	default boolean isFile() {
   
		return false;
	}
    //不同资源转换为 URL
	URL getURL() throws IOException;
    //不同资源转换为 URI
	URI getURI() throws IOException;
    //不同资源转换为 File
	File getFile() throws IOException;
	default ReadableByteChannel readableChannel() throws IOException {
   
		return Channels.newChannel(getInputStream());
	}
    //内容长度
	long contentLength() throws IOException;
    //最后修改的时间戳
	long lastModified() throws IOException;
    //基于当前资源创建一个相对资源
	Resource createRelative(String relativePath) throws IOException;
	@Nullable
	String getFilename();
    //错误处理中打印详细错误信息
	String getDescription();
}

对于不同来源的资源文件都有相应的 Resource 实现:文件(FileSystemResource)、Classpath 资源(ClasspathResource)、URL 资源(UrlResource )、InputStream 资源(InputStreamResource)、Byte 数组(ByteArrayResource)等

在这里插入图片描述

有了 Resource 接口便可以对所有资源文件进行统一处理,实现也非常简单,下面列举出 ClassPathResource 和 FileSystemResource 的获取 InputStream 的实现

ClassPathResource

@Override
public InputStream getInputStream() throws IOException {
   
    InputStream is;
    if (this.clazz 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
注:下文中的 *** 代表文件名中的组件名称。 # 包含: 中文-英文对照文档:【***-javadoc-API文档-中文(简体)-英语-对照.zip】 jar包下载地址:【***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【***.jar中文文档.zip】,再解压其中的 【***-javadoc-API文档-中文(简体).zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·本文档为双语同时展示,一行原文、一行译文,可逐行对照,避免了原文/译文来回切换的麻烦; ·有原文可参照,不再担心翻译偏差误导; ·边学技术、边学英语。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值