1. 程式人生 > >曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)

曹工說Spring Boot原始碼(12)-- Spring解析xml檔案,到底從中得到了什麼(context:component-scan完整解析)

寫在前面的話

相關背景及資源:

曹工說Spring Boot原始碼(1)-- Bean Definition到底是什麼,附spring思維導圖分享

曹工說Spring Boot原始碼(2)-- Bean Definition到底是什麼,咱們對著介面,逐個方法講解

曹工說Spring Boot原始碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,我們來試一下

曹工說Spring Boot原始碼(4)-- 我是怎麼自定義ApplicationContext,從json檔案讀取bean definition的?

曹工說Spring Boot原始碼(5)-- 怎麼從properties檔案讀取bean

曹工說Spring Boot原始碼(6)-- Spring怎麼從xml檔案裡解析bean的

曹工說Spring Boot原始碼(7)-- Spring解析xml檔案,到底從中得到了什麼(上)

曹工說Spring Boot原始碼(8)-- Spring解析xml檔案,到底從中得到了什麼(util名稱空間)

曹工說Spring Boot原始碼(9)-- Spring解析xml檔案,到底從中得到了什麼(context名稱空間上)

曹工說Spring Boot原始碼(10)-- Spring解析xml檔案,到底從中得到了什麼(context:annotation-config 解析)

曹工說Spring Boot原始碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)

工程程式碼地址 思維導圖地址

工程結構圖:

概要

本篇已經是spring原始碼第12篇,前一篇講了context:component-scan這個元素的用法,其中涉及到了各個屬性的作用。本節呢,主要就是講解該元素的解析流程,其中就會涉及到各個屬性是怎麼發揮作用的。

大體流程

本來吧,這裡畫時序圖比較好,但是uml圖一直是半桶水,visio這臺電腦也沒裝,就隨便花了下流程圖,將就看吧。

ComponentScanBeanDefinitionParser.parse

這個就是在contextnamespacehandler裡註冊的,component-scan對應的beanDefinitionParser實現。

這個類呢,也是相當簡潔明瞭,沒有亂七八糟的類結構。

