【Spring專題】Spring之Bean的生命周期源碼解析——階段二(IOC之實例化)

目錄

  • 前言
    • 閱讀準備
    • 閱讀指引
    • 閱讀建議
  • 課程內容
    • 一、SpringIOC之實例化
      • 1.1 簡單回顧
      • 1.2 概念回顧
      • 1.3 核心方法講解
    • 二、方法講解
      • 2.1 AbstractBeanFactory#getMergedLocalBeanDefinition:合并BeanDefinition
      • 2.2 AbstractAutowireCapableBeanFactory#createBean:創建Bean
      • 2.3 AbstractAutowireCapableBeanFactory#resolveBeanClass:加載類
      • *2.4 AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation:【實例化前】入口
      • 2.5 AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation:【實例化前】真正干活的地方
      • 2.6 AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization:第一次可能調用【初始化后】
      • 2.7 AbstractAutowireCapableBeanFactory#doCreateBean:【實例化】入口(包括后續的實例化過程)
      • *2.8 AbstractAutowireCapableBeanFactory#createBeanInstance:實例化
        • 2.8.1 Supplier創建對象
        • 2.8.2 工廠方法創建對象
        • 2.8.3 構造方法創建對象
      • 2.9 AbstractAutowireCapableBeanFactory#autowireConstructor:推斷構造方法
      • 2.10 AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors:BeanDefinition后置處理
      • 2.11 AbstractAutowireCapableBeanFactory#populateBean:屬性注入(包含:實例化后)
      • 方法總結后
    • 三、實例化邏輯流程圖
  • 學習總結

前言

閱讀準備

由于Spring源碼分析是一個前后聯系比較強的過程,而且這邊分析,也是按照代碼順序講解的,所以不了解前置知識的情況下,大概率沒辦法看懂當前的內容。所以,特別推薦看看我前面的文章(自上而下次序):

  • Spring底層核心原理解析【學習難度:★★☆☆☆
  • 手寫簡易Spring容器過程分析【學習難度:★★☆☆☆
  • Spring之底層架構核心概念解析【學習難度:★★★☆☆,重要程度:★★★★★
  • Bean的生命周期流程圖【學習難度:☆☆☆☆☆,重要程度:★★★★★
  • Spring之Bean的生命周期源碼解析——階段一(掃描生成BeanDefinition)【學習難度:★★☆☆☆,重要程度:★★★☆☆

(PS:特別是《Bean的生命周期流程圖》,幫大家【開天眼】,先了解下流程。畢竟【通過業務了解代碼,遠比通過代碼了解業務簡單的多】!!!!)
(PS:特別是《Bean的生命周期流程圖》,幫大家【開天眼】,先了解下流程。畢竟【通過業務了解代碼,遠比通過代碼了解業務簡單的多】!!!!)
(PS:特別是《Bean的生命周期流程圖》,幫大家【開天眼】,先了解下流程。畢竟【通過業務了解代碼,遠比通過代碼了解業務簡單的多】!!!!)

閱讀指引

