SpringBoot项目实现发布订阅模式

news2024/10/7 14:30:44

文章目录

  • 自己实现观察者模式
    • 观察者
    • 被观察者
    • 测试
  • 利用Spring精简
    • 观察者实现类:定义成Bean
    • 被观察者:自动注入Bean
  • Spring Event实现发布/订阅模式
    • 自定义事件
    • 事件监听者
      • 实现ApplicationListener接口
      • 使用@EventListener注解
      • 异步和自定义线程池
        • 异步执行
        • 自定义线程池
    • 发布事件
    • 测试
  • 小结
  • 结语

在项目里,经常会有一些主线业务之外的其它业务,比如,下单之后,发送通知、监控埋点、记录日志……

这些非核心业务,如果全部一梭子写下去,有两个问题,一个是业务耦合,一个是串行耗时。

图片下单之后的逻辑

所以,一般在开发的时候,都会把这些操作å抽象成观察者模式,也就是发布/订阅模式(这里就不讨论观察者模式和发布/订阅模式的不同),而且一般会采用多线程的方式来异步执行这些观察者方法。

图片
观察者模式

一开始,我们都是自己去写观察者模式。

自己实现观察者模式

图片观察者简图

观察者

  • 观察者定义接口
/**
 * @Author: fighter3
 * @Description: 观察者接口
 * @Date: 2022/11/7 11:40 下午
 */
public interface OrderObserver {

    void afterPlaceOrder(PlaceOrderMessage placeOrderMessage);
}
  • 具体观察者

    @Slf4j
    public class OrderMetricsObserver implements OrderObserver {
    
        @Override
        public void afterPlaceOrder(PlaceOrderMessage placeOrderMessage) {
            log.info("[afterPlaceOrder] metrics");
        }
    }
    
    @Slf4j
    public class OrderLogObserver implements OrderObserver{
    
        @Override
        public void afterPlaceOrder(PlaceOrderMessage placeOrderMessage) {
            log.info("[afterPlaceOrder] log.");
        }
    }
    
    @Slf4j
    public class OrderNotifyObserver implements OrderObserver{
    
        @Override
        public void afterPlaceOrder(PlaceOrderMessage placeOrderMessage) {
            log.info("[afterPlaceOrder] notify.");
        }
    }
    
    • 业务通知观察者
    • 日志记录观察者
    • 监控埋点观察者

被观察者

  • 消息实体定义
@Data
public class PlaceOrderMessage implements Serializable {
    /**
     * 订单号
     */
    private String orderId;
    /**
     * 订单状态
     */
    private Integer orderStatus;
    /**
     * 下单用户ID
     */
    private String userId;
    //……
}
  • 被观察者抽象类
public abstract class OrderSubject {
    //定义一个观察者列表
    private List<OrderObserver> orderObserverList = new ArrayList<>();
    //定义一个线程池,这里参数随便写的
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(6, 12, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30));

    //增加一个观察者
    public void addObserver(OrderObserver o) {
        this.orderObserverList.add(o);
    }

    //删除一个观察者
    public void delObserver(OrderObserver o) {
        this.orderObserverList.remove(o);
    }

    //通知所有观察者
    public void notifyObservers(PlaceOrderMessage placeOrderMessage) {
        for (OrderObserver orderObserver : orderObserverList) {
            //利用多线程异步执行
            threadPoolExecutor.execute(() -> {
                orderObserver.afterPlaceOrder(placeOrderMessage);
            });
        }
    }
}

这里利用了多线程,来异步执行观察者。

  • 被观察者实现类
/**
 * @Author: fighter3
 * @Description: 订单实现类-被观察者实现类
 * @Date: 2022/11/7 11:52 下午
 */
@Service
@Slf4j
public class OrderServiceImpl extends OrderSubject implements OrderService {

    /**
     * 下单
     */
    @Override
    public PlaceOrderResVO placeOrder(PlaceOrderReqVO reqVO) {
        PlaceOrderResVO resVO = new PlaceOrderResVO();
        //添加观察者
        this.addObserver(new OrderMetricsObserver());
        this.addObserver(new OrderLogObserver());
        this.addObserver(new OrderNotifyObserver());
        //通知观察者
        this.notifyObservers(new PlaceOrderMessage());
        log.info("[placeOrder] end.");
        return resVO;
    }
}

