【深入淺出Spring原理及實戰】「源碼調試分析」深入源碼探索Spring底層框架的的refresh方法所出現的問題和異常

来源:https://www.cnblogs.com/liboware/archive/2023/04/23/17347682.html
-Advertisement-
Play Games

學習Spring源碼的建議 閱讀Spring官方文檔,瞭解Spring框架的基本概念和使用方法。 下載Spring源碼,可以從官網或者GitHub上獲取。 閱讀Spring源碼的入口類,瞭解Spring框架的啟動過程和核心組件的載入順序。 閱讀Spring源碼中的註釋和文檔,瞭解每個類和方法的作用和 ...


學習Spring源碼的建議

  1. 閱讀Spring官方文檔,瞭解Spring框架的基本概念和使用方法。

  2. 下載Spring源碼,可以從官網或者GitHub上獲取。

  3. 閱讀Spring源碼的入口類,瞭解Spring框架的啟動過程和核心組件的載入順序。

  4. 閱讀Spring源碼中的註釋和文檔,瞭解每個類和方法的作用和用法。

  5. 調試Spring源碼,可以通過IDEA等工具進行調試,瞭解Spring框架的內部實現和運行過程。

  6. 參考Spring源碼的測試用例,瞭解Spring框架的各個組件的使用方法和測試方法。

  7. 參考Spring源碼的設計模式和最佳實踐,瞭解如何設計和實現高質量的Java應用程式。

  8. 參與Spring社區,與其他開發者交流和分享經驗,瞭解Spring框架的最新動態和發展趨勢。


學習Spring源碼的好處

  1. 更深入地瞭解Spring框架的內部實現和運行機制,可以更好地理解和使用Spring框架。

  2. 學習Spring源碼可以提高自己的編程能力和代碼質量,瞭解Spring框架的設計模式和最佳實踐,可以應用到自己的項目中。

  3. 學習Spring源碼可以幫助開發者解決一些複雜的問題和難點,提高自己的解決問題的能力。

  4. 學習Spring源碼可以幫助開發者更好地理解Java語言和麵向對象編程的思想,提高自己的編程水平。

  5. 學習Spring源碼可以幫助開發者更好地瞭解Java生態系統和相關技術,如AOP、IOC、MVC等。

  6. 學習Spring源碼可以幫助開發者更好地瞭解開源軟體的開發和維護過程,提高自己的開源軟體開發能力。

refresh方法所出現的問題和異常

最近抽空總結一下之前通用的Spring框架所出現的問題和異常情況,當創建屬於自己的ApplicationContext對象的時候,經常會遇到這麼幾條異常消息:

  1. LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the context: ......

LifecycleProcessor對象沒有初始化,在調用context的生命周期方法之前必須調用'refresh'方法。

  1. BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext

BeanFactory對象沒有初始化或已經關閉了,使用ApplicationContext獲取Bean之前必須調用'refresh'方法。

  1. ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: ......

ApplicationEventMulticaster對象沒有初始化,在context廣播事件之前必須調用'refresh'方法。

這幾條異常消息都與refresh方法有關,那拋出這些異常的原因到底是什麼,為什麼在這麼多情況下一定要先調用refresh方法(定義在AbstractApplicationContext類中),在此這前我們先看看refresh方法中又幹了些什麼?

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //刷新之前的準備工作,包括設置啟動時間,是否激活標識位,初始化屬性源(property source)配置
        prepareRefresh();
        //由子類去刷新BeanFactory(如果還沒創建則創建),並將BeanFactory返回
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //準備BeanFactory以供ApplicationContext使用
        prepareBeanFactory(beanFactory);
        try {
            //子類可通過修改此方法來對BeanFactory進行修改
            postProcessBeanFactory(beanFactory);
            //實例化並調用所有註冊的BeanFactoryPostProcessor對象
            invokeBeanFactoryPostProcessors(beanFactory);
            //實例化並調用所有註冊的BeanPostProcessor對象
            registerBeanPostProcessors(beanFactory);
            //初始化MessageSource
            initMessageSource();
            //初始化事件廣播器
            initApplicationEventMulticaster();
            //子類覆蓋此方法在刷新過程做額外工作
            onRefresh();
            //註冊應用監聽器ApplicationListener
            registerListeners();
            //實例化所有non-lazy-init bean
            finishBeanFactoryInitialization(beanFactory);
            //刷新完成工作,包括初始化LifecycleProcessor,發佈刷新完成事件等
            finishRefresh();
        }
        catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }
    }
}

