文章目录
代码地址:https://gitee.com/MyFreeStyleWXH/spring-annotation
1. 回顾Xml方式注册组件
先回忆一下spring配置文件的开发方式。假设我们有个组件Student需要被Spring管理
- 在src/main/java下创建com.xinhua.study.bean包,并在该包下创建类Student.
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
}
- 如果我们需要把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>
- 接下来,测试从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。
- 创建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文件。
- 在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该如何做呢?
- 修改方法名
例如:将AnnotationConfig类中的student()改为student1(),这样就将组件id由student修改为student1,此时我们再去测试通过id为student获取Student实例将会报错
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'student' available
- 在@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 {
//省略
}
测试:
- 在com.xinhua.study包下分别创建以下包:controller、service、dao
- 在controller包下创建StudentController类且在类上添加注解@Controller,在service包下创建StudentService类且在类上添加注解@Service,在dao包下创建StudentDao类且在类上添加@Repository注解
- 在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);
}
}
- 测试结果:
可以看到,@ComponentScan注解指定扫描的包下的所有组件都已经成功注册到spring容器中,除了这些我们自定义的组件外,spring容器中还包含了一些Spring内置的组件。
3.2.3 @ComponentScan过滤详解
点开@ComponentScan注解源码,里面包含了includeFilters以及excludeFilters,分别指定了包扫描时只包含哪些组件或者要排除哪些组件。
查看源码,过滤器Filter的过滤类型FilterType包含以下几种:
3.2.3.1 通过注解类型过滤
- 修改AnnotationConfig类上@ComponentScan注解,扫描com.xinhua.study包下的组件,并排除注解类型为Controller以及Service的组件
@ComponentScan(value = "com.xinhua.study",excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})
- 测试:在AnnotationConfigTest类下添加测试方法
@Test
public void testComponentScanFilter(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationConfig.class);
//获取容器中注册的所有组件的名称
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String name : beanDefinitionNames) {
log.info("spring容器中所有的组件名称:[{}]",name);
}
}
- 测试结果
可以看出StudentController和StudentService已经没有注册到spring容器中。
excludeFilters 为排除指定的组件,includeFilters为只注册指定的组件,将@ComponentScan的excludeFilters改为 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 根据指定类的类型过滤
- 添加指定类型的过滤规则,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)
- 测试AnnotationConfigTest测试类中的testComponentScanFilter()方法
StudentDao类型的组件已经被添加到spring容器中。
3.2.3.3 根据自定义规则过滤
FilterType.CUSTOM即为自定义过滤规则方式过滤。看下源码:
自定义过滤规则必须是TypeFilter的实现类,因此我们需要去实现TypeFilter接口,自定义自己的过滤规则。
- 在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");
}
}
- 在@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)
- 测试AnnotationConfigTest测试类中的testComponentScanFilter()方法
- 测试结果:
类名中包含Student的都被注册到了Spring容器中
自定义规则扫描到的类不局限于被@Controller、@Service、@Repository、@Component注解标注,所有指定包下的类都会被扫描到
3.2.3.4 其他方式过滤
还剩下FilterType.ASPECTJ及FilterType.REGEX两种过滤方式:
- FilterType.ASPECTJ为使用ASPECTJ表达式方式过滤,
- FilterType.REGEX为使用正则表达式过滤
不常用,不再演示