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

事件和监听器

生命周期监听

自定义监听器的步骤:

  1. 编写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("应用启动失败");
        }
    }
    
  2. 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 读到

  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. 自定义监听器,实现相应的监听器接口,重写相应方法,例:

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

    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. 创建事件发布者

    @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. 创建功能事件

    public class LoginSuccessEvent extends ApplicationEvent {
        /**
         * 代表是谁成功登录了
         * */
        public LoginSuccessEvent(User user) {
            super(user);
        }
    }
    
    
  7. 在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+"随机收到了一张优惠券");
        }
    
    }
    
  8. 最后在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+"登录成功";
        }
    }
    
    

自动配置原理回顾:

  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的方式

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(RobotAutoConfiguration.class)
public @interface EnableRobot {

}

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/472333.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git 安装、创建仓库、常用命令、克隆下载、上传项目、删除分支 -- 一篇文章总结

一、git安装 1、git安装地址&#xff1a;https://git-scm.com/downloads 2、选择操作系统 3、安装自己系统对应的操作位数 4、等待下载完&#xff0c;一路next安装就可以了 5、安装完成后&#xff0c;在任意文件夹点击右键&#xff0c;看到下图说明安装成功 二、创建仓库 1…

法语「奶奶」明明是阴性,为什么不用配合?柯桥法语口语学习小语种学校

咦&#xff0c;法语中“奶奶”到底怎么写&#xff1f;是Grande-mre还是Grand mre&#xff1f;又或者 Grand-mre ? 先写下你的回答&#xff0c;法语君再公布答案哦&#xff01; 面对这个问题&#xff0c;你已经开始犹豫了对不对&#xff1f; 那么在法语中&#xff0c;到底哪一个…

蓝桥杯之动态规划冲刺

文章目录 动态规划01背包小练一下01背包网格图上的DP完全背包 最长公共字符串最长递增子序列 动态规划 动态规划&#xff1a;确定好状态方程&#xff0c;我们常常是确定前 当状态来到 i 时&#xff0c;前 i 个物体的状态是怎么样的&#xff0c;我们并不是从一个点去考虑&#x…

Docker部署JumpServer3.9.0

简介 JumpServer 是什么&#xff1f; JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpServer 帮助企业以更安全的方式管控和登录所有类型的资产&#xff0c;实现事前授权、事中监察、事后审计&#xff0c;满足等保合规要求。 Jump…

C/C++炸弹人游戏

参考书籍《啊哈&#xff0c;算法》&#xff0c;很有意思的一本算法书&#xff0c;小白也可以看懂&#xff0c;详细见书&#xff0c;这里只提供代码和运行结果。 这里用到的是枚举思想&#xff0c;还有更好地搜索做法。 如果大家有看不懂的地方或提出建议&#xff0c;欢迎评论区…

如何将Excel两列数据转换为统计图、曲线图、折线图?如何自定义某一列作为Excel的统计图横纵坐标?

这样&#xff0c;横坐标就更换为指定选中的数据了 我们还可以修改统计图的样式 也可以修改统计图的类型

cordova安装安卓版本,遇到的各种坑。折腾了两天才弄好

cordova官网地址 https://cordova.apache.org/docs/en/12.x/guide/cli/index.html 1. 输入命令 npm install -g cordova 全局安装cordova 2. 创建文件和项目以及app的应用名称 cordova create hello com.example.hello HelloWorld 我写的是这个 cordova create myApp 3.co…

深入浅出Hive性能优化策略

我们将从基础的HiveQL优化讲起&#xff0c;涵盖数据存储格式选择、数据模型设计、查询执行计划优化等多个方面。会的直接滑到最后看代码和语法。 目录 引言 Hive架构概览 示例1&#xff1a;创建表并加载数据 示例2&#xff1a;优化查询 Hive查询优化 1. 选择适当的文件格…

安卓findViewById 的优化方案:ViewBinding与ButterKnife(一)

好多小伙伴现在还用findViewById来获取控件的id, 在这里提供俩种替代方案&#xff1a;ViewBinding与ButterKnife&#xff1b; 先来说说ButterKnife ButterKnife ButterKnife是一个专注于Android系统的View注入框架&#xff0c;在过去的项目中总是需要很多的findViewById来查…

