脑洞侦探
48.29MB · 2025-10-05
我们在使用一些Spring
开关时,可能会发现导入的类很多都实现了org.springframework.context.annotation.ImportSelector
接口,这个接口到底是干什么的?我们一起了解一下。
我们都知道,Spring
的开关无非是控制Spring
按需加载资源到容器中,或者通过开关加载启动类默认无法加载的资源。ImportSelector
扮演者怎样的角色呢?
ImportSelector
是 Spring 框架中一个非常强大的接口,位于 org.springframework.context.annotation
包下。它的核心作用是在运行时动态地选择一组配置类(即 @Configuration
类)或普通组件的全限定类名,并让 Spring 容器将它们导入并注册为 Bean 定义。
写过插件或者公用代码库的朋友都可能考虑过这样的问题。定义好的公用组件,肯定是独立的,可能会被引入到不同的模块项目中,其他项目默认不一定能扫描到自己的容器中。我们通常通过@Import
手动导入或者配置到spring.factories
中,以达到被加载的目的。
而今天要说的ImportSelector
和Import
的功能相同,但更具有灵活性,可以通过逻辑程序更加细粒度的控制资源的加载。可以把它理解为 “一个可以编程的、有逻辑的 @Import
注解”。
@Import
注解是静态的:必须在编译时就把要导入的配置类写在注解里。ImportSelector
是动态的:它允许你根据当前的运行时环境(如:系统属性、环境变量、项目类路径、其他 Bean 的存在情况等)来决定到底要导入哪些配置。通常情况下@Import
和ImportSelector
一起使用,通过@Import
导入ImportSelector
, 而 ImportSelector
通过逻辑动态控制资源。
selectImports
方法时在Spring 3.1
时才有的,而在Spring 5.2.4
的新版本中有新增了getExclusionFilter
,用来过滤不必要的资源。
最主要的方法就是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
接口,对应的泛型是类的全限定名。
AppConfig
)或启动类。AppConfig
上使用了 @Import(MyImportSelector.class)
。MyImportSelector
。MyImportSelector.selectImports(...)
方法。MyImportSelector
根据自身的逻辑(检查环境等)计算出需要导入的类名数组。我们假设一个场景,我们根据不同的参数或者环境,加载不同的资源。这些资源默认不会被加载。
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
,并根据不同的环境参数,加载不同的资源。
@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 被实例化了!");
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelect.class)
public @interface EnableLoadBean {
}
这里的注解没有意义,只是一个开关。通过@Import
导入MyImportSelect.class
,当然也可以去掉开关,直接加在配置类上也可以。
直接加在启动类上。
增加启动参数:-Denv=a
我们可以看到生效了:
importingClassMetadata
我们也打出来了,就是启动类。我们就可以获取启动类上的注解以及被继承的注解。
ImportSelector
的核心优势在于它将配置选择逻辑从静态的注解或XML配置中解放出来,提供了运行时动态决策的能力。这使得应用程序能够更加灵活地适应不同的运行环境和需求变化。
在实际开发中,结合 @Conditional
系列注解和 ImportSelector
,可以构建出高度可配置和可扩展的 Spring 应用程序。