SpringBoot3框架,事件和监听器、SPI

事件和监听器

生命周期监听

自定义监听器的步骤:

  1. 编写SpringApplicationRunListener实现类(各个实现方法的功能写在其sout内)

    java 复制代码
    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("应用启动失败");
        }
    }
  2. META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自定义listener的全限定符,还可以指定一个有参构造器 ,接受两个参数(SpringApplication application, String[] args)

    ini 复制代码
    org.springframework.boot.SpringApplicationRunListener=com.ergou.boot3.listener.MyAppListener

以上监听器执行流程

Listener先要从 META-INF/spring.factories 读到

  1. 引导: 利用 BootstrapContext 引导整个项目启动

    1. starting:应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行
    2. environmentPrepared:环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】
  2. 启动:

    1. contextPrepared:ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建 【调一次】
    2. contextLoaded: ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(bean没创建) =======截止以前,ioc容器里面还没造bean=======
    3. started: ioc容器刷新了(所有bean造好了),但是 runner 没调用。
    4. ready: ioc容器刷新了(所有bean造好了),所有 runner 调用完了。
  3. 运行: 以前步骤都正确执行,代表容器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配置

配置步骤:

  1. 自定义监听器,实现相应的监听器接口,重写相应方法,例:

    java 复制代码
    public class MyListener2 implements ApplicationListener<ApplicationEvent> {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            System.out.println("感知到事件:"+event);
        }
    }
  2. 配置监听器,例:

    yaml 复制代码
    org.springframework.context.ApplicationListener=com.ergou.boot3.ssm.listener.MyListener2

建议:

  • 如果项目启动前做事: BootstrapRegistryInitializerApplicationContextInitializer
  • 如果想要在项目启动完成后做事:ApplicationRunnerCommandLineRunner
  • 如果要干涉生命周期做事:SpringApplicationRunListener
  • 如果想要用事件机制:ApplicationListener

9大事件触发顺序&时机

  1. ApplicationStartingEvent:应用启动但未做任何事情, 除过注册listeners and initializers.
  2. ApplicationEnvironmentPreparedEvent: Environment 准备好,但context 未创建.
  3. ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
  4. ApplicationPreparedEvent: 容器刷新之前,bean定义信息加载
  5. ApplicationStartedEvent: 容器刷新完成, runner未调用

=========以下就开始插入了探针机制============

  1. AvailabilityChangeEventLivenessState.CORRECT应用存活; 存活探针
  2. ApplicationReadyEvent: 任何runner被调用
  3. AvailabilityChangeEventReadinessState.ACCEPTING_TRAFFIC就绪探针,可以接请求
  4. ApplicationFailedEvent :启动出错

事件驱动开发

事件驱动开发步骤:

  1. 首先创建一个事件的发布者EventPublisher 类,这个类要实现ApplicationEventPublisherAware ,springboot会通过ApplicationEventPublisherAware 接口自动注入,接着实现setApplicationEventPublisher方法,并且自定义一个方法来调用底层API发送事件,事件是广播出去的。所有监听这个事件的监听器都可以收到

  2. 我们要自定义一个登录成功事件LoginSuccessEvent ,这个事件用来绑定用户User类,并且被该功能模块下的service调用

  3. 接下来我们要在service的功能代码使用@EventListener注解来进行订阅事件

  4. 最后在controller中进行发送事件,相当于原始的调用service功能方法

  5. 创建事件发布者

    java 复制代码
    @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;
        }
    }
  6. 创建功能事件

    java 复制代码
    public class LoginSuccessEvent extends ApplicationEvent {
        /**
         * 代表是谁成功登录了
         * */
        public LoginSuccessEvent(User user) {
            super(user);
        }
    }
  7. 在service层中订阅相应事件,并做出相应业务处理

    java 复制代码
    @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+"随机收到了一张优惠券");
        }
    
    }
  8. 最后在controller层发送相应的事件即可

    java 复制代码
    @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+"登录成功";
        }
    }

自动配置原理回顾:

  1. 导入starter

  2. 依赖导入autoconfigure

  3. 寻找类路径下 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件

  4. 启动,加载所有 自动配置类 xxxAutoConfiguration

    1. 给容器中配置功能 组件
    2. 组件参数绑定到 属性类中。xxxProperties
    3. 属性类配置文件前缀项绑定
    4. @Contional派生的条件注解进行判断是否组件生效
  5. 效果:

    1. 修改配置文件,修改底层参数
    2. 所有场景自动配置好直接使用
    3. 可以注入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的方式

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(RobotAutoConfiguration.class)
public @interface EnableRobot {

}

如此,别人引入starter需要使用 @EnableRobot开启功能

相关推荐
Daniel 大东14 分钟前
idea 解决缓存损坏问题
java·缓存·intellij-idea
2401_8576363915 分钟前
共享汽车管理新纪元:SpringBoot框架应用
数据库·spring boot·汽车
wind瑞20 分钟前
IntelliJ IDEA插件开发-代码补全插件入门开发
java·ide·intellij-idea
HappyAcmen20 分钟前
IDEA部署AI代写插件
java·人工智能·intellij-idea
马剑威(威哥爱编程)26 分钟前
读写锁分离设计模式详解
java·设计模式·java-ee
鸽鸽程序猿27 分钟前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
修道-032327 分钟前
【JAVA】二、设计模式之策略模式
java·设计模式·策略模式
九圣残炎33 分钟前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
当归10241 小时前
若依项目-结构解读
java
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端