Vert.x学习笔记-Vert.x的基本处理单元Verticle

news2024/11/24 18:24:42

Verticle介绍

Verticle是Vert.x的基本处理单元,Vert.x应用程序中存在着处理各种事件的处理单元,比如负责HTTP API响应请求的处理单元负责数据库存取的处理单元负责向第三方发送请求的处理单元。Verticle就是对这些功能单元的封装,Verticle可被部署,有自己的生命周期,Verticle是Vert.x中构建异步事件处理程序及相关业务逻辑的基础。

Verticle特点

Verticle是Vert.x框架的核心概念,它是一种单一组件,所有业务功能都使用这种组件来完成。

  • 简单性(Simplicity) :Vert.x的设计采用了单一组件结构,即Verticle,这使得所有业务功能都使用同一种组件进行编程,从而让开发人员能够快速适应Vert.x编程,并加快项目的开发速度。
  • 并发性(Concurrency) :在处理多用户并发连接请求时,Vert.x摒弃了传统的多线程、同步工作、阻塞模式,而采用简单的单线程、异步工作、非阻塞模式,通过单线程内的事件驱动实现并发处理。这种处理方式更高效地利用了系统资源,并能够更好地应对高并发场景。
  • 线程隔离性(Thread Isolation) :Verticle的最大特色就是它的线程隔离性。在启动时,Verticle就被分配给了创建和start方法调用的Event Loop。当调用一个使用core API的handler的方法时,Vert.x保证这些handler将在同一个Event Loop上执行。这种线程隔离性可以保证在Verticle实例的代码是在同一个Event Loop执行,避免了多线程环境下的并发问题。

实现原理

Verticle的实现原理主要基于事件驱动和非阻塞I/O模型。在Vert.x中,应用程序将业务逻辑封装在事件驱动的处理程序中,称为Verticle。每个Verticle都是一个独立的运行单元,可以按照一定规则通过时间和状态变换来实现。
具体来说,当外部系统或客户端发起请求时,Vert.x会将请求转换为处理程序可以处理的标准格式,并将处理结果返回给客户端。在这个过程中,Vert.x采用了异步消息传递的方式,使得不同Verticle之间可以相互通信。
此外,Vert.x还提供了容器来为Verticle提供运行的环境和服务,并支持将多个Verticle部署在同一容器中。当一个Verticle需要访问网络服务器时,它可以通过容器来获取相应的服务,并且当多个Verticle实例被部署时,事件会按照轮询的方式分发到每个Verticle实例,这有助于在大量并发网络请求时最大化CPU使用率。
总之,Verticle的实现原理是基于事件驱动和非阻塞I/O模型,通过将业务逻辑封装在事件驱动的处理程序中,并借助容器提供运行环境和服务的支持,实现了高效、可扩展、易于使用的应用程序开发方式。

接口定义

public interface Verticle {

  /**
   * Get a reference to the Vert.x instance that deployed this verticle
   * 获取一个Vert.x实例的引用用来发布当前的verticle
   */
  Vertx getVertx();

  /**
   * Initialise the verticle with the Vert.x instance and the context.
   * 通过Vert.x实例和上下文来初始化一个Verticle
   * <p>
   * This method is called by Vert.x when the instance is deployed. You do not call it yourself.
   *
   * @param vertx  Vert.x实例
   * @param context 上下文
   */
  void init(Vertx vertx, Context context);

  /**
   * Start the verticle instance.
   * 启动当前的verticle实例
   * <p>
   * Vert.x calls this method when deploying the instance. You do not call it yourself.
   * <p>
   * A promise is passed into the method, and when deployment is complete the verticle should either call
   * {@link io.vertx.core.Promise#complete} or {@link io.vertx.core.Promise#fail} the future.
   *
   * @param startPromise  the future
   */
  void start(Promise<Void> startPromise) throws Exception;

  /**
   * Stop the verticle instance.
   * 停止当前的verticle实例
   * <p>
   * Vert.x calls this method when un-deploying the instance. You do not call it yourself.
   * <p>
   * A promise is passed into the method, and when un-deployment is complete the verticle should either call
   * {@link io.vertx.core.Promise#complete} or {@link io.vertx.core.Promise#fail} the future.
   *
   * @param stopPromise  the future
   */
  void stop(Promise<Void> stopPromise) throws Exception;
}

在这里插入图片描述

Verticle编写

