Open Feign 源码解析(四) --- 请求对象构造(上)

Open Feign 源码解析四 请求对象的构造(上)

源码前三篇文章写了这个图的过程

在这里插入图片描述

源码前三篇文章的内容归纳起来就是讲了这样的问题:

如何把接口转换为具有发送http请求能力的feign client对象以及如何整合到Spring容器中?

如何构造请求对象?

思路分析

Http请求对象的分析(目标)

URL: http://127.0.0.1:9000/consumer/feign/order/{1}?name=xxx&age=18

​ 协议: http

​ IP端口: 127.0.0.1:9000 -> 注册中心获取

​ URI: /consumer/feign/order/{id}

​ 路径参: {1} (path variable)

​ 请求参:name=xxx, age=18 (query)

请求头: headers

请求体: body

请求方法: Get/Post/Put/Delete …

public final class Request {private final HttpMethod httpMethod;private final String url;private final Map<String, Collection<String>> headers;private final Body body;
}
接口方法的分析(数据源)

方法本身的要素是否能表达所有Http请求的要素?

方法的要素:

​ 方法名 ×

​ 参数(名称与类型) √

​ 返回值类型 ×

URI -> 注解 或 Java对象(URI对象)表示

请求方法 -> 注解

路径参、请求参、请求头、请求体 -> 方法的入参 + 注解

问题一:注解如何设计?

1)URI 和 请求方法可以合并在一个注解中

2)对路径参、请求参、请求头、请求体分别设置对应的注解

feign:

@RequestLine/@Param/@QueryMap/@HeaderMap/@Body

open feign:

@RequestMapping/@PathVariable/@RequestParam/@SpringQueryMap/@RequestHeader/@RequestBody

URI: 类的@RequestMapping + 方法的@RequestMapping

请求方法: 方法的@RequestMapping

路径参:参数的@PathVariable

请求参:参数的@RequestParam + @SpringQueryMap

请求头: 类的@RequestMapping(produce/consume/header)

​ 方法的@RequestMapping(produce/consume/header)

​ 参数的@RequestHeader

问题二:为什么选择SpringMVC注解?

SpringMVC: http 请求 -> Java 对象

open feign:Java 对象 -> http 请求

对于方法和注解信息,可以封装在新的对象中 -> 方法元数据

方法元数据的分析

1)各种参数的位置(索引)

2)参数名称,类型

3)参数类型转换器

4)编码信息

public final class MethodMetadata implements Serializable {private static final long serialVersionUID = 1L;private String configKey;private transient Type returnType;private Integer urlIndex;private Integer bodyIndex;private Integer headerMapIndex;private Integer queryMapIndex;private boolean queryMapEncoded;private transient Type bodyType;private RequestTemplate template = new RequestTemplate();private List<String> formParams = new ArrayList<String>();private Map<Integer, Collection<String>> indexToName =new LinkedHashMap<Integer, Collection<String>>();private Map<Integer, Class<? extends Expander>> indexToExpanderClass =new LinkedHashMap<Integer, Class<? extends Expander>>();private Map<Integer, Boolean> indexToEncoded = new LinkedHashMap<Integer, Boolean>();private transient Map<Integer, Expander> indexToExpander;
}
构造请求对象整体思路:

在这里插入图片描述

构建请求对象分两步走:

1)解析方法和注解(类、方法、参数),并把信息封装到方法元数据中 -> 应用启动

2)结合方法元数据和实际参数,构建请求对象 -> 方法调用

实参的类型转换,编码,填充

问题三:如何转换成方法元数据?

1)做成一个组件(Contract)

public interface Contract {// 解析接口的注解信息并封装为方法元数据的集合List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
}

在这里插入图片描述

模板方法的设计模式

​ 接口 + 抽象实现 + 默认实现

​ 接口:提供扩展性 -> Contract

​ 抽象实现: 抽取公共逻辑 -> BaseContract

​ 默认实现:提供基本功能的使用 -> Default, SpringMvcContract

2)Contract组件从何获得?

