SpringMVC系列-5 消息转换器

背景

SpringMVC系列的第五篇介绍消息转换器,本文讨论的消息转换指代调用Controller接口后,对结果进行转换处理的过程。
内容包括介绍自定义消息转换器、SpringMVC常见的消息转换器、Spring消息转换器工作原理等三部分。

本文以 SpringMVC系列-2 HTTP请求调用链 和 SpringMVC系列-4 参数解析器 为基础,对相同内容不再重述。

1.自定义消息转换器

自定义消息转换器,需要实现HttpMessageConverter接口,该接口定义如下:

public interface HttpMessageConverter<T> {boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;List<MediaType> getSupportedMediaTypes();// ⚠️:read相关逻辑不是本文关注的部分boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
}

有三个比较重要的方法:
(1) getSupportedMediaTypes方法返回该解析器支持的MIME媒体类型;
(2) canWrite方法判断该解析器能否将目标类型的对象转化为指定的MIME媒体类型;
(3) write方法将目标对象转化为mediaType的二进制流并写入到outputMessage流对象中。

自定义消息转换器:

public class UserInfoHttpMessageConverter implements HttpMessageConverter<UserInfo> {@Overridepublic boolean canWrite(Class clazz, MediaType mediaType) {return clazz == UserInfo.class;}@Overridepublic List<MediaType> getSupportedMediaTypes() {return Collections.singletonList(MediaType.APPLICATION_JSON);}@Overridepublic void write(UserInfo userInfo, MediaType contentType, HttpOutputMessage outputMessage)throws IOException, HttpMessageNotWritableException {String result = userInfo.getId() + "_" + userInfo.getName() + "_" + LocalDateTime.now();outputMessage.getBody().write(result.getBytes(StandardCharsets.UTF_8));}//...read Ignore
}

该自定义转换器表示可以将UserInfo类型的消息以"application/json"媒体格式写出。

将自定义的消息转换器注册到SpringMVC:

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(0, new UserInfoHttpMessageConverter());}
}

注意:这里通过SpringMVC的配置类WebMvcConfigurer进行注册,注册原理在本文第三章中说明。

用例涉及的Controller接口和基础类:

@RestController
@RequestMapping("/api/user")
public class UserInfoController {@GetMapping("/query")public UserInfo query() {return new UserInfo().setName("test_sy").setId(28);}
}@Data
@Accessors(chain = true)
public class UserInfo {private Integer id;private String name;
}

使用postman调用结果如下所示:
在这里插入图片描述

2.消息转换器

SpringBoot版本为2.3.2.RELEASE

2.1 框架内置的消息解析器

框架内置的消息解析器支持的MIME类型分布如下所示:
ByteArrayHttpMessageConverter:用于处理字节数组(byte array)的转换。

ByteArrayHttpMessageConverterapplication/octet-stream*/*

StringHttpMessageConverter:用于处理字符串的转换。

StringHttpMessageConvertertext/plain*/*StringHttpMessageConvertertext/plain*/*

ResourceHttpMessageConverter:用于处理Spring Resource的实现类的转换。Spring Resource是一个抽象类,它封装了对各种资源(如文件、数据库连接等)的操作。

ResourceHttpMessageConverter*/*

ResourceRegionHttpMessageConverter:这个类是ResourceHttpMessageConverter的子类,它用于处理Resource的某个特定区域(如文件的某个部分)。

