Spring MVC系列之异步请求

概述

Spring MVC的本质其实就是一个Servlet。在理解Spring MVC如何支持异步请求之前,需要先知道Servlet3异步如何支持异步请求。参考Servlet系列之Servlet3异步。

Spring MVC对异步请求的支持主要从三个类来看:

  • AsyncWebRequest:request
  • WebAsyncManager:处理异步请求的管理器
  • WebAsyncUtils:工具类

Spring MVC将异步请求细分为Callable、WebAsyncTask、DeferredResult三种类型,前两种是一类,核心是Callable。

组件

Spring MVC中异步请求涉及到的相关组件如下。

DeferredResult

Spring提供的一种用于保存延迟处理结果的泛型类,当一个处理器返回DeferredResult类型的返回值时将启动异步处理。

org.springframework.web.context.request.async.DeferredResult源码,版本为spring-web-6.1.5

public class DeferredResult<T> {private static final Object RESULT_NONE = new Object();@Nullableprivate final Long timeoutValue;private final Supplier<?> timeoutResult;@Nullableprivate Runnable timeoutCallback;@Nullableprivate Consumer<Throwable> errorCallback;@Nullableprivate Runnable completionCallback;@Nullableprivate DeferredResultHandler resultHandler;@Nullableprivate volatile Object result = RESULT_NONE;private volatile boolean expired;
}

AsyncRestTemplate

模板类方法,位于org.springframework.web.client包路径下,引入spring-web模块即可使用,但自Spring 5.0版本就被标记为@Deprecated。替换类为org.springframework.web.reactive.function.client.WebClient

WebClient

Spring 5新增的非阻塞、响应式HTTP客户端,更适合于异步请求和响应处理。

AsyncWebRequest

org.springframework.web.context.request.async.AsyncWebRequest源码:

public interface AsyncWebRequest extends NativeWebRequest {void setTimeout(@Nullable Long timeout);// 添加请求超时处理器void addTimeoutHandler(Runnable runnable);// 添加错误处理器void addErrorHandler(Consumer<Throwable> exceptionHandler);// 添加请求处理完成处理器void addCompletionHandler(Runnable runnable);void startAsync();// 判断是否启动异步处理boolean isAsyncStarted();void dispatch();// 判断异步处理是否已经处理完成boolean isAsyncComplete();
}

实现类有两个:

  • NoSupportAsyncWebRequest:不支持异步请求
  • StandardServletAsyncWebRequest:实际用作异步请求。除实现AsyncWebRequest接口外,还实现AsyncListener接口并继承ServletWebRequest

StandardServletAsyncWebRequest源码略,封装AsyncContext类型的属性asyncContext,在startAsync方法中会将Request#startAsync返回的AsyncContext设置给它,然后在别的地方主要使用它来完成各种功能。由于StandardServletAsyncWebRequest实现AsyncListener接口,所以它自己就是一个监听器,而且在startAsync方法中在创建出AsyncContext后会将自己作为监听器添加进去。监听器实现方法中onStartAsync方法和onError方法是空实现,onTimeout方法和onComplete方法分别调用封装的两个List<Runnable>类型的属性timeoutHandlers和completionHandlers所保存的Runnable方法,这样在使用时只需要简单地将需要监听超时和处理完成的监听方法添加到这两个属性中即可。

CallableProcessingInterceptor

拦截器接口,6个方法:

default <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
}
default <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
}
default <T> void postProcess(NativeWebRequest request, Callable<T> task,@Nullable Object concurrentResult) throws Exception {
}
default <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {return RESULT_NONE;
}
default <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {return RESULT_NONE;
}
default <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
}

拦截器的作用:在不同的时间点通过执行相应的方法来做一些额外的事情,理解拦截器的核心是理解它里边的各个方法执行的时间点。beforeConcurrentHandling方法是在并发处理前执行的,会在主线程中执行,其他方法都在具体处理请求的子线程中执行。

DeferredResultProcessingInterceptor

拦截器接口,6个方法,命名和上面一模一样。

CallableInterceptorChain

