@Import注解源码分析

@Import

一、基本信息

✒️ 作者 - Lex 📝 博客 - 我的CSDN 📚 文章目录 - 所有文章 🔗 源码地址 - @Import源码

二、注解描述

@Import 是 Spring 框架的核心注解,用于导入配置类或组件到当前的 Spring 上下文中。它可以用于导入常规的 @Configuration 类、常规组件类,或实现了 ImportSelectorImportBeanDefinitionRegistrar 接口的类。ImportSelector 允许根据条件动态地选择要导入的组件,而 ImportBeanDefinitionRegistrar 提供了一种以编程方式注册bean的方法。使用 @Import 注解,我们可以更灵活、模块化地组织 Spring 的配置,确保上下文中有所需的所有组件和配置。

三、注解源码

@Import 是 Spring 框架自 3.0 版本开始引入的一个核心注解。允许我们导入一个或多个组件类,这些类通常是 @Configuration 类。它在功能上相当于 Spring XML 中的 <import/> 元素,导入类型@Configuration类、ImportSelectorImportBeanDefinitionRegistrar的实现以及其他常规组件类,在导入的 @Configuration 类中声明的 bean 定义应使用 @Autowired 进行注入。

java 复制代码
/**
 * 表示要导入的一个或多个组件类 ------ 通常是
 * Configuration @Configuration 类。
 *
 * 提供与Spring XML中的 <import/> 元素相同的功能。
 * 允许导入 @Configuration 类、ImportSelector 和
 * ImportBeanDefinitionRegistrar 的实现,以及常规组件
 * 类 (从 4.2 开始;与 AnnotationConfigApplicationContext#register 相似)。
 *
 * 在导入的 @Configuration 类中声明的 @Bean 定义应通过
 * org.springframework.beans.factory.annotation.Autowired @Autowired 注入。
 * 可以自动注入bean本身,也可以自动注入声明bean的配置类实例。
 * 后者允许在 @Configuration 类方法之间进行明确的、IDE友好的导航。
 *
 * 可以在类级别声明或作为元注解。
 *
 * 如果需要导入XML或其他非-@Configuration 的bean定义资源,
 * 请改用 ImportResource @ImportResource 注解。
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
 * @see Configuration
 * @see ImportSelector
 * @see ImportBeanDefinitionRegistrar
 * @see ImportResource
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * 要导入的 Configuration @Configuration、ImportSelector、
	 * ImportBeanDefinitionRegistrar 或常规组件类。
	 */
	Class<?>[] value();

}

四、主要功能

  1. 导入配置类

    • 允许一个 @Configuration 类引入另一个 @Configuration 类。
  2. 导入选择器

    • 通过实现 ImportSelector 接口,可以动态地选择和导入配置类。
  3. 手动注册Bean

    • 通过实现 ImportBeanDefinitionRegistrar 接口,可以在运行时手动注册 bean。
  4. 导入常规组件类

    • 从 Spring 4.2 开始,还可以导入常规的组件类。

五、最佳实践

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类,然后遍历并打印所有的bean定义名。

java 复制代码
public class ImportApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println("beanName = " + beanDefinitionName);
        }
    }
}

使用@Import 注解允许导入其他组件或配置到当前的配置类。在 MyConfiguration 类中,它导入了四个不同的组件或选择器,第一个是MyBean.class一个常规Bean组件。第二个是MyImportSelector.class一个实现了 ImportSelector 的类,用于动态选择并导入配置。第三个是MyDeferredImportSelector.class一个实现了 DeferredImportSelector 的类,用于延迟地选择并导入配置。第四个是MyImportBeanDefinitionRegistrar.class一个实现了 ImportBeanDefinitionRegistrar 的类,用于手动注册bean。

java 复制代码
@Configuration
@Import({MyBean.class, MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MyConfiguration {

}

MyImportSelector 类提供了一种动态导入 MyBeanA 组件的机制。确保 MyBeanA 被加入到Spring的上下文中。

java 复制代码
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyBeanA.class.getName()};
    }
}

MyDeferredImportSelector 类提供了一种延迟导入 MyBeanB 组件的机制,确保 MyBeanB 被添加到Spring的上下文中。与普通的 ImportSelector 不同,DeferredImportSelector 允许在Spring处理完所有其他配置类之后再进行导入,从而确保某些特定的处理顺序。

