一文带你快速了解spring框架中DeferredImportSelector接口的作用
背景介绍
之前我们在一文带你掌握spring框架中import注解的作用中讲到了@import注解的作用,并且提到@import注解中可以放一个实现了org.springframework.context.annotation.ImportSelector接口的类用来实现条件配置。当时受限于篇幅,我没有讲到ImportSelector接口其实还有个子接口叫org.springframework.context.annotation.DeferredImportSelector。和原来的ImportSelector接口相比,它的特色在于可以实现延迟导入配置的功能,springboot的自动化配置就用到了这个接口,今天我们就来好好讲讲这个接口,这对大家理解springboot是如何实现自动化配置的会有很大的帮助。

DeferredImportSelector接口的作用
延迟导入配置类
前面我们讲过DeferredImportSelector接口最大的作用是延迟导入配置,这句话是什么意思呢?意思就是:当普通的配置类都解析完成后再去解析这个配置。关于它和ImportSelector接口之间的区别,我举一个例子大家就明白了:
下面是实现了ImportSelector接口的类:
public class ImmediateConfigImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ImmediateConfig.class.getName()};
}
}
public class ImmediateConfig {
@Bean
public Apple apple() {
return new Apple();
}
public static class Apple {
}
}
下面是实现了DeferredImportSelector接口的类:
public class DeferredConfigImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{DeferredConfig.class.getName()} ;
}
}
public class DeferredConfig {
@Bean
public Fish fish() {
return new Fish();
}
public static class Fish {
}
}
接下来我在一个主配置类中通过@import注解导入这两个类:
package com.lizemin.importcase.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author lzm
* @date 2025/10/29 19:47
* @description
*/
@Configuration
@Import({ImmediateConfigImportSelector.class, DeferredConfigImportSelector.class})
public class SecurityConfig {
@Bean
public Security security() {
return new Security();
}
public static class Security {
}
}
接下来我想问大家一个问题:spring框架创建这些bean的顺序是怎样的?是先创建Apple实例,还是先创建Fish的实例?请大家在此处认真思考两分钟。
验证方法很简单,我们在创建对象的地方打个断点,然后以调试模式重启项目,程序先经过哪个断点就表示哪个对象先创建。这里我直接公布答案,先创建Apple对象,然后创建Security对象,最后创建Fish对象,为什么会这样呢?
因为创建Fish对象的DeferredConfig配置类是通过DeferredImportSelector接口导入的,而这个接口的作用就是延迟导入配置类,凡是通过DeferredImportSelector接口导入的配置类都是到最后才被spring框架解析 ,也就是说凡是通过这种方式注册的bean都是放在最后面才创建。这就导致了DeferredConfig配置类最后被spring解析,里面的bean也是最后才创建。
配置类分组处理
DeferredImportSelector接口最主要的功能还是延迟导入配置类,除此之外,它还支持对DeferredImportSelector分组处理,将同一分组中的多个DeferredImportSelector导入的配置类收集起来进行批处理, 用于实现更复杂的功能,比如对某个模块的配置类进行排序。我们之前的案例中并没有为DeferredImportSelector指定分组,这时候它们会使用默认的分组。一般来说,我们也很少会用到分组功能,只有对于特别复杂的需求才会用DeferredImportSelector的分组。
下面我来通过一个案例讲解DeferredImportSelector接口的分组功能。
创建分组的实现
对DeferredImportSelector分组需要实现DeferredImportSelector.Group接口,下面是我的分组实现:
package com.lizemin.importcase.group;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.ArrayList;
import java.util.Comparator;
/**
* @author lzm
* @date 2025/10/30 18:56
* @description 数据源分组,用来处理所有和数据库相关的配置类
*/
public class DataSourceGroup implements DeferredImportSelector.Group {
private static final Logger log = LoggerFactory.getLogger(DataSourceGroup.class);
private ArrayList<Entry> entries = new ArrayList<>();
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
String[] configClasses = selector.selectImports(metadata);
for (String clazz : configClasses) {
entries.add(new Entry(metadata, clazz));
}
}
/**
* 该方法会返回此分组下所有DeferredImportSelector最终要导入的配置类
*
* @return 该分组所有DeferredImportSelector最终要导入的配置类
*/
@Override
public Iterable<Entry> selectImports() {
for (Entry entry : entries) {
log.info("datasourceGroup中最终导入了配置类:{}", entry.getImportClassName());
}
// 这里还可以对entries进行排序,比如按照类名排序
entries.sort(Comparator.comparing(Entry::getImportClassName));
return entries;
}
}
为DeferredImportSelector指定分组
下面我写了两个DeferredImportSelector实现类,将它们都划分到前面创建的DataSourceGroup分组。
JdbcTemplateConfigSelector:
package com.lizemin.importcase.group;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author lzm
* @date 2025/10/30 19:00
* @description
*/
public class JdbcTemplateConfigSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{JdbcTemplateConfig.class.getName()};
}
/**
* 标记该selector的分组,该接口不是必须要实现的,如果
* <p>
* 如果不实现该接口,默认是null,该selector都会被放到一个默认的分组中
*
* @return 分组class
*/
@Override
public Class<? extends Group> getImportGroup() {
return DataSourceGroup.class;
}
}
MySqlConfigSelector:
package com.lizemin.importcase.group;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author lzm
* @date 2025/10/30 19:11
* @description 需要注意的是:该类不需要加@configuration,否则会影响DeferredImportSelector的正常功能
*/
public class MySqlConfigSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MySqlConfig.class.getName()};
}
@Override
public Class<? extends Group> getImportGroup() {
return DataSourceGroup.class;
}
}
在上面两个DeferredImportSelector实现类中,我们通过getImportGroup方法指定了它们的分组是DataSourceGroup。
接下来我们通过一个主配置类导入这两个DeferredImportSelector实现类:
package com.lizemin.importcase.group;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author lzm
* @date 2025/10/30 19:18
* @description
*/
@Configuration
@Import({JdbcTemplateConfigSelector.class, MySqlConfigSelector.class})
public class MainConfig {
}
重启项目,可以看到如下日志:

