SpringBoot 自动配置失效的解决方法

目录
  • 问题描述
  • @EnableConfigurationProperties 注解行为
    • 配置有效,AutoTestConfiguration 未刷新
  • prefix-type
  • @ConditionalOnProperty
    • @ConditionalOnProperty match 逻辑
    • @ConditionalOnProperty skip 逻辑
  • 总结

本文源自近期项目中遇到的问题, bug 总是出现在你自以为是的地方...

问题描述

下面是一个简单复现的代码片段,在你没有阅读完本文时,如果能做出正确的判断,那恭喜你可以节省阅读本文的时间了。

1、自动配置类:AutoTestConfiguration

@Configuration
@EnableConfigurationProperties(TestProperties.class)
@ConditionalOnProperty(prefix = "test", name = "enable")
public class AutoTestConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public TestBean testBean(TestProperties properties){
        System.out.println("this is executed.....");
        return new TestBean();
    }
}

2、配置类 TestProperties

@ConfigurationProperties(prefix = "test")
public class TestProperties {
    private boolean enable = true;
    public boolean isEnable() {
        return enable;
    }
    public void setEnable(boolean enable) {
        this.enable = enable;
    }
}

这两个类都在 root package 下,可以保证能够正常被 Spring 扫描到;那么问题是 TestBean 会不会被正常创建?当然这里的结论是不会。

可能有的同学会说你的 TestProperties 没有加 @Configuration 注解,Spring 不认识它,那真的是这样吗?很显然也不是。
在排查这个问题的过程中,也有遇到其他问题,也是之前没有遇到过的;即使 Spring 源码我看过很多遍,但是仍然会有一些边边角角让你意想不到的地方;下面就针对这个问题,慢慢来揭开它的面纱。

@EnableConfigurationProperties 注解行为

在之前的版本中,TestProperties 是有被 @Configuration 注解标注的

@Configuration // 可以被 spring 扫描
@ConfigurationProperties(prefix = "test")
public class TestProperties {
    private boolean enable = true;
    public boolean isEnable() {
        return enable;
    }
    public void setEnable(boolean enable) {
        this.enable = enable;
    }
}

常规的思路是,当 TestProperties 被扫描到之后,spring env 中就会有 test.enable=true 的 k-v 存在,当执行 AutoTestConfiguration 自动配置类刷新时,@ConditionalOnProperty(prefix = "test", name = "enable") 则会生效,进而 TestBean 被正常创建。

但事实并非如此,下面是对于此问题的验证

配置有效,AutoTestConfiguration 未刷新

两个点:

  • AutoTestConfiguration#testBean 执行会输出一个 log(用于判断 AutoTestConfiguration 是否正常刷新)
  • 监听 ApplicationReadyEvent 事件,拿 test.enable 值(用于判端配置是否正常加载,也就是 TestProperties 是否被正常刷新)

代码如下:

@SpringBootApplication
public class Application implements ApplicationListener<ApplicationReadyEvent> {
   @Autowired
   private ApplicationContext applicationContext;
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }
   @Override
   public void onApplicationEvent(ApplicationReadyEvent event) {
      System.out.println(this.applicationContext.getEnvironment().getProperty("test.enable")  + "------");
   }
}

执行得到的结果是 AutoTestConfiguration#testBean 没有被执行,但test.enable 为 true。

这里说明 TestProperties 是有被刷新的,但是并没有对 @ConditionalOnProperty 起到作用,那么这里基本可以猜到是自动配置类上的 @ConditionalOnProperty 和 @EnableConfigurationProperties 的作用顺序问题。

在验证顺序问题之前,我尝试在 application.properties 中增加如下配置,re run 项目:

test.enable=true

到这里我得到了另一个 bean 冲突的问题。

prefix-type

异常提示如下:

Parameter 0 of method testBean in com.glmapper.bridge.boot.config.AutoTestConfiguration required a single bean, but 2 were found:
 - testProperties: defined in file [/Users/glmapper/Documents/project/exception-guides/target/classes/com/glmapper/bridge/boot/config/TestProperties.class]
 - test-com.glmapper.bridge.boot.config.TestProperties: defined in null

这里出现了 test-com.glmapper.bridge.boot.config.TestProperties 这个 name 的 bean。我尝试在代码中去检查是否有显示给定这个 bean 名字,但是没有找到,那只有一种可能,就是这个是被 spring 自己创建的。

