The order of Spring Boot AutoConfig

The previous article talked about Springboot Configuration and AutoConfiguraton. We can get some rules as follows:

  • Autoconfig must not be in the path of @ComponentScan. This makes the order of Autoconfig difficult to guarantee.
    -Avoid using @ConditionalOnXXX annotation in classes other than autoconfig.
  • After SpringBoot 1.3, @Ordered can no longer be used on @Configuration classes.

Notes on Autoconfig loading

@ComponentScan is not in the same package:
  • autoConfig defined in spring.factories will be loaded
  • spring.factories does not define autoConfig and will not be loaded.
    – If other @Configuration classes @Import this autoConfig, it will be loaded
    – @Autowire the @Bean generated by spring.factories in other @Configuration, resulting in early initialization
@ComponentScan under the same package name
  • Regardless of whether it is defined in spring.factories, it will be loaded immediately after scanning.

SpringBoot prevention

As can be seen from the above, when specifying the package name to be scanned, please do not scan a package name such as org.springframework, otherwise the world will be in chaos. In order to prevent this situation, Spring Boot is fault-tolerant. It has a class that specifically detects this case to prevent you from misconfiguring it. For details, see the default implementation of ComponentScanPackageCheck.

For details, please see spring-boot-2.3.3.RELEASE.jar

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
...

See the code specifically

public class ConfigurationWarningsApplicationContextInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final Log logger = LogFactory.getLog(ConfigurationWarningsApplicationContextInitializer.class);

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
    }

    /**
     * Returns the checks that should be applied.
     * @return the checks to apply
     */
    protected Check[] getChecks() {
        return new Check[] { new ComponentScanPackageCheck() };
    }

    /**
     * {@link BeanDefinitionRegistryPostProcessor} to report warnings.
     */
    protected static final class ConfigurationWarningsPostProcessor
            implements PriorityOrdered, BeanDefinitionRegistryPostProcessor {

        private Check[] checks;

        public ConfigurationWarningsPostProcessor(Check[] checks) {
            this.checks = checks;
        }

        @Override
        public int getOrder() {
            return Ordered.LOWEST_PRECEDENCE - 1;
        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        }

        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            for (Check check : this.checks) {
                String message = check.getWarning(registry);
                if (StringUtils.hasLength(message)) {
                    warn(message);
                }
            }

        }

        private void warn(String message) {
            if (logger.isWarnEnabled()) {
                logger.warn(String.format("%n%n** WARNING ** : %s%n%n", message));
            }
        }

    }

    /**
     * A single check that can be applied.
     */
    @FunctionalInterface
    protected interface Check {

        /**
         * Returns a warning if the check fails or {@code null} if there are no problems.
         * @param registry the {@link BeanDefinitionRegistry}
         * @return a warning message or {@code null}
         */
        String getWarning(BeanDefinitionRegistry registry);

    }

    /**
     * {@link Check} for {@code @ComponentScan} on problematic package.
     */
    protected static class ComponentScanPackageCheck implements Check {

        private static final Set<String> PROBLEM_PACKAGES;

        static {
            Set<String> packages = new HashSet<>();
            packages.add("org.springframework");
            packages.add("org");
            PROBLEM_PACKAGES = Collections.unmodifiableSet(packages);
        }

        @Override
        public String getWarning(BeanDefinitionRegistry registry) {
            Set<String> scannedPackages = getComponentScanningPackages(registry);
            List<String> problematicPackages = getProblematicPackages(scannedPackages);
            if (problematicPackages.isEmpty()) {
                return null;
            }
            return "Your ApplicationContext is unlikely to start due to a @ComponentScan of "
                     + StringUtils.collectionToDelimitedString(problematicPackages, ", ") + ".";
        }

        protected Set<String> getComponentScanningPackages(BeanDefinitionRegistry registry) {
            Set<String> packages = new LinkedHashSet<>();
            String[] names = registry.getBeanDefinitionNames();
            for (String name : names) {
                BeanDefinition definition = registry.getBeanDefinition(name);
                if (definition instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition annotatedDefinition = (AnnotatedBeanDefinition) definition;
                    addComponentScanningPackages(packages, annotatedDefinition.getMetadata());
                }
            }
            return packages;
        }

        private void addComponentScanningPackages(Set<String> packages, AnnotationMetadata metadata) {
            AnnotationAttributes attributes = AnnotationAttributes
                    .fromMap(metadata.getAnnotationAttributes(ComponentScan.class.getName(), true));
            if (attributes != null) {
                addPackages(packages, attributes.getStringArray("value"));
                addPackages(packages, attributes.getStringArray("basePackages"));
                addClasses(packages, attributes.getStringArray("basePackageClasses"));
                if (packages.isEmpty()) {
                    packages.add(ClassUtils.getPackageName(metadata.getClassName()));
                }
            }
        }

        private void addPackages(Set<String> packages, String[] values) {
            if (values != null) {
                Collections.addAll(packages, values);
            }
        }

        private void addClasses(Set<String> packages, String[] values) {
            if (values != null) {
                for (String value : values) {
                    packages.add(ClassUtils.getPackageName(value));
                }
            }
        }

        private List<String> getProblematicPackages(Set<String> scannedPackages) {
            List<String> problematicPackages = new ArrayList<>();
            for (String scannedPackage : scannedPackages) {
                if (isProblematicPackage(scannedPackage)) {
                    problematicPackages.add(getDisplayName(scannedPackage));
                }
            }
            return problematicPackages;
        }

        private boolean isProblematicPackage(String scannedPackage) {
            if (scannedPackage == null || scannedPackage.isEmpty()) {
                return true;
            }
            return PROBLEM_PACKAGES.contains(scannedPackage);
        }

        private String getDisplayName(String scannedPackage) {
            if (scannedPackage == null || scannedPackage.isEmpty()) {
                return "the default package";
            }
            return "'" + scannedPackage + "'";
        }

    }

}

