Talk is cheap. Show me the code

第一步:在com.xxx.hyl.boot包下创建一个子包-beans,并在该包中创建一个Bean。

package com.xxx.hyl.boot.beans;import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
/**** 使用{@link ComponentScan}不指定包路径,当前类被扫描进IoC容器* @author 君战* */
@Component
public class TestBean {}

第二步:在com.xxx.hyl.boot包下创建一个类,使用@ComponentScan注解,然后创建注解驱动 Spring 应用上下文。

package com.xxx.hyl.boot;import com.xxx.hyl.boot.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;/**** 演示使用{@link ComponentScan}注解时不指定包路径,IoC容器依然可以扫描当前包下及其子包下的类。* @author 君战** */
@ComponentScan
public class ComponentScanDemo {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();// 使用当前类来作为配置类context.register(ComponentScanDemo.class);// 调用 refresh方法来刷新应用上下文context.refresh();// 通过getBean方法来获取TestBean在IoC容器中的实例TestBean testBean = context.getBean(TestBean.class);System.out.println("TestBean : " + testBean);}
}

第三步:查看控制台

可以发现,我们做到了和SpringBoot一样的功能,只添加了一个@ComponentScan注解,并未指定任何包路径,TestBean依然被装配进IoC容器中。

其实这并不是Spring Boot独创的,而是Spring Framework原本就有的功能,只不过因为Spring Boot的风靡而让这种用法走入大家的视野。废话不多说,接下来就进入底层源码分析。

底层源码分析

在ComponentScanAnnotationParser的parse方法中,针对@ComponentScan注解的方法返回值进行解析,并将解析到的数据设置到ClassPathBeanDefinitionScanner中。这里面有一个扩展知识点,basepackages是可以设置占位符的,具体分析请查看 你所不知道的@ComponentScan注解用法之包路径占位符 。重点是接下来的判断basePackages集合是否为空(如果未配置basePackages和basePackageClasses,那么basePackages集合就是为空),如果为空,则去获取配置类所在包来作为要扫描的包路径。重点就是使用basePackages将ClassUtils的getPackageName方法返回值保存。

// ComponentScanAnnotationParser#parse
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);// 获取@ComponentScan注解nameGenerator()方法返回值Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :BeanUtils.instantiateClass(generatorClass));// 获取@ComponentScan注解scopedProxy()方法返回值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));}// 获取@ComponentScan注解resourcePattern()方法返回值scanner.setResourcePattern(componentScan.getString("resourcePattern"));// 获取@ComponentScan注解includeFilters()方法返回值for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addIncludeFilter(typeFilter);}}// 获取@ComponentScan注解excludeFilters()方法返回值for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {for (TypeFilter typeFilter : typeFiltersFor(filter)) {scanner.addExcludeFilter(typeFilter);}}// 获取@ComponentScan注解lazyInit()方法返回值boolean lazyInit = componentScan.getBoolean("lazyInit");if (lazyInit) {scanner.getBeanDefinitionDefaults().setLazyInit(true);}Set<String> basePackages = new LinkedHashSet<>();// 获取@ComponentScan注解basePackages()方法返回值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);}// 获取@ComponentScan注解basePackageClasses()方法返回值for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}// 如果未指定@ComponentScan注解的basePackages()以及basePackageClasses(),那么basePackages将为空if (basePackages.isEmpty()) {// 获取配置类所在包并添加到basePackages集合中。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));
}

ClassUtils的getPackageName方法实现也很简单,就是通过获取最后一个“.”的下标,然后从类的全限定名截取获得包名。

// ClassUtils#getPackageName(java.lang.String)
public static String getPackageName(String fqClassName) {Assert.notNull(fqClassName, "Class name must not be null");int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}

接下来,我们就DEBUG验证下猜想,不出所料,結果正如我们所猜想的那样。

总结

Spring Boot中只添加一个@SpringBootApplication注解就可以扫描启动所在包及其子包中所有添加了@Component注解及其派生注解的Bean这样的功能归根结底还是Spring Framework原有的功能,并不是Spring Boot所独创。

Spring在处理@ComponentScan注解时,如果未指定包路径或者class路径,默认以配置类所在的包路径作为资源扫描路径。