测试

    @Test
    @DisplayName("下单")
    void placeOrder() {
        PlaceOrderReqVO placeOrderReqVO = new PlaceOrderReqVO();
        orderService.placeOrder(placeOrderReqVO);
    }
  • 测试执行结果
2022-11-08 00:11:13.617  INFO 20235 --- [pool-1-thread-1] c.f.obverser.OrderMetricsObserver        : [afterPlaceOrder] metrics
2022-11-08 00:11:13.618  INFO 20235 --- [           main] cn.fighter3.obverser.OrderServiceImpl    : [placeOrder] end.
2022-11-08 00:11:13.618  INFO 20235 --- [pool-1-thread-3] c.fighter3.obverser.OrderNotifyObserver  : [afterPlaceOrder] notify.
2022-11-08 00:11:13.617  INFO 20235 --- [pool-1-thread-2] cn.fighter3.obverser.OrderLogObserver    : [afterPlaceOrder] log.

可以看到,观察者是异步执行的。

利用Spring精简

可以看到,观察者模式写起来还是比较简单的,但是既然都用到了Spring来管理Bean的生命周期,代码还可以更精简一些。

图片Spring精简观察者模式

观察者实现类:定义成Bean

  • OrderLogObserver

    @Slf4j
    @Service
    public class OrderLogObserver implements OrderObserver {
    
        @Override
        public void afterPlaceOrder(PlaceOrderMessage placeOrderMessage) {
            log.info("[afterPlaceOrder] log.");
        }
    }
    
    
  • OrderMetricsObserver

@Slf4j
@Service
public class OrderMetricsObserver implements OrderObserver {

    @Override
    public void afterPlaceOrder(PlaceOrderMessage placeOrderMessage) {
        log.info("[afterPlaceOrder] metrics");
    }
}

  • OrderNotifyObserver
@Slf4j
@Service
public class OrderNotifyObserver implements OrderObserver {

    @Override
    public void afterPlaceOrder(PlaceOrderMessage placeOrderMessage) {
        log.info("[afterPlaceOrder] notify.");
    }
}

被观察者:自动注入Bean

  • OrderSubject

    public abstract class OrderSubject {
        /**
         * 利用Spring的特性直接注入观察者
         */
        @Autowired
        protected List<OrderObserver> orderObserverList;
    
        //定义一个线程池,这里参数随便写的
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(6, 12, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30));
    
        //通知所有观察者
        public void notifyObservers(PlaceOrderMessage placeOrderMessage) {
            for (OrderObserver orderObserver : orderObserverList) {
                //利用多线程异步执行
                threadPoolExecutor.execute(() -> {
                    orderObserver.afterPlaceOrder(placeOrderMessage);
                });
            }
        }
    }
    
    
  • OrderServiceImpl

@Service
@Slf4j
public class OrderServiceImpl extends OrderSubject implements OrderService {

    /**
     * 实现类里也要注入一下
     */
    @Autowired
    private List<OrderObserver> orderObserverList;

    /**
     * 下单
     */
    @Override
    public PlaceOrderResVO placeOrder(PlaceOrderReqVO reqVO) {
        PlaceOrderResVO resVO = new PlaceOrderResVO();
        //通知观察者
        this.notifyObservers(new PlaceOrderMessage());
        log.info("[placeOrder] end.");
        return resVO;
    }
}

这样一来,发现被观察者又简洁了很多,但是后来我发现,在SpringBoot项目里,利用Spring事件驱动驱动模型(event)模型来实现,更加地简练。

Spring Event实现发布/订阅模式

Spring Event对发布/订阅模式进行了封装,使用起来更加简单,还是以我们这个场景为例,看看怎么来实现吧。

自定义事件

  • PlaceOrderEvent:继承ApplicationEvent,并重写构造函数。ApplicationEvent是Spring提供的所有应用程序事件扩展类。
