关注「Java视界」公众号,获取更多技术干货

@ComponentScan配置老扫描不到Bean,这下彻底搞懂

一、@Configuration 和 @Bean

在说@ComponentScan注解前,先来搞明白@Configuration 和 @Bean 这两个注解是干啥的。

在没有注解驱动开发前,要想在spring中注入一个bean,是通过 .xml 文件来实现的:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="people" class="fengge.DTO.CarDTO">
        <property name="id" value=1></property>
        <property name="brand" value="BMW"></property>
    </bean>
</beans>
@AllArgsConstructor
@ToString
public class CarDTO {
    private Integer id;
    private String brand;
}
public class DemoTest {
    @Test
    public void test2() {
        // 1、默认从src下加载.xml,根据配置文件创建 容器对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2、从容器对象中取得 需要的 bean对象
        CarDTO carDTO = (CarDTO) context.getBean("carDTO");
        System.out.println(carDTO);
    }
}
CarDTO(id=1, brand=BMW)

有了注解后是这样实现的:

@Configuration // 相当于原来的xml文件,告诉spring这是个配置类
public class TestConfig {

    @Bean // 给spring注入一个bean,类型是返回值类型,id是默认是方法名
    public CarDTO carDTO(){
        return new CarDTO(1,"BMW");
    }
}
    /**
     * 注解 @Configuration 和 @Bean
     */
    @Test
    public void test() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
        CarDTO bean = applicationContext.getBean(CarDTO.class);
        Console.log(bean);
    }
CarDTO(id=1, brand=BMW)

这里可以看出:

  1. @Configuration :相当于原来的xml文件,告诉spring这是个配置类
  2. @Bean:给spring注入一个bean,类型是返回值类型,id 默认是方法名

二、容器的 getBeanDefinitionNames() 方法

applicationContext.getBeanDefinitionNames() 是获取容器中所有bean的name,通过这个可以判断 @ComponentScan()扫描配置是否正确。

这个是在这里介绍下这个方法的主要原因。

同时呢,上面我们说到了@Bean:注解生成的bean的 id 默认是方法名,若是指定了则为指定值,我们用getBeanDefinitionNames()来获取下就知道了,如下:

@Configuration // 相当于原来的xml文件,告诉spring这是个配置类
public class TestConfig {

    @Bean("car") // 给spring注入一个bean,类型是返回值类型,id是默认是方法名
    public CarDTO carDTO(){
        return new CarDTO(1,"BMW");
    }
}
    /**
     * applicationContext.getBeanDefinitionNames() 获取容器中所有bean的name,
     * 通过这个可以判断@ComponentScan()扫描配置是否正确
     */
    @Test
    public void test1() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig.class);
        Console.log(applicationContext.getBeanDefinitionNames());
    }

这里可以看到这个bean的name变成了“car”,而不是“carDTO”。

三、@ComponentScan

前面都是引子,现在开始介绍主角。

@ComponentScan(value = "XXX")  是用来告诉spring去哪扫描要注入的bean。为了兼容及灵活配置扫描路径,这个注解定义了很多的参数,具体的:

  • basePackages与value:  用于指定包的路径,进行扫描
  • basePackageClasses: 用于指定某个类的包的路径进行扫描
  • nameGenerator: bean的名称的生成器
  • useDefaultFilters: 是否开启对@Component,@Repository,@Service,@Controller的类进行检测
  • includeFilters: 包含的过滤条件

            FilterType.ANNOTATION:按照注解过滤

            FilterType.ASSIGNABLE_TYPE:按照给定的类型

            FilterType.ASPECTJ:使用ASPECTJ表达式 (不常用)

            FilterType.REGEX:正则 (不常用)

            FilterType.CUSTOM:自定义规则

  • excludeFilters: 排除的过滤条件,用法和includeFilters一样
     

为了介绍下面的例子,先把文件路径及几个bean的定义列举一下:

@Component
public interface DeptDao {

}
@Component
public class DeptDaoClass_Component {

}
@Controller
public class DeptDaoClass_Controller {

}
@Repository
public class DeptDaoClass_Repository {

}
@Service
public class DeptDaoClass_Service {

}

可以看到fengge.dao路径下有1个接口、4个类,且分别用 @Component,@Repository,@Service,@Controller 注解标注。

下面我们开始举例。 

举例一:value = "fengge.dao"

basePackages与value:  用于指定包的路径,进行扫描。这里扫描下fengge.dao路径下的bean。

@Configuration
@ComponentScan(value = "fengge.dao")
public class TestConfig01 {

}
    /**
     * 扫描路径 @ComponentScan(value = "fengge.dao")
     * 这个路径下有一个是接口类型DeptDao,不是具体的类,所以不会产生bean,控制台会打印 Ignored because not a concrete top-level 信息
     * 同时可以看到,@Bean、@Controller、@Service、@Component、@Repository注解的类都会被扫描成bean,注册到容器
     */
    @Test
    public void test3() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig01.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