Spring Boot不指定包路径就可以扫描启动类所在包及其子包下的类是怎么做到的?相关推荐

  1. Spring boot 默认静态资源路径与手动配置访问路径的方法

    这篇文章主要介绍了Spring boot 默认静态资源路径与手动配置访问路径的方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下 在application.propertis中配置 ##端口号 s ...

  2. 基于Spring boot,使用idea方便地切换启动环境

    https://blog.csdn.net/mate_ge/article/details/78624579 基于Spring boot,使用idea方便地切换启动环境 原创martsforever ...

  3. Spring Boot 2.x基础教程:找回启动日志中的请求路径列表

    如果您看过之前的Spring Boot 1.x教程,或者自己原本就对Spring Boot有一些经验,或者对Spring MVC很熟悉.那么对于Spring构建的Web应用在启动的时候,都会输出当前应 ...

  4. Spring boot配置项目访问路径server.context-path不起作用(改为server.servlet.context-path)

    配置Spring boot 访问路径 Spring boot  2.0之前的版本:server.context-path Spring boot  2.0之后的版本:server.servlet.co ...

  5. spring boot (tomcat) 指定使用ipv4 netstat -ntupl查询结果为tcp4(不是tcp6)

    问题 spring boot 打的jar包,用java命令启动.netstat -ntupl 查看端口监听,只有tcp6 没有tcp4.而阿里云通常的入栈规则是ipv4. 诱因 怀疑是此处的原因,导致 ...

  6. spring boot中配置虚拟路径,用来映射显示图片

    增加配置,继承 WebMvcConfigurerAdapter,如下: package com.wm.mogu_picture.config;import org.springframework.be ...

  7. spring boot原理_SpringBoot-02-原理初探之主启动类

    2. 原理初探 2.1 pom.xml 父依赖 主要依赖一个父项目,主要管理项目的资源过滤和插件 <parent><groupId>org.springframework.bo ...

  8. SpringBoot - 探究Spring Boot应用是如何通过java -jar 启动的

    文章目录 Pre 引导 新建工程 打包 启动 java -jar 干啥的 打包插件 spring-boot-maven-plugin简介 包结构 META-INF内容 Archive的概念 JarFi ...

  9. 多个Spring Boot项目部署在一个Tomcat容器无法启动

    Tomxin7 Simple, Interesting | 简单,有趣 本文将花费您五分钟时间 业务介绍 最近用Spring Boot开发了一个翻译的小项目,但是服务器上还跑着其他项目,包括一个同样用 ...

  10. 【Spring Boot教程】(七):自定义项目启动的彩蛋

    打开网址http://patorjk.com/software/taag/将字符转为字符画 拷贝生成的字符画到名为banner.txt文件中 将banner.txt拷贝到项目的resources目录中 ...

最新文章

  1. typedef struct 先声明后定义_C++模版和C#泛型求同存异录(二)typedef
  2. AtCoder - 3954 Painting Machines
  3. Java 9 新功能之 HTTP2 和 REPL
  4. 六.dbms_session(提供了使用PL/SQL实现ALTER SESSION命令)
  5. R语言高级算法之支持向量机(Support Vector Machine)
  6. hdu3359 Kind of a Blur
  7. 必看!linux系统如何查看内存使用情况
  8. python设置字符串的格式_Python中的字符串格式
  9. Msql 主从原理介绍-通俗易懂
  10. TCP/IP之传输层协议详解
  11. 【旅行】2月17日 南京 - 日照 451公里自驾过年
  12. ACM-ICPC 2018 北京赛区网络预赛 Tomb Raider(暴力)
  13. 使用80percent开发rails程序:gem的了解。(kaminari)
  14. Mac显示隐藏文件目录
  15. 深度清理C盘空间,让你的C盘多出20G
  16. 小程序学习笔记--进阶之路
  17. ChatGPT有效提问技巧
  18. 【STM32】:RCC时钟系统
  19. 解决Nmap扫描出现的延时
  20. 工作2年想从实施转开发还来的急吗?

热门文章

  1. 求数的绝对值一定是正数_有理数的绝对值难,那是因为你这些知识点和题型没掌握...
  2. 架构师资源汇总 从入门到精通 将近20年的工作经验毫无保留分享
  3. 算法导论 高频算法题 数据结构与算法之美
  4. 极客大学架构师训练营 系统架构 消息队列 负载均衡 数据库备份 第10课 听课总结
  5. 可访问性之于类和对象
  6. vector函数里的pair用法
  7. Android studio3.5读取项目资源文件的图片
  8. java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决办法
  9. Birth-Death process 生灭过程
  10. nginx 带宽_你知道服务器的带宽怎样设置吗?