ResourceRegionHttpMessageConverter*/*

SourceHttpMessageConverter:用于处理javax.xml.transform.Source的转换。javax.xml.transform.Source是用于XML转换的接口。

SourceHttpMessageConverterapplication/xml   text/xml application/*+xml

AllEncompassingFormHttpMessageConverter:用于处理表单提交请求,能解析复杂的form表单,包括文件上传等。

AllEncompassingFormHttpMessageConverterapplication/x-www-form-urlencodedmultipart/form-datamultipart/mixed

MappingJackson2HttpMessageConverter:用于处理JSON序列化和反序列化。

MappingJackson2HttpMessageConverterapplication/json   application/*+jsonMappingJackson2HttpMessageConverterapplication/jsonapplication/*+json

Jaxb2RootElementHttpMessageConverter:这个类使用JAXB(Java Architecture for XML Binding)进行XML序列化和反序列化。

Jaxb2RootElementHttpMessageConverterapplication/xmltext/xmlapplication/*+xml

上述内置转换器中包括2个StringHttpMessageConverter和2个MappingJackson2HttpMessageConverter。转换器的顺序决定了其优先级,因此第二个StringHttpMessageConverter和MappingJackson2HttpMessageConverter处于失效状态:
[1] HttpMessageConvertersAutoConfiguration自动装配类引入的StringHttpMessageConverter替代了默认的StringHttpMessageConverter(SpringMVC框架自带),区别是前者默认字符集为UTF_8,后者为ISO_8859_1

[2] JacksonHttpMessageConvertersConfiguration自动装配类引入的MappingJackson2HttpMessageConverter替代了默认的MappingJackson2HttpMessageConverter。区别是使用其内部实现序列化和反序列化的ObjectMapper对象来自全局Bean对象(来自JacksonAutoConfiguration自动装配类引入的ObjectMapper)。因此在配置文件中对spring.jackson属性的配置可以体现在MappingJackson2HttpMessageConverter转换器上。

2.2 MappingJackson2HttpMessageConverter转换器

(1) 匹配方法
由于MappingJackson2HttpMessageConverter是GenericHttpMessageConverter接口的实现类,匹配时根据canWrite(Type, Class<?>, MediaType)方法进行:

@Override
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {return canWrite(clazz, mediaType);
}

上述方法实现时吞掉了Type类型的参数, 调用重载的canWrite(Class<?>, MediaType)方法:

@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
if (!canWrite(mediaType)) {return false;
}
if (mediaType != null && mediaType.getCharset() != null) {Charset charset = mediaType.getCharset();if (!ENCODINGS.containsKey(charset.name())) {return false;}
}AtomicReference<Throwable> causeRef = new AtomicReference<>();
if (this.objectMapper.canSerialize(clazz, causeRef)) {return true;
}
logWarningIfNecessary(clazz, causeRef.get());
return false;
}

该方法可以分为三个部分:
[1] 调用canWrite(MediaType)判断媒体类型是否支持:

protected boolean canWrite(@Nullable MediaType mediaType) {if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {return true;}for (MediaType supportedMediaType : getSupportedMediaTypes()) {if (supportedMediaType.isCompatibleWith(mediaType)) {return true;}}return false;
}

如果mediaType为空或者*/*或者与消息解析器支持的类型匹配则返回true;框架预置MappingJackson2HttpMessageConverter时,支持的MediaType已确定,为application/jsonapplication/*+json

[2] 判断编码类型是否符合, 支持的编码格式有UTF-8,UTF-16BE,UTF-16LE,UTF-32BE,UTF-32LE,US-ASCII

if (mediaType != null && mediaType.getCharset() != null) {Charset charset = mediaType.getCharset();if (!ENCODINGS.containsKey(charset.name())) {return false;}
}

MediaType对象的Charset为空时,默认支持;

[3] 调用ObjectMapper的canSerialize方法判断是否可被序列化;

AtomicReference<Throwable> causeRef = new AtomicReference<>();
if (this.objectMapper.canSerialize(clazz, causeRef)) {return true;
}

(2) 写方法

@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {final HttpHeaders headers = outputMessage.getHeaders();// 添加Content-type: application/jsonaddDefaultHeaders(headers, t, contentType);if (outputMessage instanceof StreamingHttpOutputMessage) {StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {@Overridepublic OutputStream getBody() {return outputStream;}@Overridepublic HttpHeaders getHeaders() {return headers;}}));} else {writeInternal(t, type, outputMessage);outputMessage.getBody().flush();}
}

write方法包含两个逻辑步骤:添加默认头域和写操作,写操作的实际执行方法在writeInternal中:

@Override
protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {MediaType contentType = outputMessage.getHeaders().getContentType();// 默认为UTF_8类型JsonEncoding encoding = getJsonEncoding(contentType);JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);writePrefix(generator, object);Object value = object;Class<?> serializationView = null;FilterProvider filters = null;JavaType javaType = null;if (object instanceof MappingJacksonValue) {MappingJacksonValue container = (MappingJacksonValue) object;value = container.getValue();serializationView = container.getSerializationView();filters = container.getFilters();}if (type != null && TypeUtils.isAssignable(type, value.getClass())) {javaType = getJavaType(type, null);}ObjectWriter objectWriter = (serializationView != null ?this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());if (filters != null) {objectWriter = objectWriter.with(filters);}if (javaType != null && javaType.isContainerType()) {objectWriter = objectWriter.forType(javaType);}SerializationConfig config = objectWriter.getConfig();if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) &&config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {objectWriter = objectWriter.with(this.ssePrettyPrinter);}objectWriter.writeValue(generator, value);writeSuffix(generator, object);generator.flush();
}

上述方法可以分为三个步骤:添加前缀(如果有,内置的对象无前缀)、写内容、添加后缀(如果有,内置的对象无前缀),操作完全基于objectMapper对象;关于ObjectMappr的API用法不是本文的重点,不进行赘述。

3.工作原理

框架内置的消息转换器为处理HTTP请求和响应提供了强大的支持,基本可以满足项目的需要。这些转换器在容器启动时进行实例化和设置,后被保存在RequestMappingHandlerAdapter对象的messageConverters属性中。
当HTTP请求到达后,RequestMappingHandlerAdapter会构造一个ServletInvocableHandlerMethod对象,
且该对象拥有来自RequestMappingHandlerAdapter的消息转换器。
ServletInvocableHandlerMethod与HttpMessageConveter的关系图如下所示:
在这里插入图片描述当HTTP请求被DispatcherServlet接受时,调用链会进入RequestMappingHandlerAdapterinvokeHandlerMethod方法,构造ServletInvocableHandlerMethod对象并调用invokeAndHandle方法:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {// 反射调用Controller接口获取返回结果Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);//... //将ModelAndViewContainer对象设置为请求未处理状态mavContainer.setRequestHandled(false);//处理结果this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

说明:
从Controller接口获取返回结果后,将结果处理工作委托给了returnValueHandlers属性,该属性是HandlerMethodReturnValueHandlerComposite组合类型,内部维持了一个List<HandlerMethodReturnValueHandler> returnValueHandlers列表;因此handleReturnValue实际会根据匹配关系分派到指定的HandlerMethodReturnValueHandler中。
框架内置的HandlerMethodReturnValueHandler和匹配关系如下:

ModelAndView及其子类->ModelAndViewMethodReturnValueHandler
Model及其子类->ModelMethodProcessor
View及其子类->ViewMethodReturnValueHandler
ResponseEntity及其子类或(ResponseEntity包裹)->ResponseBodyEmitterReturnValueHandler
StreamingResponseBody及其子类或(ResponseEntity包裹)->StreamingResponseBodyReturnValueHandler
HttpEntity,ResponseEntity->HttpEntityMethodProcessor
HttpHeaders及其子类->HttpHeadersReturnValueHandler
Callable及其子类->CallableMethodReturnValueHandler
DeferredResult、ListenableFuture、CompletionStage及其子类->DeferredResultMethodReturnValueHandler
WebAsyncTask及其子类->AsyncTaskMethodReturnValueHandler
ModelAttribute注解->ModelAttributeMethodProcessor
方法或类被ResponseBody注解->RequestResponseBodyMethodProcessor
void,CharSequence及其子类->ViewNameMethodReturnValueHandler
Map及其子类->MapMethodProcessor

本文重点关注RequestResponseBodyMethodProcessor, 该结果处理器的匹配规则如下:

public boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));
}

即方法或者类被@ResponseBody注解的Controller接口使用RequestResponseBodyMethodProcessor。

当请求进入RequestResponseBodyMethodProcessor的handleReturnValue方法后:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {// 将该HTTP请求标记为已处理mavContainer.setRequestHandled(true);// 从webRequest获取HttpServletRequest的代理类ServletServerHttpRequest inputMessage = createInputMessage(webRequest);// 从webRequest获取HttpServletResponse的代理类ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

核心逻辑在writeWithMessageConverters方法:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {// ...
}

该方法较长,主要步骤如下:
(1)获取返回对象类型,并使用Object对象接收返回对象

Object body;
Class<?> valueType;
Type targetType;
if (value instanceof CharSequence) {// 字符类型,则直接进行转换body = value.toString();valueType = String.class;targetType = String.class;
} else {body = value;valueType = getReturnValueType(body, returnType);targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}

注意:valueType为对象实际类型,不包括泛型信息;targetType包含泛型信息。
如:

public Map<String, Integer> getMap() {return new HashMap<>();
}

valueType为java.util.HashMap;而 targetType表示java.util.Map<java.lang.String, java.lang.Integer>

(2)InputStreamResource和Resource资源类型的特殊处理(Ignore);

(3)协商媒体类型,确定媒体类型

HttpServletRequest request = inputMessage.getServletRequest();
// 获取HTTP请求头中接收的媒体类型,代表客户端要求的MIME类型[标注1]
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 从所有的消息转换器中取媒体类型交集,代表服务器可以处理的媒体类型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);
}
// 从服务器支持的媒体类型中筛选出客户端要求的MIME类型
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}
}if (mediaTypesToUse.isEmpty()) {if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}if (logger.isDebugEnabled()) {logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}return;
}
// 排序,按照品质因子进行[标注2]
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;} else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}
}

上述逻辑有两个地方需要补充说明一下:
[1] getAcceptableMediaTypes方法从HttpServletRequest对象中获取客户端允许的MIME类型,由于框架内置的媒体协商器是HeaderContentNegotiationStrategy,即从请求头中的ACCEPT字段获取MIME类型;
[2] Accept代表客户端允许的媒体类型,客户端可以同时支持多种类型的资源,且可通过品质因数进行排序,如下所示:
Accept: text/html;q=0.1,application/xhtml+xml;q=0.2,application/xml;q=0.3,application/json;q=0
Note: 不接受application/json类型,按照期望排序可接收text/htmlapplication/xhtml+xmlapplication/xml;类型
即q值越大,表示期望值越高。另外,出Accept外,Accept-Charset(字符集)、Accept-Encoding(压缩算法)、Accept-Language(国际化)在HTTP媒体协商过程也可携带品质因子.

(4)选择消息解析器,进行消息处理

// 删除选中的MIME的品质因子(即q值)
selectedMediaType = selectedMediaType.removeQualityValue();
// 遍历HttpMessageConverter,寻找第一个匹配的消息解析器处理body对象(待返回结果)
for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);// [标注1]if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);if (body != null) {Object theBody = body;// [标注2]addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);} else {((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}return;}
}

代码按照遍历+匹配+处理的思路铺开,逻辑比较清晰。有两个地方需要补充说明一下:
[1] 按照消息解析器是HttpMessageConverter还是GenericHttpMessageConverter,会使用不同的canWrite进行判断,后者多一个参数;write也有区别。
[2] addContentDispositionHeader用于为文件请求添加Content-Disposition头域,用于指示文件的名称和下载方式。取值范围有inlineattachmentinline表示文件直接浏览器中显示文本attachment表示文件下载到本地。

(5)异常场景处理
未匹配到消息处理器的场景,抛出异常。

----以上为所有内容----

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

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

相关文章

Selenium获取百度百科旅游景点的InfoBox消息盒

前面我讲述过如何通过BeautifulSoup获取维基百科的消息盒&#xff0c;同样可以通过Spider获取网站内容&#xff0c;最近学习了SeleniumPhantomjs后&#xff0c;准备利用它们获取百度百科的旅游景点消息盒&#xff08;InfoBox&#xff09;&#xff0c;这也是毕业设计实体对齐和属…

酷开科技 | 酷开系统沉浸式大屏游戏更解压!

随着家庭娱乐需求日益旺盛&#xff0c;越来越多的家庭消费者和游戏玩家开始追求大屏游戏带来的沉浸感。玩家在玩游戏的时候用大屏能获得更广阔的视野和更出色的视觉包围感&#xff0c;因此用大屏玩游戏已经成为了一种潮流。用酷开系统玩大屏游戏&#xff0c;过瘾又刺激&#xf…

【在英伟达nvidia的jetson-orin-nx和PC电脑ubuntu20.04上-装配ESP32开发调试环境-基础测试】

【在英伟达nvidia的jetson-orin-nx和PC电脑ubuntu20.04上-装配ESP32开发调试环境-基础测试】 1、概述2、实验环境3、 物品说明4、参考资料与自我总结5、实验过程1、创建目录2、克隆下载文件3、 拉取子目录安装和交叉编译工具链等其他工具4、添加环境变量6、将样例文件拷贝到桌面…

计算机网络——理论知识总结(下)

接上条&#xff1a; 计算机网络——理论知识总结&#xff08;上&#xff09; 四.网络层 1.功能&#xff1a;向上提供简单灵活的、无连接的、尽最大努力交付的数据报服务——所传送的分组可能出错、丢失、重复、失序或者超时&#xff0c;这就使得网络中的路由器比较简单&#…

[support2022@cock.li].faust、[tsai.shen@mailfence.com].faust勒索病毒数据怎么处理|数据解密恢复

引言&#xff1a; 威胁网络安全的恶意软件不断涌现&#xff0c;而[support2022cock.li].faust勒索病毒则是其中的一员。这个网络黑暗角落的新星&#xff0c;以其数据绑架的方式&#xff0c;一度成为数据安全的威胁焦点。本文将探究[support2022cock.li].faust勒索病毒的运作方…

Python自动处理pptx:新建、另存、添加幻灯片、添加标题、插入文本图片图形、提取文本

Python-pptx库是一个用于创建、更新和读取Microsoft PowerPoint .pptx 文件的Python库。它允许我们使用Python脚本自动化PowerPoint文件的创建、更新和读取操作&#xff0c;是一个非常方便自动化处理PPTX的工具。 安装 pip install python-pptx创建 from pptx import Prese…

常用linux命令 linux_cmd_sheet

查看文件大小 ls -al 显示每个文件的kb大小 查看系统日志 dmesg -T | tail 在 top 命令中&#xff0c;RES 和 VIRT&#xff08;或者 total-vm&#xff09;是用来表示进程内存使用的两个不同指标&#xff0c;它们之间有以下区别&#xff1a; RES&#xff08;Resident Set Size…

FoneDog iOS Unlocker(ios解锁工具) 适用macos电脑

FoneDog iOS Unlocker是一款专业的iOS设备解锁工具&#xff0c;旨在帮助用户解决iOS设备上的解锁问题。该软件支持解锁各种锁定类型&#xff0c;如数字密码锁、手势密码锁、Touch ID和Face ID等&#xff0c;可以解除iPhone、iPad和iPod Touch等设备的锁定状态。FoneDog iOS Unl…

react项目实现文件预览,比如PDF、txt、word、Excel、ppt等常见文件(腾讯云cos)

使用腾讯云文档预览&#xff0c;需要开通文档预览功能&#xff0c;该功能需要收费的。 使用限制 如果需要图片预览、视频或音频可以使用获取下载链接。 页面代码 <button onClick() > {handleClick(myself/文档.xlsx)}>预览</button><div style{{ height:…

C语言文件操作(详解)

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言进阶之路&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅数据结构探索✅C语言刷题专栏&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x…

c++编译使用log4cplus

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、log4cplus是什么&#xff1f;二、使用步骤1.下载源代码2.开始配置1.配置介绍2.开始编译 3.cmake引用4.示例 总结 前言 C很强大&#xff0c;但是仍然有很多…

AIR101 LuatOS LVGL 显示多个标签例程

屏幕资料 AIR101与屏幕连接 PC端仿真环境合宙官方PC端版本环境搭建教程 PC电脑仿真 -- sys库是标配 _G.sys require("sys") sys.taskInit(function()local cnt0lvgl.init(480,320)--lvgl初始化local cont lvgl.cont_create(nil, nil);-- lvgl.cont_set_fit(cont, …

mac安装jdk

1、下载jdk&#xff08;我的电脑要下载arm版&#xff0c;截图不对&#xff09; Java Downloads | Oraclehttps://www.oracle.com/java/technologies/downloads/#jdk17-mac 2、双击安装

docker部署prometheus+grafana服务器监控(二) - 安装数据收集器 node-exporter

在目标服务器安装数据收集器 node-exporter 1. 安装数据收集器 node-exporter wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gztar xvf node_exporter-1.6.1.linux-amd64.tar.gzmv node_exporter-1.6.1…

使用vue3 搭建一个H5手机端访问的项目

首先说明&#xff0c;我本地之前运行过vue的项目&#xff0c;所以具有一些基础的运行环境&#xff0c;这里直接按步骤讲我项目框架搭建的过程。 这个不建议使用驼峰&#xff0c;按规范单词中间加横杠就可以。一般会出现选择项&#xff0c;按方向键选择&#xff0c;我这边选择了…

Linux音频-基本概念

文章目录 机器声音的采集原理机器声音的播放原理音频相关基本概念计算机采集音频的模型Linux系统音频框架Linux音频框架的三类角色 Linux音频框架参考文章&#xff1a;Linux音频框架 机器声音的采集原理 声音是一种连续的信号&#xff0c;故其是一种模拟量。 录音设备可以捕获…

chatGPT结构及商业级相似模型应用调研

GPT前言 说明 ChatGPT这项技术的历史可以追溯到2018年&#xff0c;当时由Facebook实验室的团队开发出该技术&#xff0c;以开发聊天机器人为目的。随后&#xff0c;ChatGPT在2019年由来自谷歌的DeepMind团队在国际会议ICLR上发表了论文&#xff0c;其中提出了ChatGPT的技术框架…

京东数据分析:2023年9月京东白酒行业品牌销售排行榜

鲸参谋监测的京东平台9月份白酒市场销售数据已出炉&#xff01; 9月白酒市场的整体热度较高&#xff0c;贵州茅台先是与瑞幸联名推出酱香拿铁&#xff0c;后又宣布与德芙推出联名产品酒心巧克力&#xff0c;引起了诸多消费者的关注。在这一热度的加持下&#xff0c;从销售上看&…

前端时间分片渲染

在经典的面试题中&#xff1a;”如果后端返回了十万条数据要你插入到页面中&#xff0c;你会怎么处理&#xff1f;” 除了像 useVirtualList 这样的虚拟列表来处理外&#xff0c;我们还可以通过 时间分片 来处理 通过 setTimeout 直接上一个例子&#xff1a; <!--* Autho…

基于ARM+FPGA+AD的多通道精密数据采集仪方案

XM 系列具备了数据采集仪应具备的“操作简单、便于携带、满足各种测量需求”等功能的产品。具有超小、超轻量的手掌大小尺寸&#xff0c;支持8 种测量模块&#xff0c;还可进行最多576 Ch的多通道测量。另外&#xff0c;支持省配线系统&#xff0c;可大幅削减配线工时。使用时不…