超详细总结Spring的配置注解
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情
前言
配置注解,指@Configuration,@ComponentScan,@Scope,@Lazy,@Conditional,@Import等注解,本篇文章将对这些注解的使用进行详细总结。
Springboot版本:2.4.1
Spring版本:5.3.2
正文
一. @Configuration
@Configuration注解修饰的类就是配置类,配合@Bean注解使用,可以用于向容器注册bean。如下是示例(所有类全部放在同一包目录下)。
业务类如下所示。
public class MyService {}
由@Configuration注解修饰的业务类如下所示。
@Configuration
public class MyConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
}
}
运行测试程序,可以将MyService的bean注册到容器中,并且bean名称为myService,日志打印如下所示。
二. @ComponentScan
@ComponentScan注解用于扫描并注册bean到容器中。如下是示例(所有类全部放在同一包目录下)。
业务类如下所示。
@Component
public class MyDao {}
@Component
public class MyService {}
配置类如下所示。
@Configuration
@ComponentScan(value = "com.learn.spring.annotation.componentscan",
useDefaultFilters = true)
public class MyConfig {}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
}
}
运行测试程序后,会把com.learn.spring.annotation.componentscan包目录下的MyService和MyDao的bean注册到容器中。上面的配置类的@ComponentScan注解的value指示扫描哪个包目录及其子目录下的类,同时useDefaultFilters字段为true(不显示指定也为true)表示使用默认过滤规则,默认过滤规则就是将指定路径下所有由@Controller,@Service,@Repository和@Component注解修饰的类的bean注册到容器中。
现在将useDefaultFilters配置为true,并编写一个自定义过滤器MyTypeFilter,如下所示。
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();
// 实现功能:将类名不包含"Dao"字符串的类注册到容器中
String className = classMetadata.getClassName();
return !className.contains("Dao");
}
}
MyConfig进行如下修改。
@Configuration
@ComponentScan(value = "com.learn.spring.annotation.componentscan",
includeFilters = {@Filter(type = FilterType.CUSTOM, value = {MyTypeFilter.class})},
useDefaultFilters = false)
public class MyConfig {}
再次运行测试程序,打印如下。
MyDao的bean没有被注册到容器中。
三. @Scope
@Scope注解用于设置bean作用域。示例如下(所有类全部放在同一包目录下)。
业务类如下所示。
public class MyService {}
配置类如下所示。
@Configuration
public class MyConfig {
@Bean
@Scope(value = "prototype")
public MyService myService() {
return new MyService();
}
}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
MyService myService1 = applicationContext.getBean(MyService.class);
MyService myService2 = applicationContext.getBean(MyService.class);
System.out.println(myService1 == myService2);
}
}
运行测试程序,会打印false,这是因为将MyService的bean作用域设置为了prototype,表示原型模式(多例)。
Spring的bean的作用域如下所示。
作用域 | 说明 |
---|---|
Singleton(单例) | 容器中仅有一个bean实例 |
Prototype(多例) | 每次从容器获取到的都是新的bean实例,容器将bean交给获取方后,就不再管理该bean的生命周期 |
Request(HTTP请求) | 每次HTTP请求都会产生一个新的bean实例 |
Session(会话) | 同一次会话共享同一个bean实例,不同会话bean实例不同 |
四. @Lazy
@Lazy注解用于设置bean延迟加载,即初始化容器的时候不会初始化延迟加载的bean,等到使用时才初始化bean。示例如下(所有类全部放在同一包目录下)。
业务类如下所示。
public class MyService {
public MyService() {
System.out.println("MyService init.");
}
}
配置类如下所示。
@Configuration
public class MyConfig {
@Lazy
@Bean
public MyService myService() {
return new MyService();
}
}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println("ApplicationContext initialization complete.");
MyService myService = applicationContext
.getBean(MyService.class);
}
}
运行测试程序,打印如下。
特别注意:延时加载只针对单例生效。
五. @Conditional
@Conditional表示满足一定条件时才会注册为容器中的bean。示例如下(所有类全部放在同一包目录下)。
两个业务类如下所示。
public class MyWindowsService {}
public class MyLinuxService {}
两个条件类如下所示。
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return osName != null && osName.contains("Windows");
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String osName = environment.getProperty("os.name");
return osName != null && osName.contains("Linux");
}
}
配置类如下所示。
@Configuration
public class MyConfig {
@Bean
@Conditional(WindowsCondition.class)
public MyWindowsService myWindowsService() {
return new MyWindowsService();
}
@Bean
@Conditional(LinuxCondition.class)
public MyLinuxService myLinuxService() {
return new MyLinuxService();
}
}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
}
}
运行测试程序,打印如下。
可见由于MyWindowsService满足条件,所以容器初始化时将MyWindowsService的bean注入到了容器中。
六. @Import
@Import注解可以向容器注册bean,有如下三种方式(所有类全部放在同一包目录下)。
1. 直接注册bean
业务类如下所示。
public class MyService {}
配置类如下所示。
@Configuration
@Import({MyService.class})
public class MyConfig {}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
}
}
运行测试程序,打印如下。
可见直接将MyService注册为了容器中的bean。
2. 通过ImportSelector注册bean
新增业务类如下所示。
public class MyDao {}
自定义一个ImportSelector的实现类,如下所示。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
"com.learn.spring.annotation.importt.MyDao"
};
}
}
配置类修改如下所示。
@Configuration
@Import({MyService.class, MyImportSelector.class})
public class MyConfig {}
运行测试程序,打印结果如下。
可见通过MyImportSelector将MyDao注册到了容器中。
3. 通过ImportBeanDefinitionRegistrar注册bean
新增业务类如下所示。
public class MyController {}
自定义一个ImportBeanDefinitionRegistrar的实现类,如下所示。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解信息
* @param registry 可以注册BeanDefinition
*
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new RootBeanDefinition(MyController.class);
registry.registerBeanDefinition("myController", beanDefinition);
}
}
配置类修改如下所示。
@Configuration
@Import({MyService.class, MyImportSelector.class,
MyImportBeanDefinitionRegistrar.class})
public class MyConfig {}
运行测试程序,打印如下。
可见将MyController注册到了容器中。
七. FactoryBean
FactoryBean并不是配置注解,但是通过FactoryBean可以帮助创建复杂的bean,例如像MyBatis整合Spring中,就大量使用到了FactoryBean。示例如下(所有类全部放在同一包目录下)。
业务类如下所示。
public class MyService {}
定义一个FactoryBean的实现类,如下所示。
public class MyFactoryBean implements FactoryBean<MyService> {
@Override
public MyService getObject() {
return new MyService();
}
@Override
public Class<?> getObjectType() {
return MyService.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
配置类如下所示。
@Configuration
public class MyConfig {
@Bean
public MyFactoryBean myFactoryBean() {
return new MyFactoryBean();
}
}
测试类如下所示。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
Object myService = applicationContext.getBean("myFactoryBean");
System.out.println(myService);
Object myFactoryBean = applicationContext.getBean("&myFactoryBean");
System.out.println(myFactoryBean);
}
}
运行测试程序,打印如下。
在配置类中,将MyFactoryBean注册成了容器中的bean,但是通过名字myFactoryBean无法将MyFactoryBean的bean从容器中获取出来,而获取出来的是MyService的bean。如果需要将MyFactoryBean的bean从容器中获取出来,需要在bean的名字前面加&
符号。
通过FactoryBean向容器注册的bean实际上是保存在FactoryBeanRegistrySupport的factoryBeanObjectCache字段中,factoryBeanObjectCache字段是一个Map,key是FactoryBean自己的bean的名字,value是FactoryBean创建的bean的实例,以此来实现单例。
总结
Spring的配置注解,更多的是为如何向容器注册bean服务,那么这里给出容器注册bean的总结。
- 配置类中使用@Bean注解向容器注册bean;
- 使用@ComponentScan注解扫描指定包路径下的类并注册,默认扫描@Controller,@Service,@Component和@Repository注解修饰的类;
- 使用@Import注解,有如下三种方式
- 使用@Import(类.class)直接向容器注册bean;
- 通过定义ImportSelector接口的实现类;
- 通过定义ImportBeanDefinitionRegistrar接口的实现类.
- 使用BeanFactory封装需要注册的bean的类。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情
转载自:https://juejin.cn/post/7206602224042803260