Java Day13 多线程

多线程 1、 方式一 Thread2、实现Runnable接口3、实现 Callable接口4、与线程有关的操作方法5、线程安全问题5.1 取钱案例5.2 线程同步5.2.1 同步代码块5.2.2 同步方法5.2.3 Lock锁 6、线程池6.2 创建线程池6.2.1 使用ExecutorService创建新任务策略6.2.2 使用Executors工具类创…

2024年云仓酒庄佛山发布会:赋能

原标题&#xff1a;2024年云仓酒庄佛山发布会圆满落幕&#xff0c;朱囿臻总赋能引领行业新篇章 近日&#xff0c;备受瞩目的云仓酒庄佛山发布会圆满落幕。此次发布会汇聚了业内精英、经销商代表以及媒体人士&#xff0c;共同见证了云仓酒庄在佛山市场的启航。在此&#xff0c;…

智慧公厕:卫生、便捷、安全的新时代厕所变革

在城市快速发展的背景下&#xff0c;公共厕所的建设和管理变得越来越重要。智慧公厕作为厕所变革的一项全新举措&#xff0c;通过建立公共厕所全面感知监测系统&#xff0c;以物联网、互联网、大数据、云计算、自动化控制技术为支撑&#xff0c;实现对公共厕所的智能化管理和运…

练习4-权重衰减(李沐函数简要解析)

环境:练习1的环境 代码详解 0.导入库 import torch from torch import nn from d2l import torch as d2l1.初始化数据 这里初始化出train_iter test_iter 可以查一下之前的获取Fashion数据集后的数据格式与此对应 n_train, n_test, num_inputs, batch_size 20, 100, 200, …

50. 【Linux教程】源码安装软件

本小节介绍如何使用软件的源码包安装软件&#xff0c;以安装 nginx 源码包为例。 1.下载软件源码包 使用如下命令下载 nginx 源码包&#xff1a; wget http://nginx.org/download/nginx-1.18.0.tar.gz执行结果如下图所示&#xff1a; 2.解压源码包 下载好了压缩包之后&#…

基于Java+SpringBoot+vue+element实现前后端分离玩具商城系统

基于JavaSpringBootvueelement实现前后端分离玩具商城系统 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文…

Linux网络编程: TCP协议之序号和确认号详解

一、TCP协议首部 二、序号&#xff08;Sequence Number&#xff09; 32位&#xff0c;表示该报文段所发送数据的第一个字节的编号。 实际上 TCP 的序号并不是按照 “一条两条” 这样的方式来编号的。在TCP连接中所传输字节流的每一个字节都会按顺序编号&#xff0c;由于序列号…

CTF-reverse-每日练题-xxxorrr

题目链接 https://adworld.xctf.org.cn/challenges/list 题目详情 xxxorrr ​ 解题报告 下载得到的文件使用ida64分析&#xff0c;如果报错就换ida32&#xff0c;得到分析结果&#xff0c;有main函数就先看main main函数分析 v6 main函数中&#xff0c;v6的值是__readfsqwor…

Java基础学习笔记三

环境变量CLASSPATH classpath环境变量是隶属于java语言的&#xff0c;不是windows操作系统的&#xff0c;和PATH环境变量完全不同classpath环境变量是给classloader&#xff08;类加载器&#xff09;指路的java A 。执行后&#xff0c;先启动JVM&#xff0c; JVM启动classload…

聚类算法( clustering algorithm):

在前两章&#xff0c;我们学的是&#xff1a;线性回归&#xff0c;逻辑回归&#xff0c;深度学习(神经网络)&#xff0c;决策树&#xff0c;随即森林算法。他们都是监督学习的例子。 在这一章里&#xff0c;我们将学习非监督学习的算法。 什么是聚类算法&#xff1a; 聚类算…

C语言结构体详解

1、结构体的声明 结构体是一些值的集合&#xff0c;这些值被称为成员变量。结构体中的每个成员可以是不同类型的变量。 语法&#xff1a; struct tag //关键词 标签 { member- list ;//成员清单 }variable- list ;//变量清单 通常结构体描述的是一个复杂对象&#xff0c;比…