在日常的开发中,我们通常会采用继承AbstractVerticle抽象类的方式来实现自定义的Verticle,理论上来说,我们也可以通过实现Verticle接口的方式来定义一个Verticle实现类,但是开发人员通常会通过继承AbstractVerticle的方式来实现,因为它提供了所有Vert.x用户都会用到的事件处理程序、配置信息、执行流水线等。

Vert.x是一个库,而不是一个框架,所以你既可以选择从main方法中创建Vert.x实例,也可以选择从任何其它类中来创建,先创建好Vert.x,再部署Vertcle

一个Verticle的生命周期由start和stop事件组成,AbstractVerticle类提供了start和stop方法,我们可以覆盖它们

  • start:设置初始化工作,初始化事件处理程序,启动Http服务端口监听等

  • stop: 执行一些清理任务,例如关闭数据库连接,停止Http端口监听等

默认情况,这两个方法什么都不做

代码实例

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloVerticle extends AbstractVerticle {

  private final Logger logger = LoggerFactory.getLogger(HelloVerticle.class);
	
  //定义一个全局变量	
  private long counter = 1;

  @Override
  public void start() {
  
	//定义一个每5秒执行一次的周期性任务
    vertx.setPeriodic(5000, id -> {
      logger.info("tick");
    });
	
	//定义一个HttpServer,监听8080端口
    vertx.createHttpServer()
      .requestHandler(req -> {
        logger.info("Request #{} from {}", counter++, req.remoteAddress().host());
        req.response().end("Hello!");
      })
      //监听8080端口
      .listen(8080);

    logger.info("Open http://localhost:8080/");
  }

  public static void main(String[] args) {
  	//实例化一个全局的Vert.x实例
    Vertx vertx = Vertx.vertx();
    //部署一个Verticle
    vertx.deployVerticle(new HelloVerticle());
  }
}

通过执行上面的实例代码,可以得出一个Verticle的重要属性:事件处理是在单个事件循环线程上进行的,从日志可以看出来,他们都是在 vert.x-eventloop-thread-0 这个线程上进行执行的,该设计的好处就在于在同一个线程上进行,减少了锁对性能的影响,同时减少了线程切换的开销,

代码实例交互过程

在这里插入图片描述

HelloVerticle调用Vert.x对象上的setPeriodic创建周期性任务处理程序,该对象又使用Vert.x定时器创建周期性任务,反过来,定时器定时回调HelloVerticle中的InternalTimerHandler处理程序

事件循环线程与Verticle的关系

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloVerticle extends AbstractVerticle {

  private final Logger logger = LoggerFactory.getLogger(HelloVerticle.class);

  private long counter = 1;
  public long delay;
  public int port;
  HelloVerticle (long delay, int port) {
    this.delay = delay;
    this.port = port;
  }
  public void start() {
    start(delay, port);
  }

  public void start(long delay, int port) {

    vertx.setPeriodic(delay, id -> {
      logger.info("tick : " + delay);
    });

    vertx.createHttpServer()
      .requestHandler(req -> {
        logger.info("Request #{} from {}", counter++, req.remoteAddress().host());
        req.response().end("Hello!");
      })
      .listen(port);

    logger.info("Open http://localhost:" + port);
  }

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    vertx.deployVerticle(new HelloVerticle(5000, 8080));

    vertx.deployVerticle(new HelloVerticle(3000, 8081));
  }
}

通过运行上面的服务并查看对应的日志,就会发现每一个Verticle实例对应了一个独立的事件循环线程

阻塞和事件循环

事件处理程序运行再事件循环线程上,所以这里需要特别注意的一个点:在事件循环的线程中执行的代码所用时间要尽可能短,这样事件循环才能有更高的吞吐量处理大量的事件。所以程序员不应该在事件循环线程中执行耗时太长的任务或进行阻塞IO的操作
下面通过一个代码实例来展示一下当我们故意阻塞事件循环线程后的情形:

public class BlockEventLoop extends AbstractVerticle {

  @Override
  public void start() {
  	//定义一个1000毫秒延时的定时器
    vertx.setTimer(1000, id -> {
    	//进入死循环,模拟阻塞
      while (true);
    });
  }

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    vertx.deployVerticle(new BlockEventLoop());
  }
}

运行结果:

WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 2592 ms, time limit is 2000 ms
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 3596 ms, time limit is 2000 ms
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 4600 ms, time limit is 2000 ms
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 5603 ms, time limit is 2000 ms
io.vertx.core.VertxException: Thread blocked
	at app//chapter2.blocker.BlockEventLoop.lambda$start$0(BlockEventLoop.java:11)
	at app//chapter2.blocker.BlockEventLoop$$Lambda$78/0x00000008001a5040.handle(Unknown Source)
	at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:951)
	at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:918)
	at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:52)
	at app//io.vertx.core.impl.ContextImpl.emit(ContextImpl.java:294)
	at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)
	at app//io.vertx.core.impl.AbstractContext.emit(AbstractContext.java:49)
	at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)
	at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.run(VertxImpl.java:941)
	at app//io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
	at app//io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)
	at app//io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
	at app//io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
	at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
	at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base@11.0.21/java.lang.Thread.run(Thread.java:829)
WARN [vertx-blocked-thread-checker] BlockedThreadChecker - Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 6605 ms, time limit is 2000 ms
io.vertx.core.VertxException: Thread blocked
	at app//chapter2.blocker.BlockEventLoop.lambda$start$0(BlockEventLoop.java:11)
	at app//chapter2.blocker.BlockEventLoop$$Lambda$78/0x00000008001a5040.handle(Unknown Source)
	at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:951)
	at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.handle(VertxImpl.java:918)
	at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:52)
	at app//io.vertx.core.impl.ContextImpl.emit(ContextImpl.java:294)
	at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)
	at app//io.vertx.core.impl.AbstractContext.emit(AbstractContext.java:49)
	at app//io.vertx.core.impl.EventLoopContext.emit(EventLoopContext.java:24)
	at app//io.vertx.core.impl.VertxImpl$InternalTimerHandler.run(VertxImpl.java:941)
	at app//io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
	at app//io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)
	at app//io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
	at app//io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
	at app//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
	at app//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at app//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base@11.0.21/java.lang.Thread.run(Thread.java:829)

当事件处理程序进入阻塞状态后,日志开始输出告警信息,现在开始事件循环线程无法处理其它的事件啦,经过几轮告警(默认设置为5秒)后,就开始打印线程堆栈信息啦,但是需要注意的是,这些只是警告信息,事件循环线程的检查器并不能终止这些阻塞的操作

线程阻塞检查器配置

默认情况,事件循环线程阻塞器发出告警的时间2秒,打印线程堆栈信息的时间是5秒,根据不同设备的处理能力,该时间可以进行灵活的配置

  • -Dvertx.options.blockedThreadCheckInterval = 1000 //检查器阈值设置为1秒
  • -Dvertx.threadChecks=false //禁用线程阻塞器

生命周期事件异步通知

Verticle中的start和stop方法被称作生命周期方法,该方法在定义时是没有返回值的,根据约定,除非该方法在调用过程中发生了异常,否则则认为该调用是成功的,但是在start和stop方法中可能存在一些异步的操作,那我们如何来监听这些异步操作的成功与失败呢,下面我们就来介绍一下Promise

Vert.x的Promise是对Future-Promise模型的适配,这是一个用于处理异步结果的机制。Promise用来写入异步结果,而Future用来查看异步结果。可调用一个Promise对象的future方法以取得Vert.x中的Future类型的Future对象

package chapter2.future;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;

public class FutureVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> promise) {   // Promise的类型是Void,因为Vert.x只关心部署是否成功,不关心带了什么参数
    vertx.createHttpServer()
      .requestHandler(req -> req.response().end("Ok"))
      .listen(8080, ar -> {
        if (ar.succeeded()) {       // 这个listen函数支持异步回调,通过它就知道了结果是成功还是失败
          promise.complete();   // complete函数用于将promise标记为以完成状态,如果Promise的对象类型不是Void,complete函数也可以传递值
        } else {
          promise.fail(ar.cause()); // 如果listen操作失败,将promise标记为失败,并传递一个异常信息
        }
      });
      System.out.println("start执行完成");
  }

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    //定义一个异步的回调,来监测start方法的执行情况
    vertx.deployVerticle(new FutureVerticle(), h -> {
      if (h.succeeded()) {
        System.out.println("成功");
      } else {
        System.out.println("失败:" + h.cause().getMessage());
      }
    });

  }
}

日常开发中尽量使用有回调参数的异步方法,这样当发生错误的时候,我们就可以得到通知

Verticle配置

