Spring ComponentScan
1. 包扫描的路径
@ComponentScan 注解可以指定包扫描的路径(而且还可以声明不止一个),它的写法是使用 @ComponentScan 的 value / 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注解的组件注入进来。

因为,@ComponentScan 注解中还有一个属性:useDefaultFilters ,它代表的是“是否启用默认的过滤规则”。咱之前也讲过了,默认规则就是扫那些以 @Component 注解为基准的模式注解。
2.2 按注解排除
排除型过滤器会排除掉其他过滤规则已经包含进来的 Bean 。

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)
ComponentScans)其实它就是一次性组合了一堆 @ComponentScan 注解而已了,没啥好说的。
3.2 包扫描的组件名称生成
咱在前面刚学习注解驱动时,就知道默认情况下生成的 bean 的名称是类名的首字母小写形式( Person → person ),可是它为啥就有这个规则呢?这个问题,也可以从 @ComponentScan 注解中找到。
在 @ComponentScan 注解的属性中,有一个 nameGenerator ,它的默认值是 BeanNameGenerator 。不过这个 BeanNameGenerator 是一个接口,从文档注释中不难找到实现类是 AnnotationBeanNameGenerator 。
3.2.1 BeanNameGenerator
从名称上就知道它是 Bean 的名字生成器了,它只有一个 generateBeanName 方法:
又出现 BeanDefinition 和 BeanDefinitionRegistry 了,可见元信息、元定义在底层真的太常见了!
3.2.2 AnnotationBeanNameGenerator的实现
找到 AnnotationBeanNameGenerator 的实现:
看这段源码的实现,整体的逻辑还是非常容易理解的:
只有注解扫描注册进来的 Bean 才会被处理
既然是注解扫描进来的,那我就要看看有木有在注解中声明好了,这种声明方式就是
@Component("person")注解中找不到名,那好吧,我给你构造一个,不过这个名是按照我默认规则来的,你就别挑挑拣拣咯
上面从注解中获取的部分咱留到后面再看,这里咱只看 buildDefaultBeanName 的实现。
3.2.3 buildDefaultBeanName的实现
一路走到最底下的方法中,它会根据组件类的全限定名,截取出短类名(如 com.linkedbear.Person → Person ),最后用一个叫 Introspector 的类,去生成 bean 的名称。那想必这个 Introspector.decapitalize 方法肯定就可以把类名的首字母转为小写咯,点进去发现确实如此:
原理实现看完了,小伙伴们肯定有一个疑惑: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 深知这个设计之妙,就直接利用过来了。
最后更新于
这有帮助吗?