Our projects rely heavily on bean overriding (yes, we know, disabled now by default for good reasons, but still useful at times and a different story).

We now traced down a phenomenon that the name of a @Configuration class found by the @ComponentScan seem to affect, whether overriding with @Import (or sub-classing) works properly. This is though the ConfigurationClassParser already does a lot of handling on this case, e. g. in processConfigurationClass.

Please find a test case in https://github.com/abenneke/sandbox/tree/master/spring-import-order: It contains four @Configuration classes: Two for a "foo" bean (FooConfigurationA+B) and two for a "bar" bean (BarConfigurationA+B). However, the @Import order is different between both configurations: FooConfigurationB is importing FooConfigurationA while BarConfigurationA is importing BarConfigurationB.

In the test we now see that overriding works as expected for "foo", but fails for "bar"!?

Just to make sure we did not miss anything, we added the very same test using standard Spring methods and again using Spring Boot - but it does not make any difference here.

Comment From: encircled

Hi,

In such case, BarConfigurationB is actually loaded twice: first by componentScan and then by your manual import. Component scan is name-dependent indeed (name based order).

The problem is that at componentScan point of time it is not aware of additional imports, causing different bean overriding than expected (ConfigurationClassParser#doProcessConfigurationClass:294).

I think it is not possible to tune such behavior (without big refactoring), you should probably redesign your configuration

Comment From: abenneke

Thank you for looking into this!

I perfectly agree, that the configuration class is loaded twice. But this is normal and happens all the time: All @Configuration classes (also) imported somewhere are found by the componentScan and are processed a second time when the @Import is processed. Just the order of these processings may vary.

And for that case “a configuration class is found by the componentScan first and later imported by another configuration class (here FooConfigurationA is imported by FooConfigurationB)” this is already handled in ConfigurationClassParser:

First, FooConfigurationA is processed from componentScan and stored in this.configurationClasses as “not imported”. Then, while processing FooConfigurationB, FooConfigurationA is processed again, but this time as “imported”, the existingClass in processConfigurationClass gets the “not imported” information from the first processing and we end up at the return in line 236. This effectively ignores the second encounter of FooConfigurationA.

I might be wrong, but to me it looks like the order of the entries in this.configurationClasses is important here. What we see from this case is, that classes imported by others appear before the classes which are importing them (FooConfigurationA before FooConfigurationB in this case).

Now the other case:

While processing BarConfigurationA the @Import of BarConfigurationB is also already processed and both classes end up in this.configurationClasses, again the imported class before the class importing it (BarConfigurationB before BarConfigurationA here). So far, so fine.

However now BarConfigurationB is processed again, this time from the componentScan and “not imported”. The existingClass in processConfigurationClass gets the “imported” information from the first processing and we end up in line 241 of processConfigurationClass and are removing BarConfigurationB from this.configurationClasses. BarConfigurationB is then processed again and re-added later.

And here we have the problem IMHO: With this removal and re-adding the order in this.configurationClasses is destroyed. However, to me this order seems to be important for overriding to work properly.

I understand the comment here, that “explicit bean definition should replace an import” is intended, however this BarConfigurationB is not an “explicit bean definition” in this case, but a “scanned” one. It should therefore not replace the imported one, continue with this already processed import and the second processing should also be ignored.

To solve this, I think adding

if (configClassBeanDefinition instanceof ScannedGenericBeanDefinition) {
   // ignore scanned configuration classes already processed
   return;
}

before this removal (in line 240) should be enough, while configClassBeanDefinition is the definition belonging to the configClass. To do so, the bdCand available in line 303 of ConfigurationClassParser would have to be passed here as configClassBeanDefinition.

This does not sound like a major change to me?

Comment From: encircled

Hi,

Thanks for the detailed response :) Let me double check it

Comment From: encircled

The behavior is inconsistent indeed, I will prepare the PR soon

Comment From: encircled

As I mentioned, it is not that simple unfortunately and I'm afraid of possible side effects. Anyway, lets try to fix it

Comment From: abenneke

What side effects do you have in mind?