我100%确定,你对@ComponentScan注解的了解仅限于皮毛

开发 前端
@ComponentScan注解,用于自动检测并注册带有@Component、@Service、@Repository和@Controller等注解的类为Spring Bean。虽然许多开发者都熟悉它的基本用法,但其背后的复杂性和灵活性却往往被忽视。

环境:Spring Boot3.2.5

1. 简介

在Spring中,@ComponentScan注解是一个强大且常用的工具,但很多人对其功能和使用方法可能只是一知半解。本文将深入探讨@ComponentScan注解的各个方面,揭示其隐藏的功能和最佳实践,帮助读者彻底掌握这一重要注解。

@ComponentScan注解,用于自动检测并注册带有@Component、@Service、@Repository和@Controller等注解的类为Spring Bean。虽然许多开发者都熟悉它的基本用法,但其背后的复杂性和灵活性却往往被忽视。

本篇文章中我将详细的介绍@ComponentScan的各种配置选项,包括如何指定基础包、排除特定组件、自定义过滤器以及处理代理和作用域等配置项的使用。

首先,我们先准备以下要使用到的类

package com.pack.ioc.scan_component
@Component
public class A {}


package com.pack.ioc.scan_component.child
@Component
public class B {}


// 配置类
package com.pack.ioc.scan_component
@Configuration
@ComponentScan
public class AppConfig {
}

接下来,准备一个运行的Main类

public class ComponentScanTest {


  public static void main(String[] args) {
    try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationCo
      context.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

在目标类上存在指定的注解。

首先,我们自定义一个注解,然后通过该注解标准类。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Pack {
}
// 使用该注解标注下面的类
@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 {


  @Override
  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

指定扫描到的类是否进行延迟初始化。

@Component
public class A implements InitializingBean {


  @PostConstruct
  public void init() {
    System.err.println("A init...") ;
  }


  @Override
  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 {


  @Override
  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类如下:

@Scope
@Component
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的意义)。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PackScope {


  @AliasFor("scopeName")
  String value() default "";


  @AliasFor("value")
  String scopeName() default "";
  
  ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

该自定义作用域注解与@Scope中定义的属性一致。

自定义作用域解析器。

public class PackAnnotationScopeMetadataResolver extends AnnotationScopeMetadataResolver {


  public PackAnnotationScopeMetadataResolver() {
    setScopeAnnotationType(PackScope.class) ;
  }
}

解析器中设置我们自定义的作用域注解。

修改的类A。

@PackScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class A implements InitializingBean {}

运行结果

图片

自定义的作用域注解及自定义解析器生效了。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2009-12-09 09:30:21

PHP foreach

2009-07-09 09:48:35

Google Chro微软竞争

2015-07-16 11:31:53

虚拟化SDN

2022-01-18 16:50:07

人工智能教育双减

2022-12-19 08:14:30

注解开发配置

2021-03-19 17:37:04

数据库

2022-02-20 07:28:13

Spring注解用法

2012-12-27 10:58:24

KVMKVM概念

2022-02-14 16:53:57

Spring项目数据库

2012-10-31 09:30:19

2022-05-10 14:08:56

云计算IT运营

2023-08-08 09:49:01

2017-08-16 20:26:19

OTT网络效应监管

2024-03-25 00:20:00

AI短视频

2021-11-05 08:29:13

数据校验Spring

2012-08-15 10:50:51

IE6

2024-01-29 10:09:59

数据库INT(3)INT(11)

2021-10-15 11:12:43

GoJava语言

2024-01-08 08:27:11

注解Bean代理

2020-11-20 07:58:04

Java
点赞
收藏

51CTO技术栈公众号