(一)Spring源碼——IoC騙

来源:https://www.cnblogs.com/novae/archive/2020/03/01/12392269.html
-Advertisement-
Play Games

@[toc] 1. Spring註解的源碼分析 1.1 我如何開始分析源碼的? ==這一部分可以略過直接看第1.2節== 想必程式員都會經過這樣一個階段,當 已經能夠熟練運用。並且它的 也能夠用到 ,程式員就會找進階的入口,這時候就想到了去瞭解源碼。 作為Java程式員,首先想到的一定是瞭解Spri ...


@[toc]

1. Spring註解的源碼分析

1.1 我如何開始分析源碼的?

這一部分可以略過直接看第1.2節 想必程式員都會經過這樣一個階段,當一門編程語言的語法已經能夠熟練運用。並且它的流行框架也能夠用到五分熟,程式員就會找進階的入口,這時候就想到了去瞭解源碼。

作為Java程式員,首先想到的一定是瞭解Spring源碼,但是Spring這個東西龐然大物,從哪裡開始都是個問題!這時候我就想起了唐曾那句話:“貧僧從東土大唐而來,往西天取經而去”。真的很羡慕他,知道自己從哪來,還知道自己該往哪去。這樣的人已經不多了。那麼對Spring源碼的剖析該從哪來呢?

我經歷過這幾個階段:

  1. 網上看視頻(像B站里有很多好的關於spring源碼的視頻,但是視頻有個缺點就是如果忘記了,想複習一遍,沒那麼容易。)
  2. 看網上的博客,這就不用說了。網上的博客五花八門,魚龍混雜,想要找到一篇適合自己的很不容易。(放棄了)
  3. 啃書本,最開始我看的是《Spring源碼深度剖析》,但這本書打著Spring5的旗號,講解xml的內容,作為跟進潮流的程式員表示,我想看關於註解方式的源碼解析。然後我又看了《Spring揭秘》《Spirng 技術內幕》等等。沒有一本是符合自己的。
  4. 黃天不負有心人,最終我還是找到了一本合適的書籍《Spring5 核心原理——手寫Spirng 30 個類實戰》不得不說,這本書是我進階Spring的入門書籍。強烈推薦。以下博文內容參考這本書和我自己微薄的只是而成。
graph LR G(看視頻) --> E(看博文) E --> F(啃書籍)

1.2 Spring的模塊

Spirng的模塊有很多,這裡我不必多說。想要瞭解spring那最好是從Spring的Ioc容器DI依賴註入AOP切麵編程MVC資料庫處理這幾個入手。廢話不多說,直接上手Spring Ioc容器,請看下節。 在這裡插入圖片描述

1.3 找到一個入口

1.3.1 Spring容器

一說到Spring容器,會想到什麼呢?有些人會想到ApplicationContextBeanFactory,好像沒錯。但是理解的太狹隘了。我理解的Spring 容器是包含Bean的生命周期,從一個Bean裝載到這個Bean被初始化註冊到容器中的過程中所涉及到的容器和類的總體成為Ioc容器。從ApplicationContext這個名字也能看出來,它有上下文的意思。

隨著Spring版本的更新,最新的Spring5 配上Springboot 2.x已經全面推薦使用註解開發,AnnotationConfigWebApplicationContext只是AnnotationConfigApplicationContext的Web版本。所以接下來講到的就以AnnotationConfigApplicationContext這個類為例分析源碼。

2. Ioc容器分析

在spring環境下,先準備一個名為AppConfig的類。名字自己起。通過AnnotationConfigApplicationContext容器對它的掃描Bean、解析Bean、註冊Bean的過程來分析容器的整個創建過程。 在這裡插入圖片描述 AppConfig.java文件源碼:

package com.abc;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
@ComponentScan("com.abc")
@Scope("singleton")
public class AppConfig {
	public AppConfig() {
		System.out.println("com.abc.AppConfig");
	}
}

TestAppConfig.java文件源碼:

public class TestAppConfig {
	@Test
	public void testAnno() {
		// 創建註解上下文容器(Ioc容器)
		AnnotationConfigApplicationContext atx = new AnnotationConfigApplicationContext(AppConfig.class);
		System.out.println(atx);
		// xml載入容器方式,在這裡不做介紹。
		// ApplicationContext app = new ClassPathXmlApplicationContext("application.xml");
	}
}

