前文讲了Springboot Configuration 和 AutoConfiguraton ,我们可以得到如下的一些rule
- Autoconfig 一定不能再@ComponentScan 的路径里。这会使Autoconfig的顺序难以保证。
-避免@ConditionalOnXXX annotation在autoconfig 以外的类使用。 - 在SpringBoot 1.3 以后,@Ordered 不能再@Configuration 的类上使用。
Autoconfig 加载的注意事项
@ComponentScan不在同一个包下:
- spring.factories 中定义了autoConfig 会被加载
- spring.factories 没有定义autoConfig, 不会被加载
- 如果其他@Configuration类@Import了这个autoConfig, 会被加载
- 其他@Configuration中@Autowire了spring.factories生成的@Bean, 导致提前初始化
@ComponentScan同一包名下
- 不管spring.factories中有没有定义,扫描到后立即加载
SpringBoot 的预防
从上文可知,指定扫描的包名,请千万不要扫描到形如org.springframework这种包名,否则“天下大乱” 。 为了防止这种情况出现,Spring Boot做了容错的。它有一个类专门检测这个case防止你配置错了,具体参见ComponentScanPackageCheck预设实现。
具体请看spring-boot-2.3.3.RELEASE.jar
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
...
具体看代码
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 + "'";
}
}
}
Spring Boot下控制配置执行顺序
Spring Boot下对自动配置根据当前容器内的情况来动态的判断自动配置类的载入与否、以及载入的顺序,因此Spring Boot的自动配置它对顺序是有要求的。pring Boot给我们提供了@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder(下面统称这三个注解为“三大注解”)这三个注解来帮我们解决这种诉求。
需要注意的是:三大注解是Spring Boot提供的而非Spring Framework。其中前两个是1.0.0就有了,@AutoConfigureOrder属于1.3.0版本新增,表示绝对顺序(数字越小,优先顺序越高)。另外,这几个注解另外,这几个注解并不互斥,可以同时标注在同一个@Configuration自动配置类上。
三大注解解析时机浅析
AutoConfigureBefore 、AutoConfigureAfter 和 AutoConfigureOrder这三个注解的解析都是交给AutoConfigurationSorter来排序、处理的,做法类似于AnnotationAwareOrderComparator去解析排序@Order注解。排序算法 org.springframework.boot.autoconfigure.AutoConfigurationSorter#doSortByAfterAnnotation
class AutoConfigurationSorter {
// 唯一給外部呼叫的方法:返回排序好的Names,因此返回的是個List嘛(ArrayList)
List<String> getInPriorityOrder(Collection<String> classNames) {
...
// 先按照自然順序排一波
Collections.sort(orderedClassNames);
// 在按照@AutoConfigureBefore這三個註解排一波
orderedClassNames = sortByAnnotation(classes, orderedClassNames);
return orderedClassNames;
}
...
}
AutoConfigurationImportSelector:Spring自动配置处理器,用于载入所有的自动配置类。它实现了DeferredImportSelector介面:这也顺便解释了为何自动配置是最后执行的原因
private List<String> sortAutoConfigurations(Set<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
.getInPriorityOrder(configurations);
}
AutoConfigurations:表示自动配置@Configuration类。
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));
}