Spring ComponentScan

1. 包扫描的路径

@ComponentScan 注解可以指定包扫描的路径(而且还可以声明不止一个),它的写法是使用 @ComponentScanvalue / basePackages 属性:

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

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

@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 所在包及子包下的所有组件

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

2. 包扫描的过滤

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

2.1 按注解过滤包含

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

img

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

2.2 按注解排除

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

img

2.3 按类型过滤

2.4 正则表达式过滤

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

2.5 自定义过滤

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

2.5.1 TypeFilter接口

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

这个 match 方法有两个参数:

  • metadataReader :通过这个 Reader ,可以读取到正在扫描的类的信息(包括类的信息、类上标注的注解等)

  • metadataReaderFactory :借助这个 Factory ,可以获取到其他类的 Reader ,进而获取到那些类的信息。可以这样理解:借助 ReaderFactory 可以获取到 Reader ,借助 Reader 可以获取到指定类的信息。

2.5.2 编写自定义过滤规则

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

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

2.5.3 添加过滤规则声明

TypeFilter 写完了,不要忘记加在 @ComponentScan 上:

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

2.5.4 metadata的概念

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

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

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

3. 包扫描的其他特性

3.1 包扫描可以组合使用(ComponentScans

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

3.2 包扫描的组件名称生成

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

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

3.2.1 BeanNameGenerator

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

又出现 BeanDefinitionBeanDefinitionRegistry 了,可见元信息、元定义在底层真的太常见了!

3.2.2 AnnotationBeanNameGenerator的实现

找到 AnnotationBeanNameGenerator 的实现:

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

  1. 只有注解扫描注册进来的 Bean 才会被处理

  2. 既然是注解扫描进来的,那我就要看看有木有在注解中声明好了,这种声明方式就是 @Component("person")

  3. 注解中找不到名,那好吧,我给你构造一个,不过这个名是按照我默认规则来的,你就别挑挑拣拣咯

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

3.2.3 buildDefaultBeanName的实现

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

原理实现看完了,小伙伴们肯定有一个疑惑:Introspector 是个什么鬼哦??

3.2.4 Java的内省机制

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

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

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

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

最后更新于

这有帮助吗?