相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解node
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下git
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?正則表達式
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanspring
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的express
曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中獲得了什麼(上)json
曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中獲得了什麼(util命名空間)mybatis
曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中獲得了什麼(context命名空間上)app
曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中獲得了什麼(context:annotation-config 解析)
曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(此次來講說它的奇技淫巧)
工程結構圖:
本篇已是spring源碼第12篇,前一篇講了context:component-scan這個元素的用法,其中涉及到了各個屬性的做用。本節呢,主要就是講解該元素的解析流程,其中就會涉及到各個屬性是怎麼發揮做用的。
原本吧,這裏畫時序圖比較好,可是uml圖一直是半桶水,visio這臺電腦也沒裝,就隨便花了下流程圖,將就看吧。
這個就是在contextnamespacehandler裏註冊的,component-scan對應的beanDefinitionParser實現。
這個類呢,也是至關簡潔明瞭,沒有亂七八糟的類結構。
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
這個方法,最終返回了一個ClassPathBeanDefinitionScanner
,這個類的結構以下:
幾個接口都沒實質性內容,主要是繼承了一個父類,我整理了一下,父類裏,大概有以下字段:
// 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
:
// 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; }
其中,有兩個點值得細說:
默認的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)); } }
解析自定義的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查看):
若是你們有點忘了,能夠回到最前面看下以前的圖,這是主線的最後一個環節。
咱們直接上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的探索之旅就這麼結束了。歡迎你們留言,以爲有幫助的話,請關注我,後續會輸出更多內容。