Spring源码分析(扫描不到 Bean 定义的情况)

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 为例,原有的代码扫描了默认包而忽略了其它包;而一旦显式指定其它包,原来的默认扫描包就被忽略了。


版权声明:本文为weixin_45608986原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。