应用程序启动的时候经常需要传入不同的配置参数,我们通过Vert.x来部署Verticle的时候也可以进行参数传递和配置,配置数据需要以JSON格式来进行传递,次数使用Vert.x中JsonObject和JsonArray类中的具体API

配置传递示例代码

public class ConfigVerticle extends AbstractVerticle {

  private final Logger logger = LoggerFactory.getLogger(ConfigVerticle.class);

  @Override
  public void start() {
    logger.info("n = {}", config().getInteger("n", -1));
  }

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    for (int n = 0; n < 4; n++) {
      JsonObject conf = new JsonObject().put("n", n);
      DeploymentOptions opts = new DeploymentOptions() //该对象可以让我们更多的控制部署过程
        .setConfig(conf)
        .setInstances(n); // 该配置表示一次部署了Verticle多个实例
      vertx.deployVerticle("xxx.ConfigVerticle", opts); //当我们一次性部署Verticle多个实例时,该处需要用类的全名;如果是部署单个实例,则可以用全名或者new
    }
  }
}

Verticle部署

上面的示例中,我们通过在Verticle内嵌main方法来完成Verticle的部署,部署都是通过Vert.x对象来进行的,一个由多个Verticle组成的应用程序的典型部署方式是这样的:

  • 先部署一个主Verticle
  • 主Verticle再部署其它的Verticle
  • 被部署的Verticle可以继续部署更多的Verticle

这样的部署方式可能让我们感觉这些Verticle是存在层级关系的,实际上,这些Verticle在运行中不存在父子的概念

部署示例

待部署Verticle
public class BaseVerticle extends AbstractVerticle {

  private final Logger logger = LoggerFactory.getLogger(BaseVerticle.class);

  @Override
  public void start() {
    logger.info("Start");
  }

  @Override
  public void stop() {
    logger.info("Stop");
  }
专门用于部署Verticle的部署工具
public class Deployer extends AbstractVerticle {

  private final Logger logger = LoggerFactory.getLogger(Deployer.class);

  @Override
  public void start() {
    long delay = 1000;
    for (int i = 0; i < 50; i++) {
      vertx.setTimer(delay, id -> deploy());  // 每隔1秒部署一个 BaseVerticle 实例
      delay = delay + 1000;
    }
  }

  private void deploy() {
    vertx.deployVerticle(new BaseVerticle(), ar -> {   // vertx中的Vertcle的部署是一个异步的操作,所以这里使用带异步结果的异步方法来进行部署,部署成功会生成一个唯一的Verticle ID
      if (ar.succeeded()) {
        String id = ar.result();
        logger.info("Successfully deployed {}", id);
        vertx.setTimer(5000, tid -> undeployLater(id));   // Verticle部署成功5秒以后就卸载该实例
      } else {
        logger.error("Error while deploying", ar.cause());
      }
    });
  }

  private void undeployLater(String id) {
    vertx.undeploy(id, ar -> {  //卸载的过程和部署的过程类似,也是一个异步的操作
      if (ar.succeeded()) {
        logger.info("{} was undeployed", id);
      } else {
        logger.error("{} could not be undeployed", id);
      }
    });
  }
}

主启动类

public class Main {

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();
    vertx.deployVerticle(new Deployer());
  }
}

该方法也可以直接放在 Deployer 中,放在外面层次更分明一些

线程启动日志分析
线程ID操作行为
vert.x-eventloop-thread-2EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed 7186663f-10a6-4cdc-985a-d3357fe240db
vert.x-eventloop-thread-3EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed 88ba21cb-e8c1-403b-bf29-edde7c1f2ebc
vert.x-eventloop-thread-4EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed e1ccd570-c631-4035-b7d2-7b06bbc3c326
vert.x-eventloop-thread-5EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed 8ab2b0f5-856f-498b-8a50-0622a65233cc
vert.x-eventloop-thread-6EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed 1191576b-a3c1-49bb-a882-29cdc041a9c2
vert.x-eventloop-thread-7EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed 12512cf1-3ad7-41c6-82e0-bd1478524c73
vert.x-eventloop-thread-2EmptyVerticle - Stop
vert.x-eventloop-thread-0Deployer - 7186663f-10a6-4cdc-985a-d3357fe240db was undeployed
vert.x-eventloop-thread-1EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed cd6fbb87-10fb-46da-b1c5-710a4d0e305c
vert.x-eventloop-thread-3EmptyVerticle - Stop
vert.x-eventloop-thread-0Deployer - 88ba21cb-e8c1-403b-bf29-edde7c1f2ebc was undeployed
vert.x-eventloop-thread-0EmptyVerticle - Start
vert.x-eventloop-thread-0Deployer - Successfully deployed a574512e-509a-45cb-968c-4ae6c89f7ca2
vert.x-eventloop-thread-2EmptyVerticle - Start

