Основы SpringBoot2.x: познакомим вас с пакетом сканирования bean-компонентов автоматической регистрации

Spring Boot

Знания меняют судьбу, программирование делает меня счастливым, и я продолжу шагать в мире открытого исходного кода в 2020 году.

Ставьте лайк и смотрите снова, формируйте привычку

дай мне звезду,Нажмите, чтобы узнать о решении для посадки службы компонентного интерфейса на основе SpringBoot.

мы использовалиSpringBootдля разработки приложений, но почему аннотации автоматически регистрируются при старте проекта@Component,@Service,@RestController... помеченыBeanШерстяная ткань?

Рекомендуемое чтение

Каталог сканирования по умолчанию

SpringBootпоместите начальный класс, гдеPackageВ качестве каталога сканирования по умолчанию это также является ограничением, если мы поместим необходимость регистрации вIOCКласс, созданный в каталоге сканирования, может быть зарегистрирован автоматически, иначе он не будет зарегистрирован.

Если ваш начальный класс называетсяExampleApplication,Это расположеноorg.minbox.chapterкаталог, он будет автоматически сканироваться, когда мы запускаем приложениеorg.minbox.chapterодноуровневый каталог,подкаталогВсе аннотированные классы выглядят следующим образом:

. src/main/java
├── org.minbox.chapter
│   ├── ExampleApplication.java
│   ├── HelloController.java
│   ├── HelloExample.java
│   └── index
│   │   └── IndexController.java
├── com.hengboy
│   ├── TestController.java
└──

HelloController.java,HelloExample.javaс начальным классомExampleApplication.javaВ каталоге того же уровня, поэтому при запуске проектаможно сканировать в.

IndexController.javaОн находится в подкаталоге начального классаorg.minbox.chapter.index, так как он поддерживает сканирование каталогов нижнего уровня, он такжеможно сканировать в.

TestController.javaродыcom.hengboyПод содержанием,Невозможно сканировать по умолчанию.

Пользовательский каталог сканирования

Расположен в вышеуказанной структуре каталоговcom.hengboyв каталогеTestController.javaкласс, который по умолчанию не может быть просканирован и зарегистрирован вIOCВ контейнере, если вы хотите сканировать классы в этом каталоге, есть два метода ниже.

Способ 1: аннотировать с помощью @ComponentScan

@ComponentScan({"org.minbox.chapter", "com.hengboy"})

Способ 2. Используйте свойство scanBasePackages.

@SpringBootApplication(scanBasePackages = {"org.minbox.chapter", "com.hengboy"})

Примечание. После настройки пользовательского каталога сканированияпереопределит каталог сканирования по умолчанию, если вам также необходимо сканировать каталог по умолчанию, вам необходимо настроить каталог сканирования.В приведенной выше пользовательской конфигурации, если вы настраиваете только сканированиеcom.hengboyкаталог, затемorg.minbox.chapterКаталог не будет сканироваться.

отследить исходный код

Давайте взглянемSpringBootКак исходный код реализует автоматическое сканирование по каталогуBean, и воляBeanПроцесс регистрации в контейнере.

Поскольку процесс регистрации относительно сложен, для объяснения выбраны репрезентативные этапы процесса.

GetBasePackages

существуетorg.springframework.context.annotation.ComponentScanAnnotationParser#parseполучить методbasePackagesБизнес-логика исходного кода выглядит следующим образом:

Set<String> basePackages = new LinkedHashSet<>();
// 获取@ComponentScan注解配置的basePackages属性值
String[] basePackagesArray = componentScan.getStringArray("basePackages");
// 将basePackages属性值加入Set集合内
for (String pkg : basePackagesArray) {
  String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                                                         ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  Collections.addAll(basePackages, tokenized);
}
// 获取@ComponentScan注解的basePackageClasses属性值
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
  // 获取basePackageClasses所在的package并加入Set集合内
  basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果并没有配置@ComponentScan的basePackages、basePackageClasses属性值
if (basePackages.isEmpty()) {
  // 使用Application入口类的package作为basePackage
  basePackages.add(ClassUtils.getPackageName(declaringClass));
}

ПолучатьbasePackagesОн делится на три этапа, а именно:

  1. Получать@ComponentScanаннотацияbasePackagesзначение атрибута
  2. Получать@ComponentScanаннотацияbasePackageClassesзначение атрибута
  3. БудуApplicationгде находится начальный классpackageпо умолчаниюbasePackages

Примечание. Согласно исходному коду подтверждается, почему мы настроилиbasePackages,basePackageClassesПосле этого будет перезаписано значение по умолчанию.На самом деле здесь не перезапись.Он не будет получен вообще.Applicationначальный классpackage.

Сканировать Bean-компоненты в пакетах

получить всеPackagesПосле этого черезorg.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScanспособ сканирования каждогоPackageИспользуя регистрационную аннотацию под (@Component,@Service,@RestController...) помеченный класс, исходный код выглядит следующим образом:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // 当basePackages为空时抛出IllegalArgumentException异常
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  // 遍历每一个basePackage,扫描package下的全部Bean
  for (String basePackage : basePackages) {
    // 获取扫描到的全部Bean
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    // 遍历每一个Bean进行处理注册相关事宜
    for (BeanDefinition candidate : candidates) {
      // 获取作用域的元数据
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      // 获取Bean的Name
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      // 如果是注解方式注册的Bean
      if (candidate instanceof AnnotatedBeanDefinition) {
        // 处理Bean上的注解属性,相应的设置到BeanDefinition(AnnotatedBeanDefinition)类内字段
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      // 检查是否满足注册的条件
      if (checkCandidate(beanName, candidate)) {
        // 声明Bean具备的基本属性
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        // 应用作用域代理模式
        definitionHolder =
          AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        // 写入返回的集合
        beanDefinitions.add(definitionHolder);
        // 注册Bean
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

В приведенном выше исходном коде будет сканироваться каждыйbasePackageопределяется аннотациямиBean,ПолучатьBeanПосле регистрации объекта определения и установки некоторых основных свойств.

Зарегистрировать Бин

сканировать вbasePackageвнизBeanнепосредственно черезorg.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinitionспособ регистрации, исходный код выглядит следующим образом:

public static void registerBeanDefinition(
  BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
  throws BeanDefinitionStoreException {

  // 注册Bean的唯一名称
  String beanName = definitionHolder.getBeanName();
  // 通过BeanDefinitionRegistry注册器进行注册Bean
  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  // 如果存在别名,进行注册Bean的别名
  String[] aliases = definitionHolder.getAliases();
  if (aliases != null) {
    for (String alias : aliases) {
      registry.registerAlias(beanName, alias);
    }
  }
}

пройти черезorg.springframework.beans.factory.support.BeanDefinitionRegistry#registerBeanDefinitionМетоды в регистраторе могут напрямуюBeanзарегистрироваться наIOCвнутри контейнера иBeanNameЭто уникальное имя при жизни.

Суммировать

Благодаря объяснению этой статьи, я думаю, вы уже должны понятьSpringBootПочему приложение автоматически сканирует при запускеpackageи воляBeanзарегистрироваться наIOCВ контейнере, хотя время запуска проекта очень короткое, это очень сложный процесс, и вы можете использовать его в процессе обучения.Debugрежим, чтобы увидеть логическую обработку каждого шага.


автор личноблог

Используйте фреймворк с открытым исходным кодомApiBootПомочь вам стать архитектором сервисов интерфейса API