Controlling the configuration execution sequence under Spring Boot

The automatic configuration under Spring Boot dynamically determines whether the automatic configuration class is loaded or not, and the order in which it is loaded based on the situation in the current container. Therefore, Spring Boot’s automatic configuration has requirements for the order. pring Boot provides us with three annotations @AutoConfigureBefore, @AutoConfigureAfter, and @AutoConfigureOrder (hereinafter collectively referred to as the “three major annotations”) to help us solve this demand.

It should be noted that the three annotations are provided by Spring Boot rather than Spring Framework. The first two are available in version 1.0.0, and @AutoConfigureOrder is new in version 1.3.0, indicating absolute order (the smaller the number, the higher the priority). In addition, these annotations are not mutually exclusive and can be annotated on the same @Configuration automatic configuration class at the same time.

A brief analysis of the three major annotation parsing opportunities

The analysis of the three annotations AutoConfigureBefore, AutoConfigureAfter and AutoConfigureOrder is handed over to AutoConfigurationSorter for sorting and processing. The method is similar to the way AnnotationAwareOrderComparator parses the sorting @Order annotation. Sorting algorithm org.springframework.boot.autoconfigure.AutoConfigurationSorter#doSortByAfterAnnotation

class AutoConfigurationSorter {

    // The only way to make external calls: return the sorted Names, so what is returned is a List (ArrayList)
    List<String> getInPriorityOrder(Collection<String> classNames) {
        ...
        // First arrange a wave in natural order
        Collections.sort(orderedClassNames);
        // Arrange a wave according to the three annotations @AutoConfigureBefore
        orderedClassNames = sortByAnnotation(classes, orderedClassNames);
        return orderedClassNames;
    }
    ...
}

AutoConfigurationImportSelector: Spring automatic configuration processor, used to load all automatic configuration classes. It implements the DeferredImportSelector interface: This also explains why automatic configuration is executed last

 private List<String> sortAutoConfigurations(Set<String> configurations,
                AutoConfigurationMetadata autoConfigurationMetadata) {
            return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
                    .getInPriorityOrder(configurations);
        }

AutoConfigurations: Represents automatic configuration of @Configuration class.

 protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
        List<String> names = classes.stream().map(Class::getName).collect(Collectors.toList());
        List<String> sorted = SORTER.getInPriorityOrder(names);
        return sorted.stream().map((className) -> ClassUtils.resolveClassName(className, null))
                .collect(Collectors.toCollection(ArrayList::new));

Analysis of the three annotations AutoConfigureBefore, AutoConfigureAfter and AutoConfigureOrder

1. @AutoConfigureAfter
Used on an automatic configuration class, it means that the automatic configuration class needs to be configured after the other specified automatic configuration class is configured.

For example, the automatic configuration class of Mybatis needs to be installed after the data source automatic configuration class.

@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
}

2. @AutoConfigureBefore

This is the opposite of the @AutoConfigureAfter annotation, which means that the automatic configuration class needs to be configured before the other specified automatic configuration class.

3. @AutoConfigureOrder

There is a new annotation @AutoConfigureOrder in Spring Boot 1.3.0, which is used to determine the priority order of configuration loading.

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) //The highest priority in automatic configuration
@Configuration
@ConditionalOnWebApplication // Only for web applications
@Import(BeanPostProcessorsRegistrar.class) //Import the settings of the built-in container
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
// …
}

@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
// …
}
}

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 139213 people are learning the system