CallableInterceptorChain用于封装CallableProcessingInterceptor,将多个相应的拦截器封装到一个List类型的属性,然后在相应的方法中调用所封装的Interceptor相应方法进行处理。责任链模式。方法名与Interceptor中稍有区别,对应关系如下:

  • applyBeforeConcurrentHandling:对应Interceptor中的beforeConcurrentHandling方法
  • applyPreProcess:对应Interceptor中的preProcess方法
  • applyPostProcess:对应Interceptor中的postProcess方法
  • triggerAfterTimeout:对应Interceptor中的afterTimeout方法
  • triggerAfterCompletion:对应Interceptor中的afterCompletion方法
  • triggerAfterError:对应Interceptor中的handleError方法

DeferredResultInterceptorChain

同上,用于封装DeferredResultProcessingInterceptor。

WebAsyncTask

WebAsyncTask是一个泛型类,封装Callable方法,并提供一些异步调用相关的属性:

public class WebAsyncTask<V> implements BeanFactoryAware {// 用来实际处理请求private final Callable<V> callable;// 用于设置超时时间@Nullableprivate final Long timeout;// 用来调用callable@Nullableprivate final AsyncTaskExecutor executor;// 用容器中注册的名字配置executor@Nullableprivate final String executorName;// 用于根据名字获取executor@Nullableprivate BeanFactory beanFactory;// 用于执行超时的回调@Nullableprivate Callable<V> timeoutCallback;// 用于发生错误的回调@Nullableprivate Callable<V> errorCallback;// 用于请求处理完成的回调@Nullableprivate Runnable completionCallback;
}

executor可以直接设置到WebAsyncTask中,也可使用注册在容器中的名字来设置executorName属性:

@Nullable
public AsyncTaskExecutor getExecutor() {if (this.executor != null) {return this.executor;} else if (this.executorName != null) {Assert.state(this.beanFactory != null, "BeanFactory is required to look up an executor bean by name");return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class);} else {return null;}
}

WebAsyncManager

Spring MVC处理异步请求过程中最核心的类,管理着整个异步处理的过程。

几个重要属性:

  • timeoutCallableInterceptor:CallableProcessingInterceptor类型,专门用于Callable和WebAsyncTask类型超时的拦截器
  • timeoutDeferredResultInterceptor:DeferredResultProcessingInterceptor类型,专门用于DeferredResult和ListenableFuture类型超时的拦截器
  • callableInterceptors:Map类型,用于所有Callable和WebAsyncTask类型的拦截器
  • deferredResultInterceptors:Map类型,用于所有DeferredResult和ListenableFuture类型的拦截器
  • asyncWebRequest:为了支持异步处理而封装的request
  • taskExecutor:用于执行Callable和WebAsyncTask类型处理,如果WebAsyncTask中没有定义executor则使用WebAsyncManager中的taskExecutor。

最重要的两个方法是startCallableProcessing和startDeferredResultProcessing,是启动异步处理的入口方法。它们一共做三件事:

  • 启动异步处理;
  • 给Request设置相应属性(主要包括timeout、timeoutHandler和completionHandler);
  • 在相应位置调用相应的Spring MVC自定义的拦截器。

startCallableProcessing方法用于处理Callable和WebAsyncTask类型的异步请求,使用CallableProcessingInterceptor,拦截器封装在CallableInterceptorChain类型的拦截器链中统一调用。

startDeferredResultProcessing方法用于处理DeferredResult类型的异步请求,使用DeferredResultProcessingInterceptor拦截器,拦截器封装在DeferredResultInterceptorChain类型的拦截器链中统一调用。和startCallableProcessing方法执行过程类似,只是并没有使用taskExecutor来提交执行,这是因为DeferredResult并不需要执行处理。

startCallableProcessing方法主要做5件事:

  • 将webAsyncTask中相关属性取出并设置到对应的地方;
  • 初始化拦截器链;
  • 给asyncWebRequest设置timeoutHandler和completionHandler;
  • 执行处理器链中相应方法;
  • 启动异步处理并使用taskExecutor提交任务。

启动处理是调用startAsyncProcessing方法,源码如下:

private void startAsyncProcessing(Object[] processingContext) {synchronized (WebAsyncManager.this) {this.concurrentResult = RESULT_NONE;this.concurrentResultContext = processingContext;}Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");this.asyncWebRequest.startAsync();
}