public class PlaceOrderEvent extends ApplicationEvent {

    public PlaceOrderEvent(PlaceOrderEventMessage source) {
        super(source);
    }
}

  • PlaceOrderEventMessage:事件消息,定义了事件的消息体。
@Data
public class PlaceOrderEventMessage implements Serializable {
    /**
     * 订单号
     */
    private String orderId;
    /**
     * 订单状态
     */
    private Integer orderStatus;
    /**
     * 下单用户ID
     */
    private String userId;
    //……
}

事件监听者

事件监听者,有两种实现方式,一种是实现ApplicationListener接口,另一种是使用@EventListener注解。

图片事件监听者实现

实现ApplicationListener接口

实现ApplicationListener接口,重写onApplicationEvent方法,将类定义为Bean,这样,一个监听者就完成了。

  • OrderLogListener
@Slf4j
@Service
public class OrderLogListener implements ApplicationListener<PlaceOrderEvent> {

    @Override
    public void onApplicationEvent(PlaceOrderEvent event) {
        log.info("[afterPlaceOrder] log.");
    }
}

  • OrderMetricsListener
@Slf4j
@Service
public class OrderMetricsListener implements ApplicationListener<PlaceOrderEvent> {

    @Override
    public void onApplicationEvent(PlaceOrderEvent event) {
        log.info("[afterPlaceOrder] metrics");
    }
}

  • OrderNotifyListener
@Slf4j
@Service
public class OrderNotifyListener implements ApplicationListener<PlaceOrderEvent> {

    @Override
    public void onApplicationEvent(PlaceOrderEvent event) {
        log.info("[afterPlaceOrder] notify.");
    }
}

使用@EventListener注解

使用@EventListener注解就更简单了,直接在方法上,加上@EventListener注解就行了。

  • OrderLogListener

    @Slf4j
    @Service
    public class OrderLogListener  {
    
        @EventListener
        public void orderLog(PlaceOrderEvent event) {
            log.info("[afterPlaceOrder] log.");
        }
    }
    
    
  • OrderMetricsListener

    @Slf4j
    @Service
    public class OrderMetricsListener {
    
        @EventListener
        public void metrics(PlaceOrderEvent event) {
            log.info("[afterPlaceOrder] metrics");
        }
    }
    
    
  • OrderNotifyListener

    @Slf4j
    @Service
    public class OrderNotifyListener{
    
        @EventListener
        public void notify(PlaceOrderEvent event) {
            log.info("[afterPlaceOrder] notify.");
        }
    }
    
    

异步和自定义线程池

异步执行

异步执行也非常简单,使用Spring的异步注解@Async就可以了。例如:

  • OrderLogListener
@Slf4j
@Service
public class OrderLogListener  {

    @EventListener
    @Async
    public void orderLog(PlaceOrderEvent event) {
        log.info("[afterPlaceOrder] log.");
    }
}

当然,还需要开启异步,SpringBoot项目默认是没有开启异步的,我们需要手动配置开启异步功能,很简单,只需要在配置类上加上@EnableAsync注解就行了,这个注解用于声明启用Spring的异步方法执行功能,需要和@Configuration注解一起使用,也可以直接加在启动类上。

@SpringBootApplication
@EnableAsync
public class DailyApplication {

    public static void main(String[] args) {
        SpringApplication.run(DairlyLearnApplication.class, args);
    }

}

自定义线程池

使用@Async的时候,一般都会自定义线程池,因为@Async的默认线程池为SimpleAsyncTaskExecutor,不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。

自定义线程池有三种方式:

图片@Async自定义线程池

  • 实现接口AsyncConfigurer
  • 继承AsyncConfigurerSupport
  • 配置由自定义的TaskExecutor替代内置的任务执行器

我们来看看三种写法:

  • 实现接口AsyncConfigurer
@Configuration
@Slf4j
public class AsyncConfiguration implements AsyncConfigurer {

