大家好,又见面了,我是你们的朋友全栈君。
文章目录
- 前言
- 一、SpringBoot特性
- 二、配置形式
- 1.应用主入口
- 1.定义Bean
- 2.Configuration类配置
- 3. Spring xml配置
- 3.AutoConfiguration类配置
- 三、启动流程
- 1.流程图
- 3.Bean定义加载顺序
- 思考总结
- 最后
前言
此文章讲解SpringBoot中配置Bean的几种形式,以及在SpringBoot启动流程中的先后顺序。
一、SpringBoot特性
Spring核心特性和概念:
SpringBoot核心特型:
- 约定大于配置 提供了默认的编码Bean配置扫描机制,默认的WebServer启动机制,默认的三方包Bean加载配置机制等等。
- Java代码定义配置Bean
二、配置形式
1.应用主入口
代码语言:javascript复制@SpringBootApplication
@ImportResource(locations = {"classpath:spring-config.xml"})
public class TestWebApp {
public static void main(String[] args) {
SpringApplication.run(TestWebApp.class, args);
}
}
@SpringBootApplication注解是一个组合注解,主要由@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan这三个注解组合而成。 因此@SpringBootApplication注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关bean被注册进Spring容器。
1.定义Bean
代码语言:javascript复制@RestController
public class HelloController {
}
@Service
public class TestService {
}
2.Configuration类配置
代码语言:javascript复制@Configuration
public class TestConfiguration {
@Bean
public TestConfigBean testConfig() {
return new TestConfigBean();
}
}
3. Spring xml配置
代码语言:javascript复制 <bean id="TestWebView" class="com.wei.spring.app.testweb.bean.TestWebView">
</bean>
3.AutoConfiguration类配置
在二方包模块中的resources/META-INF/spring.factories文件中按照如下格式注册AutoConfiguration类
代码语言:javascript复制org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.wei.spring.autoconfig.TestAutoConfigurationA,
com.wei.spring.autoconfig.TestAutoConfigurationB
二方包模块中定义AutoConfiguration类
代码语言:javascript复制@Configuration
public class TestAutoConfigurationA {
@Bean
public AutoBeanA autoBeanA() {
return new AutoBeanA();
}
}
@Configuration
@AutoConfigureAfter(TestAutoConfigurationA.class)
public class TestAutoConfigurationB {
@Bean
@ConditionalOnBean(value = AutoBeanA.class)
public AutoBeanB autoBeanB(AutoBeanA autoBeanA) {
return new AutoBeanB(autoBeanA);
}
}
在主应用的pom.xml引入二方包,spring就会进行自动装载
代码语言:javascript复制 <dependency>
<groupId>com.test</groupId>
<artifactId>spring-demo-autoconfig</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
三、启动流程
1.流程图
- SpringApplicationRunListener::starting发布开始启动事件
- ApplicationListener::onApplicationEvent发布ApplicationStartingEvent
- prepareEnvironment准备环境,创建Environment,配置PropertySources和Profile
- getOrCreateEnvironment获取Environment
- configureEnvironment配置Environment
- configurePropertySources设置PropertySources
- configureProfiles设置Profile
- SpringApplicationRunListener::environmentPrepared发布environmentPrepared
- ApplicationListener::onApplicationEvent发布ApplicationEnvironmentPreparedEvent
- createApplicationContext创建ApplicationContext
- prepareContext在Context执行前,进行准备工作,配置
- ApplicationContextInitializer::initialize扩展点,调用自定义注册的ApplicationContextInitializer的initialize方法,对ApplicationContext进行自定义设置
- SpringApplicationRunListener::contextPrepared发布contextPrepared
- ApplicationListener::onApplicationEvent发布ApplicationContextInitializedEvent
- load(ConfigurableApplicationContext)装载ApplicationContext,包括启动类
- BeanDefinitionLoader::load加载Bean定义(SpringBootApplication主程序类)
- SpringApplicationRunListener::contextLoaded发布事件
- ApplicationListener::onApplicationEvent发布ApplicationPreparedEvent
- refreshContext刷新Context,Context主流程
- prepareRefresh准备刷新的上下文环境,系统属性,环境变量
- obtainFreshBeanFactory获取BeanFactory并初始化,Bean配置读取解析,定制功能包括@Qualifier和@Autowired
- prepareBeanFactory对BeanFactory进行功能填充,SpEL,属性编辑器,AspectJ等
- postProcessBeanFactory后置处理BeanFacotry,扩展点,空函数
- invokeBeanFactoryPostProcessors激活各种BeanFacotry处理器,操作BeanDefinition
- 按照硬编码,PriorityOrdered注解,Ordered注解,以及剩余的的BeanDefinitionRegistryPostProcessor::postProcessBeanDefinitionRegistry
- 执行SpringBoot扩展注册的ConfigurationClassPostProcessor,用于实现SpringBoot核心的代码注册Bean的功能
- 循环解析ConfigurationClass
- parse解析Configuration类
- 解析Configuration类
- processMemberClasses解析成员嵌套类
- processPropertySource处理@PropertySource
- componentScanParser.parse处理@ComponentScan
- 递归解析扫描到的Configuration类.
- processImports处理@Import
- Process ImportedResource处理@ImportedResource
- Proces @Bean methods处理@Bean方法
- Process default methods on interfaces解析接口上的@Bean方法
- Process superclass, if any寻找父类,递归处理
- deferredImportSelectorHandler.process执行延迟ImportSelector
- loadBeanDefinitions将所有Bean定义注册到BeanFactory中 循环处理扫描到的所有Configuration类,将Configuration类中定义的Bean,ImportResource的xml文件的Bean,注册Bean到BeanFacotry Configuration类的顺序按照扫描到的顺序,顺序有以下规则: a.当前Configuration类ComponentScan扫描扫的Configuration类优先于当前Configuration类。 b.当前Configuration类优先于延时SelectImport的Configuration类(AutoConfiguration类)
- registerBeanDefinitionForImportedConfigurationClass
- loadBeanDefinitionsForBeanMethod
- loadBeanDefinitionsFromImportedResources
- loadBeanDefinitionsFromRegistrars
- 执行SpringBoot扩展注册的ConfigurationClassPostProcessor,用于实现SpringBoot核心的代码注册Bean的功能
- 执行BeanDefinitionRegistryPostProcessor::postProcessBeanFactory
- 按照硬编码,PriorityOrdered注解,Orderd注解,以及剩余的BeanFactoryPostProcessor::postProcessBeanFactory
- 按照硬编码,PriorityOrdered注解,Ordered注解,以及剩余的的BeanDefinitionRegistryPostProcessor::postProcessBeanDefinitionRegistry
- registerBeanPostProcessors注册拦截Bean创建处理器,会在getBean中创建Bean时调用
- 顺序注册PriorityOrdered的BeanPostProcessor
- 顺序注册Ordered的BeanPostProcessor
- 注册普通的BeanPostProcessor
- 注册MergedBeanDefinitionPostProcessor
- onRefresh扩展点
- createWebServer启动WebServer
- registerListeners
- finishBeanFactoryInitialization实例化ApplicationContext中剩余未实例化的所有Bean
- finishRefresh发布事件
- ApplicationListener::onApplicationEvent发布ServletWebServerInitializedEvent和ContextRefreshedEvent和DubboApplicationStateEvent
- afterRefresh扩展点,在ApplicationContext Refresh完后触发执行
- SpringApplicationRunListener::started发布程序已经启动事件
- ApplicationListener::onApplicationEvent发布ApplicationStartedEvent和AvailabilityChangeEvent
- callRunners扩展点,当程序已经启动后,调用自定义注册的ApplicationRunner和CommandLineRunner,执行运行代码
- SpringApplicationRunListener::running发布程序运行事件
- ApplicationListener::onApplicationEvent发布ApplicationReadyEvent和AvailabilityChangeEvent
3.Bean定义加载顺序
- 主应用的Bean定义先于AutoConfiguration中的Bean定义加载
- 主应用扫描的Bean定义之间,没有明确的加载顺序
- AutoConfiguration之间可以通过注解定义加载顺序。(注解@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder)
思考总结
ConditionalOnMissingBean和ConditionalOnBean 这两个注解只在当前已经扫描加入BeanFacotry的所有Bean中判断某个Bean定义是否存在,所以依赖于Bean定义的扫描加载顺序,不同加载顺序得到判断结果完全不同。 基于上面所说的Bean定义加载顺序,有几下实践建议:
- 不能在主应用的代码中使用注解,首先AutoConfiguration还没加载(判断AutoConfiguration中的Bean必定是不存在),此时用注解判断AutoCnofiguration定义的Bean没有意义。其次如果用于判断主应用中的Bean定义是否存在,由于主应用Bean加载顺序不确定,代码和xml配置文件修改会导致Bean扫描加载顺序变动引发问题,存在风险不确定性。最重要的是,主应用代码是由当前开发团队编写,代码已经确定Bean存在不存在,不需要在用这两个注解判断,注解反而给后续的人阅读代码引起误解,存在代码不清晰和不确定性。(代码编写追求的特点之一是清晰性。)
- 在AutoConfiguration中使用这两个注解,如果要判断主应用中是否有定义Bean那么没问题。如果是判断其他AutoConfiguration是否有Bean,那么要用注解确定AutoConfiguration的顺序。最常见稳妥的使用场景,是进行兜底默认配置,判断主应用有没有Bean,如果没有,那么AutoConfiguration进行兜底创建Bean定义。
异步线程调用Dubbo Service服务引发Bean加载死锁 Spring加载Bean会加锁。主线程加载Bean时,如果此时有异步线程进行Dubbo调用,或者加载Bean,会导致两个线程死锁。 比如,在@PostConstruct中启动异步线程调用Dubbo服务初始化缓存。 所以,如果是服务运行必要的初始化内容,一定要预先初始化的,必须在Spring初始化线程中同步执行。 如果要异步初始化,则要在Spring启动完成后再进行,比如订阅ApplicationStartedEvent事件再执行。 保证同时只有一个线程在进行Spring加载Bean。
最后
要熟悉SpringBoot启动流程,以及各种配置在启动中的先后顺序,才能在开发中得心应手,避免踩坑。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/171537.html原文链接:https://javaforall.cn