做3件事:

  • 清空之前并发处理的结果
  • 将processingContext设置给concurrentResultContext属性
  • 调用asyncWebRequest的startAsync方法启动异步处理

processingContext参数传进来的是处理器中使用的ModelAndViewContainer,concurrent-ResultContext用来在WebAsyncManager中保存ModelAndViewContainer,在请求处理完成后会设置到RequestMappingHandlerAdapter中。

执行处理,执行处理使用的是taskExecutor,这里并没直接使用taskExecutor.submit(callable)来提交,而是提交新建的Runnable,并将Callable的call方法直接放在run方法里调用:

try {Future<?> future = this.taskExecutor.submit(() -> {Object result = null;try {interceptorChain.applyPreProcess(this.asyncWebRequest, callable);result = callable.call();} catch (Throwable ex) {result = ex;} finally {result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, result);}setConcurrentResultAndDispatch(result);});interceptorChain.setTaskFuture(future);
} catch (Throwable ex) {Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);setConcurrentResultAndDispatch(result);
}

这么做主要有两个作用:

  • 可以在处理过程中的相应位置调用拦截器链中相应的方法;
  • 在call方法执行完之前不会像Future#get()那样阻塞线程。

Runnable是没有返回值的,所以Callable处理的结果需要自己从run方法内部传递出来,WebAsyncManager.setConcurrentResultAndDispatch方法来处理返回的结果,这里边会将处理的结果传递出来:

// 省略日志打印
private void setConcurrentResultAndDispatch(@Nullable Object result) {// 检查asyncWebRequest和状态Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");synchronized (WebAsyncManager.this) {if (!this.state.compareAndSet(State.ASYNC_PROCESSING, State.RESULT_SET)) {return;}// 设置异步处理结果this.concurrentResult = result;// 检查Request是否已设置为异步处理完成状态(网络中断会造成Request设置为异步处理完成状态)if (this.asyncWebRequest.isAsyncComplete()) {return;}// 发送请求this.asyncWebRequest.dispatch();}
}

concurrentResult用来保存异步处理结果的属性。Spring MVC中异步请求处理完成后会再次发起一个相同的请求,然后在HandlerAdapter中使用一个特殊的HandlerMethod来处理它,具体过程后面再讲解,不过通过Request的dispatch方法发起的请求使用的还是原来的Request,也就是说原来保存在Request中的属性不会丢失。

WebAsyncUtils

源码省略。两个重载的getAsyncManager方法通过Request获取WebAsyncManager,分别是ServletRequest、WebRequest类型的Request,获取过程都是先判断Request属性里是否有保存的WebAsyncManager对象,如果有则取出后直接返回,如果没有则新建一个设置到Request的相应属性中并返回,下次再获取时直接从Request属性中取出。

createAsyncWebRequest方法用于创建AsyncWebRequest,调用上面提到的getAsyncManager方法获取WebAsyncManager,然后获取AsyncWebRequest。进而创建StandardServletAsyncWebRequest类型的Request并返回。

原理

Spring MVC对异步请求的处理主要在四个地方进行支持:

  • FrameworkServlet中给当前请求的WebAsyncManager添加CallableProcessingInterceptor类型的拦截器RequestBindingInterceptor,这是定义在FrameworkServlet内部的私有拦截器,其作用还是跟FrameworkServlet处理正常请求一样,在请求处理前将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder中,并在请求处理完成后恢复,添加过程在processRequest方法中
  • RequestMappingHandlerAdapter的invokeHandleMethod方法提供对异步请求的核心支持,其中做四件跟异步处理相关的事情,下文详述
  • 返回值处理器:一共有四个处理异步请求的返回值处理器,它们分别是AsyncTaskMethodReturnValueHandler、CallableMethodReturnValueHandler、DeferredResultMethodReturnValueHandler和ListenableFutureReturnValueHandler,每一个对应一种类型的返回值,作用主要是使用WebAsyncManager启动异步处理
  • 在DispatcherServlet的doDispatch方法中,当HandlerAdapter使用Handler处理完请求时,会检查是否已经启动异步处理,如果启动则不再往下处理,直接返回

RequestMappingHandlerAdapter

