springboot的@ConditionalOnClass註解

来源:https://www.cnblogs.com/teach/archive/2022/08/02/16519087.html
-Advertisement-
Play Games

大家好,我是“良工說技術”。 今天給大家帶來的是springboot中的@ConditionalOnClass註解的用法。上次的@ConditionalOnBean註解還記得嗎? 一、@ConditionalOnClass註解初始 看下@CodidtionalOnClass註解的定義, 需要註意的有 ...


大家好,我是“良工說技術”。

今天給大家帶來的是springboot中的@ConditionalOnClass註解的用法。上次的@ConditionalOnBean註解還記得嗎?

一、@ConditionalOnClass註解初始

看下@CodidtionalOnClass註解的定義,

需要註意的有兩點,

  1. 該註解可以用在類及方法上;類指的是標有@Configuration的類,方法是標有@Bean的方法;
  2. 該註解使用了@Conditional註解標記;這是重點

看到這裡,有小伙伴會疑惑,講了那麼多@Conditional註解的作用是什麼,不急,作用馬上來。

@ConditionalOnClass註解的作用是當項目中存在某個類時才會使標有該註解的類或方法生效;

這句話有點拗口,通俗的講,@ConditionalOnClass標識在@Configuration類上,只有存在@ConditionalOnClass中value/name配置的類該Configuration類才會生效;@ConditionalOnClass標識在@Bean方法上,只有隻有存在@ConditionalOnClass中value/name配置的類方法才會生效。看具體的實例更容易理解些

二、@ConditionalOnClass註解用法

從上面@ConditionalOnClass註解的定義中我們知道該註解可以配置兩個屬性,分別是value和name,其中value和name都是數組,只不過內容不一樣,

value是Class的數組,name是全限類名的字元串。

1、使用value屬性

開始,我一直使用value屬性進行配置,但是總是報錯,比如我配置

@Configuration
@ConditionalOnClass(value = {Client.class})
public class MyAutoConfig {
    public MyAutoConfig(){
        System.out.println("constructor MyAutoConfig");
    }
}

該Client是下麵的類,

org.springframework.boot.autoconfigure.data.elasticsearch.Client

它是ES中的一個類,我本身配置的含義是只有在Client存在的時候MyAutoConfig才會生效,但是總是不成功。你知道為什麼不成功嗎?

這是因為我沒有引ES的依賴,導致在我的classpath中沒有這個類,按照@ConditionalOnClass的理解,應該是不存在則不會生效,但是由於沒有這個類,導致的問題是:無法編譯,提示下麵的錯誤

java: 找不到符號
  符號: 類 Client

這是可以理解的,因為沒有這個類,而我要引用這個類肯定是引用不到的,所以編譯是失敗的,也就程式跑不起來。那麼存在一個問題,@ConditionalOnClass註解的value屬性要在什麼情況下使用?

這裡有一個mybatisplus的配置類,

其配置類上標識了@ConditionalOnClass註解,該註解中配置了value屬性,且配置了SqlSessionFactory和SqlSessionFactoryBean兩個類,

MyBatisPlusAutoConfiguration是在mybatis-plus-boot-starter的jar包下

SqlSessionFactory是在mybatis的jar包下

SqlSessionFactoryBean是在mybaits-spring的jar包下

這三個類分屬於不同的jar包,如果我在一個項目中引入了mybatis-plus-boot-starter的jar包,沒有引入mybatis的jar包那麼MybatisPlusAutoConfiguration不會生效,也就是只有mybatis和mybatis-spring的jar包都引入了,MybatisPlusAutoConfiguration才會生效,才會被納入spring容器的管理。

需要註意一點:為了防止少引包,在mybatis-plus-boot-starter中會依賴mybatis和mybatis-spring,這也是starter的好處,不會少引包,需要哪些依賴它都引好了。

那麼再回到問題的開始,為什麼,我配置了一個不存在的類就沒成功,那是因為java的源文件需要編譯,在編譯時會檢查類是否存在,不存在肯定是編譯不通過的;而如果引用的是jar包中的文件引用另外一個jar的,則是因為jar包經過了編譯,已經打包成功了,故不存在問題。

