Spring Boot在為開發人員提供更高層次的封裝,進而提高開發效率的同時,也為出現問題時如何進行定位帶來了一定複雜性與難度。但Spring Boot同時又提供了一些診斷工具來輔助開發與分析,如spring-boot-starter-actuator。本文分享一個基於actuator與IDEA條件斷點來定位自動配置未生效的案例。望對類似問題分析與處理提供參考。
歡迎關注我的微信公眾號:jboost-ksxy
問題確認
在前文介紹的 Spring Boot從入門到實戰:整合通用Mapper簡化單表操作 中,我們對druid連線池做了自動配置,並且注入了druid的監控統計功能,如下
但本地執行後通過 http://localhost:8080/druid/index.html 訪問時卻出現錯誤,通過瀏覽器的開發者工具檢視該請求返回404,推測上述程式碼中定義的StatViewServlet
未注入成功。我們用actuator來確認下是否如此。在專案中加入spring-boot-starter-actuator,並且application.yml中新增如下配置
management:
endpoints:
web:
exposure:
include: "*"
exclude: beans,trace
endpoint:
health:
show-details: always
在spring-boot 2.x 版本當中,作為安全性考慮,將actuator 控制元件中的埠,只預設開放/health 和/info 兩個埠,其他埠預設關閉, 因此需要新增如上配置。注意include的值
*
必須加引號,否則無法啟動。
重啟程式後訪問 http://localhost:8080/actuator/conditions 確認上述兩個例項化方法未滿足@ConditionalOnProperty
的條件,從而未執行生效,如圖
條件斷點
從上面分析確認是因為條件註解 @ConditionalOnProperty(prefix = "spring.datasource.druid", name = "druidServletSettings")
未滿足使方法未執行導致。那這個條件為什麼沒有滿足呢,檢視application.yml中也做了 spring.datasource.druid.druidServletSettings屬性的配置。
當你無法理清頭緒,確定問題原因時,那就Debug吧。檢視註解@ConditionalOnProperty
原始碼,找到其實現支援類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
方法來判斷是否滿足註解引數所指定的條件的,如下所示
@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)); }
在呼叫determineOutcome
處打斷點,除錯什麼原因導致條件未滿足,但是這裡是一個for迴圈,如果for元素過多的話,將可能需要斷點阻斷很多次才能找到你想要檢視的那個元素。所幸IDEA提供了不同型別的斷點來處理這類問題,前面 案例解析:使用IDEA異常斷點來定位java.lang.ArrayStoreException的問題 我們介紹了異常斷點的使用。這裡介紹用條件斷點來處理這類迴圈塊中的debug問題。
在上述程式碼for迴圈中呼叫determineOutcome
行打斷點,並在斷點上右鍵,彈出如下視窗
圖中Condition框即可輸入你要指定的條件,可以直接寫java判斷表示式程式碼,並引用該行程式碼處能訪問的變數,如這裡我們輸入 annotationAttributes.get("name").equals("druidServletSettings")
,然後點選Debug視窗的“Resume Program (F9)”按鈕,則在不滿足指定條件時,斷點處將不會被阻斷,直到條件滿足,這樣就能很容易定位到我們想要檢視的元素。(當然這裡allAnnotationAttributes
變數其實只有一個元素,僅僅是為了演示條件變數的使用,當集合元素很多時,使用條件斷點就能體會到它的方便之處)
問題定位
通過Debug的方式深入條件註解的判斷邏輯(其中迴圈處可使用條件斷點),最終來到如下程式碼片段
在這裡是判斷來自所有屬性源配置的屬性中,是否包含條件註解指定的屬性,即spring.datasource.druid.druidServletSettings
,由上圖可見,spring.datasource.druid.druidServletSettings
只是某些屬性的字首,並不存在完全匹配的屬性,因此返回false,導致條件不滿足。回看註解@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. *
當Environment中不包含該屬性時,則看matchIfMissing的值,該值預設為false,如果包含該屬性,則再對比屬性值與havingValue的值,相等即滿足,不等則不滿足。並且該條件註解不能用於匹配集合型別屬性。上述spring.datasource.druid.druidServletSettings
實際上屬於一個Map型別,因此不能想當然地認為該註解是隻要屬性集中某屬性名稱包含該值即滿足。
總結
當難以定位到問題原因時,可以進行Debug,跟蹤程式執行的各個步驟,當要在迴圈中Debug定位到某個元素時,可以用條件斷點來實現。@ConditionalOnProperty註解不是存在某屬性就行,還需要值相等,並且不適用於集合型別屬性。
我的個人部落格地址:http://blog.jboost.cn
我的github地址:https://github.com/ronwxy
我的微信公眾號:jboost-ksxy
———————————————————————————————————————————————————————————————
歡迎關注我的微信公眾號,及時獲取最新分享