这个过程在 spring 刷新阶段非常靠前,在排查这个问题时,还是耽误了一些时间,最后还是把问题定位一致前置到 beandefinitions 初始化才找到。

这里是 @EnableConfigurationProperties 注解的一个行为,依赖 EnableConfigurationPropertiesRegistrar,源码如下:

class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
         .getQualifiedAttributeName(EnableConfigurationPropertiesRegistrar.class, "methodValidationExcludeFilter");

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      registerInfrastructureBeans(registry);
      registerMethodValidationExcludeFilter(registry);
      ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
      // to register
      getTypes(metadata).forEach(beanRegistrar::register);
   }

通过代码比较容易看出,EnableConfigurationPropertiesRegistrar 会将目标 metadata 注册成 bean;继续 debug,找到了产生 prefix-type 格式 name 的 bean。

下面是 getName 的具体代码

private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
    // 拿 prefix
   String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
   // prefix + "-" + 类全限定名
   return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}

到这里我们先明确一个问题:

如果你使用 @EnableConfigurationProperties 来开启配置类,那么就不要在配置类上使用@Configuration 等能够被 Spring scan 识别到的注解,以免在后续的使用中同一类型的 bean 多个实例

@ConditionalOnProperty

在回到配置不生效问题上来,这里在官方 issue 是有记录的:github.com/spring-proj…
不过这里还是通过分析代码来还原下问题产生的根本原因;这里主要从两个方面来分析:

  • @ConditionalOnProperty match 值逻辑,需要明确在匹配 value 时,从哪些 PropertySource 读取的。
  • @ConditionalOnProperty match 失败和 bean 刷新的逻辑

@ConditionalOnProperty match 逻辑

首先是 @ConditionalOnProperty 在执行计算时,匹配 value 的值来源问题,通过 debug 代码很容易就得到了所有的 source 来源,如下图:

从 debug 看,本案例有 4 个来源(具体如上图),实际上从源码来看,source 涵盖了 spring env 所有来源:

[ConfigurationPropertySourcesPropertySource {name='configurationProperties'},
StubPropertySource {name='servletConfigInitParams'},
StubPropertySource {name='servletContextInitParams'},
PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'},
RandomValuePropertySource {name='random'},
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}]

所以本文案例中不生效原因就是上面这些 PropertySource 都没有 test.enable,也就是 TestProperties 没被刷新,或者其在自动配置类之后才刷新。

@ConditionalOnProperty skip 逻辑

这里主要解释 @ConditionalOnPropert 和 bean 被刷新的逻辑关系,具体实现在 ConditionEvaluator 类中

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 1、没有 Conditional 注解,则扫描时不会跳过当前 bean
    // 2、遍历 conditions 进行判断是否满足
}

所以对于自动配置类上的注解,Conditional 是作为当前类是否允许被刷新的前提,只有 Conditional 条件满足,才会将当前的自动配置类加入到待刷新 bean 列表中去,如果 Conditional 不满足,这个 bean 将直接被跳过,不会被放到 BeandefinitonMap 中去,也就不会有后续的刷新动作。

@ConditionalOnProperty 作用时机在 BeanDefiniton 被创建之前,其执行时机要比 @EnableConfigurationProperties 作用要早,这也就说明了,为什么 TestProperties 中 test.enable=true, AutoTestConfiguration 也不会刷新的原因了。

总结

本文通过一个简单 case,对于项目中遇到的 SpringBoot 配置失效导致 bean 未被刷新问题进行了回溯,总结如下:

Conditional 相关注解对于自动配置类来说,作用时机较早,用于决定当前自动配置类是否允许被刷新
@EnableConfigurationProperties enable 的类,会默认注册一个 bean,bean 名字格式为 prefix-type

