背景

默认是扫描当前application启动类所在的包及其子包

例如我们的LakerApplication代码如下:

package io.gitee.lakernote;
@SpringBootApplication
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}

扫描的包为io.gitee.lakernote包及其子包

@SpringBootApplication内部原理,其实用的就是@ComponentScan注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

excludeFilters ?FilterType?你有疑问?下面会详细介绍的,这里你先别管。

如果我们想扫描其他的多个包目录应该怎么做呢?例如,扫描下面2个包。

  • io.gitee.lakernote
  • io.gitee.lakernote2

方案

方式一 @ComponentScan

@SpringBootApplication
@ComponentScan(basePackages = {"io.gitee.lakernote2","io.gitee.lakernote"})
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}
或者
@SpringBootApplication
@ComponentScan("io.gitee")
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}
或者
@SpringBootApplication
@ComponentScan("io.gitee.lakernote")
@ComponentScan("io.gitee.lakernote2")
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}
或者
@SpringBootApplication
@ComponentScan(basePackageClasses = HelloWorldController2.class,basePackages = "io.gitee.lakernote")
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}

注意@ComponentScan会使默认application的包失效。

方式二 @SpringBootApplication(scanBasePackages = {"xxx"})

原理跟方式一相同

@SpringBootApplication(scanBasePackages = {"io.gitee.lakernote","io.gitee.lakernote2"})
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}

方式三 @ComponentScans

原理跟方式一相同

@SpringBootApplication
@ComponentScans({
        @ComponentScan(basePackageClasses = HelloWorldController2.class),
        @ComponentScan("io.gitee.lakernote")}
)
public class LakerApplication {
    public static void main(String[] args) {
        SpringApplication.run(LakerApplication.class, args);
    }
}

详解

由上可知都是基于@ComponentScan注解做的,那么我们来看下它是如何使用的。

@ComponentScan注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。

@ComponentScan注解代码如下:
注意看里面的注释,非常详细。

package org.springframework.context.annotation;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(ComponentScans.class)// 这个注解可以同时使用多个
public @interface ComponentScan {
   //  用于指定包的路径,进行扫描io.gitee.lakernote
   @AliasFor("basePackages")
   String[] value() default {};
   //  同value用于指定包的路径,进行扫描 io.gitee.lakernote
   @AliasFor("value")
   String[] basePackages() default {};
   // 用于指定某个类的包的路径进行扫描 HelloService.class
   Class<?>[] basePackageClasses() default {};
   //  bean的名称的生成器
   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
   // 用于解析@Scope注解
   Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
   // 用来设置类的代理模式
   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
   // 扫描路径 如 resourcePattern = "**/*.class"
   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
   // 默认的过滤规则是开启的,默认装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中。
   boolean useDefaultFilters() default true;
   // 对被扫描的包或类进行过滤,若符合条件,不论组件上是否有注解,Bean对象都将被创建,需要借助@ComponentScan.Filter来完成
   // @ComponentScan(value = "io.laker",includeFilters = {
   //     @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}),
   //     @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Laker.class}),
   //     @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {LakerTypeFilter.class}),
   //     @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$")
   // })
   Filter[] includeFilters() default {};
   // 指定哪些类型不进行组件扫描。  用法和includeFilters一样
   Filter[] excludeFilters() default {};
   // 指定注册扫描的Bean延迟初始化。	
   boolean lazyInit() default false;

   @Retention(RetentionPolicy.RUNTIME)
   @Target({})
   @interface Filter {
      // 指定过滤的规则,有以下几种
      //       FilterType.ANNOTATION:按照注解过滤
      //       FilterType.ASSIGNABLE_TYPE:按照给定的类型
      //       FilterType.ASPECTJ:使用ASPECTJ表达式
      //       FilterType.REGEX:正则
      //       FilterType.CUSTOM:自定义规则
      FilterType type() default FilterType.ANNOTATION;
		
      // 过滤器的参数,参数必须为class数组,单个参数可以不加大括号
      // 只能用于 ANNOTATION 、ASSIGNABLE_TYPE 、CUSTOM 这三个类型
 	  //  @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class, Service.class})
 	  //  @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {Laker.class})
 	  //  @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {LakerTypeFilter.class})
      @AliasFor("classes")
      Class<?>[] value() default {};

      @AliasFor("value")
      Class<?>[] classes() default {};
	  // 主要用于 ASPECTJ 类型和  REGEX 类型
      // ASPECTJ 参数为 ASPECTJ 表达式
      //  @ComponentScan.Filter(type = FilterType.ASPECTJ, pattern = "spring.annotation..*")
      // REGEX  参数为 正则表达式
      //  @ComponentScan.Filter(type = FilterType.REGEX, pattern = "^[A-Za-z.]+Dao$")
      String[] pattern() default {};
   }
}

场景

以下是项目中可能遇到的应用场景。

自定义注解扫描

扫描业务自定义注解@Laker标识的类到Spring容器中。

第一步自定义注解上加上@Component,参考@Controller注解其上加上@Component

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Laker {
    @AliasFor(annotation = Component.class)
    String value() default ""; 
}

第二步,在@ComponentScan注解中进行配置,如下:

@Configuration
@ComponentScan("io.gitee")
public class LakerConfig {
}

如果第一步中没有加上@Component注解,或者设置了useDefaultFilters = false,则应该设置includeFilters

@Configuration
@ComponentScan(value = "io.gitee",
        useDefaultFilters = false,
        includeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Laker.class})
        })
public class LakerConfig {
}

第三步,在类上加上自定义注解

@Laker("lakerService")
public class LakerService {
}

LakerService就会被加入到Spring容器中。

自定义扫描过滤规则

这个就是自由度非常高了,想怎么玩就怎么玩。

第一步,自定义一个类LakerTypeFilter实现TypeFilter接口

public class LakerTypeFilter implements TypeFilter {
    /**
     * 两个参数的含义:
     * metadataReader:包含读取到的当前正在扫描的类的信息
     * metadataReaderFactory:可以获取到当前正在扫描的类的其他类信息(如父类和接口)
     * match方法返回false即不通过过滤规则,true通过过滤规则
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //可以通过这个读想获取类的一些元数据信息,如类的class对象、是否是接口、是否有注解、是否是抽象类、父类名称、接口名称、内部包含的之类列表等等
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        Class curClass = null;
        try {
            //当前被扫描的类
            curClass = Class.forName(metadataReader.getClassMetadata().getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //判断curClass是否是IService类型
        boolean result = IService.class.isAssignableFrom(curClass);
        return result;
    }
}

第二步,在@ComponentScan注解中进行配置,如下:

@Configuration
@ComponentScan(value = "io.gitee",
        useDefaultFilters = true, // 装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中
        includeFilters = {
            @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {LakerTypeFilter.class})
        }
)
public class LakerConfig {
}

这个需求也可以这样做,使用FilterType.ASSIGNABLE_TYPE

@Configuration
@ComponentScan(value = "io.gitee",
        useDefaultFilters = true, // 装配标识了@Controller,@Service,@Repository,@Component注解的类到spring容器中
        includeFilters = {
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = IService.class)
        }
)
public class LakerConfig {
}

参考

  • https://blog.csdn.net/huangjhai/article/details/104600328
  • https://blog.csdn.net/chenzoff/article/details/124267671
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