How Springboot starts scanning packages

Reference Link 1
Reference Link 2
The code referenced is Springboot 2.1.1

By default, the scan range is the package and its subdirectories of the main class xxApplication, which you can see in the later implementation.

From SpringApplication.run(xxxApplication.class, args) in the main class; click all the way into the implementation of the run method, and you can see that there are several methods in the run method for context:

  • createApplicationContext()

  • prepareContext(xxx,xx)

  • refreshContext(context)

public ConfigurableApplicationContext run(String... args) {
        ...
        try {
            ...
            context = createApplicationContext();
            ...
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            ....
        catch (Throwable ex) {
        
        }

Analyze one by one:

1.createApplicationContext()

This method returns a context of type AnnotationConfigServletWebServerApplicationContext, which you can click in to see is of type AnnotationConfigServletWebServerApplicationContext.

protected ConfigurableApplicationContext createApplicationContext() {
        ...
                case SERVLET:
                    contextClass = Class.forName(**DEFAULT_SERVLET_WEB_CONTEXT_CLASS**);
                    break;
                case ...
                }
            }
            ....
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

In a parent GenericApplicationContext of this class, a beanFactory is created that implements the BeanDefinitionRegistry interface, which you will use later.

public GenericApplicationContext() {
   this.beanFactory = new DefaultListableBeanFactory();
}

The constructor of the AnnotationConfigServletWebServerApplicationContext class was called in the statement returned by createApplicationContext.

public AnnotationConfigServletWebServerApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

Enter the first AnnotatedBeanDefinitionReader in the constructor, all the way to registerAnnotationConfigProcessors:

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
      BeanDefinitionRegistry registry, @Nullable Object source) {
   //Created a beanFactory
   DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
   if (beanFactory != null) {
      .....
   }

   Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

   if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
      def.setSource(source);
       //A beanDefinition of type CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME has been added, and as you can see in the following code, its name is internalConfiguration Annotation Processor (Configuration ClassPostProcessor.class) BeanDefinition of type is used later
      beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
   }

   ....
   }
public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME =
      "org.springframework.context.annotation.internalConfigurationAnnotationProcessor";

Next, you enter the new ClassPathBeanDefinitionScanner() in the constructor.

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
      Environment environment, @Nullable ResourceLoader resourceLoader) {
    ....
    //Default useDefaultFilters at entry is true
   if (useDefaultFilters) {
      registerDefaultFilters();
   }
   setEnvironment(environment);
   setResourceLoader(resourceLoader);
}

Enter the registerDefaultFilters method,

protected void registerDefaultFilters() {
    //Component.class is added here as the default includeFilter
   this.includeFilters.add(new AnnotationTypeFilter(Component.class));
   ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
   try {
      ......
}

2.prepareContext()

There is a load function in the prepareContext() method, which enters the specific implementation of the load function.

private int load(Object source) {
   Assert.notNull(source, "Source must not be null");
   if (source instanceof Class<?>) {
      return load((Class<?>) source);
   }
   if (source instanceof Resource) {
      return load((Resource) source);
   }
   if (source instanceof Package) {
      ....
}

Since the run method of the main class passes in xxxxApplication.class, this is the first branch

private int load(Class<?> source) {
   .....
   //The isComponent determines if there are any comments for the Component and the startup class is, so here the startup class is loaded
   if (isComponent(source)) {
      this.annotatedReader.register(source);
      return 1;
   }
   return 0;
}

3.refreshContext()

Click on the implementation of refresh to see a method for invokeBeanFactoryPostProcessors

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         //Registered bean s are written here
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         ....

Enter its implementation, where the beanFactory passed in is the beanFactory created by the first method in 1.createApplicationContext()

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
   PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

   .....
}

Enter this method, because the beanFactory created in the createApplicationContext implements the BeanDefinitionRegistry interface, so let's go into this branch and have a look.

public static void invokeBeanFactoryPostProcessors(
      ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

   Set<String> processedBeans = new HashSet<>();
    //BeanDefinitionRegistry
   if (beanFactory instanceof BeanDefinitionRegistry) {
      .....
      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
      //BeanDefinitionRegistryPostProcessor
         if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
            BeanDefinitionRegistryPostProcessor registryProcessor =
                  (BeanDefinitionRegistryPostProcessor) postProcessor;
            registryProcessor.postProcessBeanDefinitionRegistry(registry);
            .....
      }

Or 1.The Configuration ClassPostProcessor class created in createApplicationContext () just implements BeanDefinitionRegistryPostProcessor, so here we look directly at the postProcessBeanDefinitionRegistry method that ConfigurationClassPostProcessor has accumulated, and you can see that the processConfigBeanDefinitions method is called here.

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   ....

   processConfigBeanDefinitions(registry);
}

Enter this method:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   .....

   Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
   Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
   do {
      parser.parse(candidates);
      parser.validate();

      .....
      }

Enter parse(candidates), where a Holder is generated for ConfigurationClassPostProcessor when it was created earlier, and the type of beanDefinition is AnnotatedBeanDefinition, so the parse method that takes this branch

public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
          //This branch
         if (bd instanceof AnnotatedBeanDefinition) {
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         }
         else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
           ....
}

