> For the complete documentation index, see [llms.txt](https://ldbmcs.gitbook.io/java/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ldbmcs.gitbook.io/java/java-frameworks-73/spring/spring-ioc/spring-componentscan.md).

# Spring ComponentScan

## 1. 包扫描的路径

`@ComponentScan` 注解可以指定包扫描的路径（而且还可以声明不止一个），它的写法是使用 `@ComponentScan` 的 `value` / `basePackages` 属性：

```java
@Configuration
@ComponentScan("com.linkedbear.spring.annotation.e_basepackageclass.bean")
public class BasePackageClassConfiguration {
    
}
```

这种方式是最常用的，也是最推荐使用的。除此之外，还有一种声明方式`basePackageClasses`，它使用的是类的 Class 字节码：

```java
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	@AliasFor("basePackages")
	String[] value() default {};

	@AliasFor("value")
	String[] basePackages() default {};

    /**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages
	 * to scan for annotated components. The package of each class specified will be scanned.
	 * <p>Consider creating a special no-op marker class or interface in each package
	 * that serves no purpose other than being referenced by this attribute.
	 */
	Class<?>[] basePackageClasses() default {};
    // ...
}
```

它的这个 `basePackageClasses` 属性，可以传入一组 Class 进去，它代表的意思，是扫描**传入的这些 Class 所在包及子包下的所有组件**。

```java
@Configuration
@ComponentScan(basePackageClasses = DemoService.class)
public class BasePackageClassConfiguration {
    
}
```

这样就会以 `DemoService` 所在的包为基准扫描了。

## 2. 包扫描的过滤

在实际开发的场景中，我们用包扫描拿到的组件不一定全部都需要，也或者只有一部分需要，这个时候就需要用到包扫描的过滤了。

### 2.1 按注解过滤包含

```java
@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class))
public class TypeFilterConfiguration {
    
}
```

注意：除了会把带有`@Animal`注解的组件注入进来，也会把带有`@ComponentScan`注解的组件注入进来。

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1668654609887-5287d88b-7965-4a8c-b64c-dc942822165f.png)

因为，`@ComponentScan` 注解中还有一个属性：`useDefaultFilters` ，它代表的是“是否启用默认的过滤规则”。咱之前也讲过了，默认规则就是扫那些以 `@Component` 注解为基准的模式注解。

```java
/**
 * Indicates whether automatic detection of classes annotated with {@code @Component}
 * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
 */
boolean useDefaultFilters() default true;
```

### 2.2 按注解排除

```java
@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class))
public class TypeFilterConfiguration {
    
}
```

排除型过滤器会排除掉其他过滤规则已经包含进来的 Bean 。

