人人都会AI编程

24.1 @Import 注解的三种用法

更新时间:2026-07-11

@Import 是 Spring 框架中一个看似低调但非常强大的注解。它的核心作用是将一个或多个类导入到当前的 Spring 容器中,使其成为受管理的 Bean。与 @ComponentScan 自动扫描不同,@Import 提供了一种显式、精确的导入机制,常被用在 Spring Boot 的自动配置、自定义 Starter 和模块化组件装配中。

在实际开发中,@Import 主要有三种典型的用法:导入普通组件类、导入 @Configuration 配置类、通过 ImportSelectorImportBeanDefinitionRegistrar 实现编程式导入。

24.1.1 用法一:直接导入普通组件类

这是最简单直接的形式。将一个标注了 @Component@Service@Repository 等注解的类,或者哪怕只是一个普通的 POJO 类,通过 @Import 显式引入到容器中。

场景示例

假设我们有一个工具类 StringUtils,它并非通过组件扫描发现,而希望显式注册为 Bean:

public class StringUtils {
    public String toUpperCase(String input) {
        return input != null ? input.toUpperCase() : null;
    }
}

现在需要将它注入到当前应用中。不用在它上面加 @Component,而是在某个配置类上使用 @Import

@Configuration
@Import(StringUtils.class)
public class AppConfig {
}

启动容器后,StringUtils 会成为一个 Spring Bean,默认的 bean 名称是全限定类名(如 com.example.StringUtils),可以被其他 Bean 注入使用。这种方式的优点是不需要修改原始类的代码,非常适合引入外部库中的类。

也可以一次导入多个类

@Configuration
@Import({StringUtils.class, DateFormatter.class})
public class AppConfig {
}

Spring 会为 StringUtilsDateFormatter 分别创建 Bean 定义并纳入容器管理。

内部机制:Spring 在处理 @Import 时,会将指定的类视作一个配置源。如果该类自身有 @Configuration@Component 等注解,会按正常流程解析;如果只是一个普通类,也会直接为它生成一个 Bean 定义,效果等同于在该类上标注了 @Component 并被扫描到。

实用提示:当需要将某个不能被包扫描覆盖的类注册为 Bean(比如来自不同 JAR 包的类),但又不想在 XML 中配置 <bean>,使用 @Import 是最快的方式。

24.1.2 用法二:导入 @Configuration 配置类

@Import 的第二种常见用法是导入另一个 @Configuration 配置类,实现配置的模块化拆分与聚合。

场景示例

假设我们将数据相关的 Bean 放在一个独立的配置类中:

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("root");
        config.setPassword("secret");
        return new HikariDataSource(config);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

主配置类并不希望使用 @ComponentScan 去扫描这个包,而是想显式引入以保持结构清晰:

@Configuration
@Import(DataSourceConfig.class)
public class AppConfig {
    // 其他中心配置
}

启动容器后,DataSourceConfig 中定义的 dataSourcejdbcTemplate Bean 也会一同加载到容器中,效果如同 AppConfig 本身包含了这些 @Bean 方法。

如果使用 @ComponentScan,往往需要指定具体的包路径,还可能意外引入不需要的 Bean。而 @Import 则做到了精准导入,每个模块提供了哪些配置一目了然。

导入多个配置类的组合

@Configuration
@Import({DataSourceConfig.class, SecurityConfig.class, CacheConfig.class})
public class AppConfig {
}

这样做的好处是:AppConfig 成为整个应用的装配中心,其他模块专注于各自领域的配置,再由中心统一导入,实现“组件化配置”。

24.1.3 用法三:通过 ImportSelector 和 ImportBeanDefinitionRegistrar 实现编程式导入

这是 @Import 最灵活、最高级的用法。通过实现 ImportSelectorImportBeanDefinitionRegistrar 接口,可以在代码运行时动态决定要导入哪些类,以及如何注册 Bean 定义。

24.1.3.1 ImportSelector:基于条件筛选导入类

ImportSelector 接口只定义了一个方法:

String[] selectImports(AnnotationMetadata importingClassMetadata);

该方法返回一组类的全限定名,Spring 会将这些类当作被导入的配置类一样处理。

场景示例:根据运行环境决定导入哪个数据源配置