通過value屬性需要結合jar包的方式,這裡就不演示了,感興趣的小伙伴可以自己嘗試。通過name屬性來指定。

2、使用name屬性

@ConditionalOnClass註解還有name屬性,name屬性指定的是全限類名,也就是包含包名+類名。看下我的配置,

@Configuration
@ConditionalOnClass(name = {"com.my.template.config.ClassA"})
public class MyAutoConfig {
    public MyAutoConfig(){
        System.out.println("constructor MyAutoConfig");
    }
}

這裡配置了“com.my.template.config.ClassA”,ClassA是我的一個存在的類,

下麵啟動,看下在啟動日誌中是否有“constructor MyConfig”列印,

constructor MyAutoConfig
constructor MyAutoConfig2
constructor classA
2022-07-30 17:18:54.113 

看到了,日誌說明name配置是生效的,也就是存在ClassA則MyAutoConfig會註冊到spring的容器中。作為對比,下麵配置一個不存在的類ClassD,

@Configuration
@ConditionalOnClass(name = {"com.my.template.config.ClassD"})
public class MyAutoConfig {
    public MyAutoConfig(){
        System.out.println("constructor MyAutoConfig");
    }
}

看下啟動日誌

constructor MyAutoConfig2
constructor classA
2022-07-30 21:43:30.550  INFO 13116 --- [  

從上面的日誌可以看到,沒有列印出來想要的日誌,說明MyAutoConfig沒有註冊到spring的容器中。

我們知道name屬性是一個數組,上面僅僅配置了一個類,如果配置多個會是什麼樣子,感興趣的可以自己嘗試,這裡這直接給出答案,只有name屬性中配置的全部滿足相應的配置類才會生效。

不知道,你是否對@ConditionalOnClass是怎麼實現的感興趣嗎,繼續往下看,大揭秘了。

三、@ConditionalOnClass是怎麼實現的

要理解@ConditionalOnClass是怎麼實現的還是要回到該註解的定義上,前邊提到該註解被

@Conditional(OnClassCondition.class)

註解標識,@Conditional註解的含有是要滿足條件才會生效,該註解後邊再看。今天的主角是OnClassCondition類,看下其繼承關係

重點關註XXCondition即可,可以看到最終實現了Condition介面,@Conditional註解的本質就是考查是否滿足Codition介面的matches()方法,所以這看SpringBootCondition中matches方法的實現,

@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //獲得該註解標準的類或方法
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
            //模板方法,該實現在OnClassCodition類中
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
            //返回是否符合條件
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "
					+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "
					+ "that class. This can also happen if you are "
					+ "@ComponentScanning a springframework package (e.g. if you "
					+ "put a @ComponentScan in the default package by mistake)", ex);
		}
		catch (RuntimeException ex) {
			throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
		}
	}

getMatchOutCome()方法使用了模板方法,實現在OnClassCondition類中,這是最要的方法,

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
         //獲得@ConditionalOnClass註解中配置的value和name屬性的值
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
            //判斷@ConditionOnClass註解配置的類是否都可以載入到,如有載入不到的則放到missing中
			List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
			if (!missing.isEmpty()) {
                //有載入不到的,則返回ConditionOutcome對下,其中屬性match為false
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
						.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
					.found("required class", "required classes")
					.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
		}
        //@ConditionalOnMissingClass的處理邏輯
		List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
		if (onMissingClasses != null) {
			List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
			if (!present.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class)
						.found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
					.didNotFind("unwanted class", "unwanted classes")
					.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
		}
        //返回ConditionOutCome對下,其match屬性為true
		return ConditionOutcome.match(matchMessage);
	}

上面的代碼已經給出了註釋,對應@ConditionalOnClass註解的處理就是解析器配置的value和name屬性,判斷配置的類是否載入到,如有未載入到的則直接返回屬性match=false的ConditionOutcome對象,那麼是如何判斷是否載入到的,是通過FilteringSpringBootCondition中的filter方法,

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
			ClassLoader classLoader) {
		if (CollectionUtils.isEmpty(classNames)) {
			return Collections.emptyList();
		}
		List<String> matches = new ArrayList<>(classNames.size());
		for (String candidate : classNames) {
            //迴圈調用matches方法
			if (classNameFilter.matches(candidate, classLoader)) {
				matches.add(candidate);
			}
		}
		return matches;
	}

