SpringBoot3——核心原理
事件和监听器
生命周期监听
场景:监听应用的生命周期
监听器
自定义 SpringApplicationRunListener 来监听事件
编写 SpringApplicationRunListener 的实现类
在 META-INF/spring.factories 中 配置 org.springframework.boot.SpringApplicationRunListener=自己的Listener,还可以指定一个有参构造器,接收两个参数 (SpringApplication application, String[] args)
springboot 在 spring-boot.jar 中配置了默认的 Listener,如下:
org.springframework.boot.SpringApplicationRunListener=\
org.springframwork.boot.context.event.EventPublishingRunListener
/** * Listener先要从 META-INF/spring.factories 读到 * * 1、引导: 利用 BootstrapContext 引导整个项目启动 * starting: 应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就直接执行 * environmentPrepared: 环境准备好(把启动参数等绑定到环境变量中),但是ioc容器还没有创建 * 2、启动 * contextPrepared: ioc容器创建并准备好,但是 sources (主配置类) 没加载,紧接着关闭引导上下文;组件都没创建 * contextLoaded: ioc容器加载了,主配置类加载进去了,但是ioc容器还没刷新(也就是bean还没创建) * =========截止以前,ioc容器里面还没有bean========= * started: ioc容器刷新了,bean都创建好了,但是还没完全启动 * ready: ioc容器完全启动了,所有的bean都创建好了,所有的runner调用完了 * 3、运行 * 如果以前步骤都正确执行,代表容器running */
生命周期全流程
事件触发时机
各种回调监听器
- BootstrapRegistryInitializer: 感知特定阶段:感知引导初始化
- META-INF/spring.factories
- 创建引导上下文bootstrapContext的时候触发。
- application.addBootstrapRegistryInitializer();
- 场景:进行密钥校对授权
- ApplicationContextInitializer: 感知特定阶段: 感知ioc容器初始化
- META-INF/spring.factories
- application.addInitializers();
- ApplicationListener: 感知全阶段:基于事件机制,感知事件。 一旦到了哪个阶段可以做别的事
- @Bean或@EventListener: 事件驱动
- SpringApplication.addListeners(…)或 SpringApplicationBuilder.listeners(…)
- META-INF/spring.factories
- SpringApplicationRunListener: 感知全阶段生命周期 + 各种阶段都能自定义操作; 功能更完善。
- META-INF/spring.factories
- ApplicationRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
- @Bean
- CommandLineRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
- @Bean
最佳实战:
- 如果想要在项目启动前做事:使用 BootstrapRegistryInitializer 和 ApplicationContextInitializer
- 如果想要在项目启动完成后做事:ApplicationRunner 和 CommandLineRunner
- 如果要干涉生命周期做事:SpringApplicationRunListener
- 如果想要用事件机制:ApplicationListener
完整触发流程
9大事件触发顺序&时机
- ApplicationStartingEvent:应用启动但未做任何事情, 除过注册listeners and initializers.
- ApplicationEnvironmentPreparedEvent: Environment 准备好,但context 未创建.
- ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
- ApplicationPreparedEvent: 容器刷新之前,bean定义信息加载
- ApplicationStartedEvent: 容器刷新完成, runner未调用
=========以下就开始插入了探针机制============
- AvailabilityChangeEvent: LivenessState.CORRECT应用存活; 存活探针
- ApplicationReadyEvent: 任何runner被调用
- AvailabilityChangeEvent:ReadinessState.ACCEPTING_TRAFFIC就绪探针,可以接请求
- ApplicationFailedEvent :启动出错
应用事件发送顺序如下:
感知应用是否存活了:可能植物状态,虽然活着但是不能处理请求。
应用是否就绪了:能响应请求,说明确实活的比较好。
SpringBoot事件驱动开发
应用启动过程生命周期事件感知(9大事件)、应用运行中事件感知(无数种)
事件发布:ApplicationEventPublisherAware 或者 注入:ApplicationEventMulticaster
事件监听:组件 + @EventListener
事件发布者
@Service
public class EventPublisher implements ApplicationEventPublisherAware {
/**
* 底层发布事件用的组件,SpringBoot会通过ApplicationEventPublisherAware接口自动注入给我们
* 事件是广播出去的,所有监听这个事件的监听器都可以收到
*/
ApplicationEventPublisher applicationEventPublisher;
/**
* 所有事件都可以发
* @param event
*/
public void seneEvent(ApplicationEvent event) {
// 调用底层API发布事件
applicationEventPublisher.publishEvent(event);
}
/**
* 这个方法会被自动调用,把真正发事件的底层组件给我们注入进来
* @param applicationEventPublisher
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
事件订阅者
@Service
public class FreeService {
@Order(1)
@EventListener
public void onEvent(LoginSuccessEvent event){
System.out.println("FreeService 感知到事件");
UserEntity source = (UserEntity) event.getSource();
addFreeTicket(source.getUsername());
}
public void addFreeTicket(String username){
System.out.println(username + "获得了一张免费券");
}
}
自动配置原理
入门理解
应用关注的三大核心:场景、配置、组件
自动配置流程
- 导入 starter
- 依赖导入 autoconfigure
- 寻找类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
- sprinboot一启动,就会加载所有的自动配置类 xxxAutoConfiguration
- 给容器中配置功能组件
- 组件参数会绑定到属性类 xxxProperties 当中
- 属性类会与配置文件的前缀项进行绑定
- @Condition派生的条件注解 最终会进行判断组件是否生效
- 效果:
- 修改配置文件,修改底层参数
- 所有场景自动配置好,可以直接使用
- 可以注入SpringBoot配置好的组件,随时都能进行使用
SPI机制
- Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想是,定义一个接口或抽象类,然后通过在 classpath 中定义实现该接口的类来实现对组件的动态发现和加载。
- SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
- 在Java中,SPI的实现方式是通过在
META-INF/services
目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。- 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
在 springboot 中,SPI机制 所寻找的文件时在 META-INF/spring 目录下的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
功能开关
- 自动配置:全部都配置好,什么都不用管,通常用于进行自动批量导入
- 项目一启动,SPI所指定的文件中的所有配置都进行加载
- @EnableXxxx:用于手动控制哪些功能可以开启
- 开启Xxxx功能
- 都是利用 @Import 将此功能所需要的组件进行导入
进阶理解
SpringBootApplication
@SpringBootConfiguration
这个注解的本质就是:@Configuration,标注了这个注解就相当于标识了这个类是容器中的组件和配置类,那么 spring ioc 启动的时候就会加载创建这个类的对象
@EnableAutoConfiguration:开启自动配置
@AutoConfigurationPackage:扫描主程序包,加载自己的组件(配置类)
- 利用 @Import(AutoConfigurationPackages.Register.class) 给容器中导入组件
- 把主程序所在的包以及它的子包中的所有组件导入进来
- 这里就解释了为什么 SpringBoot 默认只扫描主程序所在的包以及它的子包
@Import(AutoConfigurationImportSelector.class):加载所有自动配置类:加载starter导入的组件
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).getCandidates();
这里会扫描SPI文件:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@ComponentScan
组件扫描:这个注解在这里的用法就不是平常的用法了,在这里是为了排除前面已经扫描过的 配置类 和 自动配置类
完整启动加载流程
生命周期启动加载流程
- 容器在started的时候进行刷新,就会进入SpringBoot刷新十二大步,前五步为BeanFactory的处理,后面七步都是Bean组件的处理。
- BeanFactory 除了环境准备、获取工厂、准备工厂、预留处理之外,还会有后置处理机制,在这一步会 扫描SPI文件,加载所有的自动配置类,也会扫描主程序所在的包以及子包,加载其中的配置类,但是在这过程中 容器里面的组件是还没有创建的,只是先告诉容器我后面想要创建这些组件
- 在 Bean的处理中,有这么一步 finishBeanFactoryInitillization,在这一步所有的组件就会创建出来
自定义starter
场景:抽取聊天机器人场景,它可以打招呼
效果:任何项目导入此 starter 项目,都具有打招呼功能,并且问候语中的人名,可以在配置文件中进行动态修改
- 创建自定义的 starter 项目,引入 spring-boot-starter 基础依赖
- 编写模块功能,引入模块所有需要的依赖
- 编写 xxxAutoConfiguration 自动配置类,帮其他项目导入这个模块需要的所有组件
- 编写配置文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ,来指定启动需要加载的自动配置
- 其他项目引入即可使用
业务代码
要想我们自定义的配置有提示。导入以下依赖后再重新启动项目,然后编写配置文件的时候就会有提示了
@ConfigurationProperties(prefix = "robot")
@Component
@Data
public class RobotProperties {
private String name;
private String age;
private String email;
}
<!--导入配置处理器,配置文件自定义的properties配置都会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
基本抽取
- 创建 starter 项目,把公共代码需要的所有依赖进行导入
- 再将公共代码复制进来
- 然后还需要编写一个 RobotAutoConfiguration,给容器中导入这个场景需要的所有组件
- 为什么这些组件默认不会扫描进去
- 因为其他项目导入这个场景的时候,这个场景所在的包和引入这个场景的项目的主程序所在的包不是父子关系
- 但是当其他项目引用这个 starter 的时候,我们可以在主程序上面添加一个 @Import 注解,将 RobotAutoConfiguration 直接导入进来,这样子就能将这个场景中所需要使用的所有组件导入进来
- 功能生效
- 测试编写配置文件
使用 Enable 机制抽取
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(RobotAutoConfiguration.class)
public @interface EnableRobot {
}
- 在场景的包中编写一个注解,导入自动配置类
- 在其他项目想要使用这个场景的功能的时候,只需要在该项目的主程序上加上 @EnableXxx 注解 就能使用这个场景的功能了
完全自动配置
- 依赖SpringBoot的SPI机制
- META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中编写好我们自动配置类的全类名即可
- 项目启动,自动加载我们的自动配置类