springboot3生命周期监听的使用和源码解析

news2024/11/28 16:03:09

    定义SpringApplicationRunListener来监听springApplication的启动

1.通过实现springApplicationRunListener来实现监听。

2.在 META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自己的Listener。

在默认的springboot配置中就有给我们配置了事件监听器。

 配置了EvenPublishRunListener。

 自定义myListener类。

import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

import java.time.Duration;

public class myListenter implements SpringApplicationRunListener {
    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        SpringApplicationRunListener.super.starting(bootstrapContext);
        System.out.println("starting");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
        System.out.println("environmentPrepared");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        SpringApplicationRunListener.super.contextPrepared(context);
        System.out.println("contextPrepared");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        SpringApplicationRunListener.super.contextLoaded(context);
        System.out.println("contextLoaded");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        SpringApplicationRunListener.super.started(context, timeTaken);
        System.out.println("started");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        SpringApplicationRunListener.super.ready(context, timeTaken);
        System.out.println("ready");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        SpringApplicationRunListener.super.failed(context, exception);
        System.out.println("failed");
    }
}

在/META-INF/spring.factories。

org.springframework.boot.SpringApplicationRunListener=com.huang.listenter.myListenter

启动测试,效果为下:

 SpringApplicationRunListener作用位置的源码分析

starting :应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行。(此时ioc容器还没有被启动)

调用位置: 

  environmentPrepared: 环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】 

调用位置: 

 contextPrepared:ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建  【调一次】(看参数就可以推出现在要进行ioc启动时的配置)

 调用位置(此时创建了ioc容器):

在ioc创建好并准备好后将 BootstrapContext关闭。

 contextLoaded:ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(我们的bean没创建)。

 调用位置:

 且在此方法要在刷新ioc容器(将bean配置到ioc容器中)之前。

此方法就是刷新容器。(在此过程中就会自动创建服务器类,并将其配置到ioc容器中) 

started:ioc容器刷新了(所有bean造好了),但是 runner 没调用。

调用位置: 

 ready:ioc容器刷新了(所有bean造好了),所有 runner 调用完了。

 调用位置:

 总结

重点掌握流程和各个方法的作用即可。 

事件触发时机

    各种回调监听器

  • 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。卡死应用,就不会就绪(用于感知Runner的运行情况)
    • @Bean
  • CommandLineRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪(用于感知Runner的运行情况)
    • @Bean

最佳实战:

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

 编写ApplicationListener实现类。

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class myEventListener implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("触发事件:" + event);
    }
}

在spring.factories中配置ApplicationListener。

org.springframework.context.ApplicationListener=com.huang.listenter.myEventListener

运行结果为下:

通过结果分析我们可以得到下图:

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。

 实现步骤:

  在登录成功后,新建一个自定义的特定事件,通过事件发布者将此事件发布,需要被调用的service会通过监听该特定的事件,来执行对应的方法,也就是一监听到特定的事件就进行对应的方法。

1.创建自定义事件:Loginevent

import com.huang.pojo.User;
import org.springframework.context.ApplicationEvent;

//创建自定义的事件
public class LoginEvent extends ApplicationEvent {

    public LoginEvent(User user) {
        super(user);
    }
}

2.创建一个自定义的事件发布类:EventPublisher

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

@Service
public class EventPublisher implements ApplicationEventPublisherAware {

    ApplicationEventPublisher applicationEventPublisher;
    //发布事件
    public void eventSend(ApplicationEvent applicationEvent) {
        applicationEventPublisher.publishEvent(applicationEvent);
    }

    @Override
    //ApplicationEventPublisherAware自动调用此方法,此方法中的ApplicationEventPublisher会自动从ioc容器中注入
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

3.创建三个service,分别去监听对应的事件

 LoginService:

import com.huang.event.EventPublisher;
import com.huang.event.LoginEvent;
import com.huang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class LoginService {

    //在登录成功后,新建一个自定义的特定事件,通过事件发布者将此事件发布,需要被调用的service会通过监听该特定的事件,
    // 来执行对应的方法,也就是一监听到特定的事件就进行对应的方法
    //1.创建自定义事件
    //2.创建一个自定义的事件发布类
    //3.创建三个service,分别去监听对应的事件
    @Autowired
    EventPublisher eventPublisher;

    public void LoginSuccess() {
        LoginEvent loginEvent = new LoginEvent(new User("秃狼", "123456"));
        eventPublisher.eventSend(loginEvent);
    }

}