我們在上一節課已經說到過了,本次Spring源碼剖析的總入口是new AnnotationConfigApplicationContext("org.tuling.spring");,這里就不再重復解釋了。本節課要說的內容,是SpringIOC的實例化,我們這里直接給到入口吧,調用鏈如下:(調用鏈比較深,不要糾結細枝末節

  1. AbstractApplicationContext#refresh:刷新方法,不用在意
  2. AbstractApplicationContext#finishBeanFactoryInitialization:在這里實例化所有剩余的(非lazy-init)單例
  3. DefaultListableBeanFactory#preInstantiateSingletons:在這里實例化所有剩余的(非lazy-init)單例(上面的方法,核心干活的方法就是這里)
  4. DefaultListableBeanFactory#getBean:獲取Bean的方法
  5. AbstractBeanFactory#doGetBean:返回指定bean的一個實例,它可以是共享的,也可以是獨立的
  6. 上面這個AbstractBeanFactory#doGetBean里面的一段局部代碼寫的回調方法,如下:
	// 如果是單例創建bean實例if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}
  1. AbstractAutowireCapableBeanFactory#createBean:這個類的中心方法:創建一個bean實例,填充bean實例,應用后處理器,等等。

如上面的調用鏈所示,最后一個方法,才是我們本次要研究的核心方法。而且通過注釋,我想大家也看到了,這個方法不單單干了實例化的工作,還有屬性填充、各種后置處理器等。(PS:哈哈,同學們,我知道這個【實例化】調用鏈挺深的,但是大家不要煩惱,只要切記【不要糾結那些細枝末節】那一切都OK,我們老老實實地跟著主線來研究就好,畢竟這些才是核心)

閱讀建議

  1. 看源碼,切記糾結細枝末節,不然很容易陷進去。正常來說,看主要流程就好了
  2. 遇到不懂的,多看看類注釋或者方法注釋。Spring這種優秀源碼,注釋真的非常到位
  3. 如果你是idea用戶,多用F11的書簽功能。
    • Ctrl + F11 選中文件 / 文件夾,使用助記符設定 / 取消書簽 (必備)
    • Shift + F11 彈出書簽顯示層 (必備)
    • Ctrl +1,2,3…9 定位到對應數值的書簽位置 (必備)

課程內容

一、SpringIOC之實例化

這里說的【實例化】,是指單例的實例化,原型prototype不包含在內

1.1 簡單回顧

大家知道,實例化的過程是怎樣的嗎?哈,我知道大部分的同學可能都不會知道。所以呢,我希望大家真的要有去看過《 Bean的聲明周期流程圖》,因為,通過【代碼去理解業務】,遠遠比【通過業務理解代碼】難得多!直接看個圖吧,起碼咱得知道【實例化到底干了什么,有哪些步驟】,我們才能更好的去研究。
在這里插入圖片描述
如上圖所示,實例化包含了:合并BeanDefinition、加載類、實例化之前、推斷構造方法、實例化、BeanDefinition的后置處理、實例化后等,這些關鍵步驟。這就是我們本篇文章研究的核心!!

1.2 概念回顧

在這個【實例化】過程中,涉及到了一些Spring底層設計的概念,我在上一個筆記里面有大概介紹過Spring底層概念的一些講解,不記得的同學記得回去翻一翻。
主要涉及的概念有:

  • BeanDefinition(設計圖紙):BeanDefinition表示Bean定義,BeanDefinition中存在很多屬性用來描述一個Bean的特征
  • BeanPostProcessor(Spring重要的拓展點):Bean的后置處理器,對Bean做拓展操作。我們前面說過,BeanPostProcessor提供了【初始化前/后】兩個拓展方法。但是這里,Spring內部對這個接口做了很多拓展,新增了一些繼承自BeanPostProcessor的子類或者子接口。在實例化階段,主要用到的接口如下:
    • InstantiationAwareBeanPostProcessor,直譯過來就是:能感知實例化的Bean后置處理器。而這個繼承自BeanPostProcessor,顯然也具備這【初始化前/后】兩個拓展方法。另外,通過InstantiationAwareBeanPostProcessor的名字大家也能猜到了,它肯定是具有對【實例化】階段拓展的能力的。接口定義在后面。
    • SmartInstantiationAwareBeanPostProcessor:直譯過來就是:智能的,能感知實例化的Bean后置處理器。在這個接口中,拓展了InstantiationAwareBeanPostProcessor接口,新增了幾個函數,其中就包括了推斷構造的實現。另外,這是一個框架內部使用接口。接口定義在后面。
    • MergedBeanDefinitionPostProcessor:直譯過來就是:合并BeanDefinition的后置處理器。運行時用于合并bean定義的后處理器回調接口。接口定義在后面。

InstantiationAwareBeanPostProcessor接口定義如下:

/*** BeanPostProcessor的子接口,用于添加實例化前回調,以及實例化后但顯式屬性設置或自動生成之前的回調。* 通常用于抑制特定目標bean的默認實例化,例如創建具有特殊TargetSources的代理(池化目標、惰性初始化目標等),或者實現額外的注入策略,如字段注入。* 注:此接口為專用接口,主要供框架內部使用。建議盡可能實現普通的BeanPostProcessor接口。* 自:* 1.2* 參見:* org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.setCustomTargetSourceCreators, org.springframework.aop.framework.autoproxy.target.LazyInitTargetSourceCreator* 作者:* 于爾根·霍勒,羅德·約翰遜*/
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {/*** 在目標bean實例化之前應用這個BeanPostProcessor。返回的bean對象可能是要使用的代理而不是目標bean,從而有效地抑制目標bean的默認實例化。* 如果此方法返回一個非空對象,則bean創建過程將會中斷。應用的唯一進一步處理是來自配置的BeanPostProcessors的postProcessAfterInitialization回調。* 這個回調將應用于帶有bean類的bean定義,以及工廠方法定義,在這種情況下,返回的bean類型將在這里傳遞。* 后置處理器可以實現擴展的SmartInstantiationAwareBeanPostProcessor接口,以便預測它們將在這里返回的bean對象的類型。* 默認實現返回null。* 參數:* beanClass——要實例化的bean的類* beanName—bean的名稱* 返回:* 要公開的bean對象,而不是目標bean的默認實例,或者為空,繼續進行默認實例化* 拋出:* BeansException -在錯誤的情況下* 參見:* postProcessAfterInstantiation, org.springframework.beans.factory.support.AbstractBeanDefinition.getBeanClass(), org.springframework.beans.factory.support.AbstractBeanDefinition.getFactoryMethodName()*/@Nullabledefault Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {return null;}/*** 在bean通過構造函數或工廠方法實例化之后,但在Spring屬性填充(來自顯式屬性或自動裝配)發生之前執行操作。* 這是在Spring自動裝配開始之前對給定bean實例執行自定義字段注入的理想回調。* 默認實現返回true。* 參數:* Bean—已創建的Bean實例,其屬性尚未設置* beanName—bean的名稱* 返回:* 如果應該在bean上設置屬性,則為True;如果應該跳過屬性人口,則為False。正常的實現應該返回true。返回false還將阻止在此bean實例上調用任何后續的InstantiationAwareBeanPostProcessor實例。* 拋出:* BeansException -在錯誤的情況下* 參見:* postProcessBeforeInstantiation*/default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {return true;}/*** 在工廠將給定的屬性值應用到給定的bean之前,對它們進行后處理,不需要任何屬性描述符。* 如果實現提供自定義postProcessPropertyValues實現,則應該返回null(默認值),否則則返回pvs。在該接口的未來版本中(刪除了postProcessPropertyValues),默認實現將直接返回給定的pv。* 參數:* PVS—工廠將要應用的屬性值(永遠不會為空)* bean——已創建的bean實例,但其屬性尚未設置* beanName—bean的名稱* 返回:* 應用于給定bean的實際屬性值(可以是傳入的PropertyValues實例),或者null,它繼續處理現有屬性,但特別地繼續調用postProcessPropertyValues(需要為當前bean類初始化PropertyDescriptors)。* 拋出:* BeansException -在錯誤的情況下* 自:* 5.1* 參見:* postProcessPropertyValues*/@Nullabledefault PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)throws BeansException {return null;}/*** 在工廠將給定屬性值應用到給定bean之前,對它們進行后處理。允許檢查是否滿足所有依賴項,例如基于bean屬性設置器上的“Required”注釋。* 還允許替換要應用的屬性值,通常通過基于原始PropertyValues創建新的MutablePropertyValues實例,添加或刪除特定值。* 默認實現按原樣返回給定的pv。* 棄用* 從5.1開始,支持postProcessProperties(PropertyValues, Object, String)* 參數:* PVS—工廠將要應用的屬性值(永遠不會為空)* PDS——目標bean的相關屬性描述符(忽略了依賴類型——工廠專門處理的依賴類型——已經過濾掉了)* bean——已創建的bean實例,但其屬性尚未設置* beanName—bean的名稱* 返回:* 應用于給定bean的實際屬性值(可以是傳入的PropertyValues實例),或者為null以跳過屬性填充* 拋出:* BeansException -在錯誤的情況下* 參見:* postProcessProperties, org.springframework.beans.MutablePropertyValues*/@Deprecated@Nullabledefault PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {return pvs;}
}

