通常我们都会将 Application 放在项目的根路径下,此时 Spring 就会自动扫描该路径下的包与子包下所有注解,那么这个又是如何实现的呢?

@SpringBootApplication

@SpringBootApplication 注解下有一个注解是 @EnableAutoConfiguration

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @SpringBootConfiguration
  6. @EnableAutoConfiguration // 开启自动配置
  7. @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  8. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  9. public @interface SpringBootApplication {
  10. // ...
  11. }

@EnableAutoConfiguration 注解下,有一个 @AutoConfigurationPackage 注解,从这个注解的名字可以看出来,是用来配置自动扫描包的

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. @Import(AutoConfigurationPackages.Registrar.class)
  6. public @interface AutoConfigurationPackage {
  7. String[] basePackages() default {};
  8. Class<?>[] basePackageClasses() default {};
  9. }

这里使用了一个 @Import 导入了一个 AutoConfigurationPackages.Registrar.class 类,那么这个类又是做什么的呢?

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. @Override
  3. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  4. register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
  5. }
  6. @Override
  7. public Set<Object> determineImports(AnnotationMetadata metadata) {
  8. return Collections.singleton(new PackageImports(metadata));
  9. }
  10. }

我们可以看到 ,这个 Registrar 实现了 ImportBeanDefinitionRegistrar 接口,而 ImportBeanDefinitionRegistrar 类主要作用就是来动态注册 bean

我们可以看到,他首先通过 new PackageImports(metadata) 拿到当前类的 metadata

  1. PackageImports(AnnotationMetadata metadata) {
  2. AnnotationAttributes attributes = AnnotationAttributes
  3. .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
  4. List<String> packageNames = new ArrayList<>();
  5. for (String basePackage : attributes.getStringArray("basePackages")) {
  6. packageNames.add(basePackage);
  7. }
  8. for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
  9. packageNames.add(basePackageClass.getPackage().getName());
  10. }
  11. if (packageNames.isEmpty()) {
  12. packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
  13. }
  14. this.packageNames = Collections.unmodifiableList(packageNames);
  15. }

当我们没有提供任何 basePackages 或 basePackageClasses 时,就会使用当前类的包名,作为扫描路径

  1. if (packageNames.isEmpty()) {
  2. packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
  3. }
  4. public static String getPackageName(String fqClassName) {
  5. Assert.notNull(fqClassName, "Class name must not be null");
  6. int lastDotIndex = fqClassName.lastIndexOf(".");
  7. return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
  8. }

这样就完成了自动扫描包的目的:其实就是截取当前 Application 所在的包的路径