Make writing a habit together! This is the sixth day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

The preface.

The Bean scans that Spring and MyBatis use in the previous chapter are provided by Spring itself. This article looks at how Spring implements Bean scanning. I have to say that Bean scanning is a very important technology, Controller scanning in SpringMVC, and Bean scanning, Component scanning, Configuration scanning in SpringBoot, the principle I guess is implemented by this.

Environmental construction.

Because the conditions for creating a packet scan are simple, you simply configure an attribute in the Xml.

Here we go.

Read in my previous articles, we directly here to save time, directly positioning to ComponentScanBaeanDefinitionParser parse method in the class.

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
   String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
   basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
   String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
         ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

   // Actually scan for bean definitions and register them.
   // Actually, scan the bean definitions and register them.
   ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
   Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
   registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

   return null;
}
Copy the code

The first half of this code is relatively simple. It is possible that there are multiple basepackages currently passed in, so methods are used to handle the string. The more important code is in the second half. So there are three ways.

  1. configureScanner
  • Configure a scanner
  1. doScan
  • Use a scanner to scan
  1. registerComponents
  • Register scanned BeanDefintion

configureScanner

First code

boolean useDefaultFilters = true;
if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
   useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
}
Copy the code

This section declares a variable, which defaults to True and is modified in the If section below. Since we did not set this property in applicatio.xml, this is the default.

Second code

// Delegate bean definition registration to scanner class.
// Delegate the bean definition registration to the scanner class.
ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
Copy the code

CreateScanner method, is the new of a ClassPathBeanDefinitionScanner object to return back. Two more attributes are then added to the scanner.

The third code

if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
   scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
}
Copy the code

Check whether the ResourcePattern attribute is configured. If yes, set it.

The fourth code

try {
   parseBeanNameGenerator(element, scanner);
}
catch (Exception ex) {
    // ...
}

try {
   parseScope(element, scanner);
}
catch (Exception ex) {
    // ...
}
Copy the code

These two methods have something in common with the code. Check to see if a property is configured, and then set the property for Sanner. See the screenshot below.

Here these two methods are what, I thought about it, do not know, do not know where will be used, so here continue to look.

parseTypeFilters

protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
   // Parse exclude and include filter elements.
   ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
   NodeList nodeList = element.getChildNodes();
   for (int i = 0; i < nodeList.getLength(); i++) {
      Node node = nodeList.item(i);
      if (node.getNodeType() == Node.ELEMENT_NODE) {
         String localName = parserContext.getDelegate().getLocalName(node);
         try {
            if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
               TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
               scanner.addIncludeFilter(typeFilter);
            }
            else if(EXCLUDE_FILTER_ELEMENT.equals(localName)) { TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext); scanner.addExcludeFilter(typeFilter); }}catch (ClassNotFoundException ex) {
            // ...
         }
         catch (Exception ex) {
            // ...}}}}Copy the code

So let’s first look at this method called parseTypeFilters, conversion type type filters.

By looking at Spring’s DTD file, you can see that there are two more child tags under the Component-scan tag, presumably as explained in the code above.

The scanner object is then returned after the method is run.

doScan

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
}
Copy the code

In the first line, we create a BeanDefinitions Set, presumably to store the results.

Then, all the candidate Beandefinitions are found based on basePacage, and the method of obtaining them is described below.

We then iterate through the BeanDefinition we just got.

findCandidateComponents

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);
      // ... 
      for (Resource resource : resources) {
         / /... Temporarily don't see}}catch (IOException ex) {
      // ... throw
   }
   return candidates;
}
Copy the code

In the above code, as soon as the method enters, it creates a set. This set will be the value returned by the method at the end. Subsequent code will append properties to this set.

Then comes the packageSearchPath, where the variable is finally obtained by concatenating strings as follows:

Classpath * : + the transformed XML path + / *. * * class classpath * : org/springframework/study / / * * *. The class

Then, according to resourceLoader, you can load all the class files in the upper path.

Then enter the For traversal.

For traverses each resource

The current resource resource is the class file read.

for (Resource resource: resources) {
    try {
        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
        if (isCandidateComponent(metadataReader)) {
           ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
           sbd.setSource(resource);
           if (isCandidateComponent(sbd)) {
              / /... Print log
              candidates.add(sbd);
           }
           else {
              / /... Print log}}else {
           / /... Print log}}catch (FileNotFoundException ex) {
    / /... Print log
    }
    catch (Throwable ex) {
    // ... throw}}Copy the code

After entering For, first get the metadataReader. Here’s a quick code chase. There are two main things that you do, one new and one SimpleMetaDataReader. And then put the MetaDataReader in the cache. The Reader object is then returned.

Then I entered the first key method code, the isCandidateComponent method. Take a closer look at how this method is called twice, because the if also calls the isCandidateComponent method. Then I looked at the input parameter. One alderman Reader, one alderman BeanDefinition. Our first if midpoint uses the isCandidateComponent method for Reader.

isCandidateComponent(MetadataReader metadataReader)
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())) {
         returnisConditionMatch(metadataReader); }}return false;
}
Copy the code