SmartInstantiationAwareBeanPostProcessor接口定義如下:

/***  擴展了InstantiationAwareBeanPostProcessor接口,添加了一個回調函數,用于預測被處理bean的最終類型。* 注:此接口為專用接口,主要供框架內部使用。一般來說,應用程序提供的后處理器應該簡單地實現普通的BeanPostProcessor接口,或者從InstantiationAwareBeanPostProcessorAdapter類派生。即使在點發布版本中,也可能向該接口添加新方法。* 自:* 2.0.3* 參見:* InstantiationAwareBeanPostProcessorAdapter* 作者:* Juergen hoel*/
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {/*** 預測這個處理器的postProcessBeforeInstantiation回調最終返回的bean的類型。* 默認實現返回null。* 參數:* beanClass—bean的原始類beanName—bean的名稱* 返回:* bean的類型,如果不可預測,則為空* 拋出:* BeansException -在錯誤的情況下*/@Nullabledefault Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {return null;}/*** 確定要為給定bean使用的候選構造函數。* 默認實現返回null。* 參數:* beanClass—bean的原始類(不為空)beanName—bean的名稱* 返回:* 候選構造函數,如果未指定,則為空* 拋出:* BeansException -在錯誤的情況下*/@Nullabledefault Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)throws BeansException {return null;}/*** 獲取對指定bean的早期訪問的引用,通常是為了解析循環引用。* 這個回調讓后處理器有機會盡早公開包裝器——也就是說,在目標bean實例完全初始化之前。暴露的對象應該等同于postProcessBeforeInitialization / postProcessAfterInitialization所暴露的對象。請注意,此方法返回的對象將用作bean引用,除非后處理器返回與所述后處理回調不同的包裝器。換句話說:那些處理后回調可能最終公開相同的引用,或者返回那些后續回調的原始bean實例(如果受影響的bean的包裝器已經為對該方法的調用構建了,那么默認情況下它將作為最終bean引用公開)。* 默認實現按原樣返回給定的bean。* 參數:* bean—原始bean實例beanName—bean的名稱* 返回:* 要作為bean引用公開的對象(通常將傳入的bean實例作為默認值)* 拋出:* BeansException -在錯誤的情況下*/default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {return bean;}
}

MergedBeanDefinitionPostProcessor接口定義如下:

/*** 運行時用于合并bean定義的后處理器回調接口。BeanPostProcessor實現可以實現這個子接口,以便對合并的bean定義(原始bean定義的處理副本)進行后處理,Spring BeanFactory使用合并的bean定義來創建bean實例。* 例如,postProcessMergedBeanDefinition方法可以對bean定義進行內省,以便在對bean的實際實例進行后處理之前準備一些緩存的元數據。也允許修改bean定義,但只允許修改用于并發修改的定義屬性。本質上,這只適用于在RootBeanDefinition本身上定義的操作,而不適用于其基類的屬性。* 自:* 2.5* 參見:* org.springframework.beans.factory.config.ConfigurableBeanFactory.getMergedBeanDefinition* 作者:* Juergen hoel*/
public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {/*** 對指定bean的給定合并bean定義進行后處理。* 參數:* beanDefinition—為bean合并的bean定義beanType—托管bean實例的實際類型beanName—bean的名稱* 參見:* AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors*/void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);/*** 通知指定名稱的bean定義已被重置,并且此后處理器應清除受影響bean的所有元數據。* 默認實現為空。* 參數:* beanName—bean的名稱* 自:* 5.1* 參見:* DefaultListableBeanFactory.resetBeanDefinition*/default void resetBeanDefinition(String beanName) {}
}

這里還是有一點想要提醒各位的,BeanPostProcessor是被Spring定義為【鉤子】的,而鉤子,很多時候都是為了改變系統原有行為的。這種改變,通常體現在:對Bean生命周期的影響,或者某個生命周期完成對象的改變等等。(這么說有點晦澀難懂,通俗點講就是:Bean可能不會經過完整生命周期,比如【實例化前】之后,直接就跳到【初始化后】了,沒有經過原本定義的【實例化】、【實例化后】、【其他后置處理】等;或者,原本初始化動作是Spring幫我門完成的,但是由于我們自己介入了,Spring就不幫我們初始化了,而是按照程序員意愿)