看上面的測試類。想要觀察AnnotationConfigApplicationContext容器的創建過程,實際上就是看這個對象怎麼被new出來的。通過以下代碼,我們查看他的構造函數。

	// 最常用的構造函數
	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		// 實例化了一個DefaultListableBeanFactory
		this();
		// AppConfig是沒有Component註解的。但它被掃描是必要的。
		// 將AppConfig這個類註冊成一個BeanDefinition添加到Map中
		// BeanDefinition是bean容器的一個重要的類,它是bean的定義。你可以先記住,不需要理解
		register(componentClasses);
		// refresh是SpringFramework中最重要的方法,沒有之一
		refresh();
	}

其中,

  • this()方法是調用預設構造函數。
  • register()方法是將bean註冊到容器中。
  • refresh()方法是刷新容器,因為註冊的bean在這裡就需要手動刷新一下容器。

接下來,順著構造方法一起走,來看register的源碼:

	// 註冊一個註解Bean
	// 新註冊的註解Bean必須手動調用refresh方法,來刷新容器對Bean的處理。
	@Override
	public void register(Class<?>... componentClasses) {
		...省略斷言語句
		this.reader.register(componentClasses);
	}

在構造方法中的register()方法原來是委派了reader的register方法。這個reader是什麼呢?reader是AnnotationConfigApplicationContext的一個屬性。從名字看它是註解bean定義讀取器(同學們斷句一定要註意啊,把註解bean當成一個整體名詞,說白了reader就是一個讀取器,用來讀取註解bean定義的讀取器)

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {
	// 用來讀取註解的Bean定義讀取器
	// 將該讀取器存放到容器中
	private final AnnotatedBeanDefinitionReader reader;

	// Bean定義的掃描器,掃描指定路勁下的註解Bean
	private final ClassPathBeanDefinitionScanner scanner;
	......
}

順著讀取器。來看一下上面提到的this.reader.register(componentClasses);

	// 註冊多個{註解Bean定義}類
	public void register(Class<?>... componentClasses) {
		for (Class<?> componentClass : componentClasses) {
			registerBean(componentClass);
		}
	}
	// 註冊一個{註解Bean定義}類
	public void registerBean(Class<?> beanClass) {
		doRegisterBean(beanClass, null, null, null);
	}

從源碼中可以看出,在register方法中調用了四個參數的doRegisterBean方法。 doRegisterBean:

	<T> void doRegisterBean(Class<T> beanClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
		// 註解生成bean定義
		// 根據指定的註解類,創建一個數據結構——封裝註解bean定義的數據結構
		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
		if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
			return;
		}

		// 設置實例提供者
		abd.setInstanceSupplier(instanceSupplier);

		// 1. 解析bean作用域
		// 獲取 Scope註解元數據 對象
		// 解析bean的作用域
		// prototype為原型模式,singleton為單例
		ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
		// 設置註解bean定義的作用域
		abd.setScope(scopeMetadata.getScopeName());
		// 獲取beanName ,如果name沒有傳值就從beanNameGenerator生成一個
		String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
		// 2. 處理通用註解
		// 處理註解bean定義中的通用註解(如:@Lazy @Primary等)
		AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
		if (qualifiers != null) {
			for (Class<? extends Annotation> qualifier : qualifiers) {
				if (Primary.class == qualifier) {
					abd.setPrimary(true);
				}
				else if (Lazy.class == qualifier) {
					abd.setLazyInit(true);
				}
				else {
					abd.addQualifier(new AutowireCandidateQualifier(qualifier));
				}
			}
		}
		for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
			customizer.customize(abd);
		}

		// 創建一個指定bean名稱的Bean定義對象。封裝  註解bean定義數據
		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		// 3. 創建作用域的代理對象
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		// 4. 通過BeanDefinitionReaderUtils向容器註冊bean
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}

到此AnnotationConfigApplicationContext這個容器註冊Bean的過程就完成了。

看過來看過來!!!!劃重點了 過多的源碼看起來眼花繚亂,看不懂就對了!沒關係,源碼的東西別想一遍能看懂,需要反覆的看 第一遍瞭解大概,認識基本的概念比如上下文-ApplicationContext註解Bean-定義AnnotatedBeanDefinitionBean定義-BeanDefinitionBean讀取器-AnnotatedBeanDefinitionReaderBean掃描器。 第二遍鞏固概念,理解一下什麼叫做Bean定義-BeanDefinition等這些概念。然後你就能一步一步的入門了。