    @Bean("fighter3AsyncExecutor")
    public ThreadPoolTaskExecutor executor() {
        //Spring封装的一个线程池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //随便写的一些配置
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(30);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix("fighter3AsyncExecutor-");
        executor.initialize();
        return executor;
    }

    @Override
    public Executor getAsyncExecutor() {
        return executor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> log.error(String.format("[async] task{} error:", method), ex);
    }
}

  • 继承AsyncConfigurerSupport
@Configuration
@Slf4j
public class SpringAsyncConfigurer extends AsyncConfigurerSupport {

    @Bean
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        //随便写的一些配置
        threadPool.setCorePoolSize(10);
        threadPool.setMaxPoolSize(30);
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        threadPool.setAwaitTerminationSeconds(60 * 15);
        return threadPool;
    }

    @Override
    public Executor getAsyncExecutor() {
        return asyncExecutor();
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> log.error(String.format("[async] task{} error:", method), ex);
    }
}

  • 配置自定义的TaskExecutor

    @Slf4j
    @Service
    public class OrderLogListener  {
    
        @EventListener
        @Async("asyncExecutor")
        public void orderLog(PlaceOrderEvent event) {
            log.info("[afterPlaceOrder] log.");
        }
    }
    
    
    • 配置线程池

      @Configuration
      public class TaskPoolConfig {
      
          @Bean(name = "asyncExecutor")
          public Executor taskExecutor() {
              ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
              //随便写的一些配置
              executor.setCorePoolSize(10);
              executor.setMaxPoolSize(20);
              executor.setQueueCapacity(200);
              executor.setKeepAliveSeconds(60);
              executor.setThreadNamePrefix("asyncExecutor-");
              executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
              return executor;
          }
      }
      
      
    • 使用@Async注解的时候,指定线程池,推荐使用这种方式,因为在项目里,尽量做到线程池隔离,不同的任务使用不同的线程池

异步和自定义线程池这一部分只是一些扩展,稍微占了一些篇幅,大家可不要觉得Spring Event用起来很繁琐。

发布事件

发布事件也非常简单,只需要使用Spring 提供的ApplicationEventPublisher来发布自定义事件。

  • OrderServiceImpl

    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService {
        @Autowired
        private ApplicationEventPublisher applicationEventPublisher;
    
        /**
         * 下单
         */
        @Override
        public PlaceOrderResVO placeOrder(PlaceOrderReqVO reqVO) {
            log.info("[placeOrder] start.");
            PlaceOrderResVO resVO = new PlaceOrderResVO();
            //消息
            PlaceOrderEventMessage eventMessage = new PlaceOrderEventMessage();
            //发布事件
            applicationEventPublisher.publishEvent(new PlaceOrderEvent(eventMessage));
            log.info("[placeOrder] end.");
            return resVO;
        }
    }
    

在Idea里查看事件的监听者也比较方便,点击下面图中的图标,就可以查看监听者。

图片查看监听者

图片监听者

测试

最后,我们还是测试一下。

    @Test
    void placeOrder() {
        PlaceOrderReqVO placeOrderReqVO = new PlaceOrderReqVO();
        orderService.placeOrder(placeOrderReqVO);
    }
  • 执行结果
2022-11-08 10:05:14.415  INFO 22674 --- [           main] c.f.o.event.event.OrderServiceImpl       : [placeOrder] start.
2022-11-08 10:05:14.424  INFO 22674 --- [           main] c.f.o.event.event.OrderServiceImpl       : [placeOrder] end.
2022-11-08 10:05:14.434  INFO 22674 --- [sync-executor-3] c.f.o.event.event.OrderNotifyListener    : [afterPlaceOrder] notify.
2022-11-08 10:05:14.435  INFO 22674 --- [sync-executor-2] c.f.o.event.event.OrderMetricsListener   : [afterPlaceOrder] metrics
2022-11-08 10:05:14.436  INFO 22674 --- [sync-executor-1] c.f.o.event.event.OrderLogListener       : [afterPlaceOrder] log.

可以看到,异步执行,而且用到了我们自定义的线程池。

小结

