Spring boot series tutorial automatic configuration selection takes effect

Time:2020-7-26

Spring boot series tutorial automatic configuration selection takes effect

191214 spring boot series tutorial automatic configuration selection takes effect


After writing spring blog series for such a long time, I found a problem. All the previous articles are focused on making one thing work. Is there any one doing the opposite?

We know we can go through@ConditionOnXxxTo determine whether a configuration class can be loaded, suppose there is such an application scenario

  • There is an abstract interface for print and multiple implementations, such as console print output to console, fileprint output to file, dbprint output to DB
  • We use one of the specific implementations according to the user’s choice

For the above case, it can also be used@ConditionOnExpressionIn addition, a more elegant alternative injection method is recommendedImportSelector

<!– more –>

1. Configuration selection

The spring boot version used in this article is 2.1.2.release

Next, we use the importselector to implement the case mentioned above

1. Print class

One interface class, 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

Customizing a printconfigselector inherits importselector. In the implementation class, we can select which of the three configuration classes to load through our custom annotations

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 for injectionPrintConfigSelectorThe value attribute is used to select which configuration will take effect by defaultConsolePrint

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(PrintConfigSelector.class)
public @interface PrintSelector {
    Class<?> value() default PrintConfigSelector.ConsoleConfiguration.class;
}

4. Testing

//@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, through the modification@PrintSelectorTo switch between different print implementation classes

2. Extension

Although the above is demonstrated by an actual case implementationImportSelectorCan be used to select certain configuration classes to take effect. But there are some other points of knowledge that need to be pointed out

What is the loading order of beans in the configuration class selected by importselector without forcing dependency specification?

1. Demo design

Under the default loading conditions, the bean loading order under the package is based on the named ordering. 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
  • amongDemo0 DemoEIs a normal bean
  • amongDemoA, DemoCRegistered by configuration class 1
  • amongDemoB, DemoDThere is configuration class 2 registration

The specific codes are 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()};
        }
    }
}

Pay attentionConfigSelector, defaultDemoSelectorThe annotation indicates that all are loaded. The returned array contains two configuration classes, of which config2 is in front of confgi1

2. Loading sequence measurement

Slightly modify the previous startup class and add@DemoSelectorannotation

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 case above, the six beans we defined will be loaded. The default loading order is determined according to the output results

Spring boot series tutorial automatic configuration selection takes effect

From the output result, 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 objects returned by importselector. If the final output is that the beans defined in config1 are loaded first, then the returned order specifies the loading order of beans in these configuration classes

Spring boot series tutorial automatic configuration selection takes effect

The output results confirm our conjecture

Finally, in the default bean initialization sequence, is the normal bean object loading order better than that of theImportSelectorWhat about the bean to register?

  • It seems like this from the output, but this case is not enough to fully verify this point. If you want to make sure of this point, you have to go through source code analysis (although it is actually the case)

be careful

The above analysis only considers the default bean initialization order. We can still introduce it through the constructor method or@DependOnAnnotation to force the initialization order of the bean

Summary

Finally, we summarize the usage of importselector

  • The path of the full class is a string array
  • Defining beans in configuration classes
  • Returns the order of the configuration classes in the array, specifying the default loading order of the beans in the configuration class
  • adopt@ImportDirectImportSelectorInterface validation

There is also a similar interfaceDeferredImportSelectorThe difference is in implementationDeferredImportSelectorClass priority will be low and directly implementedImportSelectorAnd you can use the@OrderDetermine the priority; the higher the priority, the earlier it is called to execute

2. Others

0. Project

  • Project: https://github.com/liuyueyi/spring-boot-demo
  • Project: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/005-config-selector

1. A gray blog

It’s not as good as a letter. The above contents are all from one family. Due to 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, and thank you

The following is a gray personal blog, recording all the blog articles in study and work. Welcome to visit

  • Personal blog https://blog.hhui.top
  • A grey blog spring blog http://spring.hhui.top

Spring boot series tutorial automatic configuration selection takes effect