testConfig01
deptDaoClass_Component
deptDaoClass_Controller
deptDaoClass_Repository
deptDaoClass_Service

当然真正结果打印不止这些bean,这里只展示了fengge.dao下的bean。

可以看到:

  1. @Controller、@Service、@Component、@Repository注解的类都会被扫描成bean,注册到容器
  2. 但接口类型即使加上@Component等注解,也不会实例化成bean,比如这里的 DeptDao 接口,可以看到并未生成对应的bean。

举例二:excludeFilters 排除某些范围

这里按注解类型排除了@Controller、@Service注解的bean。

@Configuration
@ComponentScan(value = "fengge.dao", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})
})
public class TestConfig02 {

}
    /**
     * excludeFilters排除某些范围
     * 这里按注解类型排除了@Controller、@Service注解的bean
     */
    @Test
    public void test4() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig02.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
testConfig02
deptDaoClass_Component
deptDaoClass_Repository

可以看到加了@Controller、@Service注解的bean不会被扫描到。

另外,主配置类(TestConfig、TestConfig01、TestConfig02)无论如何都会生成bean,不受扫描配置的影响。

举例三:includeFilters指定某些范围

(1)先看过滤类型为:FilterType.ANNOTATION:按照注解过滤

这里按注解类型指定@Controller、@Service注解的bean才能被扫描。

@Configuration
@ComponentScan(value = "fengge.dao", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})},
        useDefaultFilters = false
)
public class TestConfig03 {

    /**
     * includeFilters指定某些范围
     * 这里按注解类型排除了@Controller、@Service注解的bean
     * useDefaultFilters默认是true。表示使用默认的过滤器。即默认Filter就会处理@Component、@Controller、@Service、@Repository这些注解的Bean。
     * 所以useDefaultFilters = true,则不仅fengge.dao下的@Controller、@Service会扫描到,@Component、@Repository也会被扫描到
     */
    @Test
    public void test5() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig03.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
testConfig03
deptDaoClass_Controller
deptDaoClass_Service

可以看到只有@Controller、@Service注解的bean被扫描并生成。

(2)再看过滤类型为:FilterType.ASSIGNABLE_TYPE:按照给定的类型

@Configuration
@ComponentScan(value = "fengge.dao", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {DeptDaoClass_Component.class})},
        useDefaultFilters = false
)
public class TestConfig04 {

}
    @Test
    public void test6() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig04.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
testConfig04
deptDaoClass_Component

可以看到只有指定类型的bean。

(3)最后看下过滤类型为: FilterType.CUSTOM:自定义规则

public class MyFilterType implements TypeFilter {

    /**
     * MetadataReader 读取到当前正在扫描类的信息
     * MetadataReaderFactory 可以获取到其他任何类信息
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前类资源(类的路径)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        System.out.println("===============>" + className);
        if (className.contains("Co")) {
            return true;
        }
        return false;
    }
}
@Configuration
@ComponentScan(value = "fengge.dao", includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyFilterType.class})},
        useDefaultFilters = false
)
public class TestConfig05 {

}
    /**
     * includeFilters指定某些范围
     * FilterType.CUSTOM是自定义扫描类型,即className包含“Co”类型
     */
    @Test
    public void test7() {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig05.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
testConfig05
deptDaoClass_Component
deptDaoClass_Controller

这里是说自定义扫描className包含“Co”类型的bean。

四、@ComponentScans

@ComponentScans可包含多个@ComponentScan,扫描范围取并集。

@Configuration
@ComponentScans(value = {
        @ComponentScan(value = "fengge.dao", includeFilters = {
                @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyFilterType.class})},
                useDefaultFilters = false
        ),
        @ComponentScan(value = "fengge.dao", includeFilters = {
                @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class})},
                useDefaultFilters = false
        )
})
public class TestConfig06 {

}
    /**
     *  注解@ComponentScans可包含多个@ComponentScan,扫描范围取并集
     */
    @Test
    public void test8() throws ParseException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TestConfig06.class);
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        Stream.of(beanDefinitionNames).forEach(System.out::println);
    }
testConfig06
deptDaoClass_Component
deptDaoClass_Controller
deptDaoClass_Service

以上代码见:  https://github.com/ImOk520/myspringcloud

五、源码

源码中到底是怎样把 @ComponentScan(value = "fengge.dao") 这样路径下的所有bean找到又转化成resouse的呢?

Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
@Override
public Resource[] getResources(String locationPattern) throws IOException {
   if (this.resourceLoader instanceof ResourcePatternResolver) {
	return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);
   }
   return super.getResources(locationPattern);
}

posted @ 2022-06-25 14:02  沙滩de流沙  阅读(2832)  评论(0编辑  收藏  举报

关注「Java视界」公众号,获取更多技术干货