public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {

configureScanner

類圖

這個方法,最終返回了一個ClassPathBeanDefinitionScanner,這個類的結構如下:

父類ClassPathScanningCandidateComponentProvider中的欄位

幾個介面都沒實質性內容,主要是繼承了一個父類,我整理了一下,父類裡,大概有如下欄位:

// ClassPathScanningCandidateComponentProvider 中的fields
// 指定包下,檔案很多,可能不止有class,還有xml,比如mybatis的mapper等;這裡指定要獲取的資源的pattern
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

// 沒啥說的,環境
private Environment environment;

// 因為使用者可以自己指定resource_pattern, (不喜歡前面那個**/*.class),這個field負責來解析使用者的resouce_pattern
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

// 這個很重要,你給它一個class,它負責給你返回一個本class的元資料reader,通過元資料reader,你能取到class上的註解等資訊
private MetadataReaderFactory metadataReaderFactory =
      new CachingMetadataReaderFactory(this.resourcePatternResolver);

private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
// 通過這個來劃定,是不是我的小弟
private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();

// 通過這個來劃定,不是我的小弟,這個裡面的都是拉黑了的
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();

這裡說下MetadataReaderFactory,因為吧,以後可能會頻繁出現。

這是個介面,在spring-core包裡,資訊如下:

MetadataReader 的工廠介面,呼叫這個介面的方法,能得到一個MetadataReader
/**
 * Factory interface for {@link MetadataReader} instances.
 * Allows for caching a MetadataReader per original resource.
 *
 */
public interface MetadataReaderFactory {

   /**
    * 根據一個類名,獲取MetadataReader;這個reader,可以幫你獲取class的class/註解等資訊
    * 
    * Obtain a MetadataReader for the given class name.
    * @param className the class name (to be resolved to a ".class" file)
    * @return a holder for the ClassReader instance (never {@code null})
    * @throws IOException in case of I/O failure
    */
   MetadataReader getMetadataReader(String className) throws IOException;

   /**
    * Obtain a MetadataReader for the given resource.
    * @param resource the resource (pointing to a ".class" file)
    * @return a holder for the ClassReader instance (never {@code null})
    * @throws IOException in case of I/O failure
    */
   MetadataReader getMetadataReader(Resource resource) throws IOException;

}

MetadataReader這個介面,也是以後的重點,這裡概覽一下:

/**
 * 通過asm,獲取類的元資料,包括註解資料
 * Simple facade for accessing class metadata,
 * as read by an ASM {@link org.springframework.asm.ClassReader}.
 *
 * @author Juergen Hoeller
 * @since 2.5
 */
public interface MetadataReader {

   /**
    * Return the resource reference for the class file.
    */
   Resource getResource();

   /**
    * 獲取Class的相關資訊
    * Read basic class metadata for the underlying class.
    */
   ClassMetadata getClassMetadata();

   /**
    * 這個就叼了,獲取Class上的註解資訊
    * Read full annotation metadata for the underlying class,
    * including metadata for annotated methods.
    */
   AnnotationMetadata getAnnotationMetadata();

}

有人可能覺得沒啥用,通過java反射也能獲取;但這裡的和java反射的方式不衝突,這個是通過asm框架來獲取,效率會更高(效率比反射低的話,spring團隊為啥不用反射呢,對吧?)

回到前面的metadataReader的factory介面,其實現類就兩個,我們這次分析的原始碼,用了CachingMetadataReaderFactory

子類ClassPathBeanDefinitionScanner中的欄位

// beanDefinition註冊中心,拿到beanDefinition後就往這裡面放
private final BeanDefinitionRegistry registry;
// 預設的beanDefinition配置,和xml裡<beans>元素裡的屬性是對應的
private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
// 自動注入時,候選bean需要滿足的pattern
private String[] autowireCandidatePatterns;
// beanName 生成器
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
// 不是很瞭解,skip
private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
// 要不要啟用 context:annotation-config元素的作用;具體可看本博文往前的兩篇
private boolean includeAnnotationConfig = true;

其實,把前面父類,和現在這個子類的欄位,合起來看,也就那麼回事吧,主要是些配置資料,把xml裡使用者的配置給存起來了。

具體配置過程解析

org.springframework.context.annotation.ComponentScanBeanDefinitionParser#configureScanner

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
   XmlReaderContext readerContext = parserContext.getReaderContext();
   // 是否使用預設的filter,預設filter,只解析component等官方註解
   boolean useDefaultFilters = true;
   if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
      useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
   }

   // 建立scanner
   ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
   // 設定預設的東西
   scanner.setResourceLoader(readerContext.getResourceLoader());
   scanner.setEnvironment(parserContext.getDelegate().getEnvironment());
   // 設定預設的東西,包括了beanDefinition的預設屬性,這個是可以從外邊傳進來的
   scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
  //這個也是外邊來的,xml裡沒這個屬性
  scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
   
   if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
      // 這個是從xml元素來的
      scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
   }

   try {
      // 這個也是xml屬性來的
      parseBeanNameGenerator(element, scanner);
   }
   catch (Exception ex) {
      readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
   }

   try {
      // 這個也是xml屬性來的
      parseScope(element, scanner);
   }
   catch (Exception ex) {
      readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
   }
   
   // 這個也是xml屬性來的,主要是解析include/exclude filter
   parseTypeFilters(element, scanner, readerContext, parserContext);

   return scanner;
}

