问题复现
● 使用如下包结构:
● 我们发现 HelloWorldController 失效了,无法找到 HelloWorldController 这个 Bean 了。这是为何?
案例分析
● 对于 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 {};
//省略其他非关键代码
}
● 而在我们的案例中,我们直接使用的是 SpringBootApplication 注解定义的 ComponentScan,它的 basePackages 没有指定,所以默认为空(即{})。此时扫描的是什么包?这里不妨带着这个问题去调试下(调试位置参考 ComponentScanAnnotationParser#parse 方法),调试视图如下:
● 从上图可以看出,当 basePackages 为空时,扫描的包会是 declaringClass 所在的包,在本案例中,declaringClass 就是 Application.class,所以扫描的包其实就是它所在的包,即 com.zp95sky.learn.springlearn.Application
。
● 如上的包结构 HelloWorldController 已经出了扫描的范围(不和 Application.java 一个包了),这个功能也就失效了。
问题修正
● 解决问题的方式是显式配置 @ComponentScan。
@SpringBootApplication
@ComponentScan("com.zp95sky.learn.springlearn.controller")
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
● 通过上述修改,我们显式指定了扫描的范围为 com.zp95sky.learn.springlearn.controller
。不过需要注意的是,显式指定后,默认的扫描范围(即 com.zp95sky.learn.springlearn.application
)就不会被添加进去了。另外,我们也可以使用 @ComponentScans 来修复问题,使用方式如下:
@ComponentScans(value = { @ComponentScan(value = "com.zp95sky.learn.springlearn.controller")
})
● 顾名思义,可以看出 ComponentScans 相比较 ComponentScan 多了一个 s,支持多个包的扫描范围指定。