java 复制代码
public class MyDeferredImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyBeanB.class.getName()};
    }
}

MyImportBeanDefinitionRegistrar 类提供手动注册 MyBeanC 组件到Spring容器的方法,而不依赖于组件扫描或其他自动配置机制。确保 MyBeanC 被添加到Spring的上下文中。

java 复制代码
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBeanC.class);
        registry.registerBeanDefinition(MyBeanC.class.getName(), beanDefinition);
    }
}

使用@Import注解和其相关的选择器或注册器来将这些bean类导入到Spring上下文中

java 复制代码
public class MyBean {

}

public class MyBeanA {

}

public class MyBeanB {

}

public class MyBeanC {

}

六、时序图

sequenceDiagram participant ImportApplication participant AnnotationConfigApplicationContext participant AbstractApplicationContext participant PostProcessorRegistrationDelegate participant ConfigurationClassPostProcessor participant ConfigurationClassParser participant DeferredImportSelectorHandler participant DeferredImportSelectorGroupingHandler participant DeferredImportSelectorGrouping participant DefaultDeferredImportSelectorGroup participant ConfigurationClassBeanDefinitionReader participant MyImportSelector participant MyDeferredImportSelector participant MyImportBeanDefinitionRegistrar participant DefaultListableBeanFactory ImportApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
初始化上下文 AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新上下文 AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)
调用BeanFactory的后处理器 AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
委托调用BeanFactory的后处理器 PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)
执行BeanDefinition的注册后处理器 PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry)
处理配置类 ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry)
处理配置类bean的定义 ConfigurationClassPostProcessor->>ConfigurationClassParser:new ConfigurationClassParser()
创建配置类解析器 ConfigurationClassParser-->>ConfigurationClassPostProcessor:返回parser ConfigurationClassPostProcessor->>ConfigurationClassParser:parser.parse(candidates)
解析候选类 ConfigurationClassParser->>ConfigurationClassParser:parse(metadata,beanName)
进一步解析类元数据 ConfigurationClassParser->>ConfigurationClassParser:processConfigurationClass(configClass,filter)
处理@Configuration类 ConfigurationClassParser->>+ConfigurationClassParser:doProcessConfigurationClass(configClass, sourceClass, filter)
实际处理配置类 ConfigurationClassParser-->>-ConfigurationClassParser:返回SourceClass ConfigurationClassParser->>+ConfigurationClassParser:processImports(configClass, sourceClass, importCandidates, filter, true)
处理导入 ConfigurationClassParser->>MyImportSelector:selectImports(importingClassMetadata)
调用自定义的导入选择器 MyImportSelector-->>ConfigurationClassParser:返回Bean数组 ConfigurationClassParser->>DeferredImportSelectorHandler:process()
处理延迟导入选择器 DeferredImportSelectorHandler->>DeferredImportSelectorGroupingHandler:processGroupImports()
处理组导入 DeferredImportSelectorGroupingHandler->>DeferredImportSelectorGrouping:getImports()
获取导入 DeferredImportSelectorGrouping->>DefaultDeferredImportSelectorGroup:process(metadata,selector)
处理默认延迟导入选择器的组 DefaultDeferredImportSelectorGroup->>MyDeferredImportSelector:selectImports(importingClassMetadata)
调用自定义的导入选择器 MyDeferredImportSelector-->>DefaultDeferredImportSelectorGroup:返回Bean数组,存储在imports字段中 DeferredImportSelectorGrouping->>DefaultDeferredImportSelectorGroup:selectImports()
选择导入 DeferredImportSelectorGrouping-->>DeferredImportSelectorGroupingHandler:返回Iterable DeferredImportSelectorGroupingHandler->>ConfigurationClassParser:processImports(configClass, sourceClass, importCandidates, filter, true)
再次处理导入 ConfigurationClassParser-->>-ConfigurationClassParser:递归处理@Configuration ConfigurationClassPostProcessor->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitions(configClasses)
加载bean定义 ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator)
加载配置类的bean定义 ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:registerBeanDefinitionForImportedConfigurationClass(configClass)
注册导入的配置类的bean定义 ConfigurationClassBeanDefinitionReader->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition)
在bean工厂中注册bean定义 ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsFromRegistrars(registrars)
从注册器中加载bean定义 ConfigurationClassBeanDefinitionReader->>MyImportBeanDefinitionRegistrar:registerBeanDefinitions(importingClassMetadata,registry)
调用自定义的bean定义注册器 MyImportBeanDefinitionRegistrar->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition)
在bean工厂中注册bean定义

