基於Spring註解的上下文初始化過程源碼解析(一)

来源:https://www.cnblogs.com/elewin/archive/2019/08/05/11299635.html
-Advertisement-
Play Games

最近工作之餘有時間和精力,加上平時對源碼比較感興趣,就開始啃起了Spring源碼。為加深印象寫了這篇博客,如有錯誤,望各位大佬不吝指正。 我看的是Spring5的源碼,從同性社區download下來後編譯,然後看源碼、寫註釋、一步一步debug,理論指導實踐,實踐再反作用於理論。 因為基於註解的開發 ...


最近工作之餘有時間和精力,加上平時對源碼比較感興趣,就開始啃起了Spring源碼。為加深印象寫了這篇博客,如有錯誤,望各位大佬不吝指正。

我看的是Spring5的源碼,從同性社區download下來後編譯,然後看源碼、寫註釋、一步一步debug,理論指導實踐,實踐再反作用於理論。

因為基於註解的開發是現在比較主流的開發模式,所以就從 AnnotationConfigApplicationContext 開始啃了。

因為代碼過多,執行流程過於複雜,就拆成了三篇來解析。

下麵就從這個類開始探究Spring上下文的初始化。

 

這裡需要留意 AnnotationConfigApplicationContext 繼承了 GenericApplicationContext

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {

	private final DefaultListableBeanFactory beanFactory;
/** * 註意這個無參構造函數
*
* 這個初始化的DefaultListableBeanFactory就是Spring的Bean工廠 */ public GenericApplicationContext() {
        this.beanFactory = new DefaultListableBeanFactory(); } }

再來看看 AnnotationConfigApplicationContext 這個類

public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry {

	/**
	 * 定義一個讀取註解的 BeanDefinition 讀取器
	 * 這個類在構造方法中被實例化
	 */
	private final AnnotatedBeanDefinitionReader reader;

	/**
	 * 定義一個掃描類路徑下加了註解的 BeanDefinition 掃描器
	 * 這個類在構造方法中被實例化
	 */
	private final ClassPathBeanDefinitionScanner scanner;

	public AnnotationConfigApplicationContext() {
		// 實例化 BeanDefinition 讀取器
		/**
		 * 註冊所有註解相關的後置處理器
		 * 最重要的一個後置處理器 ConfigurationClassPostProcessor
		 * BeanName 是 internalConfigurationAnnotationProcessor
		 */
		this.reader = new AnnotatedBeanDefinitionReader(this);
		// 實例化 BeanDefinition 掃描器
		// 但是實際上掃描包的工作並不是 scanner 這個對象來完成的,是 Spring 自己創建的一個新的 ClassPathBeanDefinitionScanner
		// 這裡的 scanner 僅僅是為了程式員能夠在外部調用 AnnotationConfigApplicationContext 對象的 scanner 方法
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
		// 調用父類的構造方法,初始化 DefaultListableBeanFactory 的 Bean 工廠
		super(beanFactory);
		// 實例化該讀取器
		this.reader = new AnnotatedBeanDefinitionReader(this);
		// 實例化該掃描器
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

	/**
	 * 這個構造方法需要傳入一個 @Configuration 註解配置類
	 * 通過註解讀取器讀取並解析
	 */
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		/**
		 * 由於繼承了父類,這裡會先去調用父類的構造方法,然後調用自身的構造方法
		 *
		 * this.beanFactory = new DefaultListableBeanFactory();
		 *
		 * 其實就是初始化一個 DefaultListableBeanFactory
		 */
		this();
		/**
		 * 將傳入的 @Configuration 配置類轉換為 BeanDefinition
		 * 並添加到 DefaultListableBeanFactory 工廠的 BeanDefinitionMap 中
		 */
		register(annotatedClasses);
		/**
		 * 1、準備刷新山下文
		 * 2、通知子類刷新內部的 bean 工廠,得到創建的 DefaultListableBeanFactory 工廠
		 * 3、配置工廠的標準上下文特征
		 * 4、允許在上下文子類中對 bean 工廠進行後置處理
		 * 5、在上下文中調用工廠處理器方法,註冊為 bean
		 * 6、註冊 BeanPostProcessor
		 * 7、初始化此上下文的消息源
		 * 8、初始化應用事件廣播器【SpringBoot 的啟動源碼中與該方法有很大關係】
		 * 9、在特定的上下文子類中初始化其他特殊 bean
		 * 10、檢查監聽器 bean 並註冊它們
		 * 11、實例化所有剩餘(非延遲初始化)單例
		 * 12、發佈相應的事件
		 */
		refresh();
	}

	public AnnotationConfigApplicationContext(String... basePackages) {
		/**
		 * 由於繼承了父類,這裡會先去調用父類的構造方法,然後調用自身的構造方法
		 *
		 * this.beanFactory = new DefaultListableBeanFactory();
		 *
		 * 其實就是初始化一個 DefaultListableBeanFactory
		 */
		this();
		scan(basePackages);
		refresh();
	}
}  