RequestMappingHandlerAdapter.invokeHandleMethod()方法源码略,四件事:

  • 创建AsyncWebRequest并设置超时时间,具体时间可以通过asyncRequestTimeout属性配置到RequestMappingHandlerAdapter中。
  • 对当前请求的WebAsyncManager设置了四个属性:taskExecutor、asyncWebRequest、callableInterceptors和deferredResultInterceptors,除了asyncWebRequest的另外三个都可以在RequestMappingHandlerAdapter中配置,taskExecutor如果没配置将默认使用MvcSimpleAsyncTaskExecutor(继承自SimpleAsyncTaskExecutor)。
  • 如果当前请求是异步请求而且已经处理出结果,则将异步处理结果与之前保存到WebAsyncManager里的ModelAndViewContainer取出来,并将WebAsyncManager里的结果清空,然后调用ServletInvocableHandlerMethod的wrapConcurrentResult方法创建ConcurrentResultHandlerMethod类型(ServletInvocableHandlerMethod内部类)的ServletInvocable-HandlerMethod来替换自己,创建出来的ConcurrentResultHandlerMethod并不执行请求,它的主要功能是判断异步处理的结果是不是异常类型,如果是则抛出,如果不是则使用ReturnValueHandler对其进行解析并返回。
  • 如果requestMappingMethod的invokeAndHandle方法执行完后检查到当前请求已经启动了异步处理,则会直接返回null。

调用ServletInvocableHandlerMethod的wrapConcurrentResult方法创建新的ServletInvocableHandlerMethod来处理异步处理的结果。ConcurrentResultHandlerMethod是在ServletInvocableHandlerMethod中定义的继承自ServletInvocableHandlerMethod的内部类:

private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {private final MethodParameter returnType;public ConcurrentResultHandlerMethod(@Nullable Object result, ConcurrentResultMethodParameter returnType) {super((Callable<Object>) () -> {if (result instanceof Exception exception) {throw exception;} else if (result instanceof Throwable throwable) {throw new ServletException("Async processing failed: " + result, throwable);}return result;}, CALLABLE_METHOD);if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);}this.returnType = returnType;}// 省略4个方法
}

ConcurrentResultHandlerMethod调用父类的构造方法(super)将HandlerMethod中的Handler和Method都替换掉,Handler用新建的匿名Callable,Method使用ServletInvocableHandlerMethod的静态属性CALLABLE_METHOD,它代码Callable的call方法。新建的Callable的执行逻辑也非常简单,就是判断异步处理的返回值是不是异常类型,如果是则抛出异常,不是则直接返回,然后使用和原来请求一样的返回值处理器处理返回值(因为在构造方法中将原来ServletInvocableHandlerMethod的返回值处理器设置给自己)。

流程

主要处理流程是这样的:首先在处理器中返回需要启动异步处理的类型时(三种类型)相应返回值处理器会调用WebAsyncManager的相关方法启动异步处理,然后在DispatcherServlet中将原来请求直接返回,当异步处理完成后会重新发出一个相同的请求,这时在RequestMappingHandlerAdapter中会使用特殊的ServletInvocableHandlerMethod来处理请求,处理方法是:如果异步处理返回的结果是异常类型则抛出异常,否则直接返回异步处理结果,然后使用返回值处理器处理,接着返回DispatcherServlet中按正常流程往下处理。

异步处理完成后会重新发起一个请求,这时会重新查找HandlerMethod并初始化PathVariable、MatrixVariable等参数,重新初始化Model中的数据并再次执行HandlerInterceptor中相应的方法。这么做主要是可以复用原来的那套组件进行处理而不需要重新定义。不过新请求的HandlerMethod是用的专门的类型,而Model是使用的原来保存在WebAsyncManager的concurrentResultContext属性中的ModelAndViewContainer所保存的Model,所以这里的查找HandlerMethod和初始化Model的过程是没用的,可进行一些优化,如,将创建ConcurrentResultHandlerMethod的过程放在HandlerMapping中(这样也更符合组件的功能),然后在调用ModelFactory的initModel方法前判断是不是异步处理dispatcher过来的请求,如果是则不再初始化,或者干脆创建新的HandlerAdapter来处理。

返回

当处理器方法返回WebAsyncTask或Callable类型时将自动启用异步处理。

