每日一面 - Spring 的 @Import 注解的作用与用法(下)

简介: 每日一面 - Spring 的 @Import 注解的作用与用法(下)

@Import相关源码解析


加载解析@Import注解位于BeanFactoryPostProcessor处理的时候:

AbstractApplicationContextrefresh方法

-> invokeBeanFactoryPostProcessors(beanFactory);

-> PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

-> registryProcessor.postProcessBeanDefinitionRegistry(registry);

这里的registryProcessor,我们指ConfigurationClassPostProcessor

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(registry)

-> processConfigBeanDefinitions(registry):

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    //省略一些配置检查与设置的逻辑
    //根据@Order注解,排序所有的@Configuration类
    configCandidates.sort((bd1, bd2) -> {
      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
      return Integer.compare(i1, i2);
    });
  // 创建ConfigurationClassParser解析@Configuration类
  ConfigurationClassParser parser = new ConfigurationClassParser(
      this.metadataReaderFactory, this.problemReporter, this.environment,
      this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    //剩余没有解析的@Configuration类
  Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  //已经解析的@Configuration类
  Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  do {
      //解析
    parser.parse(candidates);
    parser.validate();
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);
    // 生成类定义读取器读取类定义
    if (this.reader == null) {
      this.reader = new ConfigurationClassBeanDefinitionReader(
          registry, this.sourceExtractor, this.resourceLoader, this.environment,
          this.importBeanNameGenerator, parser.getImportRegistry());
    }
    this.reader.loadBeanDefinitions(configClasses);
    alreadyParsed.addAll(configClasses);
    candidates.clear();
    if (registry.getBeanDefinitionCount() > candidateNames.length) {
      //省略检查是否有其他需要加载的配置的逻辑
    }
  }
  while (!candidates.isEmpty());
  //省略后续清理逻辑
}


其中parser.parse(candidates)的逻辑主要由org.springframework.context.annotation.ConfigurationClassParser实现,功能是加载@Import注解还有即系@Import注解。reader.loadBeanDefinitions(configClasses);的逻辑主要由org.springframework.context.annotation.ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass方法实现,功能是将上面解析的配置转换为BeanDefinition就是Bean定义。


1. 加载@Import注解


org.springframework.context.annotation.ConfigurationClassParser

首先是parse方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  for (BeanDefinitionHolder holder : configCandidates) {
    BeanDefinition bd = holder.getBeanDefinition();
    try {
      if (bd instanceof AnnotatedBeanDefinition) {
          //这里的parse实际上就是调用下面即将分析的doProcessConfigurationClass
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      }
      else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
        parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
      }
      else {
        parse(bd.getBeanClassName(), holder.getBeanName());
      }
    }
    catch (BeanDefinitionStoreException ex) {
      throw ex;
    }
    catch (Throwable ex) {
      throw new BeanDefinitionStoreException(
          "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    }
  }
    //最后处理所有的`DeferredImportSelector`,符合上面提到的`DeferredImportSelector`的功能
  this.deferredImportSelectorHandler.process();
}
@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {
  //处理`@Component`注解的MemberClass相关代码...
  //处理`@PropertySource`注解相关代码...
  //处理`@ComponentScan`注解相关代码...
    //处理`@Import`注解:
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
    //处理`@ImportResource`注解相关代码...
    //处理`@Bean`注解相关代码...
    //处理接口方法相关代码...
    //处理父类相关代码...
}


通过getImports方法,采集相关的@Import里面的类。


private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
  Set<SourceClass> imports = new LinkedHashSet<>();
  Set<SourceClass> visited = new LinkedHashSet<>();
  //递归查询所有注解以及注解的注解是否包含@Import
  collectImports(sourceClass, imports, visited);
  return imports;
}
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();
      //对于非@Import注解,递归查找其内部是否包含@Import注解
      if (!annName.equals(Import.class.getName())) {
        collectImports(annotation, imports, visited);
      }
    }
    //添加@Import注解里面的所有配置类
    imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
  }
}

采集好之后,就可以解析了。


2. 解析@Import注解


解析的方法是:processImports

//在解析时,入栈,解析结束后,出栈,通过检查栈中是否有当前类,判断是否有循环依赖
private final ImportStack importStack = new ImportStack();
//记录所有的ImportBeanDefinitionRegistrar
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
//解析也是递归方法
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      boolean checkForCircularImports) {
  if (importCandidates.isEmpty()) {
    return;
  }
    //通过importStack检查循环依赖
  if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
  }
  else {
      //入栈
    this.importStack.push(configClass);
    try {
      for (SourceClass candidate : importCandidates) {
        if (candidate.isAssignable(ImportSelector.class)) {
          //处理ImportSelector接口的实现类
          Class<?> candidateClass = candidate.loadClass();
          //创建这些Selector实例
          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);
          }
          //如果是DeferredImportSelector,则用deferredImportSelectorHandler处理
          if (selector instanceof DeferredImportSelector) {
            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
          }
          else {
              //如果不是DeferredImportSelector,调用selectImports方法获取要加载的类全限定名称,递归调用本方法继续解析
            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接口的实现类
          Class<?> candidateClass = candidate.loadClass();
          //同样的,创建这些ImportBeanDefinitionRegistrar实例
          ImportBeanDefinitionRegistrar registrar =
              ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                  this.environment, this.resourceLoader, this.registry);
          //放入importBeanDefinitionRegistrar,用于后面加载
        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
        }
        else {
          //处理@Configuration注解类,或者是普通类(直接生成Bean)
          //在栈加上这个类
          this.importStack.registerImport(
              currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
          //递归回到doProcessConfigurationClass处理@Configuration注解类  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();
    }
  }
}

