Issue
I am trying to create something like @Enable... autoconfiguration. I wanted to create such an annotation for a custom library that has an extensive spring config, yet needs like 2 beans supplied, and based on those initializes various contexts. It indeed initializes beans in all @configuration classes that are returned in an array, but I also want to do some custom configauration logic based on already registered beans. Now the javadoc for that https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ImportSelector.html
States that
ImportSelectors are usually processed in the same way as regular @Import annotations, however, it is also possible to defer selection of imports until all @Configuration classes have been processed (see DeferredImportSelector for details).
So I turned to DeferredImportSelector cause that Selector is said to be run after all @Configuration beans so I can do conditional Beans. Now javadoc is quite clear here (https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/DeferredImportSelector.html)
A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional
So this is perfect for me. Well until it turns out that no matter what I do, the import selector selectImports method is run always as first before all @Configuration beans.
I figured out that maybe the main @Configuration bean is always last, and the javadoc actually mentions all imported @Configuration beans. It appears that is also not the case. I have been checking precedence using the debugger, but here is the test code I did, and it is pretty simple:
The import selector that does nothing except system out:
public class TestImportSelector implements DeferredImportSelector{
@Override
public String[] selectImports(AnnotationMetadata arg0) {
System.out.println("ImportSelector");
return new String[0];
}
}
The configuration class (the imported to check if any of mentioned ideas work)
@Configuration
public class ImportedTestContext {
@Bean
public String testBeanString2(){
System.out.println("bean2");
return "string2";
}
}
The main context class (also tried swapping the imported classes)
@Configuration
@Import({TestImportSelector.class, ImportedTestContext.class})
public class TestMainContext {
@Bean
public String testBeanString(){
System.out.println("bean1");
return "string";
}
}
And finally my main class
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TestMainContext.class);
}
}
Sooo, as You run the main. You always get the same output
ImportSelector
bean2
bean1
The import selector is always first no matter what. Additionally I tried to mess around with Ordered interface as the javadoc says
Implementations can also extend the Ordered interface or use the Order annotation to indicate a precedence against other DeferredImportSelectors
But it appears that getOrder method is not even called. Well that might be because it says that it only checks it agains other DeferredImportSelectors (and there were none but was worth trying)
I have done this with spring-context 4.3.2.RELEASE as this is what is used in my project, but just to make sure also tested with 5.0.5.RELEASE. Exactly the same result.
So I believe I do not understand something with regards to ImportSelector, DeferredImportSelector, spring or there is a little chance that the javadoc is not saying the truth or I misunderstood it....
I would appreciate any help or advice....
Just to make it clear: Based on that DeferredImportSelector, I want it to implement BeanFactoryAware (this part works, Spring indeed injects the BeanFactory) that would check what beans were already defined (like those funny test string beans) and based on that will tell spring what additional configurations should be loaded. Based on javadoc this is what it was made for.....
Solution
It looks like DeferredImportSelector
has a little bit unclear documentation. After running a few tests and checking the code, it turns out that what is deferred
is Import
, not ImportSelector
.
So, if you make use of DeferredImportSelector
, you can select a configuration class(es) which import will be deferred
.
A selectImports
method will get executed normally - during configuration files resolving/parsing, so - using BeanFactory
to check if other beans' definitions are already loaded would be certainly a bad idea (as some might not yet be).
The best approach would be to put this logic into @Conditional
annotations family (within a target configuration class) and make sure it will be processed after all user-defined configurations.
Answered By - Maciej Dobrowolski