Go all the way to the doProcessConfigurationClass method and find that this is where the ComponentScan parses the bean object

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
    .....

   // Process any @ComponentScan annotations
   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) {
         // Enter this method
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
         
      }
   }

At the end of this parse method, you can see the judgment of basePackages.isEmpty(), which is currently empty because the @ComponentScan attribute is not configured. This adds the path before the last point of the name of the startup class to basePackages, such as A.B.C.D., where the startup class is A.B.C.D., and the package name it is in.

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
   ....
   if (basePackages.isEmpty()) {
      basePackages.add(ClassUtils.getPackageName(declaringClass));
   }

   scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
      @Override
      protected boolean matchClassName(String className) {
         return declaringClass.equals(className);
      }
   });
   return scanner.doScan(StringUtils.toStringArray(basePackages));
}

Finally, enter the doScan method, where there is a findCandidateComponents method, and the bean s that get these candidates are registered in the Spring container.

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   ....
   for (String basePackage : basePackages) {
       //This method
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         ....
   return beanDefinitions;
}

Finally, you enter the scanCandidateComponents method, where Spring iterates through each of the following classes through the path passed in. The class of this method is ClassPathScanning CandidateComponentProvider, but the method above all basePackage s (ClassPathBeanDefinitionScanner of its class) breakpoints.

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
      boolean traceEnabled = logger.isTraceEnabled();
      boolean debugEnabled = logger.isDebugEnabled();
      for (Resource resource : resources) {
         if (traceEnabled) {
            logger.trace("Scanning " + resource);
         }
         if (resource.isReadable()) {
            try {
               MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                //This determines if it is a Component.class
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     .....
         }

Since the default includeFilters mentioned above include Componet.class, classes that add Component annotations and their subannotations are added to the Spring container in the same directory as the startup class.

/**
     * Determine whether the given class does not match any exclude filter
     * and does match at least one include filter.
     * @param metadataReader the ASM ClassReader for the class
     * @return whether the class qualifies as a candidate component
     */
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return false;
      }
   }
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return isConditionMatch(metadataReader);
      }
   }
   return false;
}

conclusion

The final implementation of the ClassPath component scan is the scanCandidateComponents method under the ClassPathScanning CandidateComponentProvider class to see that all scanned packages can be viewed at breakpoints by calling the doScan(String... basePackages) method of the ClassPathBeanDefinitionScanner class at its parent.



Author: WAHAHA402
Link: https://www.jianshu.com/p/260cca0ec712
Source: Short Book
Copyright belongs to the author.For commercial reprinting, please contact the author for authorization. For non-commercial reprinting, please indicate the source.

Keywords: Programming Spring SpringBoot Attribute

Added by rndilger on Tue, 17 Mar 2020 18:45:09 +0200