七、源码分析

首先来看看启动类入口,上下文环境使用AnnotationConfigApplicationContext(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个MyConfiguration组件类,然后遍历并打印所有的bean定义名。

java 复制代码
public class ImportApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println("beanName = " + beanDefinitionName);
        }
    }
}

org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext构造函数中,执行了三个步骤,我们重点关注refresh()方法。

java 复制代码
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    register(componentClasses);
    refresh();
}

org.springframework.context.support.AbstractApplicationContext#refresh方法中我们重点关注一下finishBeanFactoryInitialization(beanFactory)这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。

java 复制代码
@Override
public void refresh() throws BeansException, IllegalStateException {
     // ... [代码部分省略以简化]
     // 调用在上下文中注册为bean的工厂处理器
     invokeBeanFactoryPostProcessors(beanFactory);
     // ... [代码部分省略以简化]
}

org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中,又委托了PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()进行调用。

java 复制代码
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
    // ... [代码部分省略以简化]
}

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors方法中,首先调用了 BeanDefinitionRegistryPostProcessor(这是 BeanFactoryPostProcessor 的子接口)。它专门用来在所有其他 bean 定义加载之前修改默认的 bean 定义。

java 复制代码
public static void invokeBeanFactoryPostProcessors(
        ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
    // ... [代码部分省略以简化]
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
	// ... [代码部分省略以简化]
}

org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors方法中,循环调用了实现BeanDefinitionRegistryPostProcessor接口中的postProcessBeanDefinitionRegistry(registry)方法

java 复制代码
private static void invokeBeanDefinitionRegistryPostProcessors(
			Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
            .tag("postProcessor", postProcessor::toString);
        postProcessor.postProcessBeanDefinitionRegistry(registry);
        postProcessBeanDefRegistry.end();
    }
}

org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法中,调用了processConfigBeanDefinitions方法,该方法的主要目的是处理和注册配置类中定义的beans。

java 复制代码
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    // ... [代码部分省略以简化]
    processConfigBeanDefinitions(registry);
}

org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中,这个方法主要处理了配置类的解析和验证,并确保了所有在配置类中定义的beans都被正确地注册到Spring的bean定义注册表中。

java 复制代码
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // ... [代码部分省略以简化]

    // 步骤1:创建一个用于解析配置类的解析器
    ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    // 步骤2:初始化候选配置类集合以及已解析配置类集合
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());

    // 步骤3:循环处理所有候选配置类,直至没有候选类为止
    do {
        // 步骤3.1 解析配置类
        parser.parse(candidates);
        // 步骤3.2 验证配置类
        parser.validate();

        // 获取解析后的配置类,并从中移除已经处理过的
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // 步骤4:如果reader为空,则创建一个新的Bean定义读取器
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }

        // 步骤5:使用读取器为解析的配置类加载Bean定义
        this.reader.loadBeanDefinitions(configClasses);

        // ... [代码部分省略以简化]
        
    } while (!candidates.isEmpty());

    // ... [代码部分省略以简化]
}

我们来到org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中的步骤3.1。在org.springframework.context.annotation.ConfigurationClassParser#parse方法中,主要是遍历所有的配置类候选者,并对每一个带有注解的Bean定义进行解析。这通常涉及到查找该配置类中的@Bean方法、组件扫描指令等,并将这些信息注册到Spring容器中。

java 复制代码
public void parse(Set<BeanDefinitionHolder> configCandidates) {
    // 遍历每个配置类的持有者
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            // 步骤1:如果Bean定义是AnnotatedBeanDefinition的实例,则获取其注解元数据并解析
            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            // 步骤2:如果Bean定义是AbstractBeanDefinition并且已经有关联的Bean类,则直接解析该Bean类
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            // 步骤3:如果上述情况都不符合,则直接使用Bean的类名进行解析
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        // 如果在解析过程中捕获到BeanDefinitionStoreException,则直接抛出
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        // 对于其他类型的异常,封装并抛出一个新的BeanDefinitionStoreException
        catch (Throwable ex) {
            // ... [代码部分省略以简化]
        }
    }
    // 步骤4:在所有配置类都被解析后,处理所有延迟的导入选择器
    this.deferredImportSelectorHandler.process();
}

