Spring boot series tutorial automatic configuration selection takes effect

Posted by dsds1121 on Sun, 15 Dec 2019 13:50:17 +0100

191214 spring boot series tutorials automatic configuration selection takes effect

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

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

Topics: Programming Spring github