springboot启动扫描

419 阅读1分钟

起因

springboot工程迁移时,将原本工程拆分为多个子项目。启动springboot时,需在入口类指定扫描路径。

案例

目录结构

.
└── com
    └── pyl
        ├── example
        │   ├── CanScanBean.java
        │   └── ExampleApplication.java
        └── example2
            └── CanNotScanBean.java

入口类ExampleApplication 默认扫描 同级目录与子集目录 CanScanBean处于同级目录,可扫描;CanNotScanBean并不处于同级目录或者子集目录,故无法被扫描到。 将CanNotScanBean注入CanScanBean

@Component
public class CanScanBean {
    @Autowired
    CanNotScanBean canNotScanBean;
}

启动时,需扫描CanNotScanBean,将报错,报错如下

***************************
APPLICATION FAILED TO START
***************************

Description:

Field canNotScanBean in com.pyl.example.CanScanBean required a bean of type 'com.pyl.example2.CanNotScanBean' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)

无法找到CanNotScanBean,可见spring默认扫描仅可扫描同级目录或子级目录下的Bean。当系统复杂时,采用默认扫描都方案是不现实的。因此需要自定义扫描

自定义扫描

方案一:@ComponentScan注解

@SpringBootApplication
@ComponentScan({"com.pyl.example","com.pyl.example2"})
public class ExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

方案二:scanBasePackages属性

@SpringBootApplication(scanBasePackages = {"com.pyl.example","com.pyl.example2"})
public class ExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

源码分析

org.springframework.context.annotation.ComponentScanAnnotationParser 获取basePackages的业务逻辑

        Set<String> basePackages = new LinkedHashSet();
		//获取@ComponentScan注解配置的basePackages属性值
        String[] basePackagesArray = componentScan.getStringArray("basePackages");
/**
debug值
basePackagesArray=["com.pyl.example1", "com.pyl.example2"]
**/
        String[] var19 = basePackagesArray;
        int var21 = basePackagesArray.length;

        int var22;
        for(var22 = 0; var22 < var21; ++var22) {
            String pkg = var19[var22];
            String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg), ",; \t\n");
            Collections.addAll(basePackages, tokenized);
        }
		// 获取@ComponentScan注解的basePackageClasses属性值
        Class[] var20 = componentScan.getClassArray("basePackageClasses");
        var21 = var20.length;

        for(var22 = 0; var22 < var21; ++var22) {
            Class<?> clazz = var20[var22];
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
		// 如果并没有配置@ComponentScan的basePackages、basePackageClasses属性值
        if (basePackages.isEmpty()) {
            // 使用Application入口类的package作为basePackage
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

basePackages获取步骤: 1.获取@ComponentScan注解basePackages属性值 2.获取@ComponentScan注解的basePackageClasses属性值 3.如果并没有配置@ComponentScan的basePackages、basePackageClasses属性值 if (basePackages.isEmpty()) 逻辑使得当配置了@ComponentScan之后,不会在使用Application入口类的package作为basePackage