这样,所有的@Conditional类相关的@Import注解就加载解析完成了,这是一个大的递归过程。


3. 转换为BeanDefinition注册到容器


org.springframework.context.annotation.ConfigurationClassBeanDefinitionReaderloadBeanDefinitionsForConfigurationClass方法:

private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
  if (trackedConditionEvaluator.shouldSkip(configClass)) {
    String beanName = configClass.getBeanName();
    if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
      this.registry.removeBeanDefinition(beanName);
    }
    this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
    return;
  }
    //对Import完成的,加载其Import的BeanDefinition
  if (configClass.isImported()) {
    registerBeanDefinitionForImportedConfigurationClass(configClass);
  }
  //加载@Bean注解的方法生成的Bean的Definition
  for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
  }
    //@ImportResource 注解加载的
  loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  //加载ImportBeanDefinitionRegistrar加载的Bean的Definition
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

通过这里可以看出,为啥之前说@Bean注解的Bean会优先于ImportBeanDefinitionRegistrar返回的Bean加载。

相关文章
|
11天前
|
前端开发 Java 开发者
深入理解Spring Boot中的@Service注解
【4月更文挑战第22天】在 Spring Boot 应用开发中,@Service 注解扮演着特定的角色,主要用于标识服务层组件。本篇技术博客将全面探讨 @Service 注解的概念,并提供实际的应用示例,帮助开发者理解如何有效地使用这一注解来优化应用的服务层架构
53 1
|
11天前
|
Java 开发者 Spring
深入理解Spring Boot的@ComponentScan注解
【4月更文挑战第22天】在构建 Spring Boot 应用时,@ComponentScan 是一个不可或缺的工具,它使得组件发现变得自动化和高效。这篇博客将详细介绍 @ComponentScan 的基本概念、关键属性及其在实际开发中的应用。
26 4
|
12天前
|
Java 开发者 Spring
深入理解 Spring Boot 中的 @EnableAutoConfiguration 注解:概念与实践
【4月更文挑战第21天】在 Spring Boot 项目中,@EnableAutoConfiguration 注解是实现自动配置的核心,它可以根据项目的依赖和配置,自动地配置 Spring 应用程序中的 Bean
29 3
|
13天前
|
Java API 网络架构
深入理解 Spring Boot 中的 @RestController 注解:概念与实践
【4月更文挑战第20天】在现代Web开发中,创建RESTful服务已成为常态。Spring Boot通过提供@RestController注解,极大简化了REST API的开发过程。本篇博客旨在详细介绍@RestController的概念、优势以及在Spring Boot项目中的具体应用方法。
31 8
|
13天前
|
Java 开发者 Spring
Spring Framework 中的 @Autowired 注解:概念与使用方法
【4月更文挑战第20天】在Spring Framework中,@Autowired 注解是实现依赖注入(Dependency Injection, DI)的一种非常强大的工具。通过使用 @Autowired,开发者可以减少代码中的引用绑定,提高模块间的解耦能力
31 6
|
13天前
|
XML Java 数据库
探索 Spring Boot 中的 @Configuration 注解:核心概念与应用
【4月更文挑战第20天】在 Spring Boot 项目中,@Configuration 注解扮演了一个关键角色,它标识一个类作为配置源,这些配置用于定义和管理 Spring 应用程序中的 Bean
39 7
|
3天前
|
Java 测试技术 开发者
【亮剑】如何通过自定义注解来实现 Spring AOP,以便更加灵活地控制方法的拦截和增强?
【4月更文挑战第30天】通过自定义注解实现Spring AOP,可以更灵活地控制方法拦截和增强。首先定义自定义注解,如`@MyCustomAnnotation`,然后创建切面类`MyCustomAspect`,使用`@Pointcut`和`@Before/@After`定义切点及通知。配置AOP代理,添加`@EnableAspectJAutoProxy`到配置类。最后,在需拦截的方法上应用自定义注解。遵循保持注解职责单一、选择合适保留策略等最佳实践,提高代码可重用性和可维护性。记得测试AOP逻辑。
|
21天前
|
前端开发 Java Spring
请求映射掌握:探讨Spring MVC中@RequestMapping注解的妙用
请求映射掌握:探讨Spring MVC中@RequestMapping注解的妙用
22 1
请求映射掌握:探讨Spring MVC中@RequestMapping注解的妙用
|
21天前
|
Java Shell 测试技术
环境切换大法:掌握Spring Boot多套配置与@Profile注解的高级技巧
环境切换大法:掌握Spring Boot多套配置与@Profile注解的高级技巧
34 2
环境切换大法:掌握Spring Boot多套配置与@Profile注解的高级技巧
|
21天前
|
XML Java 数据格式
进阶注解探秘:深入Spring高级注解的精髓与实际运用
进阶注解探秘:深入Spring高级注解的精髓与实际运用
26 2