最新实战案例锦集:《Spring Boot3实战案例合集》持续更新,每天至少更新一篇文章,订阅后将赠送文章最后展示的所有MD文档(学习笔记)。
环境:Spring Boot3.2.5
1. 简介
在Spring中,@ComponentScan注解是一个强大且常用的工具,但很多人对其功能和使用方法可能只是一知半解。本文将深入探讨@ComponentScan注解的各个方面,揭示其隐藏的功能和最佳实践,帮助读者彻底掌握这一重要注解。
@ComponentScan注解,用于自动检测并注册带有@Component、@Service、@Repository和@Controller等注解的类为Spring Bean。虽然许多开发者都熟悉它的基本用法,但其背后的复杂性和灵活性却往往被忽视。
本篇文章中我将详细的介绍@ComponentScan的各种配置选项,包括如何指定基础包、排除特定组件、自定义过滤器以及处理代理和作用域等配置项的使用。
首先,我们先准备以下要使用到的类
package com.pack.ioc.scan_componentpublic class A {}package com.pack.ioc.scan_component.childpublic class B {}// 配置类package com.pack.ioc.scan_componentpublic class AppConfig {}
接下来,准备一个运行的Main类
public class ComponentScanTest {public static void main(String[] args) {try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationCocontext.registerBean(AppConfig.class) ;context.refresh() ;// 打印当前容器中注册的所有bean集合System.out.println(Arrays.toString(context.getBeanDefinitionNames())) ;}}}
在后续的每一个示例演示中都会基于上面定义的类进行,这过程中我们还会创建更多的类。
2. 实战案例
2.1 基本使用
在上面准备的基本环境中,在AppConfig类是仅仅是添加了@ComponentScan注解,没有配置任何的选项,运行程序输出如下:
[..., com.pack.ioc.scan_component.AppConfig, a, b]
这里将我们自定义的类都打印了(这里省去Spring容器自动注册bean)。根据运行结果看出,虽然A,B不再同一个包中,但是B所在的包是AppConfig所在的包的子包中,所以@ComponentScan会处理当前包及其子包中的类。
2.2 指定basePackages
@ComponentScan(basePackages = {"com.pack.ioc.scan_component.child"})public class AppConfig {}
这里只是指定了一个子包,运行结果:
[..., com.pack.ioc.scan_component.AppConfig, b]
此时,A类不会包含在内。
2.3 指定basePackageClasses
@ComponentScan(basePackageClasses = {B.class})public class AppConfig {}
这里可以通过指定Class对象,这样会根据该Class对象所在的包进行扫描,运行结果
[..., com.pack.ioc.scan_component.AppConfig, b]
Spring内部会自动将你配置的所有Class对象所在的包,作为扫描路径。
2.4 指定includeFilters
注:@ComponentScan注解还包括excludeFilters排除过滤属性,这里我们只介绍includeFilters属性。
由于includeFilters中配置的是@Filter,而该注解中定义了type属性,该type属性是FilterType枚举类,其中包括以下值:
public enum FilterType {ANNOTATION,ASSIGNABLE_TYPE,ASPECTJ,REGEX,CUSTOM}
接下来,我们将依次介绍这些枚举值。
过滤类型FilterType.ANNOTATION
在目标类上存在指定的注解。
首先,我们自定义一个注解,然后通过该注解标准类。
public Pack {}// 使用该注解标注下面的类public class C {}
在默认情况下,Spring容器是不会识别到C这个类的,通过如下配置后就不一样了
@ComponentScan(includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Pack.class})})public class AppConfig {}
运行结果
[..., com.pack.ioc.scan_component.AppConfig, a, c, b]
Spring默认的@Component注解及我们自定义的@Pack注解都生效了。
过滤类型FilterType.ASPECTJ
与目标类匹配的AspectJ类型表达式。
首先,自定义如下类
public class PackClass {}
修改配置类
@ComponentScan(includeFilters = {@Filter(type = FilterType.ASPECTJ,pattern = {"com.pack..Pack*"})})public class AppConfig {}
com.pack..Pack*:com.pack包及子包下的以Pack开头的类都将匹配。
运行结果
[..., com.pack.ioc.scan_component.AppConfig, a, b, packClass]
注:在本例中@ComponentScan扫描的包还是当前AppConfig所在的包及其子包。
过滤类型FilterType.ASSIGNABLE_TYPE
目标类可以是这里给定的类或接口。
首先,自定义DAO接口
public interface DAO {}public class Person implements DAO {}
修改配置类
@ComponentScan(includeFilters = {@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {DAO.class})})public class AppConfig {}
只要我们定义的类实现了DAO接口都将匹配。
运行结果
[..., com.pack.ioc.scan_component.AppConfig, a, b, person]
通过classes你可以指定多个接口或者是父类。
过滤类型FilterType.REGEX
与目标组件的类名匹配的正则表达式。
@ComponentScan(includeFilters = {@Filter(type = FilterType.REGEX,pattern = {"com\\.pack\\..*.Pack.*"})})public class AppConfig {}
运行结果
[..., com.pack.ioc.scan_component.AppConfig, a, b, packClass]
注意:在写正则表达式时 "." 需要进行转义。
过滤类型FilterType.CUSTOM
org.springframework.core.type.TypeFilter接口的自定义实现。
public class PackTypeFilter implements TypeFilter {public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata() ;return annotationMetadata.hasAnnotation(Pack.class.getName()) ;}}
在自定义的类型过滤中,我们判断类上是否存在@Pack。
修改配置类
@ComponentScan(includeFilters = {@Filter(type = FilterType.CUSTOM,classes = {PackTypeFilter.class})})public class AppConfig {}
这不仅包括了内置的过滤器会生效(过滤@Component注解),同时我们自定义的过滤器也会起到作用。
以上针对每一种过滤类型讲解完了,你是否有点晕,这里指定了type后,接下来是配置pattern还是classes属性呢?大家记住如下组合即可
| 类型 | 配置的属性 |
| ANNOTATION、ASSIGNABLE_TYPE、CUSTOM |
classes |
| ASPECTJ、REGEX |
pattern |
只有这两种组合没有其它组合。
2.5 指定lazyInit
指定扫描到的类是否进行延迟初始化。
public class A implements InitializingBean {public void init() {System.err.println("A init...") ;}public void afterPropertiesSet() throws Exception {System.err.println("A afterPropertiesSet...") ;}}
修改配置类
@ComponentScan(lazyInit = true)public class AppConfig {}
运行结果
并没有打印在A类中定义的初始化回调。
2.6 指定useDefaultFilters
指示是否应启用对带有@Component、@Repository、@Service或@Controller注解的类的自动检测。
修改配置类
@ComponentScan(useDefaultFilters = false,includeFilters = {@Filter(type = FilterType.CUSTOM,classes = {PackTypeFilter.class})})public class AppConfig {}
运行结果
[..., com.pack.ioc.scan_component.AppConfig, c]
我们定义的A,B两个类都没有打印(这2个类是有@Component注解的)。这说明我们就关闭了默认的过滤器功能。
2.7 指定nameGenerator
通过指定该属性,扫描到的组件将通过该值来生成对应的beanName。
自定义BeanNameGenerator
public class PackBeanNameGenerator extends AnnotationBeanNameGenerator {public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {String beanName = super.generateBeanName(definition, registry) ;beanName = beanName.replaceFirst("^.", beanName.substring(0, 1).toUpperCase()) ;return "pack" + beanName ;}}
在这里我们将所有的beanName都添加一个 pack 前缀。
修改配置类
@ComponentScan(nameGenerator = PackBeanNameGenerator.class)public class AppConfig {}
运行结果
[..., com.pack.ioc.scan_component.AppConfig, packA, packB]
除配置类外,其它beanName都以pack开头。
2.8 指定scopedProxy
指示是否应为检测到的组件生成代理。
首先,修改A类如下
public class A implements InitializingBean {}
在类A上添加了@Scope作用域注解。注:要使得scopedProxy生效你必须对bean定义作用域(只要有该注解即可,而至于什么作用域无所谓)。
修改配置类
@ComponentScan(scopedProxy = ScopedProxyMode.TARGET_CLASS)public class AppConfig {}
这里设置为代理目标类。
接下来,我们做如下的测试
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {context.registerBean(AppConfig.class) ;context.refresh() ;System.out.println(Arrays.toString(context.getBeanDefinitionNames())) ;System.err.println(context.getBean(A.class).getClass().getName()) ;}
运行结果