文章鏈接:
《【Spring專題】Spring之底層架構核心概念解析》

1.3 核心方法講解

整個IOC實例化的主干過程,主要涉及了【2個類,11個核心方法】。下面,我們將會按照調用次序,依次講解各個方法。

二、方法講解

我們在上面說到過,這里的實例化過程包含了:合并BeanDefinition、加載類、實例化之前、推斷構造方法、實例化、BeanDefinition的后置處理、實例化后等關鍵步驟。所以,在整個調用鏈上,基本上就是干了這些事情。
我們前面說過,本次的實例化代碼入口如下:

	// 如果是單例創建bean實例if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}

2.1 AbstractBeanFactory#getMergedLocalBeanDefinition:合并BeanDefinition

方法調用鏈:AbstractBeanFactory#doGetBean里面調用的
全路徑:org.springframework.beans.factory.support.AbstractBeanFactory#getMergedLocalBeanDefinition
方法注釋:返回合并的RootBeanDefinition,如果指定的bean對應于子bean定義,則遍歷父bean定義。

我們在上節課最后的地方提到過這個,這邊就不重復講了,但是,他是算在實例化過程里面的,嘻嘻

2.2 AbstractAutowireCapableBeanFactory#createBean:創建Bean

方法調用鏈:AbstractBeanFactory#doGetBean里面調用的
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean
方法注釋:這個類的中心方法:創建一個bean實例,填充bean實例,應用后處理器,等等。

