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

———————————————————————————————————————————————————————————————


欢迎关注我的微信公众号,及时获取最新分享

案例解析:springboot自动配置未生效问题定位(条件断点)的更多相关文章

  1. 小BUG大原理:重写WebMvcConfigurationSupport后SpringBoot自动配置失效

    一.背景 公司的项目前段时间发版上线后,测试反馈用户的批量删除功能报错.正常情况下看起来应该是个小 BUG,可怪就怪在上个版本正常,且此次发版未涉及用户功能的改动.因为这个看似小 BUG 我了解到不少 ...

  2. 小BUG大原理 | 第一篇:重写WebMvcConfigurationSupport后SpringBoot自动配置失效

    一.背景 公司的项目前段时间发版上线后,测试反馈用户的批量删除功能报错.正常情况下看起来应该是个小BUG,可怪就怪在上个版本正常,且此次发版未涉及用户功能的改动.因为这个看似小BUG我了解到不少未知的 ...

  3. SpringBoot自动配置注解原理解析

    1. SpringBoot启动主程序类: @SpringBootApplication public class DemoApplication { public static void main(S ...

  4. springboot自动配置源码解析

    springboot版本:2.1.6.RELEASE SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfig ...

  5. SpringBoot 系列教程自动配置选择生效

    191214-SpringBoot 系列教程自动配置选择生效 写了这么久的 Spring 系列博文,发现了一个问题,之前所有的文章都是围绕的让一个东西生效:那么有没有反其道而行之的呢? 我们知道可以通 ...

  6. SpringBoot自动配置探究

    @SpringBootApplication @SpringBootApplication表示SpringBoot应用,标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就 ...

  7. 如何编写Spring-Boot自动配置

    摘要 本文主要介绍如何把一个spring的项目(特别是一些公共工具类项目),基于spring boot的自动配置的思想封装起来,使其他Spring-Boot项目引入后能够进行快速配置. AutoCon ...

  8. SpringBoot实战之SpringBoot自动配置原理

    SpringBoot 自动配置主要通过 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @Confi ...

  9. 源码学习系列之SpringBoot自动配置(篇一)

    源码学习系列之SpringBoot自动配置源码学习(篇一) ok,本博客尝试跟一下Springboot的自动配置源码,做一下笔记记录,自动配置是Springboot的一个很关键的特性,也容易被忽略的属 ...

随机推荐

  1. 关于CORS跨域更细节的思考

    权威的资料看MDN,也可以看阮一峰的文章.不过感觉阮一峰对于CORS的描述有问题,简单请求被阮一峰描述为2次浏览器请求了.这个要自己搭个服务器试一下.跨域基本都是根据域名判断的,自己是否要再买个域名呢 ...

  2. vue项目 下载表格 java后台返回的是信息流表格如何下载解决乱码

    主要是在请求参数后面加上{responseType: 'blob'}或者arrayBuffer this.$http.get(this.api.export, { params: this.info, ...

  3. node fs 文件/目录 删除

    删除文件如下: 过程:先判断文件路径是否存在.读取该文件下所有文件.循环该文件,判断是否是文件夹还是文件. 移除文件夹使用fs.rmdirSync("路径") 移除文件使用fs.u ...

  4. 使用ant对项目进行多渠道打包时遇到问题记录

    1.打包成功后,打开apk时,会出现闪退的现象解决方法:1.配置好ant后,先把项目正常运行后,再打包,如有问题见第2步        2.找到项目中build.xml,然后右键,选择Run As - ...

  5. 使用Eclispe 查看api技巧

    使用eclispe都会知道当我们把鼠标的光标放到指定发方法上时程序会弹出一个提示,大家不要无论这个提示这个提示就是源码中的说明包含了函数參数使用方法 非常多时候我们碰到一个不会的方法的时候第一步都会选 ...

  6. Python 内置函数 —— format

    科学计数法: >> format(2**20, '.2e') '1.05e+06' 小数 ⇒ 百分数 >> format(.1234, '.1%') 12.3%

  7. win10下Linux子系统开启ssh服务

    原文:win10下Linux子系统开启ssh服务 为了便于交流共同学习,博主QQ群242629020(stm32-MCU认认真真交流群) 欢迎批评指导!!!电梯:https://jq.qq.com/? ...

  8. C#或者WPF中让某个窗体置顶

    原文:C#或者WPF中让某个窗体置顶 前记:在工作中有个需求,要求不管到那个界面,我必须让一个浮动条(其实是个窗体)置顶. 我用wpf,因为有之前有好几个界面已经设置成topmost了,所以在这几个界 ...

  9. i/o多路复用笔记

    1.用户空间和内核空间 操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备.为了保护用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分, ...

  10. 第一泰泽(Tizen)智能手机发布在俄罗斯

    请看下图: 这是韩国三星公司在俄罗斯境内公布的第一款泰泽(Tizen)智能手机(今年6月2日).这说明,Tizen操作系统没有死去. 在泰泽官网上将泰泽操作系统定义为:"The OS of ...