在配置类标注@ComponentScan注解时,底层是通过ClassPathBeanDefinitionScanner进行操作的。需要注意的是@ComponentScan不能单独使用,需要有@configuration注解结合使用。因为是先解析@configuration类,解析其注解发现@ComponentScan在进行解析。

先看下@ComponentScan的用法

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {// 先确定范围// 1. 扫描包@AliasFor("basePackages")String[] value() default {};@AliasFor("value")String[] basePackages() default {};// 也可以给出一个类,会扫描该类所在的包Class<?>[] basePackageClasses() default {};// bean名称生成器Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;// 给出了范围。还可以更细粒度的筛选// 定义过滤器,默认的过滤器会扫描@component注解boolean useDefaultFilters() default true;// 筛选出想要的Filter[] includeFilters() default {};// 从筛选出来的排除掉不想要的Filter[] excludeFilters() default {};boolean lazyInit() default false;}// 过滤器@Retention(RetentionPolicy.RUNTIME)@Target({})@interface Filter {// 过滤器默认是通过注解筛选的// 可以自定义自己的筛选方式// ANNOTATION: 通过注解筛选// ASSIGNABLE_TYPE通过类型筛选// ASPECTJ:通过切面的表达式筛选// REGEX:通过正则表达式筛选// CUSTOM:通过自定义的筛选器//FilterType type() default FilterType.ANNOTATION;@AliasFor("classes")Class<?>[] value() default {};@AliasFor("value")Class<?>[] classes() default {};String[] pattern() default {};}

举几个例子:

// 扫描com.ztryou包下的Service注解标注的
@ComponentScan(basePackages = "com.ztryou", includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)})
// 扫描com.ztryou包下,HelloService类型的。
@ComponentScan(basePackages = "com.ztryou", includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = HelloService.class)})
// 扫描com.ztryou包下。符合切面表达式的类
@ComponentScan(basePackages = "com.ztryou", includeFilters = {@ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "execution(* com.ztryou.*.*(..))")})