這裡還要註意一下AnnotatedBeanDefinitionReader的實例化,代碼跟進去發現調用了 AnnotationConfigUtils 的 registerAnnotationConfigProcessors 方法,Spring在初始化上下文時,在Bean工廠中添加了很多輔助其初始化的類

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {
	// 獲取 Bean 工廠
	DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
	if (beanFactory != null) {
		if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
			// AnnotationAwareOrderComparator 主要能解析 @Order 註解和 @Priority 註解
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
		}
		if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
			// ContextAnnotationAutowireCandidateResolver 提供處理延遲載入的功能
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
		}
	}

	/**
	 * 存放所有輔助類的信息
	 */
	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
	/**
	 * BeanDefinition 的註冊,很重要,需要理解註冊的每個 Bean 的類型和作用
	 *
	 * Spring 在初始化 ApplicationContext 和 Bean 工廠時,在 Bean 工廠中添加了很多輔助初始化 Bean 工廠的類
	 *
	 * 1.ConfigurationClassPostProcessor             類型是 BeanFactoryPostProcessor
	 * 2.AutowiredAnnotationBeanPostProcessor        類型是 BeanPostProcessor
	 * 3.CommonAnnotationBeanPostProcessor           類型是 BeanPostProcessor
	 * 4.PersistenceAnnotationBeanPostProcessor      類型是 BeanPostProcessor
	 * 5.EventListenerMethodProcessor                類型是 BeanFactoryPostProcessor
	 * 6.DefaultEventListenerFactory                 類型是 EventListenerFactory
	 */

	/**
	 * BeanName 是否包含 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
	 * BeanClass 是 ConfigurationClassPostProcessor,類型是 BeanFactoryPostProcessor
	 */
	if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	/**
	 * BeanName 是否包含 org.springframework.context.annotation.internalAutowiredAnnotationProcessor
	 * BeanClass 是 AutowiredAnnotationBeanPostProcessor,類型是 BeanPostProcessor
	 */
	if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
	/**
	 * BeanName 是否包含 org.springframework.context.annotation.internalCommonAnnotationProcessor
	 * BeanClass 是 CommonAnnotationBeanPostProcessor,類型是 BeanPostProcessor
	 */
	if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
	/**
	 * 檢查 JPA 支持,如果存在,則添加 PersistenceAnnotationBeanPostProcessor
	 *
	 * BeanName 是否包含 org.springframework.context.annotation.internalPersistenceAnnotationProcessor
	 * BeanClass 是 PersistenceAnnotationBeanPostProcessor,類型是 BeanPostProcessor
	 */
	if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition();
		try {
			def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
					AnnotationConfigUtils.class.getClassLoader()));
		} catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
		}
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
	}

	/**
	 * BeanName 是否包含 org.springframework.context.event.internalEventListenerProcessor
	 * BeanClass 是 EventListenerMethodProcessor,類型是 BeanFactoryPostProcessor
	 */
	if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
	}

	/**
	 * BeanName 是否包含 org.springframework.context.event.internalEventListenerFactory
	 * BeanClass 是 DefaultEventListenerFactory,類型是 EventListenerFactory
	 */
	if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
	}

	return beanDefs;
}

這個方法執行完後,可以看到 DefaultListableBeanFactory 中的 beanDefinitionMap 中已經有數據了,如果支持 JPA 則有6個元素,沒有則有5個。分別是 ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory,支持 JPA 的是 PersistenceAnnotationBeanPostProcessor。

 這裡實例化的 ClassPathBeanDefinitionScanner  僅僅是為了讓程式員能夠在外部調用 AnnotationConfigApplicationContext 對象的 scanner 方法。Spring 在後面又重新初始化了一個 ClassPathBeanDefinitionScanner,用新的 ClassPathBeanDefinitionScanner 進行掃描。  

回到一開始,我這裡是傳了一個@Configuration的配置類,所以從register方法往下跟,最後調用的是 AnnotatedBeanDefinitionReader 的 doRegisterBean 方法

/**
 * 將給定的 bean 類註冊為 bean,從類聲明的註釋中派生其元數據
 */