当处理器方法返回WebAsyncTask类型的返回值时,Spring MVC使用AsyncTaskMethodReturnValueHandler来加以处理:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue == null) {mavContainer.setRequestHandled(true);return;}WebAsyncTask<?> webAsyncTask = (WebAsyncTask<?>) returnValue;if (this.beanFactory != null) {webAsyncTask.setBeanFactory(this.beanFactory);}WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(webAsyncTask, mavContainer);
}

如果返回值为null,就会给mavContainer设置为请求已处理,然后返回。如果返回值不为null,调用WebAsyncManager的startCallableProcessing方法处理请求。WebAsyncManager是使用WebAsyncUtils获取的。

当返回Callable类型时,使用CallableMethodReturnValueHandler来处理,源码略。

参考

  • 看透Spring MVC:源码分析与实践

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

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

相关文章

【数据结构】:链表的带环问题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 链表的带环问题在链表中是一类比较难的问题&#xff0c;它对我们的思维有一个比较高的要求&#xff0c;但是这一类…

AI大模型探索之路-训练篇10:大语言模型Transformer库-Tokenizer组件实践

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

DS:顺序表、单链表的相关OJ题训练

欢迎各位来到 Harper.Lee 的学习小世界&#xff01; 博主主页传送门&#xff1a;Harper.Lee的博客主页 想要一起进步的uu可以来后台找我交流哦&#xff01; 在DS&#xff1a;单链表的实现 和 DS&#xff1a;顺序表的实现这两篇文章中&#xff0c;我详细介绍了顺序表和单链表的…

使用LinkAI创建AI智能体,并快速接入到微信/企微/公众号/钉钉/飞书

​ LinkAI 作为企业级一站式AI Agent 智能体搭建与接入平台&#xff0c;不仅为用户和客户提供能够快速搭建具备行业知识和个性化设定的 AI 智能体的能力&#xff1b;还基于企业级场景提供丰富的应用接入能力&#xff0c;让智能体不再是“玩具”&#xff0c;而是真正能够落地应用…

PHP的数组练习实验

实 验 目 的 掌握索引和关联数组&#xff0c;以及下标和元素概念&#xff1b; 掌握数组创建、初始化&#xff0c;以及元素添加、删除、修改操作&#xff1b; 掌握foreach作用、语法、执行过程和使用&#xff1b; 能应用数组输出表格和数据。 任务1&#xff1a;使用一维索引数…

uniapp0基础编写安卓原生插件和调用第三方jar包和编写语音播报插件之使用jar包插件

前言 如果你不会编写安卓插件,你可以先看看我之前零基础的文章(uniapp0基础编写安卓原生插件和调用第三方jar包和编写语音播报插件之零基础编写安卓插件), 我们使用第三方包,jar包编写安卓插件 开始 把依赖包,放到某个模块的/libs目录(myTestPlug/libs) 还要到build…

R语言的学习—5—多元数据直观表示

1、数据读取 ## 数据整理 d3.1read.xlsx(adstats.xlsx,d3.1,rowNamesT);d3.1 #读取adstats.xlsx表格d3.1数据 barplot(apply(d3.1,1,mean)) #按行做均值条形图 barplot(apply(d3.1,1,mean),las3) barplot(apply(d3.1,2,mean)) #按列做均值图条形图 barplot(a…

C语言数据结构 ---- 单链表实现通讯录

今日备忘录: "折磨我们的往往是想象, 而不是现实." 目录 1. 前言2. 通讯录的功能3. 通讯录的实现思路5. 效果展示6. 完整代码7. 总结 正文开始 1. 前言 顺表实现通讯录: 点击~ 顺序表实现通讯录 在日常生活中&#xff0c;我们经常需要记录和管理大量的联系人信息&…

【研发管理】产品经理知识体系-组合管理

导读&#xff1a;新产品开发的组合管理是一个重要的过程&#xff0c;它涉及到对一系列新产品开发项目进行策略性选择、优先级排序、资源分配和监控。这个过程旨在确保企业能够最大化地利用有限的资源&#xff0c;以实现其战略目标。 目录 1、组合管理、五大目标 2、组合管理的…

第74天:漏洞发现-Web框架中间件插件BurpSuite浏览器被动主动探针

