SpringBoot tutorial series automatic configuration options take effect

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 @ConditionOnXxxbe 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 @ConditionOnExpressionto 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

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 PrintConfigSelectorto 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 @PrintSelectorswitched Print different implementation class value

II. Extended

While the above approach to show a practical case achieved by ImportSelectorthe 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 DemoEis the common bean
  • Wherein DemoA, DemoCregistered by the configuration class 1
  • Wherein DemoB, DemoDthere 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 DemoSelectorannotations 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 @DemoSelectorcomment

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 ImportSelectorto 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. 项目

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

一灰灰blog

Guess you like

Origin www.cnblogs.com/yihuihui/p/12045810.html