首页 > 其他分享 >聊一聊Spring中的@Import注解

聊一聊Spring中的@Import注解

时间:2024-10-22 13:52:14浏览次数:3  
标签:configClass getMetadata Spring Bean 聊一聊 Import class sourceClass

[!NOTE]

**Spring版本:**5.3.27

**JDK版本:**1.8

一、@Import在何处处理

// ConfigurationClassParser

/**
 * 通过从源类(理解为配置类)中读取注解、成员和方法,获取到完成的配置类
 * 由于一个配置类可能关联其他的配置类等,所以这个方法可能会被调用多次
 */
protected final SourceClass doProcessConfigurationClass(
        ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
        throws IOException {

    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
        // 先递归处理成员类
        processMemberClasses(configClass, sourceClass, filter);
    }

    // 处理@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");
        }
    }

    // 处理@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());
            // 如果找到其他的配置类,在需要的情况下递归解析
            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());
                }
            }
        }
    }

    // 处理@Import注解  本文要探讨的注解
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // 处理@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);
        }
    }

    // 处理@Bean方法
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // 处理接口上的默认方法
    processInterfaces(configClass, sourceClass);

    // 超类处理
    if (sourceClass.getMetadata().hasSuperClass()) {
        String superclass = sourceClass.getMetadata().getSuperClassName();
        if (superclass != null && !superclass.startsWith("java") &&
                !this.knownSuperclasses.containsKey(superclass)) {
            this.knownSuperclasses.put(superclass, configClass);
            // 发现超类, 返回元注解并递归
            return sourceClass.getSuperClass();
        }
    }

    // 没有超类 -> 处理完成
    return null;
}

// 获取通过@Import导入的类
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    Set<SourceClass> imports = new LinkedHashSet<>();
    Set<SourceClass> visited = new LinkedHashSet<>();
    collectImports(sourceClass, imports, visited);
    return imports;
}

/**
 * 递归收集通过@Import导入的类
 * 检查每个源类上的注解,如果不是@Import,把这个注解当成源类,继续收集
 * 最终的结果就是获取到所有源类上所有层次@Import上的value
 */
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
        throws IOException {

    if (visited.add(sourceClass)) {
        for (SourceClass annotation : sourceClass.getAnnotations()) {
            String annName = annotation.getMetadata().getClassName();
            if (!annName.equals(Import.class.getName())) {
                collectImports(annotation, imports, visited);
            }
        }
        imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
    }
}

/**
 * importCandidates是上一步收集到的所有@Import注解的value
 */
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
        Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
        boolean checkForCircularImports) {

    // 省略部分代码...
    
    // 遍历处理所有@Import注解的value
    for (SourceClass candidate : importCandidates) {
        if (candidate.isAssignable(ImportSelector.class)) {
            // 候选类是ImportSelector -> 委托给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) {
                this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
            }
            else {
                String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
            }
        }
        else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
            // 候选类是ImportBeanDefinitionRegistrar ->
            // 委托给ImportBeanDefinitionRegistrar注册额外的bean定义信息
            Class<?> candidateClass = candidate.loadClass();
            ImportBeanDefinitionRegistrar registrar =
                    ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                            this.environment, this.resourceLoader, this.registry);
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        else {
            // 候选类不是ImportSelector或ImportBeanDefinitionRegistrar ->
            // 当成配置类处理(@Configuration注解的类)
            this.importStack.registerImport(
                    currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
        }
    }
}

二、@Import引入的三种类型

1、引入普通组件

package com.lazy.snail;

/**
 * @ClassName NormalComponent
 * @Description TODO
 * @Author lazysnail
 * @Date 2024/10/22 9:35
 * @Version 1.0
 */
public class NormalComponent {
}
package com.lazy.snail;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

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

}

image-20241022103210745

2、引入ImportSelector

package com.lazy.snail;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class AppConfig {

}
// EnableAsync

// 省略部分代码...