目录 思维导图 前置知识 案例一&#xff1a;浏览器插件-辅助&资产&漏洞库-Hack-Tools&Fofa_view&Pentestkit 案例二&#xff1a; BurpSuite 插件-被动&特定扫描-Fiora&Fastjson&Shiro&Log4j 思维导图 前置知识 目标&#xff1a; 1. 用…

基于springboot实现公司日常考勤系统项目【项目源码+论文说明】

基于springboot实现公司日常考勤系统演示 摘要 目前社会当中主要特征就是对于信息的传播比较快和信息内容的安全问题&#xff0c;原本进行办公的类型都耗费了很多的资源、传播的速度也是相对较慢、准确性不高等许多的不足。这个系统就是运用计算机软件来完成对于企业当中出勤率…

数据结构-链表OJ

1.删除链表中等于给定值 val 的所有结点。 . - 力扣&#xff08;LeetCode&#xff09; 思路一&#xff1a;遍历原链表&#xff0c;将值为val的节点释放掉 思路二&#xff1a;创建一个新链表&#xff0c;将值不为val的节点尾插到新链表中 /*** Definition for singly-linked …

2024年五一数学建模竞赛C题论文首发

基于随机森林的煤矿深部开采冲击地压危险预测 摘要 煤炭作为中国重要的能源和工业原料&#xff0c;其开采活动对国家经济的稳定与发展起着至关重要的作用。本文将使用题目给出的数据探索更为高效的数据分析方法和更先进的监测设备&#xff0c;以提高预警系统的准确性和可靠性…

智能消费记账|基于SSM+vue的大学生智能消费记账系统(源码+数据库+文档)

智能消费记账目录 基于SSMvue的大学生智能消费记账系统 一、前言 二、系统设计 三、系统功能设计 1 用户列表 2 预算信息管理 3 预算类型管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1…

代码随想录算法训练营DAY48|C++动态规划Part9|121.买卖股票的最佳时机、122.买卖股票的最佳时机II、123.买卖股票的最佳时机III

文章目录 121.买卖股票的最佳时机思路CPP代码 122.买卖股票的最佳时机II思路CPP代码 123.买卖股票的最佳时机III思路CPP代码 121.买卖股票的最佳时机 力扣题目链接 文章讲解&#xff1a;121.买卖股票的最佳时机 视频讲解&#xff1a;动态规划之 LeetCode&#xff1a;121.买卖股…

Android 音视频基础知识

本系列文章会介绍两个 Android NDK Demo&#xff0c;拉流端会实现一个基于 FFmpeg 的视频播放器 Demo&#xff0c;推流端会实现一个视频直播 Demo&#xff0c;当然在做 Demo 之前会介绍音视频的基础知识。以下是本系列文章的目录&#xff1a; Android 音视频基础知识 Android 音…

抢先体验:MacOS成功安装PHP8.4教程

根据官方消息&#xff0c;PHP 8.4将于2024年11月21日发布。它将通过三个 alpha 版本、三个 beta 版本和六个候选版本进行测试。 这次的重大更新将为PHP带来许多优化和强大的功能。我们很高兴能够引导您完成最有趣的更新升级&#xff0c;这些更改将使我们能够编写更好的代码并构…

Mac brew安装Redis之后更新配置文件的方法

安装命令 brew install redis 查看安装位置命令 brew list redis #查看redis安装的位置 % brew list redis /usr/local/Cellar/redis/6.2.5/.bottle/etc/ (2 files) /usr/local/Cellar/redis/6.2.5/bin/redis-benchmark /usr/local/Cellar/redis/6.2.5/bin/redis-check-ao…

WebAssembly学习记录

1.WebAssembly 1.1 指令集 概念&#xff1a;二进制编码集合。 依据计算机组成原理和计算机概论&#xff0c;指令集是一组二进制编码。 作用&#xff1a;控制硬件。 这些二进制指令直接作用于硬件电路&#xff0c;控制硬件完成指定操作。 例如&#xff1a;控制数据进入某个寄存…

【unocss】自用

unocss中文官网1 不知道简写的可以在这里查 第一步 npm install -D unocss第二步 // vite.config.ts import UnoCSS from unocss/vite import { defineConfig } from viteexport default defineConfig({plugins: [UnoCSS()] })// main.ts import virtual:uno.css第三步 在…