首页 > 其他分享 >@ComponentScan详解&@SpringBootApplication的scanBasePackages属性

@ComponentScan详解&@SpringBootApplication的scanBasePackages属性

时间:2023-01-10 11:22:31浏览次数:63  
标签:basePackages default SpringBootApplication ComponentScan 注解 com scanBasePackages

一、@ComponentScan源码

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    Filter[] includeFilters() default {};

    Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}
  • @Target({ElementType.TYPE}) 表示只可以声明在类上

  • value 表示用法如 @ComponentScan(value="")
    也可以简写为 @ComponentScan("") ,省略value=

  • basePackages,表示用法如@ComponentScan(basePackages=""),由于和value等价(@AliasFor("value")),也可简写为@ComponentScan("")

  • nameGenerator: bean的名称的生成器

  • useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测

  • includeFilters: 包含的过滤条件

    • FilterType.ANNOTATION:按照注解过滤
    • FilterType.ASSIGNABLE_TYPE:按照给定的类型
    • FilterType.ASPECTJ:使用ASPECTJ表达式
    • FilterType.REGEX:正则
    • FilterType.CUSTOM:自定义规则
  • excludeFilters: 排除的过滤条件,用法和includeFilters一样

一个稍完整的示例:

// com.jiaobuchong.business 和 com.jiaobuchong.user.servic 下的类都不会被扫描
@ComponentScan(basePackages = {"com.jiaobuchong.order.service"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX,
                pattern = "com.jiaobuchong.business\\..*"),
                @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.jiaobuchong.user.service\\..*")})

二、@SpringBootApplication源码

// 指定@SpringBootConfiguration注解的使用范围,可以用在类、接口、枚举上
@Target(ElementType.TYPE)
// 指定@SpringBootConfiguration注解的生命周期,不仅被保存到Class文件中,JVM加载Class文件之后,仍然存在。
@Retention(RetentionPolicy.RUNTIME)
// 生成JavaDoc时,会把@SpringBootConfiguration注解给显示出来。
@Documented
// 表明@SpringBootConfiguration注解可以被子类继承
@Inherited
// 表明被@SpringBootConfiguration注解标注的类是一个配置类,
// 并会将当前类内部声明的一个或多个以@Bean注解标记的方法的实例纳入到Spring容器中,并且实例名就是方法名。
@SpringBootConfiguration
// 表明被@SpringBootConfiguration注解标注的类会开启自动配置
@EnableAutoConfiguration
// 扫描当前包及其子包下被@Component,@Controller,@Service,@Repository等注解标记的类并纳入到Spring容器中进行管理。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    // 排除掉不需要自动装配的类,传入的参数是Class类型
    // 例如 @SpringBootApplication(exclude = {MybatisAutoConfiguration.class})
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    // 排除掉不需要自动装配的类,传入的参数是类的全限定名字符串
    // 例如 @SpringBootApplication(excludeName = {"org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration"})
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    // 以字符串的方式配置需要扫描包的路径
    // 例如 @SpringBootApplication(scanBasePackages = {"com.panda.map.struct.demo"})
    // @ComponentScan注解的basePackages属性的别名
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    // 指定某些类所在包下的所有组件都会被扫描为Bean被Spring容器加载
    // 例如 @SpringBootApplication(scanBasePackageClasses = User.class)
    // @ComponentScan注解的basePackageClasses属性的别名
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    // 指定Bean的Name的生成策略
    // @ComponentScan注解的nameGenerator属性的别名
    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    // 指定被@SpringBootConfiguration注解标注的类中被@Bean注解标注的方法是否可以被代理,默认可以被代理
    // @Configuration注解的proxyBeanMethods方法的别名
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

@Target、@Retention、@Documented和@Inherited注解是 Java 提供的元注解,本文不作过多的讲解。

从@SpringBootApplication注解的源码可以看出,它是一个复合注解,其核心注解有三个,分别是:标注某个类是配置类的@SpringBootConfiguration注解,启动自动配置的@EnableAutoConfiguration注解,以及指定需要扫描的包路径的@ComponentScan注解。

而@EnableAutoConfiguration注解是SpringBoot 自动化配置的核心所在。

scanBasePackages属性

本质上,SpringBootApplicatioscan中的scanBasePackages属性底层原理正是复用了@ComponentScan,因此语法和意义基本一致

@AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
String[] scanBasePackages() default {};

@AliasFor标签表示别名的意义,可以理解为等价于

因此,下面2种用法相同:

@SpringBootApplication (scanBasePackages="com.test")
public class MyClass {


package com.test;
@ComponentScan("com.test")
@SpringBootApplication
public class MyClass {

三、注意事项

当使用Spring Boot项目时,可以不指定加载路径,即不使用@ComponentScan或@SpringBootApplication (scanBasePackages="com.test"),默认会加载MyClass所在的包

举个例子,看下面定义的类:

package com.test;
@SpringBootApplication
public class MyClass {}

MyClass 的package为com.test,这个类使用了@SpringBootApplication注解,该注解定义了Spring将自动扫描包com.test及其子包下的bean。

如果你项目中所有的类都定义在com.test包及其子包下,那你不需要做任何事。

但假如你一个类定义在包com.test2下,则你需要将这个新包也纳入扫描的范围,有两个方案可以达到这个目的:

方案1, 定义@SpringBootApplication扩大范围或者定义分别扫描两个包

@SpringBootApplication(scanBasePackages = {"com"})或者@SpringBootApplication(scanBasePackages = {"com.test","com.test2"})

方案2, 使用@CoponentScan扩大范围或者定义分别扫描两个包
@CoponentScan(“com”)或者@ComponentScan({“com.test”,”com.test2”})

注解失效

  1. 如果 ComponentScan 只包括一个值且就是默认启动类目录,SpringBootApplication 生效, ComponentScan 注解失效,同时ComponentScan报错:
package com.test;
@ComponentScan("com.test")
@SpringBootApplication
public class MyClassApplication {}

ComponentScan注解上报错,提示:Redundant declaration: @SpringBootApplication already applies given @ComponentScan

  1. 如果 ComponentScan 指定多个具体子目录,此时 SpringBootApplication 会失效,Spring 只会扫描 ComponentScan 指定目录下的注解。如果恰好有ComponentScan指定目录外的 Controller 类,很遗憾,这些控制器将无法访问。

原因:在org.springframework.context.annotation.ComponentScanAnnotationParser#parse方法内有着获取basePackages的业务逻辑,源码如下所示

Set<String> basePackages = new LinkedHashSet<>();
// 获取@ComponentScan注解配置的basePackages属性值
String[] basePackagesArray = componentScan.getStringArray("basePackages");
// 将basePackages属性值加入Set集合内
for (String pkg : basePackagesArray) {
 String[] tokenized = 		StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
 Collections.addAll(basePackages, tokenized);
}
// 获取@ComponentScan注解的basePackageClasses属性值
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
 // 获取basePackageClasses所在的package并加入Set集合内
 basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果并没有配置@ComponentScan的basePackages、basePackageClasses属性值
if (basePackages.isEmpty()) {
 // 使用Application入口类的package作为basePackage
 basePackages.add(ClassUtils.getPackageName(declaringClass));
}

获取basePackages分为了那么三个步骤,分别是:

  1. 获取@ComponentScan注解basePackages属性值
  2. 获取@ComponentScan注解basePackageClasses属性值
  3. Application入口类所在的package作为默认的basePackages

根据源码也就证实了,为什么我们配置了basePackagesbasePackageClasses后会把默认值覆盖掉,这里其实也不算是覆盖,是根本不会去获取Application入口类的package

扫描Packages下的Bean

获取到全部的Packages后,通过org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan方法来扫描每一个Package下使用注册注解(@Component@Service@RestController...)标注的类,源码如下所示:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  // 当basePackages为空时抛出IllegalArgumentException异常
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  // 遍历每一个basePackage,扫描package下的全部Bean
  for (String basePackage : basePackages) {
    // 获取扫描到的全部Bean
    Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
    // 遍历每一个Bean进行处理注册相关事宜
    for (BeanDefinition candidate : candidates) {
      // 获取作用域的元数据
      ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
      candidate.setScope(scopeMetadata.getScopeName());
      // 获取Bean的Name
      String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
      if (candidate instanceof AbstractBeanDefinition) {
        postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
      }
      // 如果是注解方式注册的Bean
      if (candidate instanceof AnnotatedBeanDefinition) {
        // 处理Bean上的注解属性,相应的设置到BeanDefinition(AnnotatedBeanDefinition)类内字段
        AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
      }
      // 检查是否满足注册的条件
      if (checkCandidate(beanName, candidate)) {
        // 声明Bean具备的基本属性
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
        // 应用作用域代理模式
        definitionHolder =
          AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
        // 写入返回的集合
        beanDefinitions.add(definitionHolder);
        // 注册Bean
        registerBeanDefinition(definitionHolder, this.registry);
      }
    }
  }
  return beanDefinitions;
}

在上面源码中会扫描每一个basePackage下通过注解定义的Bean,获取Bean注册定义对象后并设置一些基本属性。

注册Bean

扫描到basePackage下的Bean后会直接通过org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition方法进行注册,源码如下所示:

public static void registerBeanDefinition(
  BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
  throws BeanDefinitionStoreException {

  // 注册Bean的唯一名称
  String beanName = definitionHolder.getBeanName();
  // 通过BeanDefinitionRegistry注册器进行注册Bean
  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  // 如果存在别名,进行注册Bean的别名
  String[] aliases = definitionHolder.getAliases();
  if (aliases != null) {
    for (String alias : aliases) {
      registry.registerAlias(beanName, alias);
    }
  }
}

非 Spring Boot项目

在非Spring Boot项目中,我们必须显式地使用@ComponentScan注解定义被扫描的包,可以通过XML文件在应用上下文中定义或在Java代码中对应用上下文定义,否则会加载不到预期的bean。

站在巨人肩膀上摘苹果

https://blog.csdn.net/m0_45406092/article/details/115700557

http://www.manongjc.com/detail/55-nqqyzwasdxnhrmu.html

https://zhuanlan.zhihu.com/p/498969192

标签:basePackages,default,SpringBootApplication,ComponentScan,注解,com,scanBasePackages
From: https://www.cnblogs.com/eternityz/p/17039617.html

相关文章