事件和监听器
生命周期监听
自定义监听器的步骤:
-
编写SpringApplicationRunListener实现类(各个实现方法的功能写在其sout内)
public class MyAppListener implements SpringApplicationRunListener { @Override public void starting(ConfigurableBootstrapContext bootstrapContext) { System.out.println("正在启动"); } @Override public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { System.out.println("环境准备完成"); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("ioc容器准备完成"); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("ioc容器加载完成"); } @Override public void started(ConfigurableApplicationContext context, Duration timeTaken) { System.out.println("启动完成"); } @Override public void ready(ConfigurableApplicationContext context, Duration timeTaken) { System.out.println("应用准备就绪"); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("应用启动失败"); } }
-
在
META-INF/spring.factories
中配置org.springframework.boot.SpringApplicationRunListener=
自定义listener的全限定符,还可以指定一个有参构造器,接受两个参数(SpringApplication application, String[] args)
org.springframework.boot.SpringApplicationRunListener=com.ergou.boot3.listener.MyAppListener
以上监听器执行流程
Listener先要从 META-INF/spring.factories 读到
- 引导: 利用 BootstrapContext 引导整个项目启动
- starting:应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行
- environmentPrepared:环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】
- 启动:
- contextPrepared:ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建 【调一次】
- contextLoaded: ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(bean没创建) =======截止以前,ioc容器里面还没造bean=======
- started: ioc容器刷新了(所有bean造好了),但是 runner 没调用。
- ready: ioc容器刷新了(所有bean造好了),所有 runner 调用完了。
- 运行: 以前步骤都正确执行,代表容器running。如果不能正常运行(以上的六个步骤有出现错误),调用failed方法。
回调监听器
回调监听器用于感知项目的生命周期的事件
- BootstrapRegistryInitializer: 感知特定阶段:感知引导初始化
META-INF/spring.factories
配置- 创建引导上下文
bootstrapContext
的时候触发。- ApplicationContextInitializer: 感知特定阶段: 感知ioc容器初始化
META-INF/spring.factories
配置- ApplicationListener: 感知全阶段:基于事件机制,感知事件。 一旦到了哪个阶段可以做别的事
META-INF/spring.factories
- SpringApplicationRunListener: 感知全阶段生命周期 + 各种阶段都能自定义操作。功能更完善。
META-INF/spring.factories
- ApplicationRunner: 感知特定阶段:感知应用就绪Ready。应用启动失败,就不会就绪
@Bean
配置- CommandLineRunner: 感知特定阶段:感知应用就绪Ready。应用启动失败,就不会就绪
@Bean
配置
配置步骤:
-
自定义监听器,实现相应的监听器接口,重写相应方法,例:
public class MyListener2 implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("感知到事件:"+event); } }
-
配置监听器,例:
org.springframework.context.ApplicationListener=com.ergou.boot3.ssm.listener.MyListener2
建议:
- 如果项目启动前做事:
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
:启动出错
事件驱动开发
事件驱动开发步骤:
首先创建一个事件的发布者EventPublisher类,这个类要实现ApplicationEventPublisherAware,springboot会通过ApplicationEventPublisherAware接口自动注入,接着实现setApplicationEventPublisher方法,并且自定义一个方法来调用底层API发送事件,事件是广播出去的。所有监听这个事件的监听器都可以收到
我们要自定义一个登录成功事件LoginSuccessEvent,这个事件用来绑定用户User类,并且被该功能模块下的service调用
接下来我们要在service的功能代码使用@EventListener注解来进行订阅事件
最后在controller中进行发送事件,相当于原始的调用service功能方法
创建事件发布者
@Service public class EventPublisher implements ApplicationEventPublisherAware { /** * 底层发送事件用的组件,springboot会通过ApplicationEventPublisherAware接口自动注入给我们 * 事件是广播出去的。所有监听这个事件的监听器都可以收到 * */ ApplicationEventPublisher applicationEventPublisher; /** * 所有事件都可以发送 * */ public void sendEvent(ApplicationEvent event){ //调用底层API发送事件 applicationEventPublisher.publishEvent(event); } //会被自动调用,把真正发送事件的底层组件注入 @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } }
创建功能事件
public class LoginSuccessEvent extends ApplicationEvent { /** * 代表是谁成功登录了 * */ public LoginSuccessEvent(User user) { super(user); } }
在service层中订阅相应事件,并做出相应业务处理
@Service public class CouponService { //当loginSuccessEvent事件发生时,@EventListener标注的方法会自动执行,称为订阅 @EventListener public void onEvent(LoginSuccessEvent loginSuccessEvent){ System.out.println("=======CouponService ======感知到事件"+loginSuccessEvent); User source = (User) loginSuccessEvent.getSource(); sendCoupon(source.getUsername()); } public void sendCoupon(String username){ System.out.println(username+"随机收到了一张优惠券"); } }
最后在controller层发送相应的事件即可
@RestController public class LoginController { @Autowired private EventPublisher eventPublisher; @GetMapping("/login") public String login(@RequestParam("username") String username,@RequestParam("password")String password){ //业务处理登录 System.out.println("业务处理登录完成...."); User user = new User(username, password); //TODO 发送事件 LoginSuccessEvent loginSuccessEvent = new LoginSuccessEvent(user); eventPublisher.sendEvent(loginSuccessEvent); //设计模式:对新增开发,对修改关闭 return username+"登录成功"; } }
自动配置原理回顾:
- 导入
starter
- 依赖导入
autoconfigure
- 寻找类路径下
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件- 启动,加载所有
自动配置类
xxxAutoConfiguration
- 给容器中配置功能
组件
组件参数
绑定到属性类
中。xxxProperties
属性类
和配置文件
前缀项绑定@Contional派生的条件注解
进行判断是否组件生效- 效果:
- 修改配置文件,修改底层参数
- 所有场景自动配置好直接使用
- 可以注入SpringBoot配置好的组件随时使用
SPI机制
- Java中的SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI的思想是,定义一个接口或抽象类,然后通过在classpath中定义实现该接口的类来实现对组件的动态发现和加载。
- SPI的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
- 在Java中,SPI的实现方式是通过在
META-INF/services
目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java的SPI机制会自动扫描classpath中的这些文件,并根据文件中指定的类名来加载实现类。- 通过使用SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
在SpringBoot中,通过
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
来进行SPI
关于配置:
- 自动配置:全部都配置好,什么都不用管。 自动批量导入
- 项目一启动,spi文件中指定的所有都加载。
@EnableXxxx
:手动控制哪些功能的开启; 手动导入。
- 开启xxx功能
- 利用 @Import 把此功能要用的组件导入进去
@SpringBootApplication注解及其相关注解
@SpringBootConfiguration
作用与@Configuration一致,容器中的组件,配置类。spring ioc启动就会加载创建这个类的组件
@EnableAutoConfiguration
开启自动配置
@AutoConfigurationPackage
- 利用@Import(AutoConfiguration.Registrar.class)给容器中导入想要的组件
- 把主程序所在的包的所有组件导入进来
@Import(AutoConfigurationImportSelector.class)
加载所有自动配置类(扫描SPI文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
)@ComponentScan
组件扫描:排除一些组件(排除前面已经扫描过的配置类和自动配置类)
生命周期启动加载机制
自定义starter
例如:
场景:抽取聊天机器人场景,它可以打招呼。
效果:任何项目导入此starter
都具有打招呼功能,并且问候语中的人名需要可以在配置文件中修改
创建
自定义starter
项目,引入spring-boot-starter
基础依赖
编写模块功能,引入模块所有需要的依赖。
编写
xxxAutoConfiguration
自动配置类,帮其他项目导入这个模块需要的所有组件
编写配置文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
指定启动需要加载的自动配置
其他项目引入即可使用
自定义的starter的配置方式还可以使用@EnableXxxx的方式
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(RobotAutoConfiguration.class)
public @interface EnableRobot {
}
如此,别人引入starter
需要使用 @EnableRobot
开启功能