 ticketService:

import com.huang.event.LoginEvent;
import com.huang.pojo.User;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class ticketService {

    @EventListener
    public void discoverEvent(LoginEvent loginEvent) {
        User user = (User) loginEvent.getSource();
        this.addTicket(user);
    }

    public void addTicket(User user) {
        System.out.println(user.getName() + "优惠劵加一");
    }
}

 totalService:

import com.huang.event.LoginEvent;
import com.huang.pojo.User;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;

@Service
public class totalService implements ApplicationListener<LoginEvent> {

    public void addTotal(User user) {
        System.out.println(user.getName() + "积分加一");
    }

    @Override
    public void onApplicationEvent(LoginEvent event) {
        User user = (User) event.getSource();
        this.addTotal(user);
    }
}

在测试类中自动注入LoginService进行测试,测试结果为下:

可以看到我两个service都被调用了,通过都是通过监听对应的事件。通过监听事件来实现业务的好处就是:不再需要再主Service中调用各个其他的service,在后续添加业务时只需要监听对应的事件即调用对应的service,不会修改到原代码,符合代码规范,大大的实现了解耦合

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

视觉SLAM十四讲——ch12实践(建图)

视觉SLAM十四讲——ch12的实践操作及避坑 0.实践前小知识介绍1. 实践操作前的准备工作2. 实践过程2.1 单目稠密重建2.2 RGB-D稠密建图2.3 点云地图2.4 从点云重建网格2.5 八叉树地图 3. 遇到的问题及解决办法3.1 cmake ..时&#xff0c;出现opencv版本问题3.2 make -j8时&#…

使用腾讯云服务器从零搭建个人网站

前期准备工作 1.服务器重装系统 选择ubuntu18的系统镜像 2.开放端口 需要开放80&#xff0c;27017&#xff0c;3000&#xff0c;22端口 80端口用于配置nginx服务27017端口用于连接mongondb数据库3000端口是启动项目的端口22端口用于ssh远程连接服务器&#xff0c;一般默认会…

SpringBoot - @Transactional注解详解

简介 Spring中的Transactional注解&#xff0c;基于动态代理的机制&#xff0c;提供了一种透明的事务管理机制&#xff0c;方便快捷的解决在开发中碰到的问题&#xff0c;Transactional 的事务开启 &#xff0c;或者是基于接口的或者是基于类的代理被创建。Spring为了更好的支…

数据库SQL查询(二)之连接查询

本文介绍SQL查询&#xff0c;如何在海量数据中筛选想要数据&#xff1b; 数据库管理系统选择&#xff1a;关系型数据库mysql 数据库管理工具选择&#xff1a;navicat 本文中查询语句和查询案例参考自&#xff1a;https://edu.csdn.net/course/detail/27673?ops_request_mis…

python django vue httprunner 实现接口自动化平台(最终版)

一、项目介绍&#xff1a; 1.1 项目地址 前端地址&#xff1a; GitHub - 18713341733/test_platform_service: django vue 实现接口自动化平台 后端地址&#xff1a; GitHub - 18713341733/test_platform_front: Django vue实现接口自动化平台 1.2 项目介绍 1.2.1 环境…

在 K8S 中部署一个应用 上

本身在 K8S 中部署一个应用是需要写 yaml 文件的&#xff0c;我们这次简单部署&#xff0c;通过拉取网络上的镜像来部署应用&#xff0c;会用图解的方式来分享一下&#xff0c;过程中都发生了什么 简单部署一个程序 我们可以通过 kubectl run 的方式来简单部署一个应用&#…

Python深度学习027:什么是梯度、梯度消失、梯度爆炸以及如何解决

文章目录 1. 梯度的概念2. 梯度更新中存在的问题2.1 梯度消失2.2 梯度爆炸3. 解决办法3.1 梯度消失3.2 梯度爆炸1. 梯度的概念 在机器学习中,梯度是指一个多元函数在某一点处的变化率以及变化的方向。 对于一个参数化的函数,梯度可以告诉我们在一个特定的点处函数值增加最快…

分布式系统概念和设计——Mach实例研究

分布式系统概念和设计 Mach实例研究 Mach主要抽象概述 任务 一个Mach任务是一个执行环境主要包括一个被保护的地址空间和一个内存管理的权能集合这些权能主要用于访问端口 线程 任务可以包含多个线程在共享内存的多处理器中&#xff0c;属于同一个任务的线程可以在不同的处理…

【案例实战】SpringBoot整合Redis实现缓存分页数据查询

正式观看本文之前&#xff0c;设想一个问题&#xff0c;高并发情况下&#xff0c;首页列表数据怎么做&#xff1f; 类似淘宝首页&#xff0c;这些商品是从数据库中查出来的吗&#xff1f;答案肯定不是&#xff0c;在高并发的情况下&#xff0c;数据库是扛不住的&#xff0c;那么…

Java解析XML文件(DOM4J解析xml文件)

内容重点1&#xff1a;DOM4J创建、解析、修改XML文件 内容重点2&#xff1a;DOM4J模拟解析web.xml配置文件&#xff0c;实现实例化servlet 1、什么是xml Xml(Extensible Markup Language) 一种扩展性标示语言,出现的意义其实与JSON字符串的意义相似,是新一代的数据交换标准…

怎么加密共享文件夹?局域网共享文件夹加密方法

相信很多企业都在使用局域网共享文件夹&#xff0c;它可以很方便地实现各部门之间的沟通协作。但是由于使用人员众多&#xff0c;数据安全非常难以得到保障。那么我们该怎么加密局域网共享文件夹呢&#xff1f; 共享文件夹加密 首先&#xff0c;我们先来了解一下共享文件夹加密…

某农业大学数据结构A-第13周作业

1.拓扑排序 【问题描述】 拓扑排序的流程如下&#xff1a; 1. 在有向图中选一个没有前驱的顶点并且输出之&#xff1b; 2. 从图中删除该顶点和所有以它为尾的弧。 重复上述两步&#xff0c;直至全部顶点均已输出&#xff0c;或者当前图中不存在无前驱的顶点为止。后一种情况则…

渲染模式差异和选择

传统服务端渲染 在过去传统开发中&#xff0c;页面渲染任务是由服务端完成的&#xff0c;服务器负责获取数据&#xff0c;拼装页面&#xff0c;客户端仅负责内容显示&#xff0c;使用这种方式的典型技术有 JSP、PHP、ASP.NET 等等。 客户端渲染 CSR Vue.js、React 这类框架之…

专访蘑菇物联沈国辉:做工业AI时代的推动者

在中国制造迈向高质量发展的进程中&#xff0c;数智化转型会成为破局之道。在这其中&#xff0c;蘑菇物联除了是一个本分的答题者&#xff0c;还是一个善于发现问题的贡献者。 作者|思杭 编辑|皮爷 出品|产业家 从广州造纸厂到广州造船厂&#xff0c;从第一橡胶厂到电池厂&a…

利用R语言通过百度地图API进行批量地理编码

利用R语言通过百度地图API进行批量地理编码 当您有大量的地点名称需要在地图上来呈现时&#xff0c;首先要在在线地图上找到该地址的坐标&#xff0c;通常是指经纬度&#xff0c;如果能够用代码来实现&#xff0c;便少了许多费时费力的体力活儿&#xff0c;以下将详细介绍地理…

数据结构的一些总结---利用Python实现

大家好&#xff0c;我是北山啦&#xff0c;本文简单介绍Python数据结构的相关内容&#xff0c;简单就是很简单的那种 文章目录 查找线性查找 O(n)二分查找(Binary Search) O(logn) 排序排序Low B三人组冒泡排序选择排序插入排序 排序NB三人组快速排序归并排序 数据结构栈和队列…

[Day 3 of 17]Building a document scanner in OpenCV

a computer vision-powered document scanner 计算机视觉驱动的文档扫描仪&#xff0c;三个步骤&#xff1a; 边缘检测edges通过边缘&#xff0c;找到代表待扫描纸张的轮廓contour应用透视转换(a perspective transform)获得文档自上而下的视图 How to Build a Kick-Ass Mob…

录音转文字的方法有哪些?这三个录音转文字的方法有哪些

你是否曾经遇到过这样的场景&#xff1a;在开会或者采访时&#xff0c;需要记录重要信息&#xff0c;但是手写记录或打字速度跟不上对话节奏&#xff0c;甚至难以记录所有细节。此时&#xff0c;录音转文字软件就派上用场了。然而&#xff0c;市场上有太多种选择&#xff0c;到…

【Vue3 生态】Vue Router 路由知识概览

前言 在 Web 前端开发中&#xff0c;路由是非常重要的一环&#xff0c;但是路由到底是什么呢&#xff1f; 从路由的用途上讲 路由是指随着浏览器地址栏的变化&#xff0c;展示给用户不同的页面。 从路由的实现原理上讲 路由是URL到函数的映射。它将 URL 和应用程序的不同部分…

低代码平台——提高研发效率的神器

一、前言 听起来像是一个噱头&#xff0c;但是低代码确实是一个能够快速提高研发效率的神器。 通过使用低代码平台&#xff0c;研发人员可以节省好几个月的时间&#xff0c;将前后端各种功能可视化以搭积木的形式快速完成。今天&#xff0c;我们将深入探讨低代码开发平台的特点…