springboot自動配置沒有生效的問題定位(條件斷點(diǎn))
Spring Boot在為開發(fā)人員提供更高層次的封裝,進(jìn)而提高開發(fā)效率的同時(shí),也為出現(xiàn)問題時(shí)如何進(jìn)行定位帶來了一定復(fù)雜性與難度。但Spring Boot同時(shí)又提供了一些診斷工具來輔助開發(fā)與分析,如spring-boot-starter-actuator。本文分享一個(gè)基于actuator與IDEA條件斷點(diǎn)來定位自動配置未生效的案例。望對類似問題分析與處理提供參考。
問題確認(rèn)
在前文介紹的 Spring Boot從入門到實(shí)戰(zhàn):整合通用Mapper簡化單表操作 中,我們對druid連接池做了自動配置,并且注入了druid的監(jiān)控統(tǒng)計(jì)功能,如下

但本地運(yùn)行后通過 http://localhost:8080/druid/index.html訪問時(shí)卻出現(xiàn)錯(cuò)誤,通過瀏覽器的開發(fā)者工具查看該請求返回404,推測上述代碼中定義的StatViewServlet未注入成功。我們用actuator來確認(rèn)下是否如此。在項(xiàng)目中加入spring-boot-starter-actuator,并且application.yml中添加如下配置
management: endpoints: web: exposure: include: "*" exclude: beans,trace endpoint: health: show-details: always
在spring-boot 2.x 版本當(dāng)中,作為安全性考慮,將actuator 控件中的端口,只默認(rèn)開放/health 和/info 兩個(gè)端口,其他端口默認(rèn)關(guān)閉, 因此需要添加如上配置。注意include的值 * 必須加引號,否則無法啟動。
重啟程序后訪問 http://localhost:8080/actuator/conditions確認(rèn)上述兩個(gè)實(shí)例化方法未滿足@ConditionalOnProperty的條件,從而未執(zhí)行生效,如圖

條件斷點(diǎn)
從上面分析確認(rèn)是因?yàn)闂l件注解 @ConditionalOnProperty(prefix = "spring.datasource.druid", name = "druidServletSettings") 未滿足使方法未執(zhí)行導(dǎo)致。那這個(gè)條件為什么沒有滿足呢,查看application.yml中也做了 spring.datasource.druid.druidServletSettings屬性的配置。
當(dāng)你無法理清頭緒,確定問題原因時(shí),那就Debug吧。查看注解@ConditionalOnProperty源碼,找到其實(shí)現(xiàn)支持類OnPropertyCondition,如下
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional({OnPropertyCondition.class})
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
boolean matchIfMissing() default false;
}
查看OnPropertyCondition源碼,了解它是通過getMatchOutcome方法來判斷是否滿足注解參數(shù)所指定的條件的,如下所示
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(
ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes,
context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
在調(diào)用determineOutcome處打斷點(diǎn),調(diào)試什么原因?qū)е聴l件未滿足,但是這里是一個(gè)for循環(huán),如果for元素過多的話,將可能需要斷點(diǎn)阻斷很多次才能找到你想要查看的那個(gè)元素。
所幸IDEA提供了不同類型的斷點(diǎn)來處理這類問題,這里介紹用條件斷點(diǎn)來處理這類循環(huán)塊中的debug問題。
在上述代碼for循環(huán)中調(diào)用determineOutcome行打斷點(diǎn),并在斷點(diǎn)上右鍵,彈出如下窗口

圖中Condition框即可輸入你要指定的條件,可以直接寫java判斷表達(dá)式代碼,并引用該行代碼處能訪問的變量,如這里我們輸入 annotationAttributes.get("name").equals("druidServletSettings"),然后點(diǎn)擊Debug窗口的“Resume Program (F9)”按鈕,則在不滿足指定條件時(shí),斷點(diǎn)處將不會被阻斷,直到條件滿足,這樣就能很容易定位到我們想要查看的元素。(當(dāng)然這里allAnnotationAttributes變量其實(shí)只有一個(gè)元素,僅僅是為了演示條件變量的使用,當(dāng)集合元素很多時(shí),使用條件斷點(diǎn)就能體會到它的方便之處)
問題定位
通過Debug的方式深入條件注解的判斷邏輯(其中循環(huán)處可使用條件斷點(diǎn)),最終來到如下代碼片段