通过上面的日志,我们可以得到这样的一些结论:

  • 针对于不同的Verticle,并不是有多少个Verticle就会有多少个线程,默认情况下,Vert.x创建的事件循环线程的数量是CPU核心数的2倍,比如说:CPU是4核,那么Vert.x默认会创建8个事件循环线程。Verticle会被轮流分配给各事件循环
  • 一个Verticle总是运行在同一个事件循环线程上,而一个事件循环线程会被多个Verticle共享,这种设计使应用程序运行时的线程数量是可以预测的

开发人员可以调整事件循环的数量,但是无法手动指定一个Verticle应该分配到那个事件循环

Vert.x相关笔记

  • 异步变成与响应式系统
  • 什么是Vert.x

在这里插入图片描述

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

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

相关文章

数据中心系统解决方案

设计思路 系统设计过程中充分考虑各个子系统的信息共享要求&#xff0c;对各子系统进行结构化和标准化设计&#xff0c;通过系统间的各种联动方式将其整合成一个有机的整体&#xff0c;使之成为一套整体的、全方位的数据中心大楼综合管理系统&#xff0c;达到人防、物防和技防…

MySQL8.0.26-unbuntu版安装

MySQL8.0.26-ubuntu版安装 在这里会有一个坑&#xff0c;就是我在安装的时候,是按照另外一种版本的安装&#xff0c;报错没有rpm这个包&#xff0c;然后我就去下载&#xff0c;然后就报错 E: 无法定位软件包 &#xff0c;害的我找了好久的资料&#xff0c;一直没有解决&#x…

2023-11-04:用go语言,如果n = 1,打印 1*** 如果n = 2,打印 1*** 3*** 2*** 如果n = 3,打印

2023-11-04&#xff1a;用go语言&#xff0c;如果n 1&#xff0c;打印 1*** 如果n 2&#xff0c;打印 1***3*** 2*** 如果n 3&#xff0c;打印 1***3*** 2***4*** 5*** 6*** 如果n 4&#xff0c;打印 1***3*** 2***4*** 5*** 6***10** 9*** 8*** 7*** 输入…

SoftwareTest5 - 你就只知道功能测试吗 ?

你就只知道功能测试吗 ? 一 . 按照测试对象划分1.1 文档测试1.2 可靠性测试1.3 容错性测试1.4 安装卸载测试1.5 内存泄漏测试1.6 弱网测试 二 . 按是否查看代码划分2.1 黑盒测试2.2 白盒测试2.3 灰盒测试 三 . 按照开发阶段划分3.1 单元测试3.2 集成测试3.3 冒烟测试3.4 系统测…

如何通过智能管理箱实现高效文件管理:关键字轻松修改文件名

在信息化时代&#xff0c;文件管理变得尤为重要。智能管理箱已经成为我们生活中不可或缺的一部分。它可以帮助我们高效地管理各种文件&#xff0c;使得我们的工作和生活更加便捷。是一种高效的文件管理工具&#xff0c;可以帮助我们轻松地整理和分类文件&#xff0c;提高工作效…

【算法】昂贵的聘礼(dijkstra算法)

题目 年轻的探险家来到了一个印第安部落里。 在那里他和酋长的女儿相爱了&#xff0c;于是便向酋长去求亲。 酋长要他用 10000 个金币作为聘礼才答应把女儿嫁给他。 探险家拿不出这么多金币&#xff0c;便请求酋长降低要求。 酋长说&#xff1a;”嗯&#xff0c;如果你能够替我…

SpringBoot+AOP+自定义注解,优雅实现日志记录

文章目录 前言准备阶段1、数据库日志表2、自定义注解编写3、AOP切面类编写4、业务层4.1、Service 层&#xff1a;4.2 Service 实现层&#xff1a; 5、测试 前言 首先我们看下传统记录日志的方式是什么样的&#xff1a; DeleteMapping("/deleteUserById/{userId}") …

【C语言:函数栈帧的创建与销毁】

