詳解SpringBoot中@ConditionalOnClass注解的使用
今天給大家?guī)淼氖莝pringboot中的@ConditionalOnClass注解的用法。上次的@ConditionalOnBean注解還記得嗎?
一、@ConditionalOnClass注解初始
看下@CodidtionalOnClass注解的定義,
需要注意的有兩點,
- 該注解可以用在類及方法上;類指的是標(biāo)有@Configuration的類,方法是標(biāo)有@Bean的方法;
- 該注解使用了@Conditional注解標(biāo)記;這是重點
看到這里,有小伙伴會疑惑,講了那么多@Conditional注解的作用是什么,不急,作用馬上來。
@ConditionalOnClass注解的作用是當(dāng)項目中存在某個類時才會使標(biāo)有該注解的類或方法生效;
這句話有點拗口,通俗的講,@ConditionalOnClass標(biāo)識在@Configuration類上,只有存在@ConditionalOnClass中value/name配置的類該Configuration類才會生效;@ConditionalOnClass標(biāo)識在@Bean方法上,只有只有存在@ConditionalOnClass中value/name配置的類方法才會生效??淳唧w的實例更容易理解些
二、@ConditionalOnClass注解用法
從上面@ConditionalOnClass注解的定義中我們知道該注解可以配置兩個屬性,分別是value和name,其中value和name都是數(shù)組,只不過內(nèi)容不一樣,
value是Class的數(shù)組,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的依賴,導(dǎo)致在我的classpath中沒有這個類,按照@ConditionalOnClass的理解,應(yīng)該是不存在則不會生效,但是由于沒有這個類,導(dǎo)致的問題是:無法編譯,提示下面的錯誤
java: 找不到符號
符號: 類 Client
這是可以理解的,因為沒有這個類,而我要引用這個類肯定是引用不到的,所以編譯是失敗的,也就程序跑不起來。那么存在一個問題,@ConditionalOnClass注解的value屬性要在什么情況下使用?
這里有一個mybatisplus的配置類,
其配置類上標(biāo)識了@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包經(jīng)過了編譯,已經(jīng)打包成功了,故不存在問題。
通過value屬性需要結(jié)合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屬性是一個數(shù)組,上面僅僅配置了一個類,如果配置多個會是什么樣子,感興趣的可以自己嘗試,這里這直接給出答案,只有name屬性中配置的全部滿足相應(yīng)的配置類才會生效。
不知道,你是否對@ConditionalOnClass是怎么實現(xiàn)的感興趣嗎,繼續(xù)往下看,大揭秘了。
三、@ConditionalOnClass是怎么實現(xiàn)的
要理解@ConditionalOnClass是怎么實現(xiàn)的還是要回到該注解的定義上,前邊提到該注解被
@Conditional(OnClassCondition.class)
注解標(biāo)識,@Conditional注解的含有是要滿足條件才會生效,該注解后邊再看。今天的主角是OnClassCondition類,看下其繼承關(guān)系
重點關(guān)注XXCondition即可,可以看到最終實現(xiàn)了Condition接口,@Conditional注解的本質(zhì)就是考查是否滿足Codition接口的matches()方法,所以這看SpringBootCondition中matches方法的實現(xiàn),
@Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //獲得該注解標(biāo)準(zhǔn)的類或方法 String classOrMethodName = getClassOrMethodName(metadata); try { //模板方法,該實現(xiàn)在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()方法使用了模板方法,實現(xiàn)在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); }
上面的代碼已經(jīng)給出了注釋,對應(yīng)@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) { //循環(huán)調(diào)用matches方法 if (classNameFilter.matches(candidate, classLoader)) { matches.add(candidate); } } return matches; }
對于@CoditionalOnClass的處理該方法傳入的參數(shù)為
List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
那么也就是調(diào)用ClassNameFilter.MISSING的matches方法,其方法如下
可以看到調(diào)用的是!isPresent方法,看下該方法的實現(xiàn),
static boolean isPresent(String className, ClassLoader classLoader) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } try { //具體實現(xiàn)邏輯 FilteringSpringBootCondition.resolve(className, classLoader); return true; } catch (Throwable var3) { return false; } }
具體的實現(xiàn)在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,類加載。
四、總結(jié)
本文主要認(rèn)識了@ConditionalOnClass注解,分析了其注解的原理,如何判斷配置的類是否存在。
- @ConditionalOnClass注解有兩個屬性,分別是value和name,注意其配置方式;
- @ConditionalOnClass注解判斷配置的類是否存在的方式是通過Class.forName的方式;
以上就是詳解SpringBoot中@ConditionalOnClass注解的使用的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot @ConditionalOnClass注解的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java Swing實現(xiàn)讓窗體居中顯示的方法示例
這篇文章主要介紹了Java Swing實現(xiàn)讓窗體居中顯示的方法,結(jié)合實例形式分析了swing使用setBounds方法控制窗口布局的相關(guān)操作技巧,需要的朋友可以參考下2017-11-11淺析Java常用API(Scanner,Random)匿名對象
這篇文章主要介紹了Java常用API(Scanner,Random)匿名對象,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Spring-cloud Config Server的3種配置方式
這篇文章主要介紹了Spring-cloud Config Server的3種配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09