这说明我们的DataSourceGroup分组中已经成功获取了它分组下所有DeferredImportSelector导入的配置类信息。到这里,DeferredImportSelector的分组功能我们也讲完了。
有些小伙伴可能会好奇DeferredImportSelector接口的延迟导入和配置类分组是如何实现的,下面我将带大家通过分析源码来了解springboot是怎么做到这一切的。
DeferredImportSelector接口延迟导入的原理
DeferredImportSelector接口之所以能够实现延迟导入,答案在这个方法中:
org.springframework.context.annotation.ConfigurationClassParser#parse。
public void parse(Set<BeanDefinitionHolder> configCandidates) {
// 首先在这个for循环中处理所有普通的配置类,也就是加了@Configuration,或者@Component的类
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
ConfigurationClass configClass;
if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
// 解析配置类
configClass = parse(annotatedBeanDef, holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) {
configClass = parse(abstractBeanDef.getBeanClass(), holder.getBeanName());
}
else {
configClass = parse(bd.getBeanClassName(), holder.getBeanName());
}
}
}
// 开始处理延迟导入的配置类
this.deferredImportSelectorHandler.process();
}
在这段代码中,首先在for循环中处理所有的普通配置类,也就是加了@Configuration,或者@Component的类。for循环结束后,普通配置类的解析也就完成了,接下来才处理DeferredImportSelector接口导入的配置类。
最后我们看下解析完成后的类是怎样的:

通过idea的值表达式查看解析后的配置类的顺序,可以看到我们写的DeferredConfig配置类放在了最后,这就是延迟导入的原理。