我们来到org.springframework.context.annotation.ConfigurationClassParser#parse方法的第一步,在org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)方法中,将注解元数据和Bean名称转化为一个配置类,然后对其进行处理。处理配置类是Spring配置驱动的核心,它涉及到许多关键操作,如解析@Bean方法、处理@ComponentScan注解、处理@Import注解等。

java 复制代码
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass方法中,处理一个给定的配置类。它首先递归地处理配置类及其父类,以确保所有相关的配置都被正确地读取并解析。在递归处理完所有相关配置后,它将配置类添加到已解析的配置类的映射中。

java 复制代码
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    // ... [代码部分省略以简化]

    // 步骤1:递归地处理配置类及其超类层次结构
    SourceClass sourceClass = asSourceClass(configClass, filter);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
    } while (sourceClass != null);

    // 步骤2:将处理后的配置类放入映射中
    this.configurationClasses.put(configClass, configClass);
}

org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法中,其中主要目的是处理给定ConfigurationClass中的@Import注解。在处理完所有的导入之后,它完成了任务并返回null。

java 复制代码
@Nullable
protected final SourceClass doProcessConfigurationClass(
    ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
    throws IOException {

    // ... [代码部分省略以简化]
    
    // 处理 sourceClass 上的所有 @Import 注解
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // ... [代码部分省略以简化]
    
    // 当前实现中,直接返回null,意味着没有超类需要进一步处理
    return null;
}

org.springframework.context.annotation.ConfigurationClassParser#processImports方法中,主要分为三个步骤。第一步是对于ImportSelectorprocessImports方法主要完成了动态选择和导入类的功能,使得Spring配置更加灵活和模块化。第二步是ImportBeanDefinitionRegistrar提供了一个在bean定义注册过程中的插入点,允许动态、条件性配置。processImports方法确保这些逻辑在处理@Import时正确执行。第三步是当遇到一个不是ImportSelectorImportBeanDefinitionRegistrar的类时,直接视其为一个普通的Spring组件或配置类,并按照常规的处理流程进行。这确保了所有通过@Import导入的类,不论它们的类型如何,都会被正确地注册和处理。

java 复制代码
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
            boolean checkForCircularImports) {
    // 如果没有要导入的候选类,直接返回
    if (importCandidates.isEmpty()) {
        return;
    }
    // 如果设置了检查循环导入,并且当前的配置类在导入堆栈中形成了一个链,报告循环导入问题
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
        this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
        // 将当前的配置类推入导入堆栈中
        this.importStack.push(configClass);
        try {
            for (SourceClass candidate : importCandidates) {
                // 步骤1:判断候选类是否实现了 ImportSelector 接口
                if (candidate.isAssignable(ImportSelector.class)) {
                    // 加载当前的候选类
                    Class<?> candidateClass = candidate.loadClass();
                    // 通过工具方法实例化 ImportSelector 接口的实现类
                    ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                        this.environment, this.resourceLoader, this.registry);
                    // 获取从 ImportSelector 指定的排除过滤器
                    Predicate<String> selectorFilter = selector.getExclusionFilter();
                    // 如果选择器提供了额外的排除过滤器,则合并当前的排除过滤器
                    if (selectorFilter != null) {
                        exclusionFilter = exclusionFilter.or(selectorFilter);
                    }
                    // 步骤1.1:检查该选择器是否是 DeferredImportSelector 的实例
                    if (selector instanceof DeferredImportSelector) {
                        // 如果是 DeferredImportSelector,使用特定的处理器来处理它
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    }
                    else {
                        // 步骤1.2: 如果不是 DeferredImportSelector,则使用选择器来选择要导入的类
                        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                        // 将这些类名转化为 SourceClass 集合
                        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                        // 递归地处理这些要导入的类
                        processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
                    }
                }
                // 步骤2:检查当前候选类是否实现了 ImportBeanDefinitionRegistrar 接口
                else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                    // 加载当前的候选类
                    Class<?> candidateClass = candidate.loadClass();
                    // 通过工具方法实例化 ImportBeanDefinitionRegistrar 接口的实现类
                    ImportBeanDefinitionRegistrar registrar =
                        ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                            this.environment, this.resourceLoader, this.registry);
                    // 将实例化的 registrar 添加到当前的配置类中
                    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                }
                // 步骤3:如果候选类既不是 ImportSelector 也不是 ImportBeanDefinitionRegistrar
                else {
                    // 在导入堆栈中为当前的源类注册导入的类
                    this.importStack.registerImport(
                        currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                    // 将候选类处理为一个配置类,并递归地处理它
                    processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
                }
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            // ... [代码部分省略以简化]
        }
        finally {
            // 在处理完成后,从导入堆栈中弹出当前配置类
            this.importStack.pop();
        }
    }
}