public class DataSourceSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String env = System.getProperty("app.env", "dev");
        if ("prod".equals(env)) {
            return new String[]{"com.example.config.ProdDataSourceConfig"};
        } else {
            return new String[]{"com.example.config.DevDataSourceConfig"};
        }
    }
}

然后在配置类上使用:

@Configuration
@Import(DataSourceSelector.class)
public class AppConfig {
}

Spring 启动时会调用 selectImports,根据运行时属性动态决定导入生产或开发环境的配置类。这种方式比使用 @Profile 更加灵活,因为逻辑可以完全自定义。

Spring Boot 的自动配置核心 @EnableAutoConfiguration 就是利用 ImportSelector 来实现的:AutoConfigurationImportSelector 读取 META-INF/spring.factoriesspring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的配置类列表,结合 @Conditional 条件注解,动态选择需要导入的自动配置类。

自定义注解封装 ImportSelector

我们可以将选择器封装成一个自定义注解,简化使用:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DataSourceSelector.class)
public @interface EnableDataSource {
    String env() default "dev";
}

使用时只需标注 @EnableDataSource(env="prod"),将参数通过 AnnotationMetadata 传递给选择器即可。

24.1.3.2 ImportBeanDefinitionRegistrar:编程式注册 Bean 定义

ImportBeanDefinitionRegistrar 提供了更底层的控制能力。它直接操作 BeanDefinitionRegistry,可以在运行时动态注册 Bean 定义,甚至可以修改已有 Bean 的定义。

接口定义:

void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
                             BeanDefinitionRegistry registry);

场景示例:根据接口扫描注册代理 Bean

假设我们希望将某个包下所有实现了 MyService 接口的类,自动代理为具有特定行为的 Bean,而不是简单地作为普通 Bean 注册。

public class MyServiceRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                        BeanDefinitionRegistry registry) {
        // 使用自定义的扫描器扫描指定包
        ClassPathBeanDefinitionScanner scanner = 
            new ClassPathBeanDefinitionScanner(registry, false);
        scanner.addIncludeFilter(
            new AssignableTypeFilter(MyService.class));
        scanner.scan("com.example.services");
        
        // 还可以对扫描到的 BeanDefinition 做后置处理
        for (String beanName : registry.getBeanDefinitionNames()) {
            BeanDefinition bd = registry.getBeanDefinition(beanName);
            if (bd.getBeanClassName() != null && 
                bd.getBeanClassName().startsWith("com.example.services")) {
                bd.getPropertyValues().add("interceptorName", "myInterceptor");
            }
        }
    }
}

然后在配置类上导入:

@Configuration
@Import(MyServiceRegistrar.class)
public class AppConfig {
}

这样,无需在每个服务类上标注 @Component,也不用通过 XML 定义大量重复配置,所有实现 MyService 的类都会被自动注册并织入额外的属性。

另一个经典应用是 MyBatis-Spring 中的 MapperScannerRegistrar@MapperScan 注解本身被 @Import(MapperScannerRegistrar.class) 标记,MapperScannerRegistrar 实现了 ImportBeanDefinitionRegistrar,在运行时根据注解指定的包路径,动态扫描接口并将它们注册为 MapperFactoryBean 的 Bean 定义,从而让 MyBatis 的 Mapper 接口可以直接被注入使用。

24.1.4 三种用法的选择与对比

| 用法 | 适用场景 | 典型代表 |
|------|----------|----------|
| 直接导入普通类 | 引入外部工具类、简单 POJO | @Import(StringUtils.class) |
| 导入 @Configuration 类 | 模块配置拆分与组合 | @Import({SecurityConfig.class, DataConfig.class}) |
| ImportSelector / ImportBeanDefinitionRegistrar | 条件化装配、框架级封装、自定义注解驱动 | @EnableAutoConfiguration / @MapperScan |

在开发自定义 Starter 或框架级组件时,后两种用法尤为重要。通常会用 ImportSelector 做条件筛选导入,用 ImportBeanDefinitionRegistrar 做更精细的 Bean 定义控制。对于一般应用开发者,掌握前两种用法已经可以解决绝大多数显式导入需求。

@Import 的精髓在于让配置变得显式和可控,与自动扫描形成互补。当你不满足于“扫描所有标注了 @Component 的类”,而需要更精确地控制哪些组件被加载、何时被加载时,@Import 就是最重要的工具之一。