接下来我们看下springboot框架是如何处理DeferredImportSelector接口的,我们回去看下this.deferredImportSelectorHandler.process();这行代码的具体实现:
void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 重点在下面两行代码,将deferredImports转成DeferredImportSelectorGroupingHandler对象
deferredImports.forEach(handler::register);
// 通过DeferredImportSelectorGroupingHandler对象来处理DeferredImportSelector导入的配置类
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
在上面的代码中,重点在这两行代码:
// 类型转换
deferredImports.forEach(handler::register);
// 处理导入的配置类
handler.processGroupImports();
- 第一行代码的作用是:将deferredImports转成DeferredImportSelectorGroupingHandler对象,类似于我们平时开发项目的toVO,toPO的方法,其实就是一个类型转换,没啥好说的。
- 第二行代码的作用是:用前面转换得到的对象来处理导入的配置类
接下来我们具体看看handler::register的实现:
void register(DeferredImportSelectorHolder deferredImport) {
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
// 如果分组为空,则使用DeferredImportSelector实现类对象作为key
(group != null ? group : deferredImport),
key -> new DeferredImportSelectorGrouping(createGroup(group))); // 这里的createGroup(group)比较重要,我们重点看下,了解它是如何构建group的
grouping.add(deferredImport);
this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getConfigurationClass());
}
上面代码中的createGroup(group)比较重要,我们重点看下:
private Group createGroup(@Nullable Class<? extends Group> type) {
// 当type为空,也就是分组为空时,使用默认的分组对象
Class<? extends Group> effectiveType = (type != null ? type : DefaultDeferredImportSelectorGroup.class);
return ParserStrategyUtils.instantiateClass(effectiveType, Group.class,
ConfigurationClassParser.this.environment,
ConfigurationClassParser.this.resourceLoader,
ConfigurationClassParser.this.registry);
}
重点是默认分组对象的代码,也就是Class<? extends Group> effectiveType = (type != null ? type : DefaultDeferredImportSelectorGroup.class); ,这行代码中包含了分组为空时的处理,具体逻辑是当分组为空时,创建默认的分组对象,也就是DefaultDeferredImportSelectorGroup。我们看下这个类的实现:
private static class DefaultDeferredImportSelectorGroup implements Group {
private final List<Entry> imports = new ArrayList<>();
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
// 这里是在调用父接口的selectImports方法,相当于做了一次类型转换,类似于适配器模式。
for (String importClassName : selector.selectImports(metadata)) {
this.imports.add(new Entry(metadata, importClassName));
}
}
@Override
public Iterable<Entry> selectImports() {
return this.imports;
}
}
接下来我们看下具体的处理方法:
void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> filter = grouping.getCandidateFilter();
// 调用group接口的两个方法获取到最终的配置类信息
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
// 这里开始解析DeferredImportSelector.Group的selectImports方法返回的配置类信息
processImports(configurationClass, asSourceClass(configurationClass, filter),
Collections.singleton(asSourceClass(entry.getImportClassName(), filter)),
filter, false);
}
}
上面的代码中,重点代码如下:
grouping.getImports(): 调用group接口的两个方法获取到最终的配置类信息processImports(configurationClass, asSourceClass(configurationClass, filter),: 解析前面得到的配置类,然后将配置类的BeanDefinition注册到spring容器中。
我们重点看下 grouping.getImports()的具体实现,可以看到跟上面spring的使用方式完全一样。
Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
//先调用process方法,解析出配置类信息
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
// 再调用selectImports方法捣鼓一番后返回
return this.group.selectImports();
}
这个方法做了两件事:
- 先调用process方法,解析出配置类信息
- 再调用selectImports方法捣鼓一番后返回
我们看下这个DeferredImportSelector.Group接口的定义:
interface Group {
/**
* @metadata: @import所在类的注解元信息
*
* @DeferredImportSelector : 该接口的实现类对象
*/
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
/**
* 返回这个分组中应该被导入的配置类信息
*/
Iterable<Entry> selectImports();
}
所以使用DeferredImportSelector是可以使用分组的,通过分组可以实现更加负责的功能,比如springboot的自动化配置需要对配置类的解析顺序进行排序,就需要用到这里的分组功能。
我们前面为了让大家快速理解DeferredImportSelector接口延迟导入配置类的作用,就没搞那么复杂,在没有指定分组的时候,springboot会自动为我们创建一个默认的分组对象,前面我们也在源码分析中讲过,默认的分组实现类是org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup。
到这里,关于DeferredImportSelector接口的作用我们就讲完了。大家有兴趣的可以按照这个思路去分析springboot的自动化配置是如何实现的,比如springboot是如何对自动化配置类进行排序,这个就当是留给大家的家庭作业吧,哈哈哈。
本文完整示例代码的地址
最后的总结
这篇文章主要带大家学习了spring框架中DeferredImportSelector接口的作用,它最主要的作用还是延迟导入配置类 ,另外就是它还支持分组,实现更加复杂的配置类导入功能,关于分组不是必须的,对于简单的延迟导入配置不需要使用它的分组功能。
最后我们还通过源码分析来帮助大家理解spring框架是如何解析这个接口,从而实现延迟导入配置类的功能的。
觉得有收获的朋友可以点个赞,您的鼓励就是我最大的动力!
更多推荐



所有评论(0)