0
点赞
收藏
分享

微信扫一扫

Spring注解:@ComponentScan

清冷的蓝天天 2021-10-15 阅读 49
技术分享
@ComponentScan

其实简单理解@ComponentScan的作用,就是根据定义好的扫描路径,把符合规则的类装配到Spring容器中。我们可以看一下@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;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.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 {};
    }
}

上述的代码内容,其中:

  • basepackages和value值:用于指定扫描路径。
  • basePackageClasses:用于指定某个类的包路径扫描。
  • useDefaultFilters:是否开启对@Component,@Repository,@Service,@Controller的类进行扫描检测。
  • includeFilters:包含的过滤条件。
  • excludeFilters: 排除的过滤条件,用法和includeFilters一样。
Demo

接下来我们来进行简单的测试:
新建一个SpringBoot项目,也可以是普通的maven项目,这里用SpringBoot项目作为演示,如果是maven项目,需要添加Spring的maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>

如果是SpringBoot项目,直接使用下面的依赖即可

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

简单创建几个类,包含dao、service、controller等。



在项目的主配置类上贴上注解@ComponentScan,并制定其扫描的包路径。

@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation")
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}

接下来写一个测试类,测试被Spring扫描的类有哪些。

@Test
public void testComponentScan() {
    ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(SpringAnnotationApplication.class);
    //getBeanDefinitionNames方法的作用是:获取容器中的Bean的名称
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : beanDefinitionNames) {
        System.out.println(name);
    }
}

测试结果如下(只截取其中一部分)

org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springAnnotationApplication
bookController
bookDao
bookServiceImpl
org.springframework.boot.autoconfigure.AutoConfigurationPackages

其实在测试结果中有很多,大部分是Spring内部需要装配的Bean,而我们自己定义的也在其中,说明了在启动Spring容器的时候,Spring已经去扫描了指定包下的贴有符合规则注解的类,并装配到容器中了。

excludeFilters

excludeFilters的作用是,指定排除规则,让Spring不要去扫描指定规则的类。

@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}

在excludeFilters里面通过@Filter注解,可以指定排除规则,那么在@Filter里面,关于过滤的规则类型,我们可以点进去看看FilterType,有如下几种:

public enum FilterType {
    ANNOTATION,
    ASSIGNABLE_TYPE,
    ASPECTJ,
    REGEX,
    CUSTOM;

    private FilterType() {
    }
}
  • ANNOTATION:注解,这也是默认的方式
  • ASSIGNABLE_TYPE:指定类型
  • ASPECTJ:AspectJ表达式
  • REGEX:正则
  • CUSTOM:自定义

上面的测试,是通过注解的方式过滤掉了贴有@Controller注解的类,那么接下来测试一下结果:

bookDao
bookServiceImpl
......

在控制台中,我们看到了只有dao和service,controller已经被过滤掉,即不会被装配到Spring容器中。那么接着继续看,因为classes中可以是数组,是可以过滤多个的。如下就是过滤了@Controller和@Service的类。

@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}

还可以用ASSIGNABLE_TYPE指定类型的方式,如下:

@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
//      @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookDao.class})
})
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}

BookDao是我们自己创建的类,用ASSIGNABLE_TYPE的方式,指定需要过滤掉的类。测试一下结果:

bookController
bookServiceImpl
......

除了使用以上这两种方式,还可以使用AspectJ表达式的方式和正则的方式,实际上这两种方式在实际应用中用到的比较少,所以在这里不做过多介绍,有兴趣的朋友可以去试试。
接着介绍一下CUSTOM自定义的方式。它其实是自己定义一个Filter规则,然后通过该规则实行过滤。做法就是创建一个自定义的过滤器,实现TypeFilter接口。

public class MyTypeFilter implements TypeFilter {

    /**
     *
     * @param metadataReader 读取到当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前的资源信息
        Resource resource = metadataReader.getResource();

        //自定义规则:如果当前类名包含Service或者Controller,就被过滤掉
        String className = classMetadata.getClassName();
        if (className.contains("Service") || className.contains("Controller")) {
            return true;
        }
        return false;
    }
}

在主配置类中做如下修改:

@SpringBootApplication
@ComponentScan(value = "com.caiyq.spring.annotation", excludeFilters = {
//      @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
//      @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookDao.class})
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class})
})
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}

测试一下结果:

bookDao
......
includeFilters

includeFilters的意思是扫描包含指定规则的类,其用法其实和excludeFilters的用法是差不多的,这里不再做重复阐述,我们用一个ANNOTATION的方式来测试一下。

@ComponentScan(value = "com.caiyq.spring.annotation", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}

当我们运行测试的时候,可想而知,其结果应该是只有@Controller的类被加载到了Spring容器中,那么看一下结果:

bookController
bookDao
bookServiceImpl
......

往往程序的执行结果和我们想的并不是完全一样,但是这样的结果,必定有其原因:
其实@Component,@Controller,@Service和@Repository三者功能相同,后三个注解是为了对应后端代码的三层结构由第一个注解"复制"而来的,查看源码可以发现,后三个注解上面都有一个@Component。也就是说@Controller,@Service和@Repository除了是它们自己,也是一个@Component。在进行includeFilters过滤时,如果useDefaultFilters =true,它会默认把所有包含@Component注解的类都进行扫描。所以上述程序,还需要加上useDefaultFilters =false,才能真正执行到如我们期望的结果。

@ComponentScan(value = "com.caiyq.spring.annotation", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
}, useDefaultFilters = false)
public class SpringAnnotationApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAnnotationApplication.class, args);
    }
}
举报

相关推荐

0 条评论