![img](https://cdn.nlark.com/yuque/0/2022/png/229542/1668654594920-103feb4f-c22a-4295-8b50-608f9bf47da8.png)

### 2.3 按类型过滤

```java
@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class)},
               excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class)})
public class TypeFilterConfiguration {
    
}
```

### 2.4 正则表达式过滤

除了按注解过滤、按类型过滤，它内置的模式还有两种表达式的过滤规则，分别是 “**切入点表达式过滤**” 和 “**正则表达式过滤**” 。

```java
@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {
                       @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.linkedbear.spring.annotation.f_typefilter.+Demo.+")
               },
               excludeFilters = {
                       @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class),
                       @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class)
               })
public class TypeFilterConfiguration {
    
}
```

### 2.5 自定义过滤

如果预设的几种模式都不能满足要求，那就需要用编程式过滤方式了，也就是**自定义过滤规则**。

#### 2.5.1 TypeFilter接口

编程式自定义过滤，需要编写过滤策略，实现 `TypeFilter` 接口。这个接口只有一个 `match` 方法：

```java
@FunctionalInterface
public interface TypeFilter {

	/**
	 * Determine whether this filter matches for the class described by
	 * the given metadata.
	 * @param metadataReader the metadata reader for the target class
	 * @param metadataReaderFactory a factory for obtaining metadata readers
	 * for other classes (such as superclasses and interfaces)
	 * @return whether this filter matches
	 * @throws IOException in case of I/O failure when reading metadata
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;

}
```

这个 `match` 方法有两个参数：

* **metadataReader** ：通过这个 Reader ，可以读取到正在扫描的类的信息（包括类的信息、类上标注的注解等）
* **metadataReaderFactory** ：借助这个 Factory ，可以获取到其他类的 Reader ，进而获取到那些类的信息。可以这样理解：**借助 ReaderFactory 可以获取到 Reader ，借助 Reader 可以获取到指定类的信息。**

#### 2.5.2 编写自定义过滤规则

`MetadataReader` 中有一个 `getClassMetadata` 方法，可以拿到正在扫描的类的基本信息，咱可以由此取到全限定类名，进而与咱需求中的 `Green` 类做匹配：

```java
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
         throws IOException {
    ClassMetadata classMetadata = metadataReader.getClassMetadata();
    return classMetadata.getClassName().equals(Green.class.getName());
}
```

返回 true ，则说明已经匹配上了。

#### 2.5.3 添加过滤规则声明

`TypeFilter` 写完了，不要忘记加在 `@ComponentScan` 上：

```java
@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {
                       @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.linkedbear.spring.annotation.f_typefilter.+Demo.+")
               },
               excludeFilters = {
                       @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class),
                       @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class),
                       @ComponentScan.Filter(type = FilterType.CUSTOM, value = GreenTypeFilter.class)
               })
public class TypeFilterConfiguration {
    
}
```

重新运行启动类的 `main` 方法，可以发现 `Green` 也没了，自定义 `TypeFilter` 生效。

#### 2.5.4 metadata的概念

讲到这里了，咱先不着急往下走，停一停，咱讲讲 **metadata** 的概念。

回想一下 JavaSE 的反射，它是不是可以根据咱写好的类，获取到类的全限定名、属性、方法等信息呀。好，咱现在就建立起这么一个概念：咱定义的类，它叫什么名，它有哪些属性，哪些方法，这些信息，统统叫做**元信息**，**元信息会描述它的目标的属性和特征**。

在 SpringFramework 中，元信息大量出现在框架的底层设计中，不只是 **metadata** ，前面咱屡次见到的 **definition** ，也是元信息的体现。后面到了 IOC 高级部分，咱会整体的学习 SpringFramework 中的元信息、元定义设计，以及 `BeanDefinition` 的全解析。

## 3. 包扫描的其他特性

### 3.1 包扫描可以组合使用（`ComponentScans`）

```java
/**
 * Container annotation that aggregates several {@link ComponentScan} annotations.
 *
 * <p>Can be used natively, declaring several nested {@link ComponentScan} annotations.
 * Can also be used in conjunction with Java 8's support for repeatable annotations,
 * where {@link ComponentScan} can simply be declared several times on the same method,
 * implicitly generating this container annotation.
 *
 * @author Juergen Hoeller
 * @since 4.3
 * @see ComponentScan
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ComponentScans {

	ComponentScan[] value();

}
```

其实它就是一次性组合了一堆 `@ComponentScan` 注解而已了，没啥好说的。

### 3.2 包扫描的组件名称生成

咱在前面刚学习注解驱动时，就知道默认情况下生成的 bean 的名称是类名的首字母小写形式（ Person → person ），可是它为啥就有这个规则呢？这个问题，也可以从 `@ComponentScan` 注解中找到。

在 `@ComponentScan` 注解的属性中，有一个 `nameGenerator` ，它的默认值是 `BeanNameGenerator` 。不过这个 `BeanNameGenerator` 是一个接口，从文档注释中不难找到实现类是 `AnnotationBeanNameGenerator` 。

```java
/**
	 * The {@link BeanNameGenerator} class to be used for naming detected components
	 * within the Spring container.
	 * <p>The default value of the {@link BeanNameGenerator} interface itself indicates
	 * that the scanner used to process this {@code @ComponentScan} annotation should
	 * use its inherited bean name generator, e.g. the default
	 * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
	 * application context at bootstrap time.
	 * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
	 * @see AnnotationBeanNameGenerator
	 * @see FullyQualifiedAnnotationBeanNameGenerator
	 */
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
```

#### 3.2.1 BeanNameGenerator

从名称上就知道它是 Bean 的名字生成器了，它只有一个 `generateBeanName` 方法：

```java
/**
 * Strategy interface for generating bean names for bean definitions.
 *
 * @author Juergen Hoeller
 * @since 2.0.3
 */
public interface BeanNameGenerator {

	/**
	 * Generate a bean name for the given bean definition.
	 * @param definition the bean definition to generate a name for
	 * @param registry the bean definition registry that the given definition
	 * is supposed to be registered with
	 * @return the generated bean name
	 */
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}
```

又出现 `BeanDefinition` 和 `BeanDefinitionRegistry` 了，可见元信息、元定义在底层真的太常见了！

#### 3.2.2 AnnotationBeanNameGenerator的实现

找到 `AnnotationBeanNameGenerator` 的实现：

```java
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    if (definition instanceof AnnotatedBeanDefinition) {
        String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
        if (StringUtils.hasText(beanName)) {
            // Explicit bean name found.
            return beanName;
        }
    }
    // Fallback: generate a unique default bean name.
    return buildDefaultBeanName(definition, registry);
}
```

看这段源码的实现，整体的逻辑还是非常容易理解的：

1. 只有注解扫描注册进来的 Bean 才会被处理
2. 既然是注解扫描进来的，那我就要看看有木有在注解中声明好了，这种声明方式就是 `@Component("person")`
3. 注解中找不到名，那好吧，我给你构造一个，不过这个名是按照我默认规则来的，你就别挑挑拣拣咯

上面从注解中获取的部分咱留到后面再看，这里咱只看 `buildDefaultBeanName` 的实现。

#### 3.2.3 buildDefaultBeanName的实现

```java
protected String buildDefaultBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
    return buildDefaultBeanName(definition);
}

protected String buildDefaultBeanName(BeanDefinition definition) {
    String beanClassName = definition.getBeanClassName();
    Assert.state(beanClassName != null, "No bean class name set");
    String shortClassName = ClassUtils.getShortName(beanClassName);
    return Introspector.decapitalize(shortClassName);
}
```

一路走到最底下的方法中，它会根据组件类的全限定名，截取出短类名（如 `com.linkedbear.Person` → `Person` ），最后用一个叫 `Introspector` 的类，去生成 bean 的名称。那想必这个 `Introspector.decapitalize` 方法肯定就可以把类名的首字母转为小写咯，点进去发现确实如此：

```java
/**
     * Utility method to take a string and convert it to normal Java variable
     * name capitalization.  This normally means converting the first
     * character from upper case to lower case, but in the (unusual) special
     * case when there is more than one character and both the first and
     * second characters are upper case, we leave it alone.
     * <p>
     * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
     * as "URL".
     *
     * @param  name The string to be decapitalized.
     * @return  The decapitalized version of the string.
     */
    public static String decapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                        Character.isUpperCase(name.charAt(0))){
            return name;
        }
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }
```

原理实现看完了，小伙伴们肯定有一个疑惑：`Introspector` 是个什么鬼哦？？

#### 3.2.4 Java的内省机制

说到这个**内省**，或许好多小伙伴都没听说过。其实**它是 JavaSE 中就有的，对 JavaBean 中属性的默认处理规则**。

回想一下咱写的所有模型类，包括 vo 类，是不是都是写好了属性，之后借助 IDE 生成 `getter` 和 `setter` ，或者借助 `Lombok` 的注解生成 `getter` 和 `setter` ？其实这个生成规则，就是利用了 Java 的内省机制。

\*\*Java 的内省默认规定，所有属性的获取方法以 get 开头（ boolean 类型以 is 开头），属性的设置方法以 set 开头。\*\*根据这个规则，才有的默认的 getter 和 setter 方法。

`Introspector` 类是 Java 内省机制中最核心的类之一，它可以进行很多默认规则的处理（包括获取类属性的 get / set 方法，添加方法描述等），当然它也可以处理这种类名转 beanName 的操作。SpringFramework 深知这个设计之妙，就直接利用过来了。
