起因
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