對於@CoditionalOnClass的處理該方法傳入的參數為

List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);

那麼也就是調用ClassNameFilter.MISSING的matches方法,其方法如下

可以看到調用的是!isPresent方法,看下該方法的實現,

static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }

            try {
                //具體實現邏輯
                FilteringSpringBootCondition.resolve(className, classLoader);
                return true;
            } catch (Throwable var3) {
                return false;
            }
        }

具體的實現在resolve方法中,且該方法被try catch包住了,如果載入不到,直接返回false。

protected static Class<?> resolve(String className, ClassLoader classLoader) throws ClassNotFoundException {
        return classLoader != null ? Class.forName(className, false, classLoader) : Class.forName(className);
    }

看到這裡,大家明白了,@ConditionalOnClass註解中判斷配置的類是否存在使用的方法是Class.forName,類載入。

四、總結

本文主要認識了@ConditionalOnClass註解,分析了其註解的原理,如何判斷配置的類是否存在。

  1. @ConditionalOnClass註解有兩個屬性,分別是value和name,註意其配置方式;
  2. @ConditionalOnClass註解判斷配置的類是否存在的方式是通過Class.forName的方式;

 

推薦閱讀

springboot的@ConditionalOnBean註解

深入理解springboot的自動註入

我的第一個springboot  starter

 

一個愛寫文章的程式員,歡迎關註我的公眾號“良工說技術”。我有故事,你有酒嗎
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近,在對公司的一個老項目進行優化調整。有個使用的三方插件報表頁面,一旦查詢時間過長就會自動異常並使瀏覽器崩潰,由於這個插件只有個前人遺留的dll文件,實在看不懂裡面的代碼無從下手,既然項目前端大部分是基於EasyUI做的,想著就直接用EasyUI的DataGrid做數據報表明細展示。 由於之前很少 ...
  • 網頁是一個頁面,網站是由多個網頁組成的! 我們在使用代碼編寫的時候能看到這樣的東西,具體內容如下,比較基礎: 認識SEO? SEO就是搜索引擎優化,作用就是你找查找相關內容時,能夠優先給你展示這個內容。 SEO三大標簽(一般由想乾人員提供): title(網頁標簽)、description(網頁描述 ...
  • 項目背景 我們的系統(一個 ToB 的 Web 單頁應用)前端單頁應用經過多年的迭代,目前已經累積有大幾十萬行的業務代碼,30+ 路由模塊,整體的代碼量和複雜度還是比較高的。 項目整體是基於 Vue + TypeScirpt,而構建工具,由於最早項目是經由 vue-cli 初始化而來,所以自然而然使 ...
  • 什麼時候精靈圖呢? 通常在渲染頁面的時候,需要伺服器向我們發送數據,但有的時候一個頁面需要多張圖時,伺服器就會處於連續發圖的工作狀態,但如果我們把需要的圖都放在一張圖上,這樣可以大大的減少服務的工作負擔,打個比喻。伺服器發一張圖是,工作流程是:找到圖片——讀取圖片——發送圖片,如果是發送5個圖片時, ...
  • 蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 我們都知道,經過多年的發展和無數Java開發者的不懈努力,Java已經由一門單純的電腦編程語言,逐漸演變成一套強大的以及仍在可持續發展中的技術體系平臺。 雖然,Java設計者們根據不同的技術規範,把 ...
  • 1. 登錄用戶數據獲取 登錄成功之後,在後續的業務邏輯中,開發者可能還需要獲取登錄成功的用戶對象,如果不使用任何安全管理框架,那麼可以將用戶信息保存在HttpSession中,以後需要的時候直接從HttpSession中獲取數據。在Spring Security中,用戶登錄信息本質上還是保存在 Ht ...
  • 24 類型標註 24.1 Python中的數據類型 在Python中有很多數據類型,比較常見如下所示: |整型 | 浮點型|字元串 | 列表|元組|字典|集合|布爾| | | | | | | | | | |int| float|str|list|tuple|dict|set|bool| 因Pytho ...
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...