宝马硬顶敞篷跑车 configuration有哪些选择?,

@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@ToStringpublic 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。为了兼容及灵活配置扫描路径,这个注解定义了很多的参数,具体的:


1. basePackages与value: 用于指定包的路径,进行扫描

2. basePackageClasses: 用于指定某个类的包的路径进行扫描

3. nameGenerator: bean的名称的生成器

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

5. includeFilters: 包含的过滤条件

FilterType.ANNOTATION:按照注解过滤

FilterType.ASSIGNABLE_TYPE:按照给定的类型

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

FilterType.REGEX:正则 (不常用)

FilterType.CUSTOM:自定义规则

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


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


​编辑


@Componentpublic interface DeptDao {}



@Componentpublic class DeptDaoClass_Component {}



@Controllerpublic class DeptDaoClass_Controller {}



@Repositorypublic class DeptDaoClass_Repository {}



@Servicepublic 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);    }



testConfig01deptDaoClass_ComponentdeptDaoClass_ControllerdeptDaoClass_RepositorydeptDaoClass_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);    }



testConfig02deptDaoClass_ComponentdeptDaoClass_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);    }



testConfig03deptDaoClass_ControllerdeptDaoClass_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);    }



testConfig04deptDaoClass_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);    }



testConfig05deptDaoClass_ComponentdeptDaoClass_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);    }



testConfig06deptDaoClass_ComponentdeptDaoClass_ControllerdeptDaoClass_Service



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


五、源码


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


​编辑


Resource<> resources = getResourcePatternResolver().getResources(packageSearchPath);



@Overridepublic Resource<> getResources(String locationPattern) throws IOException {   if (this.resourceLoader instanceof ResourcePatternResolver) {	return ((ResourcePatternResolver) this.resourceLoader).getResources(locationPattern);   }   return super.getResources(locationPattern);}



​编辑


​编辑


​编辑


​编辑


​编辑


​编辑



2024-08-04

后面没有了,返回>>电动车百科