191214-SpringBoot tutorial series automatic configuration options take effect
After writing so long Spring series Bowen, I discovered a problem, before all of the articles are let into effect around one thing; then there is no opposite it?
We know that can @ConditionOnXxx
be configured to decide whether a class can be loaded, it is assumed that there is such a scenario
- A Print abstract interface, multiple implementations, such as the output to the console ConsolePrint, FilePrint file output to output to the db DbPrint
- When we actually use, according to the user's selection, use one of the specific implementation
For the above case, of course, it can also be used @ConditionOnExpression
to implement, in addition to recommend a more elegant choice of injection methodImportSelector
I. configuration options
spring boot version of this article used to 2.1.2.RELEASE
Next we use to achieve ImportSelector case outlined above
1. Print category
An interface class, the implementation class three
public interface IPrint {
void print();
}
public class ConsolePrint implements IPrint {
@Override
public void print() {
System.out.println("控制台输出");
}
}
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. Select class
Customize a PrintConfigSelector inheritance ImportSelector, mainly in the implementation class, through our custom annotations to choose which of the three specific load configuration class
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 comment
Mainly used for injection PrintConfigSelector
to take effect, in which the property value, which is used to make a specific choice of configuration to take effect, the default registrationConsolePrint
@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, by modifying the @PrintSelector
switched Print different implementation class value
II. Extended
While the above approach to show a practical case achieved by ImportSelector
the use of gestures can be used to select certain configuration class effect. But there are other points of knowledge, it is necessary to point out what
By bean loading sequence ImportSelector selected configuration class, without enforce the dependence of what is it?
1. demo design
In the default load conditions, the following bean bag loading sequence is sorted according to the name of, let's create a case to test the load order of bean
- Under the same package, create six bean:
Demo0
,DemoA
,DemoB
,DemoC
,DemoD
,DemoE
- Which
Demo0
DemoE
is the common bean - Wherein
DemoA
,DemoC
registered by the configuration class 1 - Wherein
DemoB
,DemoD
there are two types configuration register
Specific code 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 type
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 ConfigSelector
, the default DemoSelector
annotations represent the full load, the returned array, comprising two configuration classes, wherein Config2 in front of Confgi1
2. The actual load order
Slightly modify the front of the class to start, plus @DemoSelector
comment
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, we will define six bean is loaded, based on an output result judging default loading sequence
From the output results, the object to load normal bean; bean and then load the Config2 defined, the last is defined Config1 bean;
Next, adjust the array of objects returned ImportSelector sequentially arranged two classes, if the final output is defined Config1 bean is first loaded, it can be described sequence returns to the order of loading the configuration specified in the bean class
The results output confirms our conjecture
The last question, in bean default initialization sequence during normal bean object loads if the order is superior to us ImportSelector
to register the bean it?
- From the output seems to be the case, but this case is not sufficient, not fully validated this view, you want to find out exactly this point, still have to through source code analysis (although in practice this is)
note
上面的分析只是考虑默认的 bean 初始化顺序,我们依然是可以通过构造方法引入的方式或者@DependOn
注解来强制指定 bean 的初始化顺序的
小结
最后小结一下 ImportSelector 的用法
- 实现接口,返回 String 数组,数组成员为配置类的全路径
- 在配置类中定义 bean
- 返回数组中配置类的顺序,指定了配置类中 bean 的默认加载顺序
- 通过
@Import
直接来使ImportSelector
接口生效
此外还有一个类似的接口DeferredImportSelector
,区别在于实现DeferredImportSelector
的类优先级会低与直接实现ImportSelector
的类,而且可以通过@Order
决定优先级;优先级越高的越先被调用执行
II. 其他
0. 项目
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 项目: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/005-config-selector
1. 一灰灰 Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰 Blog 个人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top