在這里是判斷來自所有屬性源配置的屬性中,是否包含條件注解指定的屬性,即spring.datasource.druid.druidServletSettings,由上圖可見,spring.datasource.druid.druidServletSettings只是某些屬性的前綴,并不存在完全匹配的屬性,因此返回false,導(dǎo)致條件不滿足?;乜醋⒔釦ConditionOnProperty的javadoc,
* If the property is not contained in the {@link Environment} at all, the
* {@link #matchIfMissing()} attribute is consulted. By default missing attributes do not
* match.
* <p>
* This condition cannot be reliably used for matching collection properties. For example,
* in the following configuration, the condition matches if {@code spring.example.values}
* is present in the {@link Environment} but does not match if
* {@code spring.example.values[0]} is present.
*
當(dāng)Environment中不包含該屬性時(shí),則看matchIfMissing的值,該值默認(rèn)為false,如果包含該屬性,則再對比屬性值與havingValue的值,相等即滿足,不等則不滿足。并且該條件注解不能用于匹配集合類型屬性。上述spring.datasource.druid.druidServletSettings實(shí)際上屬于一個(gè)Map類型,因此不能想當(dāng)然地認(rèn)為該注解是只要屬性集中某屬性名稱包含該值即滿足。
總結(jié)
當(dāng)難以定位到問題原因時(shí),可以進(jìn)行Debug,跟蹤程序運(yùn)行的各個(gè)步驟,當(dāng)要在循環(huán)中Debug定位到某個(gè)元素時(shí),可以用條件斷點(diǎn)來實(shí)現(xiàn)。@ConditionalOnProperty注解不是存在某屬性就行,還需要值相等,并且不適用于集合類型屬性。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java使用wait和notify實(shí)現(xiàn)線程之間的通信
Java 線程通信是將多個(gè)獨(dú)立的線程個(gè)體進(jìn)行關(guān)聯(lián)處理,使得線程與線程之間能進(jìn)行相互通信,下面這篇文章主要給大家介紹了關(guān)于Java使用wait和notify實(shí)現(xiàn)線程之間通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
深入Parquet文件格式設(shè)計(jì)原理及實(shí)現(xiàn)細(xì)節(jié)
這篇文章主要介紹了深入Parquet文件格式設(shè)計(jì)原理及實(shí)現(xiàn)細(xì)節(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
java eclipse 整個(gè)項(xiàng)目或包查找只定字符串并替換操作
這篇文章主要介紹了java eclipse 整個(gè)項(xiàng)目或包查找只定字符串并替換操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
java 注解實(shí)現(xiàn)一個(gè)可配置線程池的方法示例
這篇文章主要介紹了java 注解實(shí)現(xiàn)一個(gè)可配置線程池的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01
Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決
這篇文章主要介紹了Java?SpringBoot整合shiro-spring-boot-starterqi項(xiàng)目報(bào)錯(cuò)解決,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考一下2022-08-08
基于Java?Agent的premain方式實(shí)現(xiàn)方法耗時(shí)監(jiān)控問題
javaagent是在JDK5之后提供的新特性,也可以叫java代理,這篇文章主要介紹了基于Java?Agent的premain方式實(shí)現(xiàn)方法耗時(shí)監(jiān)控問題,需要的朋友可以參考下2022-10-10
使用Kubernetes和Docker部署Java微服務(wù)詳細(xì)代碼
Java微服務(wù)項(xiàng)目是一種基于Java技術(shù)棧的分布式系統(tǒng)開發(fā)方式,下面這篇文章主要給大家介紹了關(guān)于使用Kubernetes和Docker部署Java微服務(wù)的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07

