This is the 10th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

This article is based on a recent project problem, bugs always appear where you think you are…

Problem description

Here’s a simple snippet of code that can be replayed, and if you make the right judgment before you finish reading this article, you’ll save yourself some time.

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 newTestBean(); }}Copy the code

2. Configure class TestProperties

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

Both classes are in the root package and are guaranteed to be scanned normally by Spring; So the question is will the TestBean be created properly? Of course the conclusion here is no.

Some students may say that your TestProperties is not annotated with @Configuration and Spring doesn’t recognize it. Is that true? Apparently not.

In the process of troubleshooting this problem, there are also other problems that have not been encountered before; Even though I’ve read the Spring source code many times, there are still some corners and corners that you wouldn’t expect; Let’s take a look at this problem and uncover it.

@ EnableConfigurationProperties annotations

In previous versions, TestProperties was annotated with the @Configuration annotation. The general idea was that when TestProperties was scanned, Test. enable=true k-v =true @conditionAlonProperty (prefix = “test”, name = “enable”) will take effect and the TestBean will be created.

This is not the case, and the verification code for this problem is as follows:

@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")  + "-- -- -- -- -- -"); }}Copy the code

Two points:

  • The AutoTestConfiguration#testBean execution outputs a log
  • Listen for the ApplicationReadyEvent eventtest.enable

AutoTestConfiguration#testBean not executed. Test. enable is true, indicating that TestProperties has been refreshed. But not for @ ConditionalOnProperty play a role, so basic can guess here is automatically configured on the class of @ ConditionalOnProperty and the role of the @ EnableConfigurationProperties sequence problem. Before verifying the order issue, I tried adding the following configuration to application.properties, the Re Run project:

test.enable=true
Copy the code

Here I get another bean conflict problem.

prefix-type

The following exception message is displayed:

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 nullCopy the code

Here the test – com. Glmapper. Bridge. The boot. Config. TestProperties the name of the bean. I tried to check in the code to see if the given bean name was displayed, but couldn’t find it, so the only possibility is that it was created by Spring itself.

This process was very early in the Spring refresh phase, and it took some time to solve the problem. Finally, the problem was identified through beanDefinitions initialization.

Here is @ EnableConfigurationProperties annotations of a behavior, rely on 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);
   }
Copy the code

Debug finds the bean that generates the prefix-type format name.

@ConditionalOnProperty

Finally, back to the configuration does not take effect, here in the official issue is recorded: github.com/spring-proj…

However, here is a code analysis to restore the root cause of the problem.

ConditionalOnProperty match logic

ConditionalOnProperty (@conditionalonProperty) matches the source of value.

From debug, this case has 4 sources, as shown in the figure above, all sources in the following source code:

[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:/''}]
Copy the code

The reason for this failure is that none of the above PropertySource has test.enable, meaning that TestProperties was not refreshed, or it was refreshed only after the class was automatically configured.

@ ConditionalOnProperty skip logic

Here mainly to explain the @ ConditionalOnPropert and @ EnableConfigurationProperties order problems, mainly depends on ConditionEvaluator class

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 1. If there is no Conditional annotation, the current bean will not be skipped in the scan
    // 2. Check whether conditions are met
}
Copy the code

Therefore, for annotations on auto-configured classes, Conditional is a prerequisite for whether the current class is allowed to be refreshed.

conclusion

Through a simple case, this paper retrospects the problem of bean not being refreshed due to SpringBoot configuration failure encountered in the project, and concludes as follows:

  • Conditional annotations have the highest priority for auto-configured classes and directly determine whether the current auto-configured class is allowed to be refreshed
  • @ EnableConfigurationProperties enable class that will be the default registered a bean, bean name format for the prefix – type