# 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 深知这个设计之妙，就直接利用过来了。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ldbmcs.gitbook.io/java/java-frameworks-73/spring/spring-ioc/spring-componentscan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
