【spring注解】1.spring组件注册相关注解(上)

代码地址:https://gitee.com/MyFreeStyleWXH/spring-annotation

1. 回顾Xml方式注册组件

先回忆一下spring配置文件的开发方式。假设我们有个组件Student需要被Spring管理

  1. 在src/main/java下创建com.xinhua.study.bean包,并在该包下创建类Student.
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private Integer id;

    private String name;
}
  1. 如果我们需要把Student交给Spring容器管理,则需要在src/main/resources路径下创建spring.xml,且在配置文件中增加bean标签,如下代码:
<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!--  将Student组件交给Spring管理  -->
    <bean id="student" class="com.xinhua.bean.Student"/>

</beans>
  1. 接下来,测试从Spring容器中取出被Spring管理的Student组件。在src/test/java下创建包com.xinhua.study.test,并在该包下创建XmlConfigTest类。
@Slf4j
public class XmlConfigTest {

    @Test
    public void testXmlConfig(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        //通过id获取
        Student studentById = (Student) applicationContext.getBean("student");
        //通过类型获取
        Student studentByType = applicationContext.getBean(Student.class);

        log.info("通过id获取的实例:{}",studentById);

        log.info("通过类型获取的实例:{}",studentByType);

        log.info("studentById == studentByType ? {}",studentById == studentByType);
    }
}

运行结果如图所示:
配置文件方式运行结果
我们已经成功将Student组件交给Spring管理,且通过id及类型两种方式获取Student的实例对象。

2. 注解方式注册组件

通过注解方式将Student交给Spring管理,无需创建Spring.xml。

  1. 创建com.xinhua.study.config包,并在该包下创建类AnnotationConfig.
//1.添加注解
@Configuration    
public class AnnotationConfig {
    
    //2.将Student交给Spring管理,id默认为方法名
    @Bean
    public Student student(){
        return new Student();
    }
    
}

其中,@Bean注解对应配置文件中的bean标签,标明将该组件交给Spring管理。@Configuration标明这是一个配置类,等同于配置文件方式的spring.xml文件。

  1. 在src/test/java的com.xinhua.study.test包下创建AnnotationConfigTest类,测试从Spring容器中获取通过注解方式注入的Student组件
@Slf4j
public class AnnotationConfigTest {

    @Test
    public void testAnnotationConfig(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfig.class);
        Student studentById  = (Student) applicationContext.getBean("student");

        Student studentByType = applicationContext.getBean(Student.class);

        log.info("通过id获取的实例:{}",studentById);

        log.info("通过类型获取的实例:{}",studentByType);
        
		log.info("studentById == studentByType ? {}",studentById == studentByType);

    }
}

运行结果如图所示:
注解方式运行结果

3. 注解方式组件注册细节探究

3.1 修改组件id

通过2.注解方式,已经了解到,注入到spring容器的组件id默认为方法名称,如果我们要修改组件id该如何做呢?

  1. 修改方法名
    例如:将AnnotationConfig类中的student()改为student1(),这样就将组件id由student修改为student1,此时我们再去测试通过id为student获取Student实例将会报错
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'student' available
  1. 在@Bean注解后添加name属性
    @Bean(name=“student1”),此时我们如果仍通过student这个id获取Student实例,会和方式1报相同的错误.

3.2 包扫描方式注册组件

3.2.1 xml方式进行包扫描

在spring.xml配置文件中添加如下代码,扫描com.xinhua.study.bean下的所有被@Controller、@Service、@Repository、@Component注解标注的组件

<context:component-scan base-package="com.xinhua.study.bean"/>

3.2.2 注解方式进行包扫描

在AnnotationConfig配置类上添加@ComponentScan注解即可,效果等同于3.2.1xml方式。可以通过value属性配置要扫描的包路径

@Configuration
@ComponentScan(value = "com.xinhua.study")
public class AnnotationConfig {
	//省略
}

测试:

  1. 在com.xinhua.study包下分别创建以下包:controller、service、dao
  2. 在controller包下创建StudentController类且在类上添加注解@Controller,在service包下创建StudentService类且在类上添加注解@Service,在dao包下创建StudentDao类且在类上添加@Repository注解
    创建测试组件
  3. 在AnnotationConfigTest类下添加测试方法testComponentScan()
  	@Test
    public void testComponentScan(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfig.class);
        StudentDao studentDao = applicationContext.getBean(StudentDao.class);
        StudentService studentService = applicationContext.getBean(StudentService.class);
        StudentController studentController = applicationContext.getBean(StudentController.class);

        log.info("通过包扫描方式注册组件:StudentDao:[{}]",studentDao);
        log.info("通过包扫描方式注册组件:StudentService:[{}]",studentService);
        log.info("通过包扫描方式注册组件:StudentController:[{}]",studentController);

        //获取容器中注册的所有组件的名称
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            log.info("spring容器中所有的组件名称:[{}]",name);
        }
    }
  1. 测试结果:
    测试结果
    可以看到,@ComponentScan注解指定扫描的包下的所有组件都已经成功注册到spring容器中,除了这些我们自定义的组件外,spring容器中还包含了一些Spring内置的组件。