到此这篇关于SpringBoot 自动配置失效的解决方法的文章就介绍到这了,更多相关SpringBoot 自动配置失效内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 全网最深分析SpringBoot MVC自动配置失效的原因

    前言 本来没有计划这一篇文章的,只是在看完SpringBoot核心原理后,突然想到之前开发中遇到的MVC自动失效的问题,虽然网上有很多文章以及官方文档都说明了原因,但还是想亲自看一看,本以为很简单的事情,没想到却引发出一个较复杂的问题,请教了很多人都没有得到结果,网上文章也没有写清楚的,最后还是自己搞了很久才弄明白的,此篇主要记录自己的一个分析过程. 正文 引出问题 上面是SpringBoot MVC的自动配置,问题是这样的,当我们需要自己配置MVC时,有三种选择: 实现WebMvcConfig

  • SpringBoot 自动配置失效的解决方法

    目录 问题描述 @EnableConfigurationProperties 注解行为 配置有效,AutoTestConfiguration 未刷新 prefix-type @ConditionalOnProperty @ConditionalOnProperty match 逻辑 @ConditionalOnProperty skip 逻辑 总结 本文源自近期项目中遇到的问题, bug 总是出现在你自以为是的地方... 问题描述 下面是一个简单复现的代码片段,在你没有阅读完本文时,如果能做出正

  • android开发 eclipse alt+”/”自动提示失效的解决方法

    1.用于没有一点提示的情况:依次打开eclipse上面的windows --preferences --java --editor -- content assist ,在右上方有一行"select the proposal kinds contained in the 'default' content assist list" 下面,勾选Java Prolosals 复选框,保存退出即可. 2.用于打出单词缩写没有提示的情:依次打开eclipse上面的windows --prefe

  • layui表格 列自动适应大小失效的解决方法

    如下所示: 从官网复制的表格,修改成自适应宽度后失效,原因如下: 以上这篇layui表格 列自动适应大小失效的解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • vue-cli项目修改文件热重载失效的解决方法

    遇到一个很奇怪的问题,就是之前vue-cli创建的项目,在起初修改文件可以热重载,但是后面突然间就无法无刷新浏览器更新了,一只以为是热重载出问题了,折腾了半天也没纠结出什么结论,最后百度了一下,原来是编译器webstrom的锅. 问题原因 在webstrom系统设置中有一项配置,是 Use "safe write"(save changes to a temporary file first) .webstrom是自动保存的,如果勾选Use "safe write"

  • 深入浅析SpringBoot自动配置原理

    SpringBoot2.3.1版本源码 一.SpringBoot启动的时候加载主配置类,通过@EnableAutoConfiguration注解开启了自动配置功能 . 二.@EnableAutoConfiguration作用: 1. 点击该注解进入可以发现,它利用AutoConfigurationImportSelector.class 选择器给SpringBoot导入一些组件.导入哪些组件呢?可以点击选择器进入查看selectImports()方法的内容,该方法最终会返回一个configurati

  • springboot自动配置原理以及spring.factories文件的作用详解

    目录 一.springboot 自动配置原理 二.spring.factories文件的作用 spring.factories 的妙用 什么是 SPI 机制? Spring Boot 中的 SPI 机制 Spring Factories 实现原理是什么? Spring Factories 在 Spring Boot 中的应用 一.springboot 自动配置原理 先说说我们自己的应用程序中Bean加入容器的办法: package com.ynunicom.dc.dingdingcontract

  • Springboot自动配置与@Configuration配置类详解

    目录 @Configuration 注意点1 注意点2 注意点3 注意点4 springboot自动配置 @Configuration 注意点1 配置类(@Configuration下的这个类)其实相当于一个工厂, 标注 @Bean 注解的方法相当于工厂方法 考虑有如下例子: @Configuration // 注意点1: 配置类其实相当于一个工厂, 标注 @Bean 注解的方法相当于工厂方法 static class MyConfig { @Bean public Bean1 bean1()

  • 全面解析SpringBoot自动配置的实现原理

    之前一直在用SpringBoot框架,一直感觉SpringBoot框架自动配置的功能很强大,但是并没有明白它是怎么实现自动配置的,现在有空研究了一下,大概明白了SpringBoot框架是怎么实现自动配置的功能,我们编写一个最简单的自动配置功能,大概的总结一下. 一,配置属性类 其实就是值对象注入的方式去配置一些Spring常用的配置,我们编写一个最简单的配置对象. @ConfigurationProperties(prefix = "hello") //@Component //如果这

  • swiper在vue项目中loop循环轮播失效的解决方法

    长话短说,在vue(2.5.x)中使用swiper(4.3.3),轮播加了autoplay和loop.observer.observeParents等参数还是很诡异的无法循环轮播: 那么可以这样写代码试试: this.$api.queryImages().then((resp) => { if(resp && resp.data.resultCode == "0"){ this.swiperImgs = resp.data.data; this.$nextTick

随机推荐