Почему ваша автоконфигурация SpringBoot не работает?

Spring Boot задняя часть исходный код

Это 10-й день, когда я участвую в ноябрьской задаче, подробности о событии представление:Вызов последнего обновления 2021 г.

Эта статья связана с проблемами, возникшими в последних проектах, ошибки всегда появляются там, где вы думаете...

описание проблемы

Ниже приведен простой воспроизводимый фрагмент кода.Если вы можете сделать правильное суждение, не дочитав эту статью до конца, поздравляю, вы сэкономите время на чтении этой статьи.

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Наличие к-в, при исполненииAutoTestConfigurationКогда класс автоконфигурации обновляется,@ConditionalOnProperty(prefix = "test", name = "enable")вступит в силу, иTestBeanсоздается нормально.

Но это не так, ниже приведена проверка этой проблемы

Конфигурация действительна, AutoTestConfiguration не обновляется

Два момента:

  • 1. Выполнение AutoTestConfiguration # TestBean выкроет журнал (используется для оценки того, обновляется ли 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правда.

объяснил здесьTestPropertiesОбновился, но не так@ConditionalOnPropertyЭто работает, тогда вы можете в основном догадаться, что это на классе автоматической настройки@ConditionalOnPropertyи@EnableConfigurationPropertiesпорядок действий.

Перед проверкой порядка вопросов я пытаюсь добавить следующую конфигурацию в Applications.properties In, Re запустите проект:

test.enable=true

Здесь я получаю еще одну проблему конфликта бобов.

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Фасоль с таким названием. Я попытался проверить в коде, отображается ли данное имя компонента, но я его не нашел, есть только одна возможность, то есть это создается самой Spring.

Этот процесс очень продвинут на этапе весеннего обновления. При устранении этой проблемы все еще требовалось некоторое время. Наконец, местоположение проблемы было постоянно предварительно позиционировано для инициализации beansdefinitions, чтобы найти ее.

вот@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 зарегистрирует целевые метаданные как bean-компонент; продолжайте отладку и найдите bean-компонент, который генерирует имя формата префиксного типа.

image.png

Ниже приведен конкретный код 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 в классе конфигурации, чтобы избежать нескольких экземпляров одного и того же типа bean-компонента при последующем использовании.

@ConditionalOnProperty

Возвращаясь к проблеме, что конфигурация не действует, вот запись в официальном выпуске:GitHub.com/spring-pro — это…

Тем не менее, здесь все еще можно восстановить первопричину проблемы, проанализировав код, здесь в основном анализируется с двух сторон:

  • Логика сопоставления значений @ConditionalOnProperty, вам необходимо указать, из какого PropertySource следует читать при сопоставлении значений.
  • Логика для ошибки совпадения @ConditionalOnProperty и обновления bean-компонента

Логика соответствия @ConditionalOnProperty

Во-первых, это проблема сопоставления источника значения, когда @ConditionalOnProperty выполняет вычисление.Все исходные источники легко получить с помощью кода отладки, как показано на следующем рисунке:

image.png

Из DEBUG этот случай имеет четыре источника (в частности, как показано выше), фактически из исходного кода 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 не тестирует. Технология, которая не будет обновлена, или он не обновлялся, или он обновлялся после классов автоматической настройки.

Логика пропуска @ConditionalOnProperty

Здесь мы в основном объясняем логическую связь между @ConditionalOnPropert и обновляемым bean-компонентом, которая специально реализована в классе ConditionEvaluator.

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

Таким образом, для аннотации к классу автоконфигурации Conditional является предпосылкой того, разрешено ли обновлять текущий класс.Только если условие Conditional выполняется, текущий класс автоконфигурации будет добавлен в список bean-компонентов. Если условие не выполняется, bean-компонент будет пропущен напрямую и не будет помещен в BeandefinitonMap, поэтому последующего действия по обновлению не будет.

Время действия @ConditionalOnProperty предшествует созданию BeanDefiniton, и время его выполнения больше, чем@EnableConfigurationPropertiesЭффект проявляется раньше, что также объясняет, почему test.enable=true в TestProperties,AutoTestConfigurationИ не обновляет почему.

Суммировать

В этой статье на простом примере прослеживается проблема, заключающаяся в том, что компонент не обновляется из-за сбоя конфигурации SpringBoot, обнаруженного в проекте.

  • Примечания, связанные с условиями. Для класса автоматической конфигурации время более раннее, чтобы определить, разрешено ли обновление текущего класса автоматической конфигурации.
  • Класс @EnableConfigurationProperties enable регистрирует bean-компонент по умолчанию, а формат имени bean-компонента является префиксным.