與此三條異常消息相關的方法分別為:

finishRefresh

LifecycleProcessor not initialized - call 'refresh' before invoking lifecycle methods via the context:

protected void finishRefresh() {
    // //初始化LifecycleProcessor
    initLifecycleProcessor();
    // Propagate refresh to lifecycle processor first.
    getLifecycleProcessor().onRefresh();
    // Publish the final event.
    publishEvent(new ContextRefreshedEvent(this));
    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}

如果沒有調用finishRefresh方法,則lifecycleProcessor成員為null。

obtainFreshBeanFactory

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();//刷新BeanFactory,如果beanFactory為null,則創建
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

refreshBeanFactory()為一抽象方法,真正實現在AbstractRefreshableApplicationContext類中:

@Override
protected final void refreshBeanFactory() throws BeansException {
	//如果beanFactory已經不為null,則銷毀beanFactory中的Bean後自行關閉
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();//創建beanFactory
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;//對beanFactory成員進行賦值
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

如果沒有調用obtainFreshBeanFactory()方法則beanFactory成員為null。

initApplicationEventMulticaster

protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
        }
    }
    else {
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
                    APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                    "': using default [" + this.applicationEventMulticaster + "]");
        }
    }
}

而這三個方法調用都在refresh()方法中,由上面的分析可知,如果沒有調用refresh方法,則上下文中的lifecycleProcessor,beanFactory,applicationEventMulticaster成員都會為null。至此可以來詳細分析這三條異常消息的緣由了。

下麵是針對上面三條異常消息的三段測試代碼,順序相對應:

異常的測試案例(1)

public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
    applicationContext.setConfigLocation("application-context.xml");
    applicationContext.start();
    applicationContext.close();
}

對於第一條異常消息,異常堆棧出錯在applicationContext.start();下麵是start()方法源碼:

public void start() {
    getLifecycleProcessor().start();
    publishEvent(new ContextStartedEvent(this));
}

可以看到start()方法中要先獲取lifecycleProcessor對象,而預設構造方法中並沒用調用refresh方法,所以lifecycleProcessor為null,故而在getLifecycleProcessor()方法中拋出了此異常消息。

異常的測試案例(2)

public static void main(String[] args) {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
    applicationContext.setConfigLocation("application-context.xml");
    applicationContext.getBean("xtayfjpk");
    applicationContext.close();
}

第二條異常消息,異常堆棧出錯在applicationContext.getBean("xtayfjpk"),applicationContext.getBean()方法調用的是上下文中beanFactory的getBean()方法實現的,獲取BeanFactory對象的代碼在其基類ConfigurableListableBeanFactory中的getBeanFactory()方法中:

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
    synchronized (this.beanFactoryMonitor) {
        if (this.beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                    "call 'refresh' before accessing beans via the ApplicationContext");
        }
        return this.beanFactory;
    }
}

由於ClassPathXmlApplicationContext的預設構造方法沒有調用refresh()方法,所以beanFactory為null,因此拋出異常。

異常的測試案例(3)

public static void main(String[] args) {
    GenericApplicationContext parent = new GenericApplicationContext();
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.setParent(parent);
    context.refresh();
    context.start();
    context.close();
}

這其中提到了生命周期方法,其實就是定義在org.springframework.context.Lifecycle介面中的start(), stop(), isRunning()三個方法,如果是剛開始學習Spring的話,創建ClassPathXmlApplicationContext對象時應該是這樣的:ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application-context.xml")。

這樣直接調用start()方法卻又不會出現異常,這是為什麼呢?這是因為ClassPathXmlApplicationContext(String configLocation)這個構造方法最終調用的是:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {//refresh傳遞值為true,這樣就自動調用了refresh方法進行了刷新
        refresh();
    }
}