这篇文章里,从最开始自己实现的观察者模式,再到利用Spring简化的观察者模式,再到使用Spring Event实现发布/订阅模式,可以看到,Spring Event用起来还是比较简单的。除此之外,还有Guava EventBus这样的事件驱动实现,大家更习惯使用哪种呢?

结语

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。

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

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

相关文章

SSM框架-Spring(二)

目录 1 手写spring框架 2 Spring IoC注解式开发 2.1 回顾注解 2.2 声明Bean的注解 2.3 Spring注解的使用 2.4 选择性实例化Bean 2.5 负责注入的注解 2.5.1 Value 2.5.2 Autowired与Qualifier 2.5.3 Resource 2.6 全注解式开发 3 JdbcTemplate 3.1 环境准备 3.2 新…

深化服务成工业品电商角逐新焦点

配图来自Canva可画 随着产业互联网的持续深入&#xff0c;TO B赛道就变得愈发火热起来&#xff0c;除了时下比较兴盛的各类SaaS应用之外&#xff0c;工业品电商赛道也再次汇集了外界的众多关注。据日前发布的《中国工业品数字化发展白皮书2022》显示&#xff0c;2022年上半年工…

应急响应-日志分析

Windows日志分析 日志概述在Windows系统中&#xff0c;日志文件包括:系统日志、安全性日志、应用程序日志&#xff1a; 在Windows Vista/windwos 7/windows 8/windows 10/windows server 2008及以上版本中&#xff1a; 系统日志的存放位置&#xff1a;%SystemRoot%\System32\…

【多线程 (一)】实现多线程的三种方式、线程休眠、线程优先级、守护线程

文章目录多线程1.1简单了解多线程1.2并发和并行1.3进程和线程1.4实现多线程方式一&#xff1a;继承 Thread类1.5实现多线程的方式二&#xff1a;实现 Runnable接口1.6实现多线程方式三&#xff1a;实现Callable接口1.7三种实现方式的对比1.8设置和获取线程名称1.9线程休眠1.10线…

电商网站运营的 7 大关键指标

本文介绍电商网站用户运营转化的相关指标体系&#xff0c;通过对这些指标的统计、监测和分析&#xff0c;可以及时发现电商运营的问题&#xff0c;以便有效及时改进和优化&#xff0c;提升电商转化率和销售额。 其中&#xff0c;不同类别指标对应电商运营的不同环节&#xff0…

02 使用jenkins实现K8s持续集成

1.项目架构的代码仓库使用gitlab托管 架构描述我不打算用过多文字描述了&#xff0c;来我们一起直接看图吧....二.将测试代码上传到gitlab 1.注册gitlab账户 此处使用本地搭建仓库2.创建仓库名称为"idiaoyan" 如下图所示&#xff0c;安装图解方式创建相应的用户即…

基于Multisim的LC正弦波振荡器的设计与仿真

目 录 1、绪论 1 1.1选题背景及意义 1 1.2国内外研究现状 1 1.3研究主要内容 2 2、系统整体设计 3 2.1开发环境Multisim的介绍 3 2.2方案比较与论证 4 2.2.1振荡电路方案选择 4 2.2.2 控制电路设计方案 4 2.3系统整体设计 5 3、工作原理、硬件电路的设计和参数的计算 6 3.1 反馈…

2022中科院分区表即将公布,今年迎来较大变化

再有一段时间&#xff0c;备受科研人员关注的中科院分区表就要公布了。 据中科院文献情报中心分区表小编今天留言透露&#xff0c;今年的分区表预计11月底或12月初上线。 不少科研人已经开始期待了 。和往年相比&#xff0c;今年的分区表将会有较大变化。 只有升级版期刊分区…

Java多线程(一)

目录 一、基本概念 程序、进程、线程 单核CPU和多核CPU 并行与并发 使用多线程的优点 二、线程的创建与使用 线程的创建和启动 Thread类 Thread类的特性 Thread类的构造器 API中创建线程的两种方式 创建线程方式一&#xff1a;继承Thread类 创建继承Thread类线程方…

魔众文库系统 v3.5.0 预览页数调整,批量操作命令,多处优化

