曹工说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实现。

这个类呢,也是相当简洁明了,没有乱七八糟的类结构。

Copy
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {

configureScanner#

类图#

这个方法,最终返回了一个ClassPathBeanDefinitionScanner,这个类的结构如下:

父类ClassPathScanningCandidateComponentProvider中的字段#

几个接口都没实质性内容,主要是继承了一个父类,我整理了一下,父类里,大概有如下字段:

Copy
// 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包里,信息如下:

Copy
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这个接口,也是以后的重点,这里概览一下:

Copy
/** * 通过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中的字段#

Copy
// 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里用户的配置给存起来了。

具体配置过程解析#

Copy
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

    Copy
    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

    Copy
    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); } } } } }
    Copy
    @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:

    Copy
    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:

Copy
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这块。

Copy
/** * 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来判断的过程也很有意思:

Copy
/** * 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的探索之旅就这么结束了。欢迎大家留言,觉得有帮助的话,请关注我,后续会输出更多内容。




posted @   三国梦回  阅读(1783)  评论(2编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
历史上的今天:
2018-01-19 powerdesigner将name的名字赋给comment
2018-01-19 双击表,powerdesigner pdm 没有 comment列(no comment)
点击右上角即可分享
微信分享提示
CONTENTS