源碼如下:

	@Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {if (logger.isTraceEnabled()) {logger.trace("Creating instance of bean '" + beanName + "'");}RootBeanDefinition mbdToUse = mbd;// 步驟一:加載類Class<?> resolvedClass = resolveBeanClass(mbd, beanName);if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {mbdToUse = new RootBeanDefinition(mbd);mbdToUse.setBeanClass(resolvedClass);}// 不用管這個try {mbdToUse.prepareMethodOverrides();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),beanName, "Validation of method overrides failed", ex);}try {// 步驟二:實例化前Object bean = resolveBeforeInstantiation(beanName, mbdToUse);if (bean != null) {return bean;}}catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,"BeanPostProcessor before instantiation of bean failed", ex);}try {// 步驟三:創建BeanObject beanInstance = doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace("Finished creating instance of bean '" + beanName + "'");}return beanInstance;}catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {// A previously detected exception with proper bean creation context already,// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.throw ex;}catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);}}

方法解讀:上面代碼老長一段,trt-catch占了一半,啊哈哈。 這里面的步驟就三個,下面也會講到。

  1. resolveBeanClass:加載類
  2. resolveBeforeInstantiation:實例化前
  3. doCreateBean:真正創建Bean的地方

2.3 AbstractAutowireCapableBeanFactory#resolveBeanClass:加載類

PS:不算很重要,知道有這個步驟就好

方法調用鏈:由2.2的createBean調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeanClass
方法注釋:這個類的中心方法:創建一個bean實例,填充bean實例,應用后處理器,等等。

源碼如下:

protected Class<?> resolveBeanClass(RootBeanDefinition mbd, String beanName, Class<?>... typesToMatch)throws CannotLoadBeanClassException {if (mbd.hasBeanClass()) {return mbd.getBeanClass();}if (System.getSecurityManager() != null) {return AccessController.doPrivileged((PrivilegedExceptionAction<Class<?>>)() -> doResolveBeanClass(mbd, typesToMatch), getAccessControlContext());}else {return doResolveBeanClass(mbd, typesToMatch);}
}

方法解讀:這個代碼外層挺簡單的,也許大家對比System.getSecurityManager() != null這個比較難理解而已。這是Spring內部的一個安全管理器,很多時候都是不開啟的。后面也會出現大量這種代碼,反正遇到這個就直接看else的邏輯就好了。至于里層的doResolveBeanClass源碼如下:

    private Class<?> doResolveBeanClass(RootBeanDefinition mbd, Class<?>... typesToMatch)throws ClassNotFoundException {// 步驟一:獲取默認的類加載器ClassLoader beanClassLoader = getBeanClassLoader();ClassLoader dynamicLoader = beanClassLoader;boolean freshResolve = false;// 步驟二:在我們這個調用鏈上不會走這里,因為typesToMatch為nullif (!ObjectUtils.isEmpty(typesToMatch)) {ClassLoader tempClassLoader = getTempClassLoader();if (tempClassLoader != null) {dynamicLoader = tempClassLoader;freshResolve = true;if (tempClassLoader instanceof DecoratingClassLoader) {DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader;for (Class<?> typeToMatch : typesToMatch) {dcl.excludeClass(typeToMatch.getName());}}}}// 步驟三String className = mbd.getBeanClassName();if (className != null) {// 步驟3.1:這里也不用怎么看,這是Spring表達式解析的東西。Spring表達式這個東西我們用的很少(類似EL表達式,這樣來理解)Object evaluated = evaluateBeanDefinitionString(className, mbd);if (!className.equals(evaluated)) {if (evaluated instanceof Class) {return (Class<?>) evaluated;}else if (evaluated instanceof String) {className = (String) evaluated;freshResolve = true;}else {throw new IllegalStateException("Invalid class name expression result: " + evaluated);}}if (freshResolve) {if (dynamicLoader != null) {try {// 步驟3.2 加載類return dynamicLoader.loadClass(className);}catch (ClassNotFoundException ex) {if (logger.isTraceEnabled()) {logger.trace("Could not load class [" + className + "] from " + dynamicLoader + ": " + ex);}}}return ClassUtils.forName(className, dynamicLoader);}}// 步驟四:正常來說走的是這里。使用默認的類加載器加載類return mbd.resolveBeanClass(beanClassLoader);}

方法解讀:在這里,大體分為4個步驟。步驟一就是獲取類加載器,往里追蹤會發現,實際上調用的是ClassUtils.getDefaultClassLoader()方法。里面的代碼挺簡單的,獲取原則如下:(需要一點JVM底子才懂,不了解也無所謂

  1. 優先返回當前線程中的ClassLoader
  2. 線程中類加載器為null的情況下,返回ClassUtils類的類加載器
  3. 如果ClassUtils類的類加載器為空,那么則表示是Bootstrap類加載器加載的ClassUtils類,那么則返回系統類加載器

步驟二,就是第一個if,在我們這里的調用鏈上是不會進來這里的,因為typeToMatch是null;
步驟三,可看可不看,一般也不會進來這里。這是Spring表達式解析的東西。Spring表達式這個東西我估計大部分人都沒用過吧…(類似EL表達式,這樣來理解)
步驟四,正常來說會執行到這里,然后在這里進行類加載,并且通過反射獲得class對象,代碼如下所示:

	public Class<?> resolveBeanClass(@Nullable ClassLoader classLoader) throws ClassNotFoundException {String className = getBeanClassName();if (className == null) {return null;}Class<?> resolvedClass = ClassUtils.forName(className, classLoader);this.beanClass = resolvedClass;return resolvedClass;}

*2.4 AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation:【實例化前】入口

(PS:終于來到這里了,這才是核心考點

方法調用鏈:由2.2的createBean調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
方法注釋:應用實例化前的后處理器,解析指定bean是否存在實例化前的快捷方式。

源碼如下:

	protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {Object bean = null;if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {// Make sure bean class is actually resolved at this point.if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {Class<?> targetType = determineTargetType(beanName, mbd);if (targetType != null) {bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);if (bean != null) {bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);}}}mbd.beforeInstantiationResolved = (bean != null);}return bean;}

方法解讀:代碼短短的幾行。首先通過hasInstantiationAwareBeanPostProcessors看看是否有InstantiationAwareBeanPostProcessors。有,則調用對應的【實例化前】方法,即applyBeanPostProcessorsBeforeInstantiation這里才是真正干活的地方,后面介紹)。

這里有一個細節,那就是applyBeanPostProcessorsBeforeInstantiation如果返回的對象不為空,則直接調用applyBeanPostProcessorsAfterInitialization走bean的【初始化后】方法了。這說明Bean的生命周期被改變!!而且,如果這里的Bean有值的話,外層直接返回創建成功了,不會繼續往下走了,如下所示:(2.2的createBean調用處代碼)

Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {return bean;
}

2.5 AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation:【實例化前】真正干活的地方

方法調用鏈:由2.4的resolveBeforeInstantiation調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation
方法注釋:將InstantiationAwareBeanPostProcessors應用到指定的bean定義(通過類和名稱),調用它們的postProcessBeforeInstantiation方法。如果返回不為空,則無需Spring幫我們實例化了

源碼如下:

	@Nullableprotected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {Object result = bp.postProcessBeforeInstantiation(beanClass, beanName);if (result != null) {return result;}}return null;}

方法解讀:這里終于出現我們之前說的核心InstantiationAwareBeanPostProcessor。老樣子,遍歷所有的InstantiationAwareBeanPostProcessor,然后調用postProcessBeforeInstantiation()。這里只要找到一個InstantiationAwareBeanPostProcessorpostProcessBeforeInstantiation()返回不為空,則直接停止遍歷。
這個方法其實隱含的意思是:在Spring幫我們實例化之前,Spring會先去找找看,用戶有沒有對當前beanName對應的class有自己的實例化想法,如果有,則Spring就不幫我們創建了。

2.6 AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization:第一次可能調用【初始化后】

方法調用鏈:由2.4的resolveBeforeInstantiation調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
方法注釋:應用初始化后方法。通常調用此方法的時候,會認為已經經過了屬性填充、初始化等

源碼如下:

	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessAfterInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;}

方法解讀:看,這里直接獲取所有BeanPostProcessor 接口,然后應用postProcessAfterInitialization()方法。這里有個空判斷,是接口要求這么做的。返回null表示不想繼續執行剩余的BeanPostProcessor 接口

2.7 AbstractAutowireCapableBeanFactory#doCreateBean:【實例化】入口(包括后續的實例化過程)

方法調用鏈:由2.2的createBean調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
方法注釋:AbstractAutowireCapableBeanFactory這個類的中心方法:創建一個bean實例,填充bean實例,應用后處理器,等等

源碼很長,就不截取了。在這里個方法里面,主要干了4件事情:

  1. 實例化(推斷構造方法、實例化)
  2. BeanDefinition的后置處理
  3. 屬性注入(實例化后、屬性注入)(對的,你沒看錯,【實例化后】是在屬性注入這個方法里面被調用的)
  4. 初始化

方法解讀:正如方法注釋說的,這里【創建一個bean實例,填充bean實例,應用后處理器】等等。這意味著,在這里包含了【實例化】過程中剩余的==【推斷構造方法、實例化、BeanDefinition的后置處理、實例化后等】==步驟。另外,下一個章節會說的【IOC屬性填充】、【IOC-Aware回調】、【IOC初始化】等階段入口其實也是在這個方法中

*2.8 AbstractAutowireCapableBeanFactory#createBeanInstance:實例化

方法調用鏈:由2.7的doCreateBean調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance
方法注釋:AbstractAutowireCapableBeanFactory這個類的中心方法:創建一個bean實例,填充bean實例,應用后處理器,等等

源碼如下:

 protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// 步驟一:再次確保類已經被加載Class<?> beanClass = resolveBeanClass(mbd, beanName);if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());}// 步驟二:使用Supplier創建對象Supplier<?> instanceSupplier = mbd.getInstanceSupplier();if (instanceSupplier != null) {return obtainFromSupplier(instanceSupplier, beanName);}// 步驟三:工廠方法創建對象if (mbd.getFactoryMethodName() != null) {return instantiateUsingFactoryMethod(beanName, mbd, args);}// Shortcut when re-creating the same bean...boolean resolved = false;boolean autowireNecessary = false;if (args == null) {synchronized (mbd.constructorArgumentLock) {if (mbd.resolvedConstructorOrFactoryMethod != null) {resolved = true;autowireNecessary = mbd.constructorArgumentsResolved;}}}if (resolved) {if (autowireNecessary) {return autowireConstructor(beanName, mbd, null, null);}else {return instantiateBean(beanName, mbd);}}// 步驟四:推斷構造方法Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);}// Preferred constructors for default construction?ctors = mbd.getPreferredConstructors();if (ctors != null) {return autowireConstructor(beanName, mbd, ctors, null);}// 步驟五:實例化return instantiateBean(beanName, mbd);}

方法解讀:這里在實例化Bean的時候,采用了多種方式。如:

2.8.1 Supplier創建對象

Supplier,直譯:供應商。
首先判斷BeanDefinition中是否設置了Supplier,如果設置了則調用Supplier的get()得到對象。得直接使用BeanDefinition對象來設置Supplier,比如:

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setInstanceSupplier(new Supplier<Object>() {@Overridepublic Object get() {return new UserService();}
});
context.registerBeanDefinition("userService", beanDefinition);

2.8.2 工廠方法創建對象

如果沒有設置Supplier,則檢查BeanDefinition中是否設置了factoryMethod,也就是工廠方法,有兩種方式可以設置factoryMethod,比如:
方式一:

<bean id="userService" class="com.zhouyu.service.UserService" factory-method="createUserService" />

對應的UserService類為:

public class UserService {public static UserService createUserService() {System.out.println("執行createUserService()");UserService userService = new UserService();return userService;}public void test() {System.out.println("test");}}

方式二:

<bean id="commonService" class="com.zhouyu.service.CommonService"/>
<bean id="userService1" factory-bean="commonService" factory-method="createUserService" />

對應的CommonService的類為:

public class CommonService {public UserService createUserService() {return new UserService();}
}

Spring發現當前BeanDefinition方法設置了工廠方法后,就會區分這兩種方式,然后調用工廠方法得到對象。
值得注意的是,我們通過@Bean所定義的BeanDefinition,是存在factoryMethod和factoryBean的,也就是和上面的方式二非常類似,@Bean所注解的方法就是factoryMethod,AppConfig對象就是factoryBean。如果@Bean所所注解的方法是static的,那么對應的就是方式一。

2.8.3 構造方法創建對象

RT。

2.9 AbstractAutowireCapableBeanFactory#autowireConstructor:推斷構造方法

方法調用鏈:由2.7的createBeanInstance調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#autowireConstructor
方法注釋:推斷構造方法,并且創建對象。

代碼好長,我就不截取了。這里的代碼看不看都還好,主要還是理解流程跟原則就好。

  1. 如果一個類只有一個構造方法,不管這個構造方法是有參還是無參,Spring都會使用這個構造方法創建對象
  2. 如果有多個構造方法,則看有沒有無參構造方法。因為無參構造方法有默認的含義,若有,則選擇無參的構造方法創建對象;
  3. 如果有多個構造方法,且沒有無參,則看構造方法上是否有@Autowired注解,有就選擇,沒有就報錯

額外的,在推斷構造方法邏輯中除開會去選擇構造方法以及查找入參對象意外,會還判斷是否在對應的類中是否存在使用**@Lookup注解**了方法。如果存在則把該方法封裝為LookupOverride對象并添加到BeanDefinition中。
?

在實例化時,如果判斷出來當前BeanDefinition中沒有LookupOverride,那就直接用構造方法反射得到一個實例對象。如果存在LookupOverride對象,也就是類中存在@Lookup注解了的方法,那就會生成一個代理對象。
@Lookup注解就是方法注入,使用demo如下:

@Component
public class UserService {private OrderService orderService;public void test() {OrderService orderService = createOrderService();System.out.println(orderService);}@Lookup("orderService")public OrderService createOrderService() {return null;}}

2.10 AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors:BeanDefinition后置處理

方法調用鏈:由2.7的doCreateBean調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
方法注釋:將MergedBeanDefinitionPostProcessors應用于指定的bean定義,調用它們的postProcessMergedBeanDefinition方法。

源碼如下:

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {for (MergedBeanDefinitionPostProcessor processor : getBeanPostProcessorCache().mergedDefinition) {processor.postProcessMergedBeanDefinition(mbd, beanType, beanName);}}

方法解讀:Bean對象實例化出來之后,接下來就應該給對象的屬性賦值了。在真正給屬性賦值之前,Spring又提供了一個擴展點,即當前的MergedBeanDefinitionPostProcessors。這里的主要難點還是,這種類型的BeanPostProcessor的應用場景,可能才是大家比較關心的。那你們覺得,這里的主要應用場景應該是什么呢?其實,按照Spring的生命周期來說,他現在提供給你的拓展點,只能是【實例化】階段后的生命周期了,畢竟過去已經發生的已經沒辦法改變。所以,我們可以在這里,干預【初始化】這個階段,如下示例:

public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Object newUser = context.getBean("user");System.out.println(newUser);
}// 聲明bean
@Component
public class User {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public void myInit() {System.out.println("這是我自己指定的初始化方法");}
}// 聲明一個MergedBeanDefinitionPostProcessor,然后改變了User這個Bean的初始化方法
@Component
public class MyBeanPostProcessor implements MergedBeanDefinitionPostProcessor {@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {if (beanName.equals("user")) {beanDefinition.setInitMethodName("myInit");}}
}

PS:在Spring源碼中,AutowiredAnnotationBeanPostProcessor就是一個MergedBeanDefinitionPostProcessor,它的postProcessMergedBeanDefinition()中會去查找注入點,并緩存在AutowiredAnnotationBeanPostProcessor對象的一個Map中(injectionMetadataCache),為依賴注入做準備。

2.11 AbstractAutowireCapableBeanFactory#populateBean:屬性注入(包含:實例化后)

(PS:屬性注入不是我們這里需要關注的地方,但因為【實例化后】在這個方法里面,所以就只能點進來了)

方法調用鏈:由2.7的doCreateBean調用過來
全路徑:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
方法注釋:使用來自bean定義的屬性值在給定的BeanWrapper中填充bean實例(屬性填充/依賴注入)。

populateBean中關鍵源碼如下:

	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {if (!bp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}

方法解讀:在處理完BeanDefinition后,Spring又設計了一個擴展點:InstantiationAwareBeanPostProcessorpostProcessAfterInstantiation(),比如:

@Component
public class ZhouyuInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {if ("userService".equals(beanName)) {UserService userService = (UserService) bean;userService.test();}return true;}
}

上述代碼就是對userService所實例化出來的對象進行處理。這個擴展點,在Spring源碼中基本沒有怎么使用。估計是為了后面拓展做準備

方法總結后

以后可能不這么寫了,挺麻煩的。我這樣圈關鍵方法跟鏈路的原因,純粹是想加深自己的印象,我也不知道放出來給大伙看,你們能否學習到什么。

三、實例化邏輯流程圖

在這里插入圖片描述

學習總結

  1. 學習了實例化的過程
  2. 學習了實例化過程中用到的2種【Bean后置處理器】:InstantiationAwareBeanPostProcessorMergedBeanDefinitionPostProcessor以及他們內部的一些方法

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/38181.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/38181.shtml
英文地址,請注明出處:http://en.pswp.cn/news/38181.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

oracle修改臨時表出現已使用的事務正在處理臨時表問題

錯誤提示&#xff1a; ORA-14450:試圖訪問已經在使用的事務處理臨時表 解決方法&#xff1a; 通過第一句sql來查找臨時表的object_id &#xff0c;然后代入第二局sql來生成第三句sql語句。 最后再執行第三句sql語句即可kill session&#xff0c;執行修改表的操作。 SELECT * F…

華為OD機試-射擊比賽成績

題目描述 射擊比賽成績統計 給定一個射擊比賽成績單 包含多個選手若干次射擊的成績分數 請對每個選手按其最高三個分數之和進行降序排名 輸出降序排名后的選手ID序列 條件如下: 一個選手可以有多個射擊成績的分數 且次序不固定 如果一個選手成績小于三個 則認為選手的所有成績…

【Go 基礎篇】Go語言基本數據類型轉換:字符串、整數、浮點數、字符與布爾類型的轉換

介紹 在計算機編程中&#xff0c;不同的數據類型用于表示不同種類的數據。在Go語言&#xff08;Golang&#xff09;中&#xff0c;基本數據類型包括字符串、整數、浮點數、字符和布爾類型。在實際開發中&#xff0c;經常需要進行不同數據類型之間的轉換&#xff0c;以滿足不同…

安達發APS|APS排產軟件之計劃甘特圖

在當今全球化和競爭激烈的市場環境下&#xff0c;制造業企業面臨著巨大的壓力&#xff0c;如何在保證產品質量、降低成本以及滿足客戶需求的同時&#xff0c;提高生產效率和競爭力成為企業需要迫切解決的問題。在這個背景下&#xff0c;生產計劃的制定和執行顯得尤為重要。然而…

2023年京東按摩儀行業數據分析(京東銷售數據分析)

近年來&#xff0c;小家電行業憑借功能與顏值&#xff0c;取代黑電和白電&#xff0c;成為家電市場的主要增長點。在這一市場背景下&#xff0c;顏值更高、功能更豐富、品種更齊全的各類按摩儀&#xff0c;借助新消費和電子商務的風潮&#xff0c;陸續被推上市場。今年&#xf…

【Cocos Creator 項目實戰 】消滅星星加強版(附帶完整源碼工程)

本文乃Siliphen原創&#xff0c;轉載請注明出處 目錄 概述 游戲整體流程 游戲框架設計 單一職責的類 主要流程控制類 核心玩法模塊 UI&#xff1a; 游戲世界&#xff1a; 本文項目的代碼組織結構 作者項目實踐總結 場景只有一個入口腳本 盡量少在節點上掛載腳本 構…

從零構建深度學習推理框架-8 卷積算子實現

其實這一次課還蠻好理解的&#xff1a; 首先將kernel展平&#xff1a; for (uint32_t g 0; g < groups; g) {std::vector<arma::fmat> kernel_matrix_arr(kernel_count_group);arma::fmat kernel_matrix_c(1, row_len * input_c_group);for (uint32_t k 0; k < k…

macOS(m芯片)連接服務器及其進行文件傳輸的各種方式的詳解

說明&#xff1a;使用了macOS后發現&#xff0c;win系統能使用的xshell、xftp等連接服務器及其文件傳輸等軟件均不能使用了&#xff0c;沒有兼容的版本。所以我們剛切換到mac系統該如何去適應呢。 一、連接遠程服務器 macOS中前文也說道我們使用的是iterm2進行終端控制的&…

基于深度信念神經網絡的礦石產量預測,基于DBN的礦石產量預測,DBN的詳細原理

目錄 背影 DBN神經網絡的原理 DBN神經網絡的定義 受限玻爾茲曼機(RBM) DBN的礦石產量預測 基本結構 主要參數 數據 MATALB代碼 結果圖 展望 背影 DBN是一種深度學習神經網絡,擁有提取特征,非監督學習的能力,是一種非常好的分類算法,本文將DBN算法進行礦石產量預測 DB…

Spring Boot Maven package時顯式的跳過test內容

在pom.xml的編譯插件部分顯式的增加一段內容&#xff1a; <plugin> <!-- maven打包時&#xff0c;顯式的跳過test部分 --><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.…

流量日志分析--實操

[鶴城杯 2021]流量分析 <--第一道流量分析不難,主要就是布爾盲注的流量包分析,直接查看http請求包即可我們可以通過觀察看到注入成功的響應長度不同,這里成功的為978字節,失敗的994字節.不要問為什么.其實也可以直接判斷.978的流量比994的少了非常多 顯然就是成功的(因為這里…

Docker中部署redis

1.部署redis要求 2.部署教程 連接容器中的redis redis部署完畢

大模型基礎:GPT家族與提示學習

大模型基礎:GPT 家族與提示學習 從 GPT-1 到 GPT-3.5 GPT(Generative Pre-trained Transformer)是 Google 于2018年提出的一種基于 Transformer 的預訓練語言模型。它標志著自然語言處理領域從 RNN 時代進入 Transformer 時代。GPT 的發展歷史和技術特點如下: GPT-12018年6月…

QQ附近人引流的幾個詳細方法,qq附近人引流腳本實操演示教程

大家好我是你們的小編一辭腳本&#xff0c;今天給大家分享新的知識&#xff0c;很開心可以在CSDN平臺分享知識給大家,很多伙伴看不到代碼我先錄制一下視頻 在給大家做代碼&#xff0c;給大家分享一下qq引流腳本的知識和視頻演示 不懂的小伙伴可以認真看一下&#xff0c;我們一…

【CSS】CSS 布局——常規流布局

<h1>基礎文檔流</h1><p>我是一個基本的塊級元素。我的相鄰塊級元素在我的下方另起一行。</p><p>默認情況下&#xff0c;我們會占據父元素 100%的寬度&#xff0c;并且我們的高度與我們的子元素內容一樣高。我們的總寬度和高度是我們的內容 內邊距…

Flink筆記

下面是你提供的文字整理后的結果&#xff1a; 1. Flink是一個針對流數據和批數據的分布式處理引擎&#xff0c;同時支持原生流處理的開源框架。 - 延遲低(毫秒級)&#xff0c;且能夠保證消息傳輸不丟失不重復。 - 具有非常高的吞吐(每秒千萬級)。 - 支持原生流處理。…

echarts-convert.js使用

echarts-convert.js demo 點擊下載 1、本地安裝phantom.js插件 點擊下載 2、更改文件路徑 &#xff08;D:\phantomjs-2.1.1-windows\bin&#xff09;改為本地項目文件路徑 3、打開cmd命令行&#xff0c;并格式化語言 運行以下命令 將命令行語言改為中文簡體 chcp 65001…

(二分查找) 11. 旋轉數組的最小數字 ——【Leetcode每日一題】

?劍指 Offer 11. 旋轉數組的最小數字 難度&#xff1a;簡單 把一個數組最開始的若干個元素搬到數組的末尾&#xff0c;我們稱之為數組的旋轉。 給你一個可能存在 重復 元素值的數組 numbers &#xff0c;它原來是一個升序排列的數組&#xff0c;并按上述情形進行了一次旋轉…

springboot整合kafka多數據源

整合kafka多數據源 項目背景依賴配置生產者消費者消息體 項目背景 在很多與第三方公司對接的時候&#xff0c;或者處在不同的網絡環境下&#xff0c;比如在互聯網和政務外網的分布部署服務的時候&#xff0c;我們需要對接多臺kafka來達到我們的業務需求&#xff0c;那么當kafk…

【Vue-Router】路由過渡動效

在 Vue Router 中&#xff0c;你可以通過過渡動效&#xff08;Transition Effects&#xff09;為路由切換添加平滑的過渡效果&#xff0c;從而提升用戶體驗。過渡動效可以使用 Vue 的 <transition> 組件和 CSS 過渡來實現。 基本使用&#xff1a; 對導航使用動畫&#…