org.springframework.context.annotation.ConfigurationClassParser#processImports方法中的步骤1.2中,最后调用到我们自定义的实现逻辑中来,selectImports方法始终返回MyBeanA类的完全限定名,表示当这个ImportSelector被处理时,MyBeanA类将被导入到Spring应用上下文中。

java 复制代码
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyBeanA.class.getName()};
    }
}

我们回到org.springframework.context.annotation.ConfigurationClassParser#parse方法的第四步,在org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process方法中,首先获取当前待处理的延迟导入选择器,然后使用DeferredImportSelectorGroupingHandler来按组处理它们,最后确保再次初始化延迟导入选择器列表,为下一次处理做准备。

java 复制代码
/**
 * 处理所有注册的延迟导入选择器。
 */
public void process() {
    // 获取当前的延迟导入选择器列表
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    // 将当前的延迟导入选择器列表设为null,表示它们即将被处理
    this.deferredImportSelectors = null;
    
    try {
        // 如果存在待处理的延迟导入选择器
        if (deferredImports != null) {
            // 创建一个新的分组处理器,用于处理延迟导入选择器的分组逻辑
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            
            // 对延迟导入选择器进行排序,确保按预期的顺序进行处理
            deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
            
            // 遍历每个延迟导入选择器,使用分组处理器进行注册
            deferredImports.forEach(handler::register);
            
            // 使用分组处理器处理分组的导入
            handler.processGroupImports();
        }
    }
    // 确保在方法结束时,无论是否发生异常,都将延迟导入选择器列表重新初始化为一个新的空列表
    finally {
        this.deferredImportSelectors = new ArrayList<>();
    }
}

org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports方法中,遍历每个已注册的延迟导入选择器分组,并对每个分组中的每个导入条目进行处理。处理的内容包括递归地处理所有相关的@Import@Bean定义。

java 复制代码
/**
 * 处理每个分组中的延迟导入选择器。
 */
public void processGroupImports() {
    // 遍历每个已注册的延迟导入选择器分组
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        // 获取分组的候选类排除过滤器
        Predicate<String> exclusionFilter = grouping.getCandidateFilter();
        
        // 遍历分组中的每个导入选择器
        grouping.getImports().forEach(entry -> {
            // 获取与当前导入条目关联的配置类
            ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
            
            try {
                // 对每个导入条目进行处理,包括递归地处理所有相关的@Import和@Bean定义
                processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
                               Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
                               exclusionFilter, false);
            }
            // 如果在处理过程中捕获到BeanDefinitionStoreException,则直接抛出
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            // 对于其他类型的异常,处理相关逻辑(此处简化,不展开详细的异常处理逻辑)
            catch (Throwable ex) {
                // ... [代码部分省略以简化]
            }
        });
    }
}

org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports方法中,遍历每个已注册的延迟导入选择器,将它们的相关信息传递给分组进行处理,然后最终使用分组实例来选择并返回要导入的类。

java 复制代码
public Iterable<Group.Entry> getImports() {
    // 遍历每个已注册的延迟导入选择器
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        // 对于每个延迟导入选择器,将其关联的配置类的元数据和其选择器传递给分组进行处理
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                           deferredImport.getImportSelector());
    }
    // 使用分组实例来选择并返回要导入的类
    return this.group.selectImports();
}

org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup#process方法中,主要给传入的DeferredImportSelector,找出它选择导入的所有类,并将这些类与提供的注解元数据一起存储在一个列表中,以供以后使用。

java 复制代码
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
    // 使用选择器和提供的元数据选择要导入的类
    for (String importClassName : selector.selectImports(metadata)) {
        // 对于每个选定的导入类,创建一个新的Entry对象并将其添加到当前分组的导入列表中
        this.imports.add(new Entry(metadata, importClassName));
    }
}

