After writing a series of Spring blogs for so long, I found a problem. All previous articles focused on making one thing work. Is there anything that goes against it?
We know that @ ConditionOnXxx can be used to determine whether a configuration class can be loaded. Suppose there is such an application scenario
- There is an abstract interface for Print, with multiple implementations, such as ConsolePrint output to console, FilePrint output to file, DbPrint output to db
- In actual use, we use one of the specific implementations according to the user's choice
For the above case s, of course, you can also use @ ConditionOnExpression to implement. In addition, we recommend a more elegant choice injection method, ImportSelector
<!-- more -->
I. configuration selection
The spring boot version used in this article is 2.1.2.RELEASE
Next, we use the ImportSelector to implement the above case
1. Print class
One interface class and three implementation classes
public interface IPrint { void print(); } public class ConsolePrint implements IPrint { @Override public void print() { System.out.println("console output "); } } public class DbPrint implements IPrint { @Override public void print() { System.out.println("db print"); } } public class FilePrint implements IPrint { @Override public void print() { System.out.println("file print"); } }
2. selection class
A custom PrintConfigSelector inherits the ImportSelector, mainly in the implementation class. Through our custom annotation, we can choose which of the three configuration classes to load
public class PrintConfigSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(PrintSelector.class.getName())); Class config = attributes.getClass("value"); return new String[]{config.getName()}; } public static class ConsoleConfiguration { @Bean public ConsolePrint consolePrint() { return new ConsolePrint(); } } public static class FileConfiguration { @Bean public FilePrint filePrint() { return new FilePrint(); } } public static class DbConfiguration { @Bean public DbPrint dbPrint() { return new DbPrint(); } } }
3. PrintSelector annotation
It is mainly used to inject PrintConfigSelector to take effect. The value property is used to select which configuration takes effect. ConsolePrint is registered by default
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(PrintConfigSelector.class) public @interface PrintSelector { Class<?> value() default PrintConfigSelector.ConsoleConfiguration.class; }
4. test
//@PrintSelector(PrintConfigSelector.FileConfiguration .class) //@PrintSelector(PrintConfigSelector.DbConfiguration .class) @PrintSelector @SpringBootApplication public class Application { public Application(IPrint print) { print.print(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
In the actual test, you can change the value of @ PrintSelector to switch between different Print implementation classes
II. extension
Although the above shows the usage posture of ImportSelector through an actual case implementation, it can be used to select some configuration classes to take effect. But there are other points of knowledge. It is necessary to point out
What is the loading order of the bean s in the configuration class selected by the ImportSelector without forcing the dependency to be specified?
1. demo design
Under the default loading condition, the bean loading order under the package is based on the named sorting. Next, let's create a case to test the bean loading order
- Under the same package, create 6 beans: demo0, demoa, DemoB, democ, demod, demoe
- Where Demo0 DemoE is a common bean
- Where demoa and democ are registered by configuration class 1
- Where DemoB and demod have configuration class 2 Registration
The specific code is as follows
@Component public class Demo0 { private String name = "demo0"; public Demo0() { System.out.println(name); } } public class DemoA { private String name = "demoA"; public DemoA() { System.out.println(name); } } public class DemoB { private String name = "demoB"; public DemoB() { System.out.println(name); } } public class DemoC { private String name = "demoC"; public DemoC() { System.out.println(name); } } public class DemoD { private String name = "demoD"; public DemoD() { System.out.println(name); } } @Component public class DemoE { private String name = "demoE"; public DemoE() { System.out.println(name); } }
Corresponding configuration class
public class ToSelectorAutoConfig1 { @Bean public DemoA demoA() { return new DemoA(); } @Bean public DemoC demoC() { return new DemoC(); } } public class ToSelectorAutoConfig2 { @Bean public DemoB demoB() { return new DemoB(); } @Bean public DemoD demoD() { return new DemoD(); } } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ConfigSelector.class) public @interface DemoSelector { String value() default "all"; } public class ConfigSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(DemoSelector.class.getName())); String config = attributes.getString("value"); if ("config1".equalsIgnoreCase(config)) { return new String[]{ToSelectorAutoConfig1.class.getName()}; } else if ("config2".equalsIgnoreCase(config)) { return new String[]{ToSelectorAutoConfig2.class.getName()}; } else { return new String[]{ToSelectorAutoConfig2.class.getName(), ToSelectorAutoConfig1.class.getName()}; } } }
Note that ConfigSelector. The default DemoSelector annotation indicates that all are loaded. The returned array contains two configuration classes, of which Config2 is in front of Confgi1
2. Actual measurement of loading sequence
Modify the previous startup class a little and add the @ DemoSelector annotation
PrintSelector(PrintConfigSelector.FileConfiguration .class) //@PrintSelector(PrintConfigSelector.DbConfiguration .class) //@PrintSelector @DemoSelector @SpringBootApplication public class Application { public Application(IPrint print) { print.print(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
In the above case, the six bean s we defined will be loaded, and the default loading order will be determined according to the output results
From the output results, first load the ordinary bean object, then load the bean defined in Config2, and finally the bean defined in Config1;
Next, adjust the order of the two configuration classes in the array object returned by the ImportSelector. If the final output is that the beans defined in Config1 are loaded first, then the order returned specifies the loading order of the beans in these configuration classes
The output results confirm our conjecture
Finally, in the default bean initialization sequence, is the normal bean object loading sequence better than the bean registered through the ImportSelector?
- It seems that the output result is like this, but this case is not sufficient, and it is impossible to fully verify this point. If you want to make sure of this, you have to go through source code analysis (although it is actually like this)
Be careful
The above analysis only considers the default bean initialization order. We can still force to specify the bean initialization order through the way of construction method introduction or @ DependOn annotation
Summary
Finally, a brief summary of the usage of ImportSelector
- Implementation interface, return String array, array member is the full path of configuration class
- Defining bean s in configuration classes
- Returns the order of configuration classes in the array, specifying the default loading order of bean s in the configuration class
- Validate the ImportSelector interface directly through @ Import
In addition, there is a similar interface, DeferredImportSelector. The difference is that the priority of the class implementing DeferredImportSelector is lower than that of the class directly implementing ImportSelector, and the priority can be determined by @ Order. The higher the priority is, the earlier it is called for execution
II. other
0. project
- Works: https://github.com/liuyueyi/spring-boot-demo
- Item: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/005-config-selector
1. A grey Blog
The best letter is not as good as the above. It's just a one-of-a-kind remark. Due to the limited personal ability, there are inevitably omissions and mistakes. If you find a bug or have better suggestions, you are welcome to criticize and correct. Thank you very much
Here is a grey personal blog, recording all the blogs in study and work. Welcome to visit
- A grey Blog personal Blog https://blog.hhui.top
- A grey blog spring special blog http://spring.hhui.top