private <T> void doRegisterBean(Class<T> annotatedClass, @Nullable String name, 
                   @Nullable Class<? extends Annotation>[] qualifiers,
                   @Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { // 根據指定的 bean 創建一個 AnnotatedGenericBeanDefinition AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); // 若這個類是需要跳過解析的類,則返回 if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { return; } // 指定創建 bean 實例的回調方法,此時為 null abd.setInstanceSupplier(supplier); // 解析類的作用域元數據 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); // 添加類的作用域元數據 abd.setScope(scopeMetadata.getScopeName()); /** * 生成 BeanName * 調用 AnnotationBeanNameGenerator.generateBeanName() */ String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); /** * 處理類當中的通用註解 * 主要處理 @Lazy @DependsOn @Primary @Role @Description 等註解 * 處理完成之後將值賦給到 AnnotatedGenericBeanDefinition 對應的屬性中 */ AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); /** * 如果在向容器註冊註解 BeanDefinition 時,使用了額外的限定符註解則解析 * byName 和 qualifiers 變數是 Annotation 類型的數組,裡面不僅存了 @Qualifier 註解 * 所以 Spring 遍歷這個數組判斷是否加了指定註解 * * 此時 qualifiers 為 null,if 語句不執行 */ if (qualifiers != null) { for (Class<? extends Annotation> qualifier : qualifiers) { if (Primary.class == qualifier) { // 設置 primary 屬性值 abd.setPrimary(true); } else if (Lazy.class == qualifier) { // 設置 lazyInit 屬性值 abd.setLazyInit(true); } else { /** * 如果使用了除 @Primary 和 @Lazy 以外的其他註解 * 則為該 Bean 添加一個根據名字自動裝配的限定符 */ // 向 Map<String, AutowireCandidateQualifier> qualifiers 集合中添加值 abd.addQualifier(new AutowireCandidateQualifier(qualifier)); } } } /** * 如果存在一個或多個用於自定義工廠的回調 * * 此時 customizers 為 null,if 語句不執行 */ if (customizers != null) { for (BeanDefinitionCustomizer customizer : customizers) { customizer.customize(abd); } } /** * 獲取 BeanDefinitionHolder 持有者容器 * 裡面包含的屬性值有 String beanName,BeanDefinition beanDefinition 和 String[] aliases 別名集合 */ BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); /** * 解析代理模型 */ definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); /** * 將最終獲取到的 BeanDefinitionHolder 持有者容器中包含的信息註冊給 BeanDefinitionRegistry * * AnnotationConfigApplicationContext 在初始化的時候通過調用父類的構造方法,實例化了一個 DefaultListableBeanFactory * 這一步就是把 BeanDefinitionHolder 這個數據結構中包含的信息註冊到 DefaultListableBeanFactory 中 * * DefaultListableBeanFactory 實現了 BeanDefinitionRegistry * * 此時傳入的 @Configuration 配置類已經註冊到 DefaultListableBeanFactory 工廠中 */ BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); }

 這裡比較重要的是最後一行代碼,進行註冊操作,代碼如下:

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

	// Register bean definition under primary name.
	// 用類的主要名稱註冊 BeanDefinition
	String beanName = definitionHolder.getBeanName();
	/**
	 * 將 beanName 註冊到 DefaultListableBeanFactory 工廠中
	 */
	registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

	// Register aliases for bean name, if any.
	// 如果有別名則為 BeanName 註冊別名
	String[] aliases = definitionHolder.getAliases();
	if (aliases != null) {
		for (String alias : aliases) {
			/**
			 * 註冊別名
			 */
			registry.registerAlias(beanName, alias);
		}
	}
}
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
		throws BeanDefinitionStoreException {

	if (beanDefinition instanceof AbstractBeanDefinition) {
		try {
			((AbstractBeanDefinition) beanDefinition).validate();
		} catch (BeanDefinitionValidationException ex) {
			throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
					"Validation of bean definition failed", ex);
		}
	}

	// 先從 Map<String, BeanDefinition> beanDefinitionMap 中獲取一次
	BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
	// 已註冊過則進行邏輯校驗
	if (existingDefinition != null) {
		// 是否允許 BeanDefinition 覆蓋
		if (!isAllowBeanDefinitionOverriding()) {
			// 不允許則拋異常
			throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
		}
		// 已存在的角色是否小於傳入的 beanDefinition 角色
		else if (existingDefinition.getRole() < beanDefinition.getRole()) {
			// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
			if (logger.isInfoEnabled()) {
				logger.info("Overriding user-defined bean definition for bean '" + beanName +
						"' with a framework-generated bean definition: replacing [" +
						existingDefinition + "] with [" + beanDefinition + "]");
			}
		}
		// 如果傳入的 beanDefinition 和已存在的不相等
		else if (!beanDefinition.equals(existingDefinition)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Overriding bean definition for bean '" + beanName +
						"' with a different definition: replacing [" + existingDefinition +
						"] with [" + beanDefinition + "]");
			}
		} else {
			if (logger.isTraceEnabled()) {
				logger.trace("Overriding bean definition for bean '" + beanName +
						"' with an equivalent definition: replacing [" + existingDefinition +
						"] with [" + beanDefinition + "]");
			}
		}
		// 重新存入 Map<String, BeanDefinition> beanDefinitionMap 中
		this.beanDefinitionMap.put(beanName, beanDefinition);
	}
	// 不存在
	else {
		// 是否已經啟動 bean 創建
		if (hasBeanCreationStarted()) {
			// Cannot modify startup-time collection elements anymore (for stable iteration)
			synchronized (this.beanDefinitionMap) {
				// 註冊到 Map<String, BeanDefinition> beanDefinitionMap 集合中
				this.beanDefinitionMap.put(beanName, beanDefinition);
				// 用新的 beanDefinitionNames 替換舊的
				List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
				updatedDefinitions.add(beanName);
				this.beanDefinitionNames = updatedDefinitions;
				// 從集合中移除該 bean 名稱
				removeManualSingletonName(beanName);
			}
		}
		// 還未啟動 bean 創建,即仍在啟動註冊階段
		else {
			// 註冊到 Map<String, BeanDefinition> beanDefinitionMap 集合中
			this.beanDefinitionMap.put(beanName, beanDefinition);
			// 添加 bean 名稱到 beanDefinitionNames 數據集合中
			this.beanDefinitionNames.add(beanName);
			// 從集合中移除該 bean 名稱
			removeManualSingletonName(beanName);
		}
		this.frozenBeanDefinitionNames = null;
	}

	if (existingDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
	}
}