3.2.3 @ComponentScan过滤详解

点开@ComponentScan注解源码,里面包含了includeFilters以及excludeFilters,分别指定了包扫描时只包含哪些组件或者要排除哪些组件。
@ComponentScan注解结构
查看源码,过滤器Filter的过滤类型FilterType包含以下几种:
过滤器的过滤类型

3.2.3.1 通过注解类型过滤
  1. 修改AnnotationConfig类上@ComponentScan注解,扫描com.xinhua.study包下的组件,并排除注解类型为Controller以及Service的组件
@ComponentScan(value = "com.xinhua.study",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
  1. 测试:在AnnotationConfigTest类下添加测试方法
	@Test
    public void testComponentScanFilter(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfig.class);

        //获取容器中注册的所有组件的名称
        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames) {
            log.info("spring容器中所有的组件名称:[{}]",name);
        }
    }
  1. 测试结果
    注解类型过滤测试结果
    可以看出StudentController和StudentService已经没有注册到spring容器中。
    excludeFilters 为排除指定的组件,includeFilters为只注册指定的组件,将@ComponentScan的excludeFilters改为 includeFilters,测试结果如图:
    includeFilters测试结果
    不是说includeFilters是只包含哪些组件吗?为什么StudentDao依然注册成功了?
    这是因为我们没有禁用掉默认的过滤规则,默认的过滤规则是扫描指定包下所有的组件,因此我们需要先禁用掉默认的过滤规则,只包含才会生效,设置useDefaultFilters = false
@ComponentScan(value = "com.xinhua.study",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}),
    },useDefaultFilters = false)

再次测试:
禁用默认过滤规则测试结果
ok,和我们想的一致了,StudentDao没有注册在spring容器中。

3.2.3.2 根据指定类的类型过滤
  1. 添加指定类型的过滤规则,type = FilterType.ASSIGNABLE_TYPE
@ComponentScan(value = "com.xinhua.study", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {StudentDao.class})
}, useDefaultFilters = false)
  1. 测试AnnotationConfigTest测试类中的testComponentScanFilter()方法
    FilterType.ASSIGNABLE_TYPE过滤规则测试
    StudentDao类型的组件已经被添加到spring容器中。
3.2.3.3 根据自定义规则过滤

FilterType.CUSTOM即为自定义过滤规则方式过滤。看下源码:
FilterType.CUSTOM源码注释
自定义过滤规则必须是TypeFilter的实现类,因此我们需要去实现TypeFilter接口,自定义自己的过滤规则。

  1. 在com.xinhua.study.config包下创建MyTypeFilter类,实现TypeFilter接口,在过滤方法中只打印一下扫描到的类的名称
@Slf4j
public class MyTypeFilter implements TypeFilter {

    /**
     * 过滤规则
     *
     * @param metadataReader 读取当前正在扫描的类的信息
     * @param 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();
        log.info("扫描到的类的类名称:[{}]",className);
        
        //如果类名称中包含Student,就注册到容器中
        return className.contains("Student");
    }
}
  1. 在@ComponentScan注解上添加过滤规则,并注释到之前测试的过滤规则
@ComponentScan(value = "com.xinhua.study", includeFilters = {
//        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}),
//        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {StudentDao.class}),
        @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
}, useDefaultFilters = false)
  1. 测试AnnotationConfigTest测试类中的testComponentScanFilter()方法
  2. 测试结果:
    自定义规则测试结果
    类名中包含Student的都被注册到了Spring容器中

自定义规则扫描到的类不局限于被@Controller、@Service、@Repository、@Component注解标注,所有指定包下的类都会被扫描到

3.2.3.4 其他方式过滤

还剩下FilterType.ASPECTJ及FilterType.REGEX两种过滤方式:

  • FilterType.ASPECTJ为使用ASPECTJ表达式方式过滤,
  • FilterType.REGEX为使用正则表达式过滤

不常用,不再演示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值