/**
 * 启用Spring的异步方法执行能力
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// AsyncConfigurationSelector会被选择出来进行处理
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {

	Class<? extends Annotation> annotation() default Annotation.class;

	boolean proxyTargetClass() default false;

	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;

}
// ConfigurationClassParser.processImports截取

if (candidate.isAssignable(ImportSelector.class)) {
    // Candidate class is an ImportSelector -> delegate to it to determine imports
    Class<?> candidateClass = candidate.loadClass();
    // 实例化了AsyncConfigurationSelector
    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);
    }
    // 这种类型后续SpringBoot自动装配再聊
    if (selector instanceof DeferredImportSelector) {
        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
    } else {
        // 此处的selectImports调用的是AsyncConfigurationSelector父类AdviceModeImportSelector的selectImports
        // 各种选择逻辑后返回org.springframework.scheduling.annotation.ProxyAsyncConfiguration
        String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
        // 根据全限定名找源Class
        Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
        // 把上面找到的源类当做@Import引入的类,从走取经路
        // 就本案例而言,ProxyAsyncConfiguration是普通组件,只有一个@Bean方法,所以会在容器中注册一个AsyncAnnotationBeanPostProcessor
        processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
    }
}

// AdviceModeImportSelector
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
    // 省略部分代码...
    
    // AsyncConfigurationSelector的selectImports方法
    String[] imports = selectImports(adviceMode);
    if (imports == null) {
        throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
    }
    return imports;
}

// AsyncConfigurationSelector
/**
 * 这个方法返回了一个类的全限定名
 * org.springframework.scheduling.annotation.ProxyAsyncConfiguration
 */
public String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
        case PROXY:
            return new String[] {ProxyAsyncConfiguration.class.getName()};
        case ASPECTJ:
            return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME};
        default:
            return null;
    }
}

AsyncConfigurationSelector类结构图:

AdviceModeImportSelector

解析结束后,容器中的beanDefinitionMap:

image-20241022111017349

3、引入ImportBeanDefinitionRegistrar

package com.lazy.snail;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 此处导入了ImportBeanDefinitionRegistrar类型的注册器
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	boolean proxyTargetClass() default false;

	boolean exposeProxy() default false;

}
// ConfigurationClassParser.processImports

else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    Class<?> candidateClass = candidate.loadClass();
    // AspectJAutoProxyRegistrar实例
    ImportBeanDefinitionRegistrar registrar =
            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                    this.environment, this.resourceLoader, this.registry);
    // 注册器加到了配置类importBeanDefinitionRegistrars中,这个集合中的注册器会在加载bean定义信息的时候使用
    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}

// ConfigurationClassPostProcessor.processConfigBeanDefinitions

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 省略部分代码...
    this.reader.loadBeanDefinitions(configClasses);
    // 省略部分代码...
}

// ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass
private void loadBeanDefinitionsForConfigurationClass(
        ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

    // 省略部分代码...
    // 此处处理之前添加的注册器
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

/**
 * 遍历注册器,调用注册器的registerBeanDefinitions方法
 */
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
            registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
// AspectJAutoProxyRegistrar
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 注册AspectJAnnotationAutoProxyCreator的bean定义信息
		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

		AnnotationAttributes enableAspectJAutoProxy =
				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
		if (enableAspectJAutoProxy != null) {
			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
			}
			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
			}
		}
	}

}

注册器处理完后,容器中的beanDefinitionMap中:

image-20241022120055570

三、总结

1. 导入普通组件

使用方式:
@Import(SomeConfig.class)
处理机制:
  • @Import 注解的 value 中传入的是普通的组件类(包括普通的 Java 类和标记了 @Configuration 的类),Spring 容器会将这些类作为Bean直接注册到容器中。

  • 解析顺序

    • Spring 会将这些类当作标准的配置类进行处理(如果带有 @Configuration)。
    • 如果是普通类(没有 @Configuration),也会作为普通的 Bean 定义注册进容器。
使用场景:
  • 简单场景下,直接将某些普通组件或配置类引入容器进行自动装配和依赖注入。

2. 导入实现 ImportSelector 接口的类

使用方式:
@Import(MyImportSelector.class)
处理机制:
  • ImportSelector 是一个接口,其中最核心的方法是 selectImports,它返回一个字符串数组,表示要导入的类名。

  • 解析顺序

    • Spring 会调用 selectImports 方法,获取需要导入的 Bean 名称列表(这些类并不需要提前在 @Import 注解中明确声明)。
    • Spring 根据返回的类名,动态地将这些类的定义加载到容器中。
    • 可以通过条件、配置、逻辑判断来选择哪些类需要被导入,实现更灵活的组件注册。
使用场景:
  • 需要基于逻辑条件动态引入配置类或组件,比如在某些场景下,按需引入特定 Bean。
  • 常用于自动配置机制,例如 Spring Boot 的 @EnableAutoConfiguration,其背后通过 ImportSelector 实现了动态组件引入。

3. 导入实现 ImportBeanDefinitionRegistrar 接口的类