魔众文库系统基于文档系统知识&#xff0c;建立平台与领域&#xff0c;打造流量、用户、付费和变现的闭环&#xff0c;帮助您更好的搭建文库系统。 魔众文库系统发布v3.5.0版本&#xff0c;新功能和Bug修复累计23项&#xff0c;预览页数调整&#xff0c;批量操作命令&#xff…

Java基础深化和提高 ---- 反射技术

目录 反射机制介绍 什么是反射 反射的作用 创建对象过程 Java创建对象的三个阶段 创建对象时内存结构 反射的具体实现 创建Users类 通过getClass()方法获取Class对象 通过.class 静态属性获取Class对象 通过forName()获取Class对象 获取类的构造方法 通过构造方法创…

设计模式之享元模式(十)

目录 1. 享元模式概述 2. 享元模式在Integer中的应用 1. 享元模式概述 享元模式&#xff08;Flyweight Pattern&#xff09; 也叫 蝇量模式&#xff0c;运用共享技术有效地支持大量细粒度的对象。简单来说就是共享对象。 享元模式能够解决重复对象的内存浪费的问题&#xff…

Python3,os模块还可以这样玩,自动删除磁盘文件,非必要切勿操作。

删除磁盘下所有的文件1、引言2、代码实战2.1 模块介绍2.2 获取盘符2.3 获取盘符下的目录2.3.1 os.listdir()2.3.2 os.environ2.3.3 os.getenv()2.4 删除文件2.4.1 删除指定文件下文件2.4.2 删除所有文件下文件3、总结1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 请教你个问题…

Flink基础篇(基础算子+WaterMarker)

Flink高可用HA 依赖于zkFlink ON Yarn两种模式Session模式Per-Job模式前置说明Flink原理数据在两个operator算子之间传递的时候有两种模式&#xff1a;Operator ChainTaskSlot And SharingFlink执行图&#xff08;ExecutionGraph&#xff09;APISourceTransformationSink控制台…

【图像识别-车牌识别】基于BP神经网络求解车牌识别问题含GUI界面和报告

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

11个销售心理学方法,帮你搞定客户!

销售心理学中&#xff0c;站在客户的角度&#xff0c;客户都会有以下几个疑问&#xff1a; 1、你是谁&#xff1f; 2、你要跟我介绍什么&#xff1f; 3、你介绍的产品和服务对我有什么好处&#xff1f; 4、如何证明你介绍的是真实的&#xff1f; 5、为什么我要跟你买&…

Linux学习之expect操作详解

一、expect安装介绍 1.expect命令安装 安装语句:yum install expect 2.expect命令含义 expect是一种脚本语言&#xff0c;它能够代替人工实现与终端的交互&#xff0c;主要应用于执行命令和程序时&#xff0c;系统以交互形式要求输入指定字符串&#xff0c;实现交互通信。 …

疟原虫蛋白复合物疫苗科研

疟疾是一种蚊媒疾病&#xff0c;感染者通常会出现发烧、发冷和流感样疾病。如果不及时治疗&#xff0c;严重者甚至会危及生命。世卫组织新近发布的数据表明&#xff0c;2019 年全球估计发生 2.29 亿疟疾病例&#xff0c;死于该病的人数超过 40 万例。 图 1. 2000 年有病例的国…

Flutter 应用程序中的 Quick Actions

Flutter 应用程序中的 Quick Actions 原文 https://medium.com/vijay-r/quick-actions-in-flutter-app-75b63acc420b 前言 在这篇文章中&#xff0c;我们将讨论如何添加 Quick Actions 在我们的 Flutter 应用程序&#xff0c;使我们的应用程序更加友好的用户。 正文 插件 quick…

LVM逻辑卷

要求&#xff1a;在系统下做LVM逻辑卷2G&#xff0c;并将LVM进行扩容到5G 操作环境&#xff1a;7.8.2003 [rootlocalhost ~]# lsblk #列出所有可用块设备信息 我们使用vdb和vdc两块硬盘做lvm 先将一个盘进行分区&#xff08;/dev/vdb&#xff09; [r…