最后调用到我们自定义的实现逻辑中来,selectImports方法始终返回MyBeanB类的完全限定名,表示当这个DeferredImportSelector被处理时,MyBeanB类将被导入到Spring应用上下文中。

java 复制代码
public class MyDeferredImportSelector implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MyBeanB.class.getName()};
    }
}

我们来到org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中的步骤5。在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions方法中,遍历给定的一组ConfigurationClass对象(这些对象代表的是@Configuration注解的类),并为每个类加载其相关的Bean定义。

java 复制代码
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
        loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
}

org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中,如果配置类是通过@Import注解或其他机制导入的,则该方法会为其注册一个Bean定义,另外该方法会处理与ConfigurationClass关联的所有ImportBeanDefinitionRegistrar。这些注册器允许在解析配置类时以编程方式动态地添加额外的Bean定义。

java 复制代码
private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    // ... [可能的初始化或其他预处理]

    // 步骤1:如果这个配置类是由于@Import或其他机制而被导入的,那么为它注册一个Bean定义。
    // 这确保了导入的@Configuration类也被视为一个Bean,并可以在ApplicationContext中被检索。
    if (configClass.isImported()) {
        registerBeanDefinitionForImportedConfigurationClass(configClass);
    }
    
    // ... [可能的其他处理或Bean定义加载]

    // 步骤2:从当前配置类关联的ImportBeanDefinitionRegistrars加载Bean定义。
    // ImportBeanDefinitionRegistrar允许我们在解析@Configuration类时进行编程式地注册额外的Bean定义。
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

我们来到org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中步骤1,在org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass方法中,首先,它根据配置类的注解元数据创建一个Bean定义。然后,它确定并设置Bean的作用域,例如单例或原型。为Bean定义生成一个唯一的名称后,该方法处理了其上的常见注解,例如@Lazy@Primary。如果Bean的作用域如请求或会话需要代理,会应用相应的代理。最后,它将Bean定义注册到Spring容器,并将生成的名称与原始配置类关联。这确保了导入的配置类在Spring中也可以作为一个常规Bean进行访问或注入。

java 复制代码
/**
 * 为导入的ConfigurationClass注册Bean定义。
 *
 * @param configClass 要处理的ConfigurationClass
 */
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
    // 获取configClass的元数据
    AnnotationMetadata metadata = configClass.getMetadata();

    // 创建基于元数据的Bean定义
    AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

    // 解析Bean的作用域(例如:singleton, prototype)
    ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
    configBeanDef.setScope(scopeMetadata.getScopeName());

    // 为configBeanDef生成Bean名称
    String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);

    // 处理常见的注解定义(例如:@Lazy, @Primary)
    AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

    // 将Bean定义封装在BeanDefinitionHolder中,以携带其他配置元数据
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);

    // 根据需要应用作用域代理模式(例如,对于"request"和"session"作用域的Beans)
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

    // 在Bean定义注册表中注册Bean定义
    this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());

    // 将生成的Bean名称设置到configClass中
    configClass.setBeanName(configBeanName);

    // 如果日志级别为TRACE,记录一条关于注册的消息
    if (logger.isTraceEnabled()) {
        logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
    }
}

我们来到org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中步骤2,每个ImportBeanDefinitionRegistrar都有机会基于其自定义逻辑在Spring容器中注册额外的Bean定义。

java 复制代码
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
                       registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}

最后调用到我们自定义的实现逻辑中来,MyImportBeanDefinitionRegistrar 类提供手动注册 MyBeanC 组件到Spring容器的方法,而不依赖于组件扫描或其他自动配置机制。确保 MyBeanC 被添加到Spring的上下文中。

java 复制代码
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBeanC.class);
        registry.registerBeanDefinition(MyBeanC.class.getName(), beanDefinition);
    }
}

八、注意事项

  1. 避免循环引用

    • 确保我们没有创建循环引用,即一个配置类导入另一个配置类,而后者又反过来导入前者。
  2. @ComponentScan的关系

    • @Import@ComponentScan都可以用于注册bean,但是它们的用途稍有不同。@ComponentScan用于自动扫描和注册bean,而@Import用于明确地导入其他配置类。
  3. 属性覆盖

    • 如果从多个配置类中导入相同的bean定义,并设置了不同的属性或值,那么后导入的bean定义将覆盖先导入的bean定义。
  4. @Profile的关系

    • 可以结合使用@Import@Profile,这样只有在特定的激活配置文件中才会导入某个配置类。