3. IoC總結

還是重!點! 回頭再來看IoC容器,現在你可以說實現BeanFactory的都可以叫做IoC容器。(包括中間的Bean定義、存儲Bean的數據結構等等)

IoC只是Spring這座大山的一角而已,IoC的功能就是,讀取了Bean放在自己的容器中。

我們都知道Bean是一個簡單對象,現在把Bean理解成一個汽車輪子,裡面的屬性都是輪子的組件——輪胎、輪軸、輪盤等等。BeanDefinition就是存儲輪子的數據結構的一種描述!

而Bean讀取器-AnnotatedBeanDefinitionReader則是讀取這種描述的,相當於把造輪子的圖紙讀了一下,而AnnotatedBeanDefinitionReader是applicationContext的一個屬性,也就是說applicationContext讀取了造輪子的圖紙,那麼applicationContext就可以造輪子了。

第二節applicationContext構造方法中的register()方法就是讀取輪子圖紙的過程。refresh是刷新的作用。

經過下麵 三個方法後,IoC容器算是真的可以造輪子(製造Bean)了。註意是可以造,但還沒造呢! 但是它並沒有執行造輪子的請求。想要造輪子就需要依賴註入DI或者getBean顯示調用了

  1. this
  2. register
  3. refresh
	// 最常用的構造函數
	public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
		// 實例化了一個DefaultListableBeanFactory
		this();
		register(componentClasses);
		// refresh是SpringFramework中最重要的方法,沒有之一
		refresh();
	}

好了到此,IoC的簡單分析已經完畢。如有錯誤,請留言更正,謝謝!!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、運算符 運算符 功能 是否支持 字元串 列表 元組 字典 集合 + 合併 √ √ √ * 複製 √ √ √ in 判斷是否存在 √ √ √ √ √ not in 判斷是否不存在 √ √ √ √ √ 2、公共方法 len() 統計容器中元素的個數 del/del() 刪除 max() 返回容器中元 ...
  • 春節期間熟悉了TP6, 也寫了一個TP6的博客程式,但系統的異常頁面實在另外頭疼,很多時候無法查看到是哪行代碼出的問題。 所以就特別的想把whoops引進來,經過一系列的研究,終於找到瞭解決的辦法: 1. 通過composer安裝whoops 運行命令: composer require filp/ ...
  • 目錄 1. 隱式類型轉換 2. 顯示類型轉換/強制類型轉換( static_cast、const_cast、reinterpret_cast、dynamic_cast) 3. 類型轉換函數、轉換構造函數 類型轉換可分為 隱式類型轉換(編譯器自動完成) 與 顯示類型轉換(強制類型轉換,需要自己操作)。 ...
  • 先用一個數組表示一個二叉樹搜索樹,也就是一個排好序的二叉樹,其中左子結點<根結點<右子結點 利用結構數組的形式來表示,id , left , right 代表結點id ,左子樹 ,右子樹 下麵這個二維數組 $data[]=['id'=>8,'left'=>2,'right'=>10,'data'=> ...
  • 上一篇我們已經根據路徑讀取到了我們需要的位元組碼文件,就以java.lang.Object這個類為例,可以看到類似下麵這種東西,那麼這些數字是什麼呢? 要瞭解這個,我們大概可以猜到這是十進位的,線上將十進位轉為十六進位看看https://tool.oschina.net/hexconvert/,註意上 ...
  • [TOC] 下麵介紹pandas常見的基本功能,和python的基本數據類型進行比較可以看到pandas在操作大型數據集中的優勢。 1.重建索引 (1)函數:reindex (2)作用:創建一個符合新索引的新對象。 (3)內容: Series調用reindex方法時,會將數組按照新的索引進行排列,如 ...
  • 基於JSP+Servlet開發失物招領系統開發環境: Windows操作系統開發工具:Myeclipse+Jdk+Tomcat7+MYSQL資料庫運行效果圖 源碼及原文鏈接:https://javadao.xyz/forum.php?mod=viewthread&tid=115 ...
  • 基於JSP+Servlet開發公交線上查詢(前臺+後臺):( 開發環境: Windows操作系統開發工具: Eclipse+Jdk+Tomcat+MYSQL資料庫運行效果圖 源碼及原文鏈接:https://javadao.xyz/forum.php?mod=viewthread&tid=120 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...