第三條異常消息,異常堆棧出錯在context.refresh(),但是如果沒有設置父上下文的話context.setParent(parent),例子代碼是不會出現異常的。這是因為在refresh方法中的finishRefresh()方法調用了publishEvent方法:

public void publishEvent(ApplicationEvent event) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }
    getApplicationEventMulticaster().multicastEvent(event);
    if (this.parent != null) {
        this.parent.publishEvent(event);
    }
}

從上面可以看到:如果父上下文不為null,則還需要調用父容器的pushlishEvent方法,而且在該方法中調用了getApplicationEventMulticaster()方法以獲取一個事件廣播器,問題就出現在這裡:

private ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
    if (this.applicationEventMulticaster == null) {//如果為null則拋異常
        throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
                "call 'refresh' before multicasting events via the context: " + this);
    }
    return this.applicationEventMulticaster;
}

而applicationEventMulticaster就是在refresh方法中的initApplicationEventMulticaster方法在實例化的,則於父上下文沒有調用過refresh方法,所以父上下文的applicationEventMulticaster成員為null,因此拋出異常。

問題總結

綜上所述,其實這三條異常消息的根本原因只有一個,就是當一個上下文對象創建後沒有調用refresh()方法。在Spring中ApplicationContext實現類有很多,有些實現類在創建的過程中自動調用了refresh()方法,而有些又沒有,如果沒有則需要自己手動調用refresh()方法。一般說來實現WebApplicationContext介面的實現類以及使用預設構造方法創建上下文對象時不會自動refresh()方法,其它情況則會自動調用。

本文來自博客園,作者:洛神灬殤,轉載請註明原文鏈接:https://www.cnblogs.com/liboware/p/17347682.html,任何足夠先進的科技,都與魔法無異。


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

-Advertisement-
Play Games
更多相關文章
  • 在前端工程化中,JavaScript 依賴包管理是非常重要的一環。依賴包通常是項目所依賴的第三方庫、工具和框架等資源,它們能夠幫助我們減少重覆開發、提高效率並且確保項目可以正確的運行。 ...
  • 需求 根據許可權編碼禁用按鈕 阻止當前 dom 綁定的點擊事件,禁用狀態(opacity 半透明?? 或者 display: none?? ) 嘗試 開發環境用 Chrome 跑,一切正常,構建打包後去真機跑,按鈕沒控制住 (用 HBX -發行-原生應用 app 製作 wgt 包)開發環境: HBX: ...
  • 防抖(debounce) 一句話概括:防抖是給定一個時間周期,如果觸發事件的周期小於該事件(也就是觸發過快),則不會觸發事件。 舉個例子:我給定的時間周期是1s,如果我在觸發第一次事件後1s內觸發該事件,則重新開始計時,直到觸發周期大於1s才會執行事件的方法。 function debounce(f ...
  • 前言 數組是幾乎所有編程語言的基礎語法,JavaScript因為語法特性,之前缺少一些集合類對象,對數組的使用就會更多一些,因此我們更需要理解數組知識。 然而大部分人對數組都已經非常熟悉了,所以本文將不會介紹數組的基礎語法和用法,而是從JavaScript中數組的一些特殊之處入手,通過這些少有特性的 ...
  • 交互設計原則有很多,《小紅書的52條設計原則》可以學習下,非常棒的輸出,值得做產品設計的童鞋學習一下。 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹 VLD 源碼的編譯。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. VLD 庫的依賴文件 以 vld2.5.1 版本為例,下載源碼 後,源碼包中各文件的用途可看本人另一篇博客 【VLD】源碼文件概覽。使用 vld2.5.1- ...
  • 本案例實現一個test命名空間,此命名空間內有兩個函數,分別為getName()和getNameSpace(); 聲明命名空間及函數 namespace test{ const std::string& getName()和(); const std::string& getNameSpace(); ...
  • 常量指針與指針常量 #include<iostream> using namespace std; int main() { int a = 10; int b = 20; // 常量指針與指針常量 // 1.常量指針 const修飾指針 指針的指向是可以修改的(指針變數中存的地址值可以修改) 指針 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...