Spring Boot 的自动配置并非魔法,它的底层依赖 Spring 框架的 @Import 注解。理解 @Import 的工作机制,有助于开发者更灵活地定制 Bean 装配逻辑,甚至实现自己的轻量级框架整合方案。本文围绕 @Import 的三种扩展方式展开,从基础语法到 Spring Boot 源码中的实际应用,结合代码示例一步步拆解。

一、@Import 注解的三种用法
@Import 注解可以标注在配置类上,用来导入其他组件。它的参数可以是一个或多个 Class 对象,这些 Class 对象可以是普通的配置类、ImportSelector 接口的实现类,或者 ImportBeanDefinitionRegistrar 接口的实现类。这三种用法分别对应不同的装配粒度:普通配置类用于导入一组预先定义好的 Bean;ImportSelector 用于根据条件动态选择需要导入的类;ImportBeanDefinitionRegistrar 则允许在运行时直接向容器注册 BeanDefinition,灵活性最高。
先看最简单的用法:导入一个普通的配置类。假设我们有一个核心配置类 CoreConfig,其中定义了一个 DataSource Bean。另一个配置类 AppConfig 可以通过 @Import 将 CoreConfig 引入,这样在创建应用上下文时,DataSource 就会自动注册到容器中。
@Configuration
public class CoreConfig {
@Bean
public DataSource dataSource() {
return new HikariDataSource();
}
}
@Configuration
@Import(CoreConfig.class)
public class AppConfig {
// 其他 Bean 定义
}
这种方式的优点是简单直观,但缺点是不够灵活,导入的类是固定的。如果希望根据某些条件(例如类路径中是否存在某个依赖)来决定是否导入,就需要用到 ImportSelector。ImportSelector 接口要求实现 selectImports 方法,返回一个字符串数组,每个字符串是要导入的类的全限定名。Spring 会在解析配置类时调用该方法,并将返回的类注册为配置类。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 判断是否存在某个类,如果存在则导入对应配置
if (ClassUtils.isPresent("com.mysql.cj.jdbc.Driver", null)) {
return new String[]{"com.demo.config.MySqlConfig"};
}
return new String[0];
}
}
ImportBeanDefinitionRegistrar 则更进一步,它不局限于导入配置类,而是直接操作 BeanDefinitionRegistry 来注册任意 BeanDefinition。开发者可以在 registerBeanDefinitions 方法中读取注解元数据,完成复杂的注册逻辑,例如根据注解属性动态生成代理 Bean 或批量注册具有相似结构的 Bean。下面会专门用一节演示。
二、Spring Boot 自动配置如何利用 ImportSelector
Spring Boot 的自动配置核心是 @EnableAutoConfiguration 注解,它本质上是一个组合注解,其中包含 @Import(AutoConfigurationImportSelector.class)。这个 AutoConfigurationImportSelector 就是 ImportSelector 的一个典型应用,它负责从 META-INF/spring.factories 文件中读取所有候选的自动配置类,并将它们批量导入到容器中。
打开 Spring Boot 的源码可以看到,AutoConfigurationImportSelector 的 selectImports 方法会调用 getAutoConfigurationEntry,内部通过 SpringFactoriesLoader 加载 EnableAutoConfiguration 键对应的所有自动配置类。这些类通常使用 @Configuration 标注,并且配合大量条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty)来决定是否真正生效。因此,虽然一次性导入了上百个自动配置类,但只有满足条件的才会被实例化,从而实现了“按需装配”。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
如果我们想要自定义一个自动配置类,只需要在项目的 META-INF/spring.factories 文件中添加一行:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.demo.config.MyAutoConfiguration
注意上面示例中的反斜杠是换行续接符,实际文件中如果只有一个配置类,可以不加反斜杠。这个自动配置类可以使用 @ConditionalOnClass 来确保只有当某个依赖存在时才创建 Bean。
@Configuration
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
通过这种方式,Spring Boot 将“导入哪些配置类”与“是否生效”两个维度解耦,使得自动配置具备了极高的可扩展性和可维护性。这也是为什么开发者只需要引入 starter 依赖就能获得大量默认 Bean 的原因。
三、动手实现 ImportBeanDefinitionRegistrar 动态注册 Bean
ImportBeanDefinitionRegistrar 接口比 ImportSelector 更底层,它允许在配置类解析阶段直接向 BeanDefinitionRegistry 注册自定义的 BeanDefinition。与 ImportSelector 返回类名不同,Registrar 可以完全控制 BeanDefinition 的创建过程,包括设置属性值、构造函数参数、初始化方法等。这个特性非常适合用来实现类似 MyBatis 的 Mapper 扫描、Feign 客户端代理注册等功能。
下面实现一个简单的例子:自定义注解 @EnableAnimal,通过该注解的 names 属性批量创建 Animal 类型的 Bean,每个 Bean 的名称和属性都不同。首先定义注解,并使用 @Import 引入我们的 Registrar。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(AnimalRegistrar.class)
public @interface EnableAnimal {
String[] names() default {"cat", "dog"};
}
然后实现 AnimalRegistrar,在 registerBeanDefinitions 方法中读取注解属性,遍历名称数组,为每个名称创建一个 RootBeanDefinition,并设置 Bean 的类为 Animal,同时通过构造函数参数注入名称。
public class AnimalRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
Map<String, Object> attrs = importingClassMetadata
.getAnnotationAttributes(EnableAnimal.class.getName());
if (attrs == null) return;
String[] names = (String[]) attrs.get("names");
for (String name : names) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(Animal.class);
ConstructorArgumentValues cav = new ConstructorArgumentValues();
cav.addGenericArgumentValue(name);
beanDefinition.setConstructorArgumentValues(cav);
registry.registerBeanDefinition(name + "Animal", beanDefinition);
}
}
}
假设 Animal 类有一个构造器接收字符串参数,那么通过上述 Registrar,只要在某个配置类上标注 @EnableAnimal(names = {"tiger", "lion"}),容器中就会自动生成 tigerAnimal 和 lionAnimal 两个 Bean。这种方式比在配置类中手写多个 @Bean 方法更加简洁,尤其适合需要根据注解元数据动态计算 Bean 数量的场景。
该方法在真实框架中的应用非常广泛。例如 Spring Cloud OpenFeign 通过 @EnableFeignClients 触发 FeignClientsRegistrar,它会扫描指定包下所有标注 @FeignClient 的接口,并为每个接口注册一个代理对象。MyBatis 的 @MapperScan 也使用了类似的 Registrar 来注册 MapperFactoryBean。深入理解 ImportBeanDefinitionRegistrar 后,你也能轻松实现类似的扩展机制。
四、实战:整合自定义 Import 机制到 Spring Boot Starter
将上述机制封装成一个 Spring Boot Starter,可以让其他项目通过简单引入依赖和注解即可获得能力。一个典型 Starter 包含自动配置类、业务组件以及 META-INF/spring.factories 配置。我们可以把之前实现的 Animal 组件包装成一个 starter,使任何 Spring Boot 应用只要引入该 starter 并使用 @EnableAnimal 注解,就能动态注册 Animal Bean。
首先创建 animal-spring-boot-starter 模块,主配置类使用 @Configuration 和 @EnableAnimal,并用 @ConditionalOnMissingBean 防止重复配置。然后在 META-INF/spring.factories 中将该配置类注册为自动配置。
@Configuration
@EnableAnimal(names = {"defaultCat", "defaultDog"})
@ConditionalOnProperty(prefix = "animal", name = "enabled", havingValue = "true", matchIfMissing = true)
public class AnimalAutoConfiguration {
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.demo.animal.AnimalAutoConfiguration
用户项目引入该 starter 后,默认就会创建 defaultCatAnimal 和 defaultDogAnimal 两个 Bean。用户也可以在 application.properties 中设置 animal.enabled=false 来关闭自动配置。如果用户想使用自定义名称,只需在自己的配置类上使用 @EnableAnimal 并指定 names 即可,因为 @ConditionalOnMissingBean 的存在,默认的自动配置会退让。
这个例子虽然简单,却完整展示了 @Import、ImportBeanDefinitionRegistrar、条件注解和 Spring Boot 自动配置的联动方式。在实际项目中,你可以将这种组合用于多数据源动态注册、第三方 SDK 客户端批量初始化、或者按环境变量创建不同的策略 Bean。掌握 @Import 机制,就等于拿到了 Spring 容器装配的万能钥匙。
Spring BootImport注解自动装配修改时间:2026-10-03 23:09:19