register 方法執行完成,此時傳入的 @Configuration 配置類已經註冊到 DefaultListableBeanFactory 工廠中

那這一篇就先到這裡,有問題或者錯誤歡迎大家溝通交流

下一篇從 refresh 方法開始往下跟代碼


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

-Advertisement-
Play Games
更多相關文章
  • 3.1.goland中項目結構 (1)在goland中創建標準Go項目 (2)goland配置 創建項目Learn-Go file-settings-go-GOPATH-添加 在項目目錄下創建src目錄,在src目錄下創建demo目錄,在demo目錄下創建demo.go文件 在項目目錄下創建main ...
  • 最近的類看著很疼,堅持就是勝利~~~ python中的類,什麼是類?類是由屬性和方法組成的。類中可能有很多屬性,以及方法。 我們這樣定義一個類: 前面是class關鍵字 後面school是一個類的名字,在後面就是圓括弧和括弧裡面的object關鍵字,它是跟類,所有的類繼承它。最後記住冒號結尾。 創建 ...
  • 1:包裝類: byte Byte short Short int nteger long Long char Character boolean Boolean double Double float Float 2.基本類型轉成字元串類型 String.valueOf() String ss =I ...
  • 周總結:1.面向對象:把不同的功能封裝在不同的對象中,用到什麼功能就找相應的對象 首先要定義描述對象的類,類是用來創建對象的 new Person() >JVM使用的是Person.class來創建對象的,位元組碼中定義了說什麼,對象中就有什麼 2.成員變數:作用域是整個類,有預設值,在堆中開闢記憶體3 ...
  • 本文為原創??? 作者寫這篇文章的時候剛剛初一畢業…… 如有錯誤請各位大佬指正 從例題入手 洛谷P3915[HNOI2008]玩具裝箱toy Step0:讀題 Q:暴力? 如果您學習過dp 不難推出dp方程 設dp[i]表示放置前i個物品需要的最小價值 dp[i]=min(dp[j]+(sum[i] ...
  • 10.10 多表連接查詢 10.101 內連接 把兩張表有對應關係的記錄連接成一張虛擬表 #應用: select * from emp,dep where emp.dep_id = dep.id and dep.name = "技術"; select * from emp inner join de ...
  • 語句表達式: 在Python中支持遍歷迴圈的對象:可迭代器對象,支持迭代協議的對象 比如列表list沒有迭代功能只是可迭代對象 迭代:迭代協議 --> 例:f.__next__() 屬於f的迭代方法,全局的迭代方法為next(f) 迭代工具 --> for,…推導… map… 迭代器對象 已經實現 ...
  • 在上一篇文章中,我們已經瞭解了一個starter實現自動配置的基本流程,在這一小結我們將復現上一過程,實現一個自定義的starter。 先來分析starter的需求: 在項目中添加自定義的starter依賴,自動在Spring中載入starter中的Bean; 從application.proper ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...