
01 引言
我们在使用一些Spring开关时,可能会发现导入的类很多都实现了org.springframework.context.annotation.ImportSelector接口,这个接口到底是干什么的?我们一起了解一下。
我们都知道,Spring的开关无非是控制Spring按需加载资源到容器中,或者通过开关加载启动类默认无法加载的资源。ImportSelector扮演者怎样的角色呢?
02 简介
ImportSelector 是 Spring 框架中一个非常强大的接口,位于 org.springframework.context.annotation 包下。它的核心作用是在运行时动态地选择一组配置类(即 @Configuration 类)或普通组件的全限定类名,并让 Spring 容器将它们导入并注册为 Bean 定义。
写过插件或者公用代码库的朋友都可能考虑过这样的问题。定义好的公用组件,肯定是独立的,可能会被引入到不同的模块项目中,其他项目默认不一定能扫描到自己的容器中。我们通常通过@Import手动导入或者配置到spring.factories中,以达到被加载的目的。
而今天要说的ImportSelector和Import的功能相同,但更具有灵活性,可以通过逻辑程序更加细粒度的控制资源的加载。可以把它理解为 “一个可以编程的、有逻辑的 @Import 注解”。
@Import注解是静态的:必须在编译时就把要导入的配置类写在注解里。ImportSelector是动态的:它允许你根据当前的运行时环境(如:系统属性、环境变量、项目类路径、其他 Bean 的存在情况等)来决定到底要导入哪些配置。
通常情况下@Import和ImportSelector一起使用,通过@Import导入ImportSelector, 而 ImportSelector通过逻辑动态控制资源。
03 核心方法和工作原理
3.1 源码

selectImports方法时在Spring 3.1时才有的,而在Spring 5.2.4的新版本中有新增了getExclusionFilter,用来过滤不必要的资源。
3.2 方法说明
最主要的方法就是selectImports,用来导入所需要的资源信息。
AnnotationMetadata importingClassMetadata:
不得不说,Spring为参数取的名字非常有讲究,能够帮助我们理解其含义。importingClassMetadata表示导入类的元数据。如果你在 @Configuration /@Component修改的类(如:ExampleConfig.java)上使用了 @Import(MyImportSelector.class),那么这个 importingClassMetadata 对象就能让你获取到该配置类(ExampleConfig.java)上的所有注解信息(如 @Configuration 的属性,或者其他自定义注解)。
String[]:
方法需要返回一个由类的全限定名组成的字符串数组。这些类将会被 Spring 容器处理,就好像它们被直接写在 @Import 注解里一样。这些类通常就是 @Configuration 类,但也可以是任何需要被 Spring 管理的组件类(如标有 @Component, @Service 等的类)。
getExclusionFilter
是Predicate接口,对应的泛型是类的全限定名。
3.3 工作流程
- Spring 容器启动,解析一个配置类(比如
AppConfig)或启动类。 - 发现
AppConfig上使用了@Import(MyImportSelector.class)。 - 容器实例化
MyImportSelector。 - 调用
MyImportSelector.selectImports(...)方法。 MyImportSelector根据自身的逻辑(检查环境等)计算出需要导入的类名数组。- Spring 容器拿到这个数组,将这些类名对应的类进行解析、注册为 Bean 定义。
- 继续正常的启动流程
04 最佳实践
我们假设一个场景,我们根据不同的参数或者环境,加载不同的资源。这些资源默认不会被加载。
4.1 定义动态选择类
public class MyImportSelect implements ImportSelector, EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println(importingClassMetadata.getClassName());
String env = environment.getProperty("env");
if (StringUtils.equalsIgnoreCase(env, "a")) {
return new String[]{AConfig.class.getName()};
}
if (StringUtils.equalsIgnoreCase(env, "b")) {
return new String[]{BConfig.class.getName()};
}
return new String[]{AConfig.class.getName(),
BConfig.class.getName()};
}
}
默认加载AConfig、BConfig,并根据不同的环境参数,加载不同的资源。
4.2 定义资源
@Component
public class AConfig {
@Bean
public A a() {
return new A();
}
}
public class A {
public A() {
System.out.println("A 被实例化了!");
}
}
@Component
public class BConfig {
@Bean
public B b() {
return new B();
}
}
public class B {
public B() {
System.out.println("B 被实例化了!");
}
}
4.3 定义开关
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelect.class)
public @interface EnableLoadBean {
}
这里的注解没有意义,只是一个开关。通过@Import导入MyImportSelect.class,当然也可以去掉开关,直接加在配置类上也可以。
4.4 打开开关

直接加在启动类上。
4.5 测试
增加启动参数:-Denv=a

我们可以看到生效了:

importingClassMetadata我们也打出来了,就是启动类。我们就可以获取启动类上的注解以及被继承的注解。
05 小结
ImportSelector 的核心优势在于它将配置选择逻辑从静态的注解或XML配置中解放出来,提供了运行时动态决策的能力。这使得应用程序能够更加灵活地适应不同的运行环境和需求变化。
在实际开发中,结合 @Conditional 系列注解和 ImportSelector,可以构建出高度可配置和可扩展的 Spring 应用程序。

被折叠的 条评论
为什么被折叠?



