Spring Boot 在为开发人员提供更高层次的封装, 进而提高开发效率的同时, 也为出现问题时如何进行定位带来了一定复杂性与难度. 但 Spring Boot 同时又提供了一些诊断工具来辅助开发与分析, 如 spring-boot-starter-actuator. 本文分享一个基于 actuator 与 IDEA 条件断点来定位自动配置未生效的案例. 望对类似问题分析与处理提供参考.
问题确认
在前文介绍的 Spring Boot 从入门到实战: 整合通用 Mapper 简化单表操作 中, 我们对 druid 连接池做了自动配置, 并且注入了 druid 的监控统计功能, 如下
但本地运行后通过 http://localhost:8080/druid/index.html 访问时却出现错误, 通过浏览器的开发者工具查看该请求返回 404, 推测上述代码中定义的 StatViewServlet 未注入成功. 我们用 actuator 来确认下是否如此. 在项目中加入 spring-boot-starter-actuator, 并且 application.YAML 中添加如下配置
- 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.YAML 中也做了 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
---------------------------------------------------------------
来源: https://www.cnblogs.com/spec-dog/p/11094890.html