Bean A生成的是通过cglib代理后的对象。
2.9 指定scopeResolver
用于解析检测到的组件的作用域。在默认情况下检测的是@Scope定义的作用域。
特别说明:只有当@ComponentScan中的scopedProxy属性设置为ScopedProxyMode.DEFAULT时,该属性才会生效。
接下来,通过实例讲解
首先,我们自定义作用域注解(为了体现自定义自定义ScopeMetadataResolver的意义)。
public PackScope {String value() default "";String scopeName() default "";ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;}
该自定义作用域注解与@Scope中定义的属性一致。
自定义作用域解析器
public class PackAnnotationScopeMetadataResolver extends AnnotationScopeMetadataResolver {public PackAnnotationScopeMetadataResolver() {setScopeAnnotationType(PackScope.class) ;}}
解析器中设置我们自定义的作用域注解。
修改的类A
(proxyMode = ScopedProxyMode.TARGET_CLASS)public class A implements InitializingBean {}
运行结果

自定义的作用域注解及自定义解析器生效了。
以上是关于@ComponentScan注解使用的全部内容,你学会了吗?欢迎留言讨论。
以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏
推荐文章
Spring Boot 不同HTTP客户端 同步&异步请求对比
Spring Boot 结合 Redis客户端缓存技术让系统性能飙升