进入方法org.springframework.context.annotation.ComponentScanAnnotationParser#parse,spring是如何解析该注解的

 public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {// 创建ClassPathBeanDefinitionScanner 对象,并设置是否使用默认的过滤器ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);// 接续注解的属性信息。并赋值给ClassPathBeanDefinitionScanner 对象Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");if (scopedProxyMode != ScopedProxyMode.DEFAULT) {scanner.setScopedProxyMode(scopedProxyMode);}else {Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));}scanner.setResourcePattern(componentScan.getString("resourcePattern"));// 添加筛选的过滤器for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addIncludeFilter(typeFilter);}}// 添加排除的过滤器for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addExcludeFilter(typeFilter);}}boolean lazyInit = componentScan.getBoolean("lazyInit");if (lazyInit) {scanner.getBeanDefinitionDefaults().setLazyInit(true);}// 解析处包名。Set<String> basePackages = new LinkedHashSet<>();String[] basePackagesArray = componentScan.getStringArray("basePackages");for (String pkg : basePackagesArray) {String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);Collections.addAll(basePackages, tokenized);}// 如果传进来的是class信息、得到该类所处的包for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}// 如果范围依旧是没有设置,就处理标注注解的那个类所在的包。if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(declaringClass));}scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {@Overrideprotected boolean matchClassName(String className) {return declaringClass.equals(className);}});return scanner.doScan(StringUtils.toStringArray(basePackages));}

就是解析注解的属性,赋值给scanner对象;解析包名的时候有不同的处理,如果没有给包名 ,就会解析该注解标注的类的所在的包。

 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) {// 找到每个包中的所有文件。并创建元数据读取器,封装在beanDefinition中。Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {// 解析scopeScopeMetadata 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);}// 解析bean中的注解,;例如@primary.@dependon等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);// 注册beanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}

主要逻辑在从包名中得到所有的文件:findCandidateComponents

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents

 private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {// 拼接为classpath*:/com/ztryou/**/*.classString 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);// 这里应用了过滤器if (isCandidateComponent(metadataReader)) {// 符合的就封装为beanDefinitionScannedGenericBeanDefinition 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);.....return candidates;}

文件路径的解析器,递归,子路径向父路径递归,知道遇到准确的。
路径解析器,得到准确的路径后,使用类加载器加载。得到绝对路径。之后得到该路径的匹配路径。知道返回到子文件。
这块的逻辑有点乱,以我现在的能力还不足以讲清楚。

Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

【Spring-IOC】bean扫描器ClassPathBeanDefinitionScanner详解相关推荐

  1. Spring中bean的scope详解

    如何使用spring的作用域: <bean id="role" class="spring.chapter2.maryGame.Role" scope=& ...

  2. Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!

    点击下方链接回顾往期 不要再说不会Spring了!Spring第一天,学会进大厂! Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官! 今天讲解Spring中Bean的生命周期. ...

  3. Spring生命周期Bean初始化过程详解

    Spring生命周期Bean初始化过程详解 Spring 容器初始化 Spring Bean初始化 BeanFactory和FactoryBean 源码分析 Bean的实例化 preInstantia ...

  4. spring(7)---深入理解Spring核心技术——Spring中的各模块详解

    深入理解Spring核心技术--Spring中的各模块详解 Spring框架的两个基本概念IOC容器和AOP,相信大家现在对Spring中的这两个部分的基本概念有了一定的认识,好了,那么今天我们就来正 ...

  5. Spring 3.0 注解注入详解

    Spring 3.0 注解注入详解 2011-04-15 09:44 17ZOUGUO ITEYE博客 我要评论(1) 字号:T | T AD: 一.各种注解方式 1.@Autowired注解(不推荐 ...

  6. 【JAVA秘籍心法篇-Spring】Spring XML解析源码详解

    [JAVA秘籍心法篇-Spring]Spring XML解析源码详解 所谓天下武功,无坚不摧,唯快不破.但有又太极拳法以快制慢,以柔克刚.武功外式有拳打脚踢,刀剑棍棒,又有内功易筋经九阳神功.所有外功 ...

  7. ElasticSearch——Spring Boot 集成 ES 操作详解

    文章目录 ElasticSearch--Spring Boot 集成 ES 操作详解 1.SpringBoot 集成 ES 2.索引的API操作详解 3.文档的API操作详解 ElasticSearc ...

  8. 超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享...

    超轻量级DI容器框架Google Guice与Spring框架的区别教程详解及其demo代码片段分享 DI框架 Google-Guice入门介绍 转载于:https://www.cnblogs.com ...

  9. Spring Boot的启动器Starter详解

    Spring Boot的启动器Starter详解 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs Spring Boot ...

  10. 跟着小马哥学系列之 Spring AOP(Pointcut 组件详解)

    学好路更宽,钱多少加班. --小马哥 版本修订 2021.5.19:去除目录 2021.5.21:引用 Spring 官方 Pointcut 概念,修改 Pointcut 功能表述 简介 大家好,我是 ...

最新文章

  1. arttemplate 不转义html,使用artTemplate模板引擎渲染错误
  2. 常用 Java Profiling 工具的分析与比较
  3. 什么是“小小输入法”软件? 用其进行郑码输入练习
  4. 【软件工程】知识点梳理(全)
  5. FJ的字符串java问题_蓝桥杯VIP试题 之 基础练习 FJ的字符串- JAVA
  6. 湖北网络安全的产业机遇在哪里
  7. java面试题常见的坑_java那些年踩过面试题的坑,你是否依旧记忆犹新!
  8. 扩容效率提升10倍,腾讯云发布一站式资源运维利器TIC
  9. php curl iis,解决IIS运行PHP出现Call to undefined function curl_init()的问题
  10. 32位ubuntu 编译android源码,ubuntu 下编译android源码错误解决记录
  11. Easy Algorithms系列——详解递归与分治
  12. 【51单片机】(手把手教你)1602液晶屏-基础篇
  13. 手把手教你处理 JS 逆向之图片伪装
  14. win10睡眠按啥键唤醒_win10关闭屏幕后无法唤醒怎么办?电脑黑屏只能重启的解决方法...
  15. ES8中对字符串补白的方式
  16. windows和linux系统文件目录
  17. EMBA课程小记(5)——“财务管理”课程体会
  18. 版本号(version number)
  19. jQuery学习: lt与gt
  20. Power BI(二十四)power pivot之产品/客户分类分析(ABC分析)

热门文章

  1. 多个路由指向同一个页面_Flutter路由导航
  2. 推荐方法-1:UserCFItemCF
  3. C/C++[codeup 1967]数组逆置
  4. Python数据的精度
  5. 翻译: 4.4. 模型选择Model Selection、欠拟合Underfitting和过拟合Overfitting pytorch
  6. 算法: 1和0子集合的个数 474. Ones and Zeroes
  7. 容器技术Docker K8s 27 容器服务ACK基础与进阶-监控管理
  8. 机器学习- 吴恩达Andrew Ng - week3-4 solve overfitting
  9. 数据集:各地区化妆品销量、人口数量和人均收入
  10. 596. 超过5名学生的课