使用方式:
@Import(MyImportBeanDefinitionRegistrar.class)
处理机制:
  • ImportBeanDefinitionRegistrar 提供更底层的控制,它允许在 Spring Bean 定义的注册阶段,手动向容器中注册 Bean 定义信息。

  • 解析顺序

    • Spring 调用 registerBeanDefinitions 方法,该方法允许开发者直接向 BeanDefinitionRegistry 中注册 Bean 定义,而不仅仅是类名。
    • 在这个方法里,开发者可以完全自定义 Bean 的定义、依赖关系、属性等。
    • 这是最强大的扩展方式,因为它可以直接操作 Bean 定义元数据,而不仅仅是控制导入哪些类。
使用场景:
  • 需要完全自定义 Bean 注册过程,甚至可以动态生成 BeanDefinition
  • 通常用于更复杂的场景,如框架级别的开发,动态注册特定类型的 Bean 或者与其他框架集成。

三者对比总结

形式处理方式使用场景
普通组件(类)直接将类作为 Bean 注册到容器中引入静态组件、配置类,或使用简单的 @Configuration
实现 ImportSelector 接口通过返回类名字符串数组,动态选择和注册类按条件动态选择要注册的 Bean,用于自动配置等场景
实现 ImportBeanDefinitionRegistrar 接口手动操作 BeanDefinitionRegistry 注册 Bean 定义完全自定义 Bean 的注册过程,实现更复杂的动态注册逻辑

标签:configClass,getMetadata,Spring,Bean,聊一聊,Import,class,sourceClass
From: https://blog.csdn.net/indolentSnail/article/details/143156333

相关文章

  • SpringBoot个人理财系统:构建你的数字钱包
    摘要随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了个人理财系统的开发全过程。通过分析个人理财系统管理的不足,创建了一个计算机管理个人理财系统的方案。文章介绍了个人理财系统的系统分析部分,包括可行性分析等,系统设计部......
  • SpringBoot与个人理财:打造现代财务管理工具
    摘要随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了个人理财系统的开发全过程。通过分析个人理财系统管理的不足,创建了一个计算机管理个人理财系统的方案。文章介绍了个人理财系统的系统分析部分,包括可行性分析等,系统设计部......
  • Spring AI : Java写人工智能的应用框架
    SpringAI:为Java开发者提供高效集成大模型能力的框架当前Java调用大模型时,面临缺乏优质AI应用框架的挑战。Spring作为资深的Java应用框架提供者,通过推出SpringAI来解决这一问题。它借鉴了langchain的核心理念,并结合了Java面向对象编程的特点,为开发者提供了统一且可灵活替换......
  • vue-springboot基于JavaWeb的智慧养老院管理系统的设计与实现 附源码
    目录项目介绍系统实现截图源码获取地址下载技术栈开发核心技术介绍:为什么选择最新的Vue与SpringBoot技术核心代码部分展示项目介绍该系统从三个对象:由管理员和家属、护工来对系统进行设计构建。主要功能包括:个人信息修改,对家属信息、护工信息、老人入住、外出报备、......
  • SpringBoot 面试常见问答总结(一)
    1.什么是SpringBoot?SpringBoot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,使开发者能快速上手。2.为什么要用SpringBoot?快速开发,快速整合,配置简化、内嵌服务容器3.SpringBoot与Spring......
  • 【开源免费】基于SpringBoot+Vue.JS读书笔记共享平台(JAVA毕业设计)
    本文项目编号T029,文末自助获取源码\color{red}{T029,文末自助获取源码}......
  • 【开源免费】基于SpringBoot+Vue.JS母婴商城系统 (JAVA毕业设计)
    本文项目编号T030,文末自助获取源码\color{red}{T030,文末自助获取源码}......
  • SpringBoot框架下个人理财系统的设计与实现
    1系统概述1.1研究背景随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理个人理财系统的相关信息成为必然。开发合适的个人理财系统,可以方便管理人员对个人理财系统......
  • 个人理财新助手:SpringBoot驱动的财务管理系统
    1系统概述1.1研究背景随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理个人理财系统的相关信息成为必然。开发合适的个人理财系统,可以方便管理人员对个人理财系统......
  • SpringBoot框架下宠物用品交易网站的构建与实现
    摘要随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了在线宠物用品交易网站的开发全过程。通过分析在线宠物用品交易网站管理的不足,创建了一个计算机管理在线宠物用品交易网站的方案。文章介绍了在线宠物用品交易网站的系统分......