计算机系统应用教程网站

网站首页 > 技术文章 正文

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

btikc 2024-09-30 13:06:28 技术文章 11 ℃ 0 评论

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


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的定义列举一下:


?编辑


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



?编辑


?编辑


?编辑


?编辑


?编辑


?编辑



?

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表