文章目录 前言一、前期准备1.寄存器2.汇编指令3.测试代码 二、解开函数栈帧的神秘面纱1.栈帧大体轮廓2.main函数栈帧的创建3.main函数内执行有效代码4.烫烫烫5.函数参数的传递6.add函数栈帧的创建7.add函数内执行有效代码8.add是如何获得参数的9. add函数栈帧的销毁10.main函数…

IDEA中如何移除未使用的import

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是全栈工…

@Slf4j将日志记录到磁盘和数据库

文章目录 1、背景介绍2、存本地2.1、配置文件2.2、使用 3、存数据库3.1、配置文件改造3.2、过滤器编写3.3、表准备3.4、添加依赖3.5、测试 4、优化4.1、日志定期删除 1、背景介绍 现在我一个SpringBoot项目想记录日志&#xff0c;大概可以分为下面这几种&#xff1a; 用户操作…

速学数据结构 | 链表实现队列究竟有什么优势?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《速学数据结构》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! &#x1f4cb; 前言 &#x1f308;hello&#xff01; 各位宝子们大家好啊&#xff0c;栈区的实现我们前面已经讲了&#…

0005Java安卓程序设计-ssm基于Android的网店系统

文章目录 **摘要**目录系统设计开发环境 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 摘要 随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化&#xff0c;网络化和电子化。网上管…

系统提示缺少或找不到emp.dll文件的详细解决方案

我今天打开一款《游戏》。然而&#xff0c;在游戏中遇到了一个非常棘手的问题&#xff1a;游戏报错找不到emp.dll,无法继续执行代码。这让我们非常苦恼&#xff0c;因为这个问题严重影响了我们的游戏体验。 在经过一番努力之后&#xff0c;我终于找到了4个解决方法&#xff0c…

要讨个公道,要分辨真假

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Qt利用VCPKG和CMake和OpenCV和Tesseract实现中英文OCR

文章目录 1. 开发平台2. 下载文件2.1 下载安装 OpenCV 库2.2 下载安装 Tesseract-OCR库2.3 下载训练好的语言包 3. CMakeLists.txt 内容4. Main.cpp4.1 中英文混合OCR 5. 在Qt Creator 中设置 CMake vcpkg5.1 在初始化配置文件里修改5.2 在构建配置里修改 说明&#xff1a;在Q…

C语言--判断一个年份是否是闰年(详解)

一.闰年的定义 闰年是指在公历&#xff08;格里高利历&#xff09;中&#xff0c;年份可以被4整除但不能被100整除的年份&#xff0c;或者可以被400整除的年份。简单来说&#xff0c;闰年是一个比平年多出一天的年份&#xff0c;即2月有29天。闰年的目的是校准公历与地球公转周…

Git 的基本操作 ——命令行

Git 的工作流程 详解如下&#xff1a; 本地仓库&#xff1a;是在开发人员自己电脑上的Git仓库,存放我们的代码(.git 隐藏文件夹就是我们的本地仓库) 远程仓库&#xff1a;是在远程服务器上的Git仓库,存放代码(可以是github.com或者gitee.com 上的仓库,或者自己该公司的服务器…

【小白专用】PHP基本语法 23.11.04

PHP基本语法 PHP是超文本预处理器 由服务器解析执行 可以与 html 进行混编(嵌入) ,PHP是一种弱类型语言 1.1 PHP标记 PHP和其他Web语言一样&#xff0c;都是用一对标记将PHP代码包含起来&#xff0c;以便和HTML代码区分开来。PHP支持4种风格的标记&#xff0c;如表所示。 标…

王道p18 6.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同(c语言代码实现)

视频讲解在这里&#xff1a;&#x1f447; 顺序表p18 第6题wd数据结构课后代码题&#xff08;c语言代码实现&#xff09;_哔哩哔哩_bilibili 本题代码如下 void deleterepeat(struct sqlist* L) {if (L->length 0)printf("表空");int i 0;int k 0;for (i 1…

Vue3项目嵌套企业微信扫码登录

企业微信登录流程 企业微信提供了OAuth的授权登录方式&#xff0c;可以让从企业微信终端打开的网页获取成员的身份信息&#xff0c;从而免去登录的环节。 整个流程采用的是OAuth2&#xff0c;流程如下&#xff1a; 前端操作思路 配置一些参数&#xff0c;渲染登录模板也就是…