Springboot自动装配 + 从FeignContext获取

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {@Bean@ConditionalOnMissingBeanpublic Contract feignContract(ConversionService feignConversionService) {return new SpringMvcContract(this.parameterProcessors, feignConversionService);}
}

源码解读

BaseContract

解析注解的顺序:类 -> 方法 -> 参数

abstract class BaseContract implements Contract {/** 解析接口的注解信息并封装为方法元数据的集合 */@Overridepublic List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {// 接口不能带有泛型checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",targetType.getSimpleName());// 接口最多只能有一个父接口checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",targetType.getSimpleName());// 如果传入的接口有一个父接口 那么该父接口必须是顶级接口 if (targetType.getInterfaces().length == 1) {checkState(targetType.getInterfaces()[0].getInterfaces().length == 0,"Only single-level inheritance supported: %s",targetType.getSimpleName());}// 新建一个结果集容器  Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();// 获取所有public方法,包括从父接口继承而来的  for (Method method : targetType.getMethods()) {// 排除掉从Object继承的方法,static方法,接口中的default方法if (method.getDeclaringClass() == Object.class ||(method.getModifiers() & Modifier.STATIC) != 0 ||Util.isDefault(method)) {continue;}// 把方法解析为方法元数据 【关键代码】 MethodMetadata metadata = parseAndValidateMetadata(targetType, method);// 重写方法不支持  checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s",metadata.configKey());result.put(metadata.configKey(), metadata);}return new ArrayList<>(result.values());}/** 解析方法的注解并封装为方法元数据对象 */protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {// 创建MethodMetadata对象MethodMetadata data = new MethodMetadata();// 设置返回值data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));// 设置configKey,方法的唯一标识: 接口名#方法名(参数类型名称1,参数类型名称2)data.configKey(Feign.configKey(targetType, method));// 如果有父接口先处理父接口if (targetType.getInterfaces().length == 1) {processAnnotationOnClass(data, targetType.getInterfaces()[0]);}// 再处理当前接口 【关键代码】processAnnotationOnClass(data, targetType);// 处理方法的注解 【关键代码】for (Annotation methodAnnotation : method.getAnnotations()) {processAnnotationOnMethod(data, methodAnnotation, method);}// 只支持GET POST等http方法checkState(data.template().method() != null,"Method %s not annotated with HTTP method type (ex. GET, POST)",method.getName());// 获取参数原始类型Class<?>[] parameterTypes = method.getParameterTypes();// 获取参数通用类型Type[] genericParameterTypes = method.getGenericParameterTypes();// 获取参数注解 二维数组:因为可以有多个参数 每个参数有多个注解Annotation[][] parameterAnnotations = method.getParameterAnnotations();int count = parameterAnnotations.length;for (int i = 0; i < count; i++) {boolean isHttpAnnotation = false;if (parameterAnnotations[i] != null) {// 处理每个参数的注解 如果其中有一个注解属于http注解 则isHttpAnnotation为true // 哪些属于http注解?如SpringMVC的@RequestHeader @PathVariable @RequestParam @SpringQueryMap//【关键代码】isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);}if (parameterTypes[i] == URI.class) {data.urlIndex(i);} else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {// 参数类型不是URI或Options 也没有加http注解 则该参数判定为body checkState(data.formParams().isEmpty(),"Body parameters cannot be used with form parameters.");checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);// 设置body的位置和类型【关键代码】data.bodyIndex(i);data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));}}// ...return data;}/** 处理类上的注解 */protected abstract void processAnnotationOnClass(MethodMetadata data, Class<?> clz);/** 处理方法上的注解 */protected abstract void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method);/** 处理参数上的注解 */protected abstract boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex);}
SpringMvcContract

类:@RequestMapping

方法:@RequestMapping

参数:@PathVariable @SpringQueryMap @RequestHeader @RequestParam

@RequestMapping

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {@AliasFor("path")String[] value() default {};@AliasFor("value")String[] path() default {};/*** The HTTP request methods to map to, narrowing the primary mapping:* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.*/RequestMethod[] method() default {};String[] params() default {};String[] headers() default {};/*** header的Content-Type*/String[] consumes() default {};/*** header的Accept*/String[] produces() default {};}
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {private static final String ACCEPT = "Accept";private static final String CONTENT_TYPE = "Content-Type";private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Iterable.class);private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();// 参数处理器 可以自动装配也可以使用默认的处理器private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors;private final Map<String, Method> processedMethods = new HashMap<>();private final ConversionService conversionService;private final ConvertingExpanderFactory convertingExpanderFactory;private ResourceLoader resourceLoader = new DefaultResourceLoader();public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,ConversionService conversionService) {Assert.notNull(annotatedParameterProcessors,"Parameter processors can not be null.");Assert.notNull(conversionService, "ConversionService can not be null.");// 初始化参数处理器List<AnnotatedParameterProcessor> processors;if (!annotatedParameterProcessors.isEmpty()) {processors = new ArrayList<>(annotatedParameterProcessors);}else {processors = getDefaultAnnotatedArgumentsProcessors();}this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);// 创建参数转换器工厂 真正的转换功能来自conversionServicethis.conversionService = conversionService;this.convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);}/** 获取默认处理器 */private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor()); // 处理@MatrixVariableannotatedArgumentResolvers.add(new PathVariableParameterProcessor()); // 处理@PathVavirableannotatedArgumentResolvers.add(new RequestParamParameterProcessor()); // 处理@RequestParamannotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); // 处理@RequestHeaderannotatedArgumentResolvers.add(new QueryMapParameterProcessor()); // 处理@SpringQueryMapannotatedArgumentResolvers.add(new RequestPartParameterProcessor()); // 处理@RequestPartreturn annotatedArgumentResolvers;}@Overridepublic MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {// 方法先放入缓存中 表示已经处理this.processedMethods.put(Feign.configKey(targetType, method), method);// 调用父类的parseAndValidateMetadataMethodMetadata md = super.parseAndValidateMetadata(targetType, method);// 处理类上的RequestMapping注解// 因为RequestMapping注解可以加在类上和方法上 两者中注解值有优先级问题RequestMapping classAnnotation = findMergedAnnotation(targetType,RequestMapping.class);if (classAnnotation != null) {// 解析header中的produces// 此时可能已经从方法的RequestMapping注解获得produces的值// 这样处理表示方法上的RequestMapping注解优先于类上的RequestMapping注解if (!md.template().headers().containsKey(ACCEPT)) {parseProduces(md, method, classAnnotation);}// 解析header中的consumes 原理同producesif (!md.template().headers().containsKey(CONTENT_TYPE)) {parseConsumes(md, method, classAnnotation);}// 解析headersparseHeaders(md, method, classAnnotation);}return md;}/** 处理类上的注解(RequestMapping) */@Overrideprotected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {if (clz.getInterfaces().length == 0) {RequestMapping classAnnotation = findMergedAnnotation(clz,RequestMapping.class);// 这里只处理类上RequestMapping的path,// 其他produces, consumes, headers放在解析方法上的RequestMapping注解之后if (classAnnotation != null) {// 如果类上的@RequestMapping有value(path) 处理后放入uri中if (classAnnotation.value().length > 0) {String pathValue = emptyToNull(classAnnotation.value()[0]);// 解析path中的${} pathValue = resolve(pathValue);// 保证uri以/开头if (!pathValue.startsWith("/")) {pathValue = "/" + pathValue;}// 放入uri中data.template().uri(pathValue);}}}}/** 处理方法上的注解(RequestMapping) */@Overrideprotected void processAnnotationOnMethod(MethodMetadata data,Annotation methodAnnotation, Method method) {// 如果不是@RequestMapping注解本身 也不带有@RequestMapping注解的话就返回if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {return;}RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);// 解析HTTP MethodRequestMethod[] methods = methodMapping.method();if (methods.length == 0) {methods = new RequestMethod[] { RequestMethod.GET };}checkOne(method, methods, "method");data.template().method(Request.HttpMethod.valueOf(methods[0].name()));// 解析pathcheckAtMostOne(method, methodMapping.value(), "value");if (methodMapping.value().length > 0) {String pathValue = emptyToNull(methodMapping.value()[0]);if (pathValue != null) {pathValue = resolve(pathValue);if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {pathValue = "/" + pathValue;}data.template().uri(pathValue, true);}}// 解析header中的producesparseProduces(data, method, methodMapping);// 解析header中的consumesparseConsumes(data, method, methodMapping);// 解析headersparseHeaders(data, method, methodMapping);data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());}/** 处理参数上的注解 */@Overrideprotected boolean processAnnotationsOnParameter(MethodMetadata data,Annotation[] annotations, int paramIndex) {boolean isHttpAnnotation = false;AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data, paramIndex);Method method = this.processedMethods.get(data.configKey());for (Annotation parameterAnnotation : annotations) {// 根据参数注解类型获取对应的参数处理器AnnotatedParameterProcessor processor = this.annotatedArgumentProcessors.get(parameterAnnotation.annotationType());if (processor != null) {Annotation processParameterAnnotation;processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation, method, paramIndex);// 参数处理器处理【关键代码】isHttpAnnotation |= processor.processArgument(context,processParameterAnnotation, method);}}// 如果是http注解并且没有对应的expander// 什么expander -> 参数转换器if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);if (this.conversionService.canConvert(typeDescriptor,STRING_TYPE_DESCRIPTOR)) {Param.Expander expander = this.convertingExpanderFactory.getExpander(typeDescriptor);if (expander != null) {data.indexToExpander().put(paramIndex, expander);}}}return isHttpAnnotation;}// ...
}
AnnotatedParameterProcessor

PathVariableParameterProcessor:@PathVariable 解析路径参数

QueryMapParameterProcessor: @SpringQueryMap 解析请求参数

RequestHeaderParameterProcessor: @RequestHeader 解析请求头

RequestParamParameterProcessor:@RequestParam 解析请求参数

MatrixVariableParameterProcessor: @MatrixVariable 解析矩阵参数

RequestPartParameterProcessor: @RequestPart 解析form表单 File文件

QueryMapParameterProcessor 与 RequestParamParameterProcessor的区别:

前者可以解析自定义实体对象,Map和基本类型,没有特别的限制

后者只能解析Map和基本类型不能解析自定义对象类型

QueryMapParameterProcessor
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {private static final Class<SpringQueryMap> ANNOTATION = SpringQueryMap.class;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic boolean processArgument(AnnotatedParameterContext context,Annotation annotation, Method method) {int paramIndex = context.getParameterIndex();MethodMetadata metadata = context.getMethodMetadata();// 对@SpringQueryMap注解所对应的参数的类型没有限制if (metadata.queryMapIndex() == null) {metadata.queryMapIndex(paramIndex);metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());}return true;}
}
RequestParamParameterProcessor
public class RequestParamParameterProcessor implements AnnotatedParameterProcessor {private static final Class<RequestParam> ANNOTATION = RequestParam.class;@Overridepublic Class<? extends Annotation> getAnnotationType() {return ANNOTATION;}@Overridepublic boolean processArgument(AnnotatedParameterContext context,Annotation annotation, Method method) {int parameterIndex = context.getParameterIndex();Class<?> parameterType = method.getParameterTypes()[parameterIndex];MethodMetadata data = context.getMethodMetadata();// 参数必须是Map类型 否则不可以成为QueryMapif (Map.class.isAssignableFrom(parameterType)) {checkState(data.queryMapIndex() == null,"Query map can only be present once.");data.queryMapIndex(parameterIndex);return true;}RequestParam requestParam = ANNOTATION.cast(annotation);String name = requestParam.value();checkState(emptyToNull(name) != null,"RequestParam.value() was empty on parameter %s", parameterIndex);context.setParameterName(name);Collection<String> query = context.setTemplateParameter(name,data.template().queries().get(name));data.template().query(name, query);return true;}
}

实参类型转换和填充

interface Expander {/*** Expands the value into a string. Does not accept or return null.*/String expand(Object value);
}
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);private static class ConvertingExpanderFactory {private final ConversionService conversionService;ConvertingExpanderFactory(ConversionService conversionService) {this.conversionService = conversionService;}Param.Expander getExpander(TypeDescriptor typeDescriptor) {return value -> {Object converted = this.conversionService.convert(value, typeDescriptor,STRING_TYPE_DESCRIPTOR);return (String) converted;};}}
}

Java 中的所有类型
raw type:原始类型,对应 Class 即我们通常说的引用类型,包括普通的类,例如 String.class、List.class 也包括数组(Array.class)、接口(Cloneable.class)、注解(Annotation.class)、枚举(Enum.class)等

primitive types:基本类型,对应 Class 包括 Built-in 内置类型,例如 int.class、char.class、void.class 也包括 Wrappers 内置类型包装类型,例如 Integer.class、Boolean.class、Void.class

parameterized types:参数化类型,对应 ParameterizedType 带有类型参数的类型,即常说的泛型,例如 List、Map<Integer, String>、List<? extends Number> 实现类 sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl

type variables:类型变量类型,对应 TypeVariable即参数化类型 ParameterizedType 中的 E、K 等类型变量,表示泛指任何类实现类 sun.reflect.generics.reflectiveObjects.TypeVariableImpl

array types:泛型数组类型,对应 GenericArrayType元素类型是参数化类型或者类型变量的泛型数组类型,例如 T[]实现类 sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl
Type 接口的另一个子接口 WildcardType 代表通配符表达式类型,或泛型表达式类型,比如?、? super T、? extends T,他并不是 Java 类型中的一种。

private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {private final QueryMapEncoder queryMapEncoder;protected final MethodMetadata metadata;private final Map<Integer, Expander> indexToExpander = new LinkedHashMap<Integer, Expander>();/** 通过metadata信息和实参创建RequestTemplate */@Overridepublic RequestTemplate create(Object[] argv) {// 把metadata中的半成品template拷贝一份  RequestTemplate mutable = RequestTemplate.from(metadata.template());// 处理URI对象if (metadata.urlIndex() != null) {int urlIndex = metadata.urlIndex();checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);mutable.target(String.valueOf(argv[urlIndex]));}//  Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {int i = entry.getKey();Object value = argv[entry.getKey()];if (value != null) { // Null values are skipped.if (indexToExpander.containsKey(i)) {value = expandElements(indexToExpander.get(i), value);}for (String name : entry.getValue()) {varBuilder.put(name, value);}}}RequestTemplate template = resolve(argv, mutable, varBuilder);// 处理queryMapif (metadata.queryMapIndex() != null) {// add query map parameters after initial resolve so that they take// precedence over any predefined valuesObject value = argv[metadata.queryMapIndex()];Map<String, Object> queryMap = toQueryMap(value);template = addQueryMapQueryParameters(queryMap, template);}// 处理headerMapif (metadata.headerMapIndex() != null) {template =addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);}return template;}@SuppressWarnings("unchecked")private RequestTemplate addHeaderMapHeaders(Map<String, Object> headerMap,RequestTemplate mutable) {for (Entry<String, Object> currEntry : headerMap.entrySet()) {Collection<String> values = new ArrayList<String>();Object currValue = currEntry.getValue();if (currValue instanceof Iterable<?>) {Iterator<?> iter = ((Iterable<?>) currValue).iterator();while (iter.hasNext()) {Object nextObject = iter.next();values.add(nextObject == null ? null : nextObject.toString());}} else {values.add(currValue == null ? null : currValue.toString());}mutable.header(currEntry.getKey(), values);}return mutable;}@SuppressWarnings("unchecked")private RequestTemplate addQueryMapQueryParameters(Map<String, Object> queryMap,RequestTemplate mutable) {for (Entry<String, Object> currEntry : queryMap.entrySet()) {Collection<String> values = new ArrayList<String>();boolean encoded = metadata.queryMapEncoded();Object currValue = currEntry.getValue();if (currValue instanceof Iterable<?>) {Iterator<?> iter = ((Iterable<?>) currValue).iterator();while (iter.hasNext()) {Object nextObject = iter.next();values.add(nextObject == null ? null: encoded ? nextObject.toString(): UriUtils.encode(nextObject.toString()));}} else {values.add(currValue == null ? null: encoded ? currValue.toString() : UriUtils.encode(currValue.toString()));}mutable.query(encoded ? currEntry.getKey() : UriUtils.encode(currEntry.getKey()), values);}return mutable;}// ...
}

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

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

相关文章

普通表计读数开发思路

一、普通表计类型介绍&#x1f349; 常见的普通表计有SF6&#xff0c;压力表&#xff0c;油位表&#xff08;指针类&#xff09;等。 图1&#xff1a;( 压力表) 图2&#xff1a;&#xff08;油位表-指针类&#xff09; 图3&#xff1a;&#xff08;SF6表&#xff09; 图4:&a…

linux 磁盘管理、分区管理常用命令

文章目录 基础命令挂载新硬盘/分区添加内存交换分区swaplvm分区管理模式 基础命令 查看目录文件大小 du -sh /backup du -sh /backup/* du -sh *查看磁盘挂载信息 df -lhT查看某个目录挂载在哪个分区&#xff0c;以及分区的磁盘使用情况 df [目录] #例如&#xff1a;df /ho…

(二) Windows 下 Sublime Text 3 安装离线插件 Anaconda

1 下载 Sublime Text 3 免安装版 Download - Sublime Text 2 下载 Package Control&#xff0c;放到 Sublime Text Build 3211\Data\Installed Packages 目录下。 Installation - Package Control 3 页面搜索 anaconda anaconda - Search - Package Control Anaconda - Pac…

vue3通过provide和inject实现多层级组件通信

父组件 <template><div><h1>我是父组件 {{num}}</h1><hr><child></child></div> </template><script setup> import child from ./child.vue; import { ref,provide } from vue; let num ref(520) provide(pare…

kafka的详细安装部署

简介&#xff1a; Kafka是一个分布式流处理平台&#xff0c;主要用于处理高吞吐量的实时数据流。Kafka最初由LinkedIn公司开发&#xff0c;现在由Apache Software Foundation维护和开发。 Kafka的核心是一个分布式发布-订阅消息系统&#xff0c;它可以处理大量的消息流&#…

PHP TCP服务端监听端口接收客户端RFID网络读卡器上传的读卡数据

本示例使用设备&#xff1a;WIFI/TCP/UDP/HTTP协议RFID液显网络读卡器可二次开发语音播报POE-淘宝网 (taobao.com) <?php header("content-type:text/html;charsetGBK");set_time_limit(0); $port39169; //监听端口if(($socket socket_create(AF_INET, SOCK…

共享模型之不可变

前言 该文章后续还需要进行修改&#xff01;&#xff01; 不可变的解释是对象属性不可以更改。 在多线程下&#xff0c;格式转化使用SimpleDateFormat可能会报错。这是因为线程之间互相影响导致。 public class test {public static void main(String[] args) {SimpleDateFo…

抽象类-Java

抽象类 一、父类方法的不确定性二、抽象类介绍三、抽象类细节四、练习题 一、父类方法的不确定性 引入&#xff1a;对于一个动物&#xff0c;不知道它吃什么&#xff0c;比如猫吃鱼&#xff0c;兔子吃萝卜。动物类中的 eat 方法往往由它的子类去具体实现。 class Animal {pub…

qgis添加arcgis的FeatureServer

左侧浏览器-ArcGIS要素服务器-新建连接 http://sampleserver6.arcgisonline.com/arcgis/rest/services/ 展开-双击即可

sql中group by和having的使用

group by&#xff1a;按照某个字段或者某些字段进行分组。 having&#xff1a;对分组之后的数据进行再次过滤&#xff0c;having必须和group by一起用&#xff0c;且在group by后面。 比如person表如下&#xff08;以下查询均基于此表&#xff09;&#xff1a; 1.group by 用法…

为何要隐藏IP地址?网络上哪些行为需要隐藏IP和更换IP?

网络已经成为现代人生活的重要组成部分&#xff0c;人们在网络上交流、学习、娱乐、购物等。但是&#xff0c;在享受网络带来的便利时&#xff0c;我们也需要时刻保护自己的隐私和安全。其中&#xff0c;IP地址作为网络通信中的重要标识&#xff0c;如何隐藏以及在哪些情况下需…

C语言题目强化-DAY12

题型指引 一、选择题二、编程题 ★★写在前面★★ 本题库源自互联网&#xff0c;仅作为个人学习使用&#xff0c;记录C语言题目练习的过程&#xff0c;如果对你也有帮助&#xff0c;那就点个赞吧。 一、选择题 1、请阅读以下程序&#xff0c;其运行结果是&#xff08; &#x…

CMake语法解读 | Qt6需要用到

CMake 入门CMakeLists.txtmain.cpp编译示例cmake常用参数入门 Hello CMake CMake 是一个用于配置跨平台源代码项目应该如何配置的工具建立在给定的平台上。 ├── CMakeLists.txt # 希望运行的 CMake命令 ├── main.cpp # 带有main 的源文件 ├── include # 头文件目录 …

GLM: 自回归空白填充的多任务预训练语言模型

当前&#xff0c;ChatGLM-6B 在自然语言处理领域日益流行。其卓越的技术特点和强大的语言建模能力使其成为对话语言模型中的佼佼者。让我们深入了解 ChatGLM-6B 的技术特点&#xff0c;探索它在对话模型中的创新之处。 GLM: 自回归空白填充的多任务预训练语言模型 ChatGLM-6B 技…

C++二分查找视频教程:两数之和

作者推荐 利用广度优先或模拟解决米诺骨牌 本文涉及的基础知识点 二分查找算法合集 题目 给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 n…

Webhook端口中的自签名身份验证

概述 有时&#xff0c;可能需要通过 Webhook 端口从交易伙伴处接收数据&#xff0c;但该交易伙伴可能需要更多的安全性&#xff0c;而不仅仅是用于验证入站 Webhook 请求的基本身份验证用户名/密码 – 或者您可能只想在入站 Webhook 消息上添加额外的安全层。 使用 Webhook 端…

使用STM32和蓝牙模块进行无线数据传输的实践

无线数据传输在现代通信领域中具有重要的地位&#xff0c;而蓝牙技术是一种常用的无线数据传输技术。本文介绍了如何使用STM32微控制器和蓝牙模块实现无线数据传输的方案&#xff0c;包括硬件设计、蓝牙模块配置、数据发送和接收等步骤&#xff0c;并给出相应的代码示例。 一、…

Codebeamer—软件全生命周期管理轻量级平台

产品概述 Codebeamer涵盖了软件研发的生命周期&#xff0c;在一个整合的平台内支持需求管理、测试管理、软件开发过程管理以及项目管理等&#xff0c;同时具有IToperations&DevOps相关的内容&#xff0c;并支持变体管理的功能。对于使用集成的应用程序生命周期管理&#xf…

13.端点、簇、属性

源码地址&#xff1a;13.端点、簇、属性 端点&#xff08;endPoint&#xff09; 一个端点就是一个应用 一个字节编号&#xff0c;数据收和发送的基本单元&#xff0c;在模块通信的时候&#xff0c;发送模块必须指定收发双方模块的网络地址和端点。端点要使用必须要和模块里的…

MFC添加窗体菜单栏和消息响应

在资源视图右键,添加资源,选择Menu,新建 添加的菜单在资源菜单的Menu目录下 双击直接编辑输入菜单 之后在要添加菜单的窗体的属性Menu里面填写菜单的ID就可以了 如何给菜单添加点击响应? OnCommand是MFC中的一个消息处理函数,用于处理在窗口或控件被激活时发出的WM_CO…