其中,有兩個點值得細說:

  1. 預設的filter

    ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
    
    一路簡單跳轉後,進入到:
    
    public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
        if (useDefaultFilters) {
            // 註冊預設的filter
            registerDefaultFilters();
        }
        this.environment = environment;
    }
    
    // 註冊3個註解型別的fitler,分別對應了Component/javax.annotation.ManagedBean/javax.inject.Named 這幾個註解
    protected void registerDefaultFilters() {
         /**
          * 預設掃描Component註解
          */
         this.includeFilters.add(new AnnotationTypeFilter(Component.class));
         ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
         try {
             this.includeFilters.add(new AnnotationTypeFilter(
                     ((Class<? extends Annotation>) cl.loadClass("javax.annotation.ManagedBean")), false));
         }
         try {
             this.includeFilters.add(new AnnotationTypeFilter(
                     ((Class<? extends Annotation>) cl.loadClass("javax.inject.Named")), false));
         }
     }
  2. 解析自定義的filter

    protected void parseTypeFilters(
          Element element, ClassPathBeanDefinitionScanner scanner, XmlReaderContext readerContext, ParserContext parserContext) {
    
       // Parse exclude and include filter elements.
       ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
       // 因為include-filter和exclude-filter是以子元素方式配置的,不是屬性來配置的;所以獲取子節點並便利   
       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);
              // 如果是include型別...
              if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
                  // 建立typefilter
                  TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
                  scanner.addIncludeFilter(typeFilter);
              }
              else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
                  TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
                  scanner.addExcludeFilter(typeFilter);
              }
             }
          }
       }
    }
    @SuppressWarnings("unchecked")
    protected TypeFilter createTypeFilter(Element element, ClassLoader classLoader) {
       String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
       String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
        // filter 一共5種類型,所以下面在各種if判斷
        if ("annotation".equals(filterType)) {
            return new AnnotationTypeFilter((Class<Annotation>) classLoader.loadClass(expression));
        }
        else if ("assignable".equals(filterType)) {
            return new AssignableTypeFilter(classLoader.loadClass(expression));
        }
        else if ("aspectj".equals(filterType)) {
            return new AspectJTypeFilter(expression, classLoader);
        }
        else if ("regex".equals(filterType)) {
            return new RegexPatternTypeFilter(Pattern.compile(expression));
        }
        else if ("custom".equals(filterType)) {
            Class filterClass = classLoader.loadClass(expression);
            if (!TypeFilter.class.isAssignableFrom(filterClass)) {
                throw new IllegalArgumentException(
                    "Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
            }
            return (TypeFilter) BeanUtils.instantiateClass(filterClass);
        }
        else {
            throw new IllegalArgumentException("Unsupported filter type: " + filterType);
        }
    
    }

    表格總結一下,就是:

    filter-type 對應型別的class 說明 我的理解
    annotation AnnotationTypeFilter "annotation" indicates an annotation to be present at the type level in target components; 匹配指定型別的註解
    assignable AssignableTypeFilter "assignable" indicates a class (or interface) that the target components are assignable to (extend/implement); 判斷一個class是不是這裡指定的型別或其子類
    aspectj AspectJTypeFilter "aspectj" indicates an AspectJ type expression to be matched by the target components; 需要滿足aspectj表示式,類似於指定切點那種
    regex RegexPatternTypeFilter "regex" indicates a regex expression to be matched by the target components' class names; 需要滿足正則表示式
    custom 由xml元素裡指定型別 "custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface. 自定義實現TypeFilter介面

    這裡的typefilter介面,介面如下,主要就是,傳給你一個class的元資料,你判斷是否留下,留下就返回true:

    public interface TypeFilter {
    
       /**
        * Determine whether this filter matches for the class described by
        * the given metadata.
        * @param metadataReader the metadata reader for the target class
        * @param metadataReaderFactory a factory for obtaining metadata readers
        * for other classes (such as superclasses and interfaces)
        * @return whether this filter matches
        * @throws IOException in case of I/O failure when reading metadata
        */
       boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
             throws IOException;
    
    }

    有如下實現(圖小,可單獨tab檢視):

doScan-具體掃描beanDefinition執行者

如果大家有點忘了,可以回到最前面看下之前的圖,這是主線的最後一個環節。

我們直接上code:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
   for (String basePackage : basePackages) {
      /**
       * 1:基於前面的include/exclude filter等,篩選出滿足條件的beanDefinition集合
       * 但這時候的beanDefinition還不是完整的,還有些屬性沒設定
       */
      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);
         // 一些處理,根據autowireCandidatePatterns field,判斷當前bean是否夠格,作為自動注入的候選者
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         // 呼叫setPrimary/setLazyInit/setDependsOn/setTole來設定beanDefiniiton屬性
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         // 這裡,註冊到beanDefinitionRegistry
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            // 註冊到beanDefinitionRegistry
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

大體流程,就這樣結束了。

裡面有意思的細節,主要是,查詢指定包下,滿足條件的beanDefiniiton這塊。

/**
 * Scan the class path for candidate components.
 * @param basePackage the package to check for annotated classes
 * @return a corresponding Set of autodetected bean definitions
 */
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + "/" + this.resourcePattern;
      Resource[] resources = this.resourcePatternResolver.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 = this.metadataReaderFactory.getMetadataReader(resource);
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     if (debugEnabled) {
                        logger.debug("Identified candidate component class: " + resource);
                     }
                     candidates.add(sbd);
                  }
                  else {
                     if (debugEnabled) {
                        logger.debug("Ignored because not a concrete top-level class: " + resource);
                     }
                  }
               }
               else {
                  if (traceEnabled) {
                     logger.trace("Ignored because not matching any filter: " + resource);
                  }
               }
            }
            catch (Throwable ex) {
               throw new BeanDefinitionStoreException(
                     "Failed to read candidate component class: " + resource, ex);
            }
         }
         else {
            if (traceEnabled) {
               logger.trace("Ignored because not readable: " + resource);
            }
         }
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }
   return candidates;
}

注意哈,這裡獲取的beanDefinition的型別是什麼?

ScannedGenericBeanDefinition

學了這麼多講,是時候回頭看看曾經走過的路了:

根據include/exclude filter來判斷的過程也很有意思:

/**
 * 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, this.metadataReaderFactory)) {
         return false;
      }
   }
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
         if (!metadata.isAnnotated(Profile.class.getName())) {
            return true;
         }
         AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
         return this.environment.acceptsProfiles(profile.getStringArray("value"));
      }
   }
   return false;
}

總結

component-scan的探索之旅就這麼結束了。歡迎大家留言,覺得有幫助的話,請關注我,後續會輸出更多內容