九、总结

最佳实践总结

  1. 启动类

    • 通过ImportApplication类,我们初始化了Spring的上下文环境,选择了AnnotationConfigApplicationContext,这是Spring提供的使用Java注解配置方式。在该上下文中,我们指定了MyConfiguration作为主要的配置类,并遍历了上下文中所有已注册的bean名称。
  2. 主配置类

    • MyConfiguration类通过使用@Import注解导入了四种不同类型的组件或选择器:

      • MyBean:一个普通的Java类。

      • MyImportSelector:一个实现了ImportSelector接口的类。

      • MyDeferredImportSelector:一个实现了DeferredImportSelector接口的类。

      • MyImportBeanDefinitionRegistrar:一个实现了ImportBeanDefinitionRegistrar接口的类。

  3. 选择器与注册器

    • MyImportSelector通过实现ImportSelector接口,动态地选择并导入了MyBeanA类。

    • MyDeferredImportSelector作为DeferredImportSelector的实现,延迟地选择并导入了MyBeanB类。与ImportSelector不同,它允许在所有其他配置类被处理后再进行导入。

    • MyImportBeanDefinitionRegistrar提供了手动注册bean的功能。在这个示例中,它将MyBeanC手动注册到了Spring上下文中。

  4. 组件定义

    • 在我们最佳实践中包含四个Java类,分别为MyBeanMyBeanAMyBeanBMyBeanC,它们都被上述的选择器或注册器选择并导入到Spring上下文中。

源码分析总结

  1. 启动阶段

    • 在Spring启动时,会使用AnnotationConfigApplicationContext来加载和解析配置类,这个配置类可能包含有@Import注解。
  2. 解析配置类

    • AnnotationConfigApplicationContext的构造函数中,通过refresh()方法开始刷新容器并解析Bean定义。进一步,在AbstractApplicationContext#refresh方法中,会调用invokeBeanFactoryPostProcessors方法来处理在上下文中注册为bean的工厂处理器。
  3. 处理@Import注解

    • 处理过程中会特别关注BeanDefinitionRegistryPostProcessor,这是BeanFactoryPostProcessor的子接口,专门用于在所有其他bean定义加载之前修改默认的bean定义。然后,系统解析配置类并验证其内容,这主要涉及查找该类中的@Bean方法、组件扫描指令等,并将这些信息注册到Spring容器中。
  4. 处理ImportSelectorDeferredImportSelector

    • 在上述解析过程中,如果配置类包含一个实现了ImportSelectorDeferredImportSelector接口的类,那么它们将被用来选择并导入其他的类。在示例中,MyImportSelectorMyDeferredImportSelector是这样的选择器,分别导入MyBeanAMyBeanB
  5. 处理ImportBeanDefinitionRegistrar

    • 在加载Bean定义的过程中,也会处理与ConfigurationClass关联的所有ImportBeanDefinitionRegistrar。这些注册器允许在解析时动态地、以编程方式地添加额外的Bean定义,如MyImportBeanDefinitionRegistrar示例中为MyBeanC进行的手动注册。
  6. 注册导入的配置类

    • 如果一个@Configuration类是由于@Import或其他机制导入的,Spring不仅会处理它的@Bean方法和其他注解,还会为这个类本身注册一个Bean定义,以便它也可以被注入到其他Bean或由应用程序检索。
  7. 额外Bean的注册

    • 通过ImportBeanDefinitionRegistrar和其自定义逻辑,能够根据需要将任意数量的额外Bean定义注册到容器中。
相关推荐
成富4 小时前
文本转SQL(Text-to-SQL),场景介绍与 Spring AI 实现
数据库·人工智能·sql·spring·oracle
鹿屿二向箔5 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的汽车租赁共享平台系统
spring·mvc·mybatis
豪宇刘5 小时前
SpringBoot+Shiro权限管理
java·spring boot·spring
一只爱打拳的程序猿6 小时前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
ajsbxi9 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
鹿屿二向箔10 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的咖啡馆管理系统
spring·mvc·mybatis
NoneCoder11 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
paopaokaka_luck17 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
Yaml419 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
aloha_78919 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot