@Import 有哪几种用法?ImportSelector 和 ImportBeanDefinitionRegistrar 是什么?
简化版
@Import 是 Spring 「把外部的类导入成 Bean」的机制,有三种用法:① 直接导入一个类(@Import(A.class),把 A 注册成 Bean,最简单);② 导入 ImportSelector 实现类(它的 selectImports() 返回一批「要导入的类名」字符串数组,可以根据条件动态决定导入哪些类——Spring Boot 自动配置就靠它批量导入配置类);③ 导入 ImportBeanDefinitionRegistrar 实现类(它能直接手动往容器注册 BeanDefinition,最灵活,MyBatis 的 Mapper 扫描就靠它动态注册一堆 Mapper 代理 Bean)。三者能力递增:直接导入 < 批量选择导入 < 手动注册。
详细版
三种用法:
| 用法 | 导入内容 | 能力 | 典型应用 |
|---|---|---|---|
@Import(SomeClass.class) | 一个/几个具体类 | 把类注册成 Bean(静态) | 引入某个配置类 |
@Import(XxxImportSelector.class) | ImportSelector 实现 | 返回类名数组,动态批量导入 | Spring Boot 自动配置(AutoConfigurationImportSelector) |
@Import(XxxRegistrar.class) | ImportBeanDefinitionRegistrar 实现 | 手动注册 BeanDefinition,最灵活 | MyBatis Mapper 扫描、动态代理 Bean |
// 用法1:直接导入类
@Configuration
@Import(RedisConfig.class) // 把 RedisConfig 注册成 Bean
public class AppConfig { }
// 用法2:ImportSelector —— 动态决定导入哪些类
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
// 可以根据条件/配置,返回不同的类名数组
return new String[]{"com.example.AConfig", "com.example.BConfig"};
}
}
// 用法3:ImportBeanDefinitionRegistrar —— 手动注册 BeanDefinition
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 直接构造 BeanDefinition 并注册(可动态生成任意数量的 Bean)
BeanDefinition bd = new RootBeanDefinition(MyService.class);
registry.registerBeanDefinition("myService", bd);
}
}
⚠️ 很多
@EnableXxx注解(@EnableAutoConfiguration、@EnableScheduling、@EnableCaching…)底层都是用@Import实现的——@Enable注解上标注@Import(某个Selector或Registrar),一「Enable」就通过 Import 把相关的配置类/Bean 导入进来。所以理解@Import的三种用法,就理解了 Spring 一整套@Enable注解的实现套路。
完整版教学
一、@Import 解决什么:把类「引入」容器
Spring 注册 Bean 的常规方式是「组件扫描」(@ComponentScan 扫到 @Component 就注册)。但有些类你没法或不想加 @Component——比如第三方库的类(你改不了源码)、或需要「有条件/动态」注册的类。@Import 就是补这个的:
组件扫描:扫到 @Component 才注册 —— 但第三方类没这注解、动态类还不存在
@Import:主动把指定的类"引入"容器,不需要它们有 @Component
→ 适合注册第三方类、配置类、以及"根据条件动态决定注册什么"
@Import 的价值是主动、可编程地控制「注册什么 Bean」——不依赖被动扫描,而是由你(或框架)明确指定或动态计算要导入的类。这在框架开发里极其重要:Spring Boot、MyBatis、Dubbo 等都用 @Import 来「把自己的一堆 Bean 装进用户的容器」。理解三种用法的能力差异,就理解了框架「自动装配」的实现手段。
二、用法一:直接导入类(最简单)
最基础的用法——@Import(类.class),直接把指定的类注册成 Bean:
@Configuration
@Import({RedisConfig.class, KafkaConfig.class}) // 把这两个配置类注册进来
public class AppConfig { }
效果等同于在这些类上加 @Component 让它们被扫描到,但不需要改那些类(适合第三方类),也不依赖它们在扫描路径下。导入的可以是:普通类(注册成 Bean)、@Configuration 配置类(连同它的 @Bean 一起导入)、@ComponentScan 类等。
这种用法是「静态、明确」的——你在编译期就写死了要导入哪几个类。它适合「我明确知道要注册这几个类」的场景。但它不够灵活——如果要导入的类数量不定、或要根据条件决定,就得用后面两种。所以直接导入是「入门级」用法,能力最弱但最简单。
三、用法二:ImportSelector(动态批量导入)
ImportSelector 是能力升级——它不直接指定类,而是提供一个方法 selectImports(),返回一个「要导入的类名」的字符串数组,可以在方法里根据条件动态计算返回哪些类:
public class CacheImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
// 能拿到注解元数据、环境,据此动态决定导入哪些类
String cacheType = getCacheType();
if ("redis".equals(cacheType)) {
return new String[]{"com.example.RedisCacheConfig"};
} else {
return new String[]{"com.example.CaffeineCacheConfig"};
}
}
}
关键能力:返回的是「类名字符串数组」,而且是运行时动态计算的——所以能实现「根据配置/条件,导入不同的、数量不定的类」。这正是 Spring Boot 自动配置的核心机制:
@EnableAutoConfiguration 上标注 @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector 是一个 ImportSelector:
它的 selectImports() 读取所有 jar 的 AutoConfiguration.imports 文件
→ 得到几百个自动配置类的类名
→ 返回这个数组 → Spring 把这几百个配置类全导入(再由 @Conditional 筛选生效的)
所以「自动配置为什么能批量导入几百个配置类」的答案就是 ImportSelector——它能返回一个动态计算的、任意长度的类名数组。这是 @Import 从「导入固定几个类」进化到「导入一批动态计算的类」的关键。
四、用法三:ImportBeanDefinitionRegistrar(手动注册,最灵活)
ImportBeanDefinitionRegistrar 是能力最强的——它不返回类名,而是给你一个 BeanDefinitionRegistry,让你直接手动构造和注册 BeanDefinition:
public class MapperScanRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 完全手动控制:可以扫描、可以动态生成、可以注册任意数量的 Bean
for (Class<?> mapperInterface : scanMappers()) {
// 为每个 Mapper 接口注册一个"代理 Bean"的定义
BeanDefinition bd = buildMapperBeanDefinition(mapperInterface);
registry.registerBeanDefinition(mapperInterface.getName(), bd);
}
}
}
它和 ImportSelector 的区别:ImportSelector 只能「返回类名让 Spring 去注册」,Registrar 能「亲自动手注册 BeanDefinition」——你能完全控制 Bean 的类型、属性、作用域,甚至动态生成本不存在的 Bean(如代理)。
MyBatis 的 @MapperScan 就是最经典的应用:你的 Mapper 都是接口(没有实现类),MyBatis 怎么把它们变成可注入的 Bean?就是靠 ImportBeanDefinitionRegistrar——它扫描所有 Mapper 接口,为每个接口动态注册一个 MapperFactoryBean 的 BeanDefinition(运行时生成接口的代理实现)。这种「为一批接口动态生成代理 Bean 并注册」的活,只有 Registrar 能干。所以框架里「批量动态注册 Bean」的场景(MyBatis Mapper、Dubbo 服务引用、Feign Client)几乎都用它。
五、三者能力对比与选型
三种用法能力递增,用一张表看清:
| 维度 | 直接 @Import(类) | ImportSelector | ImportBeanDefinitionRegistrar |
|---|---|---|---|
| 导入方式 | 写死具体类 | 返回类名数组(动态) | 手动注册 BeanDefinition |
| 动态性 | 无(编译期固定) | 有(运行时算类名) | 最强(可动态生成 Bean) |
| 控制粒度 | 类级别 | 类级别(批量) | BeanDefinition 级别(最细) |
| 典型应用 | 引入配置类 | Spring Boot 自动配置 | MyBatis Mapper、动态代理 Bean |
选型逻辑:明确知道导入哪几个类 → 直接 @Import;要根据条件动态导入一批类 → ImportSelector;要手动构造/动态生成 Bean(尤其是代理、批量接口) → ImportBeanDefinitionRegistrar。能力越强的用起来越复杂,按需选择——大部分业务代码用直接 @Import 就够,框架开发才需要后两者。
六、@Enable 注解的统一套路
理解 @Import 后,就看穿了 Spring 一整套 @EnableXxx 注解的实现——它们本质都是「@Import + 一个 Selector/Registrar/配置类」的封装:
// @EnableScheduling 的实现(简化)
@Import(SchedulingConfiguration.class) // 用 @Import 导入定时任务的配置
public @interface EnableScheduling { }
// @EnableAutoConfiguration 的实现(简化)
@Import(AutoConfigurationImportSelector.class) // 用 @Import + ImportSelector
public @interface EnableAutoConfiguration { }
// @MapperScan(MyBatis)的实现(简化)
@Import(MapperScannerRegistrar.class) // 用 @Import + Registrar
public @interface MapperScan { }
规律:@EnableXxx = @Import(对应的 Selector/Registrar/Config)——一「Enable」,就通过 Import 把相关功能的 Bean/配置装进容器。所以看到任何 @Enable 注解,点开它十有八九能看到一个 @Import。这套「用注解开关 + @Import 装配」的模式,是 Spring/Spring Boot「模块化、可插拔」的实现基础——想启用某功能就加对应的 @Enable,背后靠 @Import 把这个功能的所有 Bean 引入。
记忆钩子:「@Import 三用法能力递增:直接导入类(静态)< ImportSelector(返回类名数组、动态批量、自动配置用它)< ImportBeanDefinitionRegistrar(手动注册 BeanDefinition、最灵活、MyBatis Mapper 用它);@EnableXxx 注解本质都是 @Import + Selector/Registrar 的封装」。
七、常见误区与追问
- 误区:@Import 只能导入一个具体类。 有三种用法——直接导入类、导入 ImportSelector(动态返回一批类名)、导入 ImportBeanDefinitionRegistrar(手动注册 BeanDefinition),能力递增。
- 误区:ImportSelector 和 ImportBeanDefinitionRegistrar 差不多。 ImportSelector 只返回类名让 Spring 注册;Registrar 能亲自构造并注册 BeanDefinition,可动态生成本不存在的 Bean(如代理),更灵活。
- 误区:Spring Boot 自动配置是靠组件扫描导入的。 是靠 @EnableAutoConfiguration 上的 @Import(AutoConfigurationImportSelector)——ImportSelector 动态返回几百个配置类名批量导入。
- 误区:@EnableXxx 是独立的魔法注解。 它们本质是 @Import 的封装——@Import(对应的 Selector/Registrar/配置类),一 Enable 就通过 Import 装配功能 Bean。
- 追问:MyBatis 的 Mapper 接口没有实现类,怎么变成 Bean 的? 靠 @MapperScan 里的 ImportBeanDefinitionRegistrar——扫描 Mapper 接口,为每个接口动态注册一个 MapperFactoryBean 的 BeanDefinition(运行时生成接口代理)。
- 追问:ImportSelector 为什么能实现自动配置的批量导入? 它的 selectImports 返回「类名字符串数组」且是运行时动态计算的,能读配置文件得到几百个自动配置类名并全部返回导入。
- 追问:@Import 和 @ComponentScan 有什么区别? @ComponentScan 被动扫描带 @Component 的类;@Import 主动导入指定的类(不需 @Component),还能动态/手动注册,适合第三方类和框架级批量装配。
八、加强记忆
@Import 是 Spring「主动把类引入容器」的机制(区别于被动的组件扫描,适合第三方类和框架级装配),有三种能力递增的用法:① 直接 @Import(类.class)——静态注册指定的类/配置类(最简单,明确知道导入哪几个时用);② @Import(ImportSelector)——selectImports() 返回运行时动态计算的「类名数组」,实现「按条件批量导入一批类」(Spring Boot 自动配置的核心:AutoConfigurationImportSelector 读配置文件返回几百个自动配置类名批量导入,再由 @Conditional 筛选);③ @Import(ImportBeanDefinitionRegistrar)——拿到 BeanDefinitionRegistry 亲自手动注册 BeanDefinition,最灵活、能动态生成本不存在的 Bean(MyBatis @MapperScan 的核心:为每个 Mapper 接口动态注册代理 Bean)。三者选型:明确类用直接导入、动态批量用 Selector、手动生成用 Registrar。更重要的是——几乎所有 @EnableXxx 注解都是 @Import + Selector/Registrar/配置类 的封装,一「Enable」就靠 @Import 把功能 Bean 装进容器,这是 Spring 模块化可插拔的实现基础。一句话「@Import 三用法:直接导入类、ImportSelector 动态批量(自动配置)、Registrar 手动注册(MyBatis Mapper),@Enable 注解都是它的封装」。