We don’t have to look at the excludeFilters at the top, we just look at the include at the bottom

I’m not going to read the rest of the code, but I guess I’m going to use Reader to read the annotations on the class and see if there are any annotations that are currently set in filter. Return true if yes.

Continue with the logic
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
   if (debugEnabled) {
      logger.debug("Identified candidate component class: " + resource);
   }
   candidates.add(sbd);
}
Copy the code

Just after the outer If is True, this will create a ScannedGenericBeanDefinition, since be BeanDefinition, that can be Spring loaded.

I then put the created BeanDefinition into the isCandidateComponent method.

isCandidateComponent(AnnotatedBeanDefintion)
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
   AnnotationMetadata metadata = beanDefinition.getMetadata();
   return (metadata.isIndependent() && (metadata.isConcrete() ||
         (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

@Override
public boolean isIndependent(a) {
   / / enclosingClassName is null
   return (this.enclosingClassName == null || this.independentInnerClass);
}

default boolean isConcrete(a) {
   return! (isInterface() || isAbstract()); }Copy the code

This method basically returns the first judgment. At the beginning of isIndependent method, I was confused about two words, enclosingClass and innerClass, maybe because my English is not good or the basic is not good, I found it after baidu search. I’m not going to talk about it here, but if you’re interested you can do a search. Their search memories are stronger. This is True if it is a normal Component.

In the case of isConcrete, the method determines whether the current class is an interface or an abstract class. It is obvious that if it is a normal Component, it is false and then reversed to True.

Continue with the logic
if (isCandidateComponent(sbd)) {
   if (debugEnabled) {
      logger.debug("Identified candidate component class: " + resource);
   }
   candidates.add(sbd);
}
Copy the code

Return True when you pass in a BeanDefinition, go to If, add the current BeanDefinition to the result set, return the result set.

DoScan continues with the logic

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
   ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
   candidate.setScope(scopeMetadata.getScopeName());
   String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
   if (candidate instanceof AbstractBeanDefinition) {
      postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
   }
   if (candidate instanceof AnnotatedBeanDefinition) {
      AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
   }
   if (checkCandidate(beanName, candidate)) {
      BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
      definitionHolder =
            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
      beanDefinitions.add(definitionHolder);
      registerBeanDefinition(definitionHolder, this.registry); }}Copy the code

Take the BeanDefinition that you just got and walk through it.

The first step is to get the MetaData, which was written in the previous code. It then assigns its ScopeName to MetaData.

The next two if’s set some parameters to this BeanDefinition. You can take a quick look. Capture some key information.I’m going to set a property inside of this one, so I’m going to write it down for you to see later.Inside this is some other annotations that we added to the class to add some configuration to the BeanDefinition. I see some familiar annotations, Lazy, Primary, and Description.

DoScan last IF

if (checkCandidate(beanName, candidate)) {
   BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
   definitionHolder =
         AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
   beanDefinitions.add(definitionHolder);
   registerBeanDefinition(definitionHolder, this.registry);
}
Copy the code

At a glance, there are a couple of important things to look at here, one of which is the condition for entering an If, registering a BeanDefinition. As for the applyScopedProxyMode method, there is no Scope annotation on the class I don’t have, so there is no proxy configuration here. That is, just return the BeanDefinition that’s currently passed in.

CheckCandidate method

protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
   if (!this.registry.containsBeanDefinition(beanName)) {
      return true;
   }
   BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
   BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
   if(originatingDef ! =null) {
      existingDef = originatingDef;
   }
   if (isCompatible(beanDefinition, existingDef)) {
      return false;
   }
   // ... throw Exception.
}
Copy the code

BeanDefinitionRegister does not have BeanDefinition because it is entered by Bean scanning. So I’m just going to return True, and I’m not going to go down here. And at this point you can think about what happens if you go down here. Welcome to the comments section.

Continue code logic

beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
Copy the code

I’m going to go to the registerBeanDefinition, and then I’m going to pass Registry into the method, so that’s pretty obvious. So here’s registering a BeanDefinition.

Come to an end

So I’m done here. Because in this link, the scanner put BeanDefinition in the Registry, so in the Refresh method after finishBeanFactoryInitialization method will be finished all BeanDefinition instantiation.

See this, give it a thumbs up before you go, Bao ~

conclusion

The purpose of writing an article is to help you consolidate your knowledge. You can point out your bad or wrong writing in the comments section. If you read the article and think it is helpful to you, you can like it. If you find some questions and have doubts, or do not understand, you can comment and add my wechat. Of course, I also hope to make friends with you and learn from each other.