Spring-隐式扫描找不到 Bean 定义
我们再创建 Spring Boot 项目时通常会初始化一个简易版的 Web 项目。
初始化项目在 Application
的启动程序类中没有 @ComponentScan
的注解,俗称为隐式扫描。
但是隐式扫描有个坑,两个关键类需要位于同一个包下才可以。
没有添加注解 @ComponentScan
时,扫描的包是 declaringClass
包,也就是本案例中的 Application.class 。
当需要扫描的包和 Application.class 不同包时,此时就要添加注解 @ComponentScan
指定需要扫描位置,如图
我们需要扫描的类是 HelloWorldController ,此时就要在 Application 类中添加扫描注解:
@ComponentScan(basePackages = "com.spring.error.example1.controller")
否则就会出现 404 的错误。
阅读源码很重要,接下来可以挖掘背后的原理,看看这个问题是如何解决的。
要了解 HelloWorldController 为什么会失效,就需要先了解之前是如何生效的。对于 Spring Boot 而言,关键点在于 Application.java 中使用了 SpringBootApplication 注解。而这个注解继承了另外一些注解,具体定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//省略非关键代码
}
从定义可以看出,SpringBootApplication 开启了很多功能,其中一个关键功能就是 ComponentScan,参考其配置如下:
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
当 Spring Boot 启动时,ComponentScan 的启用意味着会去扫描出所有定义的 Bean。
那么扫描什么位置呢?这是由 ComponentScan 注解的 basePackages 属性指定的,具体可参考如下定义:
public @interface ComponentScan { /** * Base packages to scan for annotated components. * <p>{@link #value} is an alias for (and mutually exclusive with) this * attribute. * <p>Use {@link #basePackageClasses} for a type-safe alternative to * String-based package names. */ @AliasFor("value") String[] basePackages() default {}; //省略其他非关键代码 }
根据断点调试可以看出:对比重组包结构前后,自然就找到了这个问题的根源:在调整前,HelloWorldController 在扫描范围内,而调整后,它已经远离了扫描范围(不和 Application.java 一个包了),虽然代码没有一丝丝改变,但是这个功能已经失效了。所以要追根溯源。
注意点:
在指定扫描的范围之后,就不会扫描之前默认的包了。
比如我们指定的扫描范围是@ComponentScan(basePackages = "com.spring.error.example1.controller")
那就不能扫描默认的包了,即 Application.class 项目的包,比如本案例中的@ComponentScan(basePackages = "com.spring.error.example1.application")
解决方法:
可以用@ComponentScans
来解决此问题。
@ComponentScans
相比 @ComponentScan
最大的不同就是多了个 s ,顾名思义 @ComponentScans
可以支持扫描多个范围。比如在本案例中可以这样写:
@ComponentScans(value = { @ComponentScan(value = "com.spring.puzzle.class1.example1.controller") })
通过本次总结可以发现:
如果对源码缺乏了解,很容易会顾此失彼。以 ComponentScan
为例,原有的代码扫描了默认包而忽略了其它包;而一旦显式指定其它包,原来的默认扫描包就被忽略了。