文章目录
1. @Import导入的四种方式
@Import
是spring中的一个注解,在其他框架与spring整合时应用广泛,比如redis
的自动配置RedisAutoConfiguration
,就是通过@Import
注解在容器启动时为容器中添加redis的环境配置。一个外来组件想要与spring整合,自然是要把一些配置提前布局在容器中的。
先看一下@Import
注解的源码注释
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
*
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
通过注释可以看到,@Import
可以导入四种对象,分别是:
- 普通类
@Configuration
配置类- 实现了
ImportSelector
接口的类 - 实现了
ImportBeanDefinitionRegistrar
接口的类
下面就来看一下@Import
注解把类注入容器的几种方式
2. 准备工作
-
创建四个配置类:ConfigA、ConfigB、ConfigC、ConfigD。其中ConfigB中增加@Configuration注解,表示为配置类,其余三个均为普通类。
public class ConfigA { public void print() { System.out.println("输出:ConfigA.class"); } }
@Configuration public class ConfigB { public void print() { System.out.println("输出:ConfigB.class"); } }
public class ConfigC { public void print() { System.out.println("输出:ConfigC.class"); } }
public class ConfigD { public void print() { System.out.println("输出:ConfigD.class"); } }
-
再创建一个主配置类
Config
,并试图通过@Resource注解将上面四个配置类进行注入。当然,这样是不成功的,还需要将它们进行导入。@Configuration public class Config { @Resource ConfigA configA; @Resource ConfigB configB; @Resource ConfigC configC; @Resource ConfigD configD; public void print() { configA.print(); configB.print(); configC.print(); configD.print(); } }
-
准备一个
main
方法,运行时执行一下Config
类的print()
方法!public class ImportDemo { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); Config config = ctx.getBean(Config.class); config.print(); } }
运行时报错:说明ConfigA、ConfigB、ConfigC、ConfigD都还未曾注入容器,获取不到!
接下来使用@Improt
注解,通过四种方式把ConfigA、ConfigB、ConfigC、ConfigD
注入容器再来看结果
①:导入普通类ConfigA
@Configuration
@Import(ConfigA.class)
public class Config {
...
}
②:导入@Configuration
配置类ConfigB
导入配置类与导入普通类一样,在@Import注解中传入目标类的Class对象。
@Configuration
@Import({ConfigA.class,
ConfigB.class})
public class Config {
...
}
③:导入ImportSelector
的实现类
ImportSelector
接口的全类名为org.springframework.context.annotationImportSelector
。其主要作用的是收集需要导入的配置类,并根据条件来确定哪些配置类需要被导入。
该接口的实现类同时还可以实现以下任意一个Aware
接口,它们各自的方法将在selectImport
之前被调用:
EnvironmentAware
BeanFactoryAware
BeanClassLoaderAware
ResourceLoaderAware
另外,该接口实现类可以提供一个或多个具有以下形参类型的构造函数:
Environment
BeanFactory
ClassLoader
ResourceLoader
DeferredImportSelector的作用
public interface DeferredImportSelector extends ImportSelector
DeferredImportSelector
是一个接口,继承了ImportSelector
接口,如果你想要推迟导入配置类,直到处理完所有的@Configuration
。那么你可以使用DeferredImportSelector
。
springboot的启动类注解@SpringBootApplication
的元注解EnableAutoConfiguration
,就通过@Import
导入了一个实现DeferredImportSelector
的AutoConfigurationImportSelector
,目的就是等到所有spring原生的配置类导入完毕后,在导入一些第三方整合过来的配置类!
AutoConfigurationImportSelector
继承关系如下
下面回归正题,创建一个实现该接口的类 MyImportSelector
。
需要重写selectImports
方法,入参AnnotationMetadata
为主配置类 Config
的注解元数据。返回值为目标配置类 ConfigC
的全类名,这里是一个数组,表示可以导入多个配置类。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.chinalife.demo.config.ConfigC"};
}
}
在配置类 Config
中导入 MyImportSelector
类。
@Configuration
@Import({ConfigA.class,
ConfigB.class,
MyImportSelector.class})
public class Config {
...
}
④:导入ImportBeanDefinitionRegistrar
的实现类
通过注册bean定义
的方式注册bean,可以修改bean的属性,比如懒加载、构造方法、甚至是.Class,功能强大,可以改变类的本质!只需自定义一个类,实现ImportBeanDefinitionRegistrar
接口,并重写registerBeanDefinitions
过程即可,在自定义类被加载过程中,容器会顺带执行registerBeanDefinitions
帮我们注册指定的类。
创建一个实现 ImportBeanDefinitionRegistrar
接口的类 MyImportBeanDefinitionRegistrar
,并在 registerBeanDefinitions
方法中注册 configD
类。
- 入参
AnnotationMetadata
为主配置类 Config 的注解元数据; - 入参
BeanDefinitionRegistry
可以注册Bean的定义信息。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("configD", new RootBeanDefinition(ConfigD.class));
}
}
在配置类 Config
中导入 MyImportBeanDefinitionRegistrar
类。
@Configuration
@Import({ConfigA.class,
ConfigB.class,
MyImportSelector.class,
MyImportBeanDefinitionRegistrar.class})
public class Config {
...
}
3. 测试结果
4. 源码解读
spring源码中对@Import
的处理是在refresh()
中的invokeBeanFactoryPostProcessors
方法中,更多细节请查看 Spring源码解读!,下面只贴出处理@Import
的源码!
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 1.首先会递归的处理所有成员类,即@Component注解
processMemberClasses(configClass, sourceClass, filter);
}
// 2.处理所有@PropertySource注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 3.处理所有@ComponentScan注解
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// 配置类的注解为@ComponentScan-> 立即执行扫描
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 检查扫描过的BeanDefinition集合,看看是否有其他配置类,如果需要,递归解析
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// 4.处理所有@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// 5.处理所有@ImportResource注解
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// 6.处理标注为@Bean注解的方法
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 7.处理配置类的接口上的默认方法
processInterfaces(configClass, sourceClass);
// 8.处理配置类的超类(如果有的话)
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
// 处理完成
return null;
}
其中processImports
会处理所有的@Improt
注解
该方法会循环处理每一个由@Import
导入的类:
ImportSelector
类的处理ImportBeanDefinitionRegistrar
类的处理- 其它类统一按照
@Configuration
类来处理,所以加不加@Configuration注解都能被导入
/**
* 处理配置类上的@Import注解引入的类
*
* @param configClass 配置类,这里是Config类
* @param currentSourceClass 当前资源类
* @param importCandidates 该配置类中的@Import注解导入的候选类列表
* @param exclusionFilter 排除过滤器
* @param checkForCircularImports 是否循环检查导入
*/
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 如果该@Import注解导入的列表为空,直接返回
if (importCandidates.isEmpty()) {
return;
}
// 循环检查导入
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 循环处理每一个由@Import导入的类
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// 1. ImportSelector类的处理
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
// 1.1 若是DeferredImportSelector接口的实现,则延时处理
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 1.2 在这里调用我们的ImportSelector实现类的selectImports方法
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 1.3 递归处理每一个selectImports方法返回的配置类
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 2. ImportBeanDefinitionRegistrar类的处理
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// 3. 其它类统一按照@Configuration类来处理,所以加不加@Configuration注解都能被导入
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}