5.Feign与ReflectiveFeign

前言

Feign对象作为feign框架的启动门户, 提供构建和运行框架的统一入口, 也是feign框架的核心组件之一

核心逻辑

Feign类结构

public abstract class Feign {public static Builder builder() {return new Builder();}// 获取方法唯一标识public static String configKey(Class targetType, Method method) {...}// 创建接口实例public abstract <T> T newInstance(Target<T> target);// 建造者模式用来构建feignpublic static class Builder extends BaseBuilder<Builder, Feign> {...}// 解码器public static class ResponseMappingDecoder implements Decoder {...}
}

Feign提供的方法不算多

  1. 首先是一个获取建造者对象Builder的方法
  2. 提供了一个获取方法唯一标识的方法configKey
  3. 定义了一个抽象方法newInstance, 用于创建接口的实例
  4. Builder作为Feign的静态内部类, 用来真正创建Feign对象, 自然它是少不了可以传入自定义组件个性化我们的框架
  5. ResponseMappingDecoder, 解码器静态代理对象, 没啥用

configKey

/*** 获取方法唯一标识; 格式: 简单类名#方法名(参数名1, 参数名2)*/
public static String configKey(Class targetType, Method method) {// 简单类名builder.append(targetType.getSimpleName());// 简单类名#方法名(builder.append('#').append(method.getName()).append('(');// 获取方法参数的泛型类型for (Type param : method.getGenericParameterTypes()) {// 从当前类中获取参数的类型param = Types.resolve(targetType, targetType, param);// 参数的原始类型; 例如, List<String> 返回List, 如果不是泛型类型,则返回参数本身// 简单类名#方法名(参数名,builder.append(Types.getRawType(param).getSimpleName()).append(',');}// 去掉末尾的逗号if (method.getParameterTypes().length > 0) {builder.deleteCharAt(builder.length() - 1);}// 简单类名#方法名(参数名1, 参数名2)return builder.append(')').toString();}

这个方法比较简单, 但是也需要对泛型有一些了解, 这里是为了返回方法的签名, 格式为: 简单类名#方法名(参数名1, 参数名2), 在打印日志时方便标记。

Builder静态内部类继承了BaseBuilder, 用于设置一些全局变量以及提供构建目标对象的模板方法(一个常用的模板方法模式)

BaseBuilder

BaseBuilder类定义设计

这里我们学习一下这种BaseBuilder抽象类的设计意图

public static class Builder extends BaseBuilder<Builder, Feign> {}public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {/*** 子类实例*/private final B thisB;
}

BaseBuilder设计成了抽象一个父类, 它希望子类以链式调用的方式设置一些属性值所以要借助B thisB

举个例子

static class Parent {private String name;private Integer age;public Parent name(String name) {this.name = name;return this;}public Parent age(Integer age) {this.age = age;return this;}}static class Child extends Parent {private String gender;private String address;public Child gender(String gender) {this.gender = gender;return this;}public Child address(String address) {this.address = address;return this;}}

链式调用

public static void main(String[] args) {Parent parent = new Child();((Child)parent.name("qiao")).gender("male");
}

这里是先调用父类的name方法, 然后需要强转到子类, 再调用子类的gender方法; 那如果我的调用顺序是 parent.name -> child.gender -> parent.age -> child.address呢, 那么调用链将会是如下样子

((Child)(((Child)parent.name("qiao")).gender("male").age(18))).address("湖北");

当这种互相穿插的越多, 这种强转就会有很多次, 这种调用形式简直惨不忍睹, 下面我们看看用泛型传入当前对象的样子

static class Parent<Child extends Parent> {private Child child;public Parent() {child = (Child) this;}private String name;private Integer age;public Child name(String name) {this.name = name;return child;}public Child age(Integer age) {this.age = age;return child;}
}static class Child extends Parent<Child> {private String gender;private String address;public Child gender(String gender) {this.gender = gender;return this;}public Child address(String address) {this.address = address;return this;}
}

链式调用

public static void main(String[] args) {Parent<Child> parent = new Child();parent.name("qiao").gender("male").age(18).address("湖北");
}

这样就简单多了

当然我们也可以把下面这段给简化一下, 改成private Child child = (Child) this;即可

private Child child;public Parent() {child = (Child) this;
}

这种设计在mybatis-plus和netty中也都有体现

// mybatis-plus
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T>implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {/*** 指向子类*/protected final Children typedThis = (Children) this;
}// netty
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {// 指向子类private B self() {return (B) this;}
}

组件认识

public abstract class BaseBuilder<B extends BaseBuilder<B, T>, T> implements Cloneable {/*** 子类实例*/private final B thisB;/*** 请求拦截器*/protected final List<RequestInterceptor> requestInterceptors =new ArrayList<>();/*** 响应结果拦截器*/protected final List<ResponseInterceptor> responseInterceptors = new ArrayList<>();/*** 日志级别*/protected Logger.Level logLevel = Logger.Level.NONE;/*** 对代理类和其中的方法签名做处理的约定的校验*/protected Contract contract = new Contract.Default();/*** 重试器; 提供请求失败或者处理返回结果错误时的补偿*/protected Retryer retryer = new Retryer.Default();/*** 日志记录器*/protected Logger logger = new NoOpLogger();/*** 编码器; 对参数的编码*/protected Encoder encoder = new Encoder.Default();/*** 解码器; 对返回值的解码*/protected Decoder decoder = new Decoder.Default();/*** 解码响应结果后是否理解关闭流*/protected boolean closeAfterDecode = true;/*** 是否对void方法返回值做处理*/protected boolean decodeVoid = false;/*** @QueryMap和@HeaderMap参数的编码器*/protected QueryMapEncoder queryMapEncoder = QueryMap.MapEncoder.FIELD.instance();/*** 请求异常时, 对错误信息进行解码的解码器*/protected ErrorDecoder errorDecoder = new ErrorDecoder.Default();/*** 请求头参数*/protected Options options = new Options();/*** 用来创建jdk代理处理对象InvocationHandler的工厂*/protected InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();/*** 404异常时, 是否忽略; true:忽略, false:抛出异常*/protected boolean dismiss404;/*** 重试异常时, 抛出异常类型的策略; NONE:直接抛异常, UNWRAP:抛出原始异常*/protected ExceptionPropagationPolicy propagationPolicy = NONE;/*** 对当前builder对象中字段进行增强, 允许用户扩展当前Builder类中的配置项*/protected List<Capability> capabilities = new ArrayList<>();/*** 模板方法*/public final T build() {// 先对当前build类中的字段进行增强return enrich().internalBuild();}
}

这里就是feign框架所有的可以扩展的属性和组件了, 这里给分个类

解析接口/方法的参数们

  1. contract; 用来解析接口、方法、参数, 生成方法模板MethodMetadata
  2. encoder, 对参数进行编码
  3. queryMapEncoder, @QueryMap和@HeaderMap参数的编码器

请求时的参数们:

  1. requestInterceptors, 执行请求前可以对RequestTemplate做处理, 修改参数什么的
  2. retryer, 请求失败或者处理响应失败时候补偿的重试器
  3. options, 添加请求头参数

请求响应的参数们:

  1. responseInterceptors, 处理返回结果的拦截器
  2. retryer, 请求失败或者处理响应失败时候补偿的重试器
  3. decoder, 对返回值进行解码
  4. closeAfterDecode, 对响应结果解码后是否立即关闭响应流
  5. decodeVoid, 对返回类型为void方法是否进行解码响应结果
  6. errorDecoder, 请求异常时, 对错误信息进行解码的解码器
  7. dismiss404, 404异常时, 是否忽略; true:忽略, false:抛出异常
  8. propagationPolicy, 重试异常时, 抛出异常类型的策略; NONE:直接抛异常, UNWRAP:抛出原始异常

其它:

invocationHandlerFactory: 用来创建jdk代理处理对象InvocationHandler的工厂

capabilities: 1.对当前build对象中的字段进行增强 2.对处理响应结果的责任链executionChain进行增强, 做一些额外的扩展

logLevel: 日志级别, 用在请求和响应时

logger: 日志工具, 用在请求和响应时

对于模板方法build

  1. 对当前对象中的字段进行增强
  2. 使用抽象方法internalBuild构建目标接口的代理对象

enrich

/*** 对Build对象中的所有字段进行enrich增强*/@SuppressWarnings("unchecked")B enrich() {if (capabilities.isEmpty()) {return thisB;}try {B clone = (B) thisB.clone();// 遍历非capabilities字段getFieldsToEnrich().forEach(field -> {field.setAccessible(true);try {final Object originalValue = field.get(clone);final Object enriched;if (originalValue instanceof List) {// List<T>中的T 类型或者List中的具体元素类型Type ownerType =((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];// 对某个字段增强enriched = ((List) originalValue).stream().map(value -> Capability.enrich(value, (Class<?>) ownerType, capabilities)).collect(Collectors.toList());} else {enriched = Capability.enrich(originalValue, field.getType(), capabilities);}field.set(clone, enriched);} catch (IllegalArgumentException | IllegalAccessException e) {throw new RuntimeException("Unable to enrich field " + field, e);} finally {field.setAccessible(false);}});return clone;} catch (CloneNotSupportedException e) {throw new AssertionError(e);}}/*** 获取当前类和父类中需要增强的所有字段, 排除capabilities、thisB、动态生成的字段、枚举类型、基本类型字段*/List<Field> getFieldsToEnrich() {return Util.allFields(getClass()).stream()// 排除动态生成的, 例如lambda表达式生成的.filter(field -> !field.isSynthetic())// and capabilities itself.filter(field -> !Objects.equals(field.getName(), "capabilities"))// 当前类本身字段也排除.filter(field -> !Objects.equals(field.getName(), "thisB"))// 基本类型也排除.filter(field -> !field.getType().isPrimitive())// 枚举类型的字段也排除.filter(field -> !field.getType().isEnum()).collect(Collectors.toList());}

方法小结

  1. getFieldsToEnrich方法获取当前类和父类中所有的字段
  • 排除了Object类
  • 排除了增强字段capabilities和指向子类的thisB字段
  • 排除了基本类型字段
  • 排除了枚举字段
  1. 使用capabilities对list中的每个值进行增强处理, 或者对普通非list单个字段进行增强处理
    • 这里要求定义的Capability中的方法名为enrich, 并且返回值类型是对应增强的字段类型或者List泛型中的参数类型T
  2. Capability接口中默认提供了对一些字段增强的空实现

举个例子

public class OptionsCapability implements Capability {@Overridepublic Request.Options enrich(Request.Options options) {System.out.println("默认链接超时时长:" + options.connectTimeout());return Capability.super.enrich(options);}
}public class InterceptCapability implements Capability {@Overridepublic RequestInterceptor enrich(RequestInterceptor interceptor) {System.out.println("拦截器类名:" + interceptor.getClass().getSimpleName());return interceptor;}
}
@Test
void capabilityFunc() {DemoClient client = Feign.builder().logLevel(feign.Logger.Level.FULL).requestInterceptor(template-> {}).addCapability(new InterceptCapability()).addCapability(new OptionsCapability()).logger(new Slf4jLogger()).dismiss404().target(DemoClient.class, "http://localhost:8080");
}

结果如下

拦截器类名:DemoTest$$Lambda$359/0x00000008010ad200
默认链接超时时长:10

注意: 添加的capabilities, 也用作在了ResponseInterceptor.Chain字段上, 并且是每次调用的时候都做了增强处理

/*** response拦截器组成链条*/protected ResponseInterceptor.Chain responseInterceptorChain() {ResponseInterceptor.Chain endOfChain =ResponseInterceptor.Chain.DEFAULT;ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream().reduce(ResponseInterceptor::andThen).map(interceptor -> interceptor.apply(endOfChain)).orElse(endOfChain);// 这里对ResponseInterceptor.Chain进行增强return (ResponseInterceptor.Chain) Capability.enrich(executionChain,ResponseInterceptor.Chain.class, capabilities);}

Feign.Builder

类定义

public static class Builder extends BaseBuilder<Builder, Feign> {private Client client = new Client.Default(null, null);// 构建接口代理对象的入口; apiType:目标接口, url:请求目标地址public <T> T target(Class<T> apiType, String url) {return target(new HardCodedTarget<>(apiType, url));}public <T> T target(Target<T> target) {// 对build对象中的字段进行增强(如果有), 再执行internalBuild方法, 再执行Feign#newInstancereturn build().newInstance(target);}@Overridepublic Feign internalBuild() {final ResponseHandler responseHandler =new ResponseHandler(logLevel, logger, decoder, errorDecoder,dismiss404, closeAfterDecode, decodeVoid, responseInterceptorChain());// 构建方法处理器的工厂; 用来创建创建对外调用的方法MethodHandler.Factory<Object> methodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,responseHandler, logger, logLevel, propagationPolicy,new RequestTemplateFactoryResolver(encoder, queryMapEncoder),options);// invocationHandlerFactory默认是 InvocationHandlerFactory.Default// contract:用来生成方法模版 methodHandlerFactory:用来生成真正调用的方法对象 invocationHandlerFactory:用来调用生成的方法return new ReflectiveFeign<>(contract, methodHandlerFactory, invocationHandlerFactory,() -> null);}}
}

方法小结

  1. 定义了一个默认的全局变量Client, 它是通过HttpURLConnection来实现Http请求的对象
  2. 定义了两个target方法, 提供了创建目标接口代理对象的方法; 其中HardCodedTarget在之前的文章中有聊到过, 这里就不解释了
  3. 最后是internalBuild方法, 它是实现的父类BaseBuilder的抽象方法, 就这一个方法, 就把整个feign框架的组件都画出来了, 主要是构建了ReflectiveFeign对象, 然后它依赖了三个核心组件
    • Contract: 用来解析目标接口生成方法模板
    • SynchronousMethodHandler.Factory: 用来构建同步请求句柄
    • InvocationHandlerFactory: 用来创建feign接口动态代理回调对象的工厂(feign使用的是jdk动态代理,代理方法是InvocationHandler#invoke)

ReflectiveFeign

看名字就是跟发射有关的对象

看下类定义

public class ReflectiveFeign<C> extends Feign {private final ParseHandlersByName<C> targetToHandlersByName;// 这里是InvocationHandlerFactoryprivate final InvocationHandlerFactory factory;private final AsyncContextSupplier<C> defaultContextSupplier;/*** 创建目标接口的代理对象*/public <T> T newInstance(Target<T> target, C requestContext) {...}/*** jdk动态代理的方法代理句柄*/static class FeignInvocationHandler implements InvocationHandler {...}/*** 根据名称解析成Handler的对象*/private static final class ParseHandlersByName<C> {...}/*** target目标校验器*/private static class TargetSpecificationVerifier {...}
}

看了这个类的整个结构比较清晰, 就是用来校验、创建动态代理, 下面看具体实现

/*** 创建目标接口的代理对象
*/
public <T> T newInstance(Target<T> target, C requestContext) {// 1.校验target类TargetSpecificationVerifier.verify(target);// 2.创建方法句柄, 并将方法和方法句柄映射起来Map<Method, MethodHandler> methodToHandler =targetToHandlersByName.apply(target, requestContext);InvocationHandler handler = factory.create(target, methodToHandler);// 3. 创建jdk动态代理的方法代理句柄T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);// 4. 处理默认方法for (MethodHandler methodHandler : methodToHandler.values()) {if (methodHandler instanceof DefaultMethodHandler) {((DefaultMethodHandler) methodHandler).bindTo(proxy);}}return proxy;}

方法小结

  1. 校验target目标类

    • 目标对象必须是一个接口, 也就是定义feign请求定义的类必须是一个接口
    • 如果方法返回值是CompletableFuture类型, 返回的CompletableFuture上必须有泛型参数, 且泛型参数不能是通配符类型, 也就是?, 例如 CompletableFuture<Student>是可以的, 但是``CompletableFuture<?>`类型不允许
  2. 创建方法的方法句柄, 并将方法和方法句柄映射起来;

    这里方法句柄是feign自定义的MethodHandler, 而jdk默认的是 java.lang.invoke.MethodHandle

  3. 创建jdk动态代理对象

  4. 处理默认方法(接口的中default修饰的方法)

关于接口中的default方法, 感兴趣的同学可以参考我的这篇文章接口中的default和static方法

FeignInvocationHandler

用来创建jdk动态代理的方法代理句柄的工厂, 实现了java.lang.reflect.InvocationHandler接口

BaseBuilder中的invocationHandlerFactory创建

/*** 用来创建jdk代理处理对象InvocationHandler的工厂*/
protected InvocationHandlerFactory invocationHandlerFactory = new InvocationHandlerFactory.Default();public interface InvocationHandlerFactory {static final class Default implements InvocationHandlerFactory {@Overridepublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);}}
}

核心方法

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("equals".equals(method.getName())) {try {// 如果是equals方法, 并且第一个参数不为null, 那么获取这个参数的代理方法句柄; 如果参数args[0]不是代理对象, 那么会抛异常Object otherHandler =args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;return equals(otherHandler);} catch (IllegalArgumentException e) {// args[0]不是代理对象直接返回falsereturn false;}} else if ("hashCode".equals(method.getName())) {return hashCode();} else if ("toString".equals(method.getName())) {return toString();} else if (!dispatch.containsKey(method)) {throw new UnsupportedOperationException(String.format("Method \"%s\" should not be called", method.getName()));}// 调用方法句柄return dispatch.get(method).invoke(args);}

方法小结

  1. 如果接口中定义了equals方法, 如果第一个参数是代理类, 那么返回该代理的java.lang.reflect.InvocationHandler对象, 并且该InvocationHandlerFeignInvocationHandler类型, 且对象中的target属性和当前FeignInvocationHandler中的target属性相等才返回true (不太好理解, 尽量不要这么干就行了)
  2. 支持hashCode和toString方法
  3. 从解析的方法与方法句柄映射中获取方法句柄来执行

ParseHandlersByName

关于ParseHandlersByName类, 它包装了ContractMethodHandler.Factory, 用来解析接口、方法生成MethodMetadata,并根据MethodMetadata生成方法句柄MethodHandler, 同时它也支持对default方法的处理

public Map<Method, MethodHandler> apply(Target target, C requestContext) {final Map<Method, MethodHandler> result = new LinkedHashMap<>();// 校验target并获取类中方法的metadatafinal List<MethodMetadata> metadataList = contract.parseAndValidateMetadata(target.type());for (MethodMetadata md : metadataList) {final Method method = md.method();// 忽略Object类中的方法if (method.getDeclaringClass() == Object.class) {continue;}// 创建方法句柄final MethodHandler handler = createMethodHandler(target, md, requestContext);result.put(method, handler);}// 提供对default方法的支持for (Method method : target.type().getMethods()) {if (Util.isDefault(method)) {final MethodHandler handler = new DefaultMethodHandler(method);result.put(method, handler);}}return result;}private MethodHandler createMethodHandler(final Target<?> target,final MethodMetadata md,final C requestContext) {// 忽略方法if (md.isIgnored()) {return args -> {throw new IllegalStateException(md.configKey() + " is not a method handled by feign");};}// 创建方法句柄return factory.create(target, md, requestContext);}}

总结

  1. Feign类作为feign框架的门户类, 提供了所有的框架可以自定义组件的设置入口
  2. Feign类提供了target方法用来构建feign接口的代理对象
  3. 其中的Builder静态内部类使用了父子泛型的方式供使用者可以以简单链式写法构建参数
  4. Builder提供了capabilities增强器允许使用者对框架内的字段进行增强(排除了Object类,capabilities字段,thisB,基本类型字段,枚举类型字段)
  5. 同时也允许我们对ResponseInterceptor.Chain字段进行增强
  6. ReflectiveFeign对象提供了将方法解析成方法句柄的功能, 并通过jdk动态代理将目标接口生成代理对象
  7. 限制了代理对象必须是接口, 并且当方法的返回值是CompletableFuture类型的时候, 必须指定其泛型类型(不能是通配符?类型)
  8. 支持接口中的方法是default方法, 也支持了hashCode, toString方法, 但是对于equals方法, 要求equals方法的第一个参数是jdk代理对象

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

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

相关文章

docker 通过Dockerfile自定义的镜像部署Springboot项目

一、镜像结构介绍&#xff1a; 镜像&#xff1a;层&#xff08;Layer&#xff09;添加安装包、依赖、配置等&#xff0c;每一次操作都形成新的一层&#xff1b;基础镜像&#xff08;BaseImage&#xff09;应用依赖的系统函数库、环境、配置、文件等&#xff1b;入口&#xff0…

【Canvas与图标】GUI图标

【成图】 120*120的png图标 各种大小图&#xff1a; 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>GUI图标 Draft1</titl…

Spring Boot 整合 ELK 全面指南:实现日志采集、分析与可视化

一、ELK简介 1.1 什么是ELK&#xff1f; ELK 是三个开源工具的组合&#xff1a; Elasticsearch&#xff1a;一个分布式全文搜索和分析引擎&#xff0c;用于存储和查询日志数据。Logstash&#xff1a;一个数据处理管道工具&#xff0c;用于收集、解析和处理日志数据。Kibana&…

数据库编程(sqlite3)

一&#xff1a;数据库分类 常用的数据库 大型数据库 &#xff1a;Oracle商业、多平台、关系型数据库功能最强大、最复杂、市场占比最高的商业数据库 中型数据库 &#xff1a;Server是微软开发的数据库产品&#xff0c;主要支持windows平台 小型数据库 : mySQL是一个小型关系型…

CCF GESP C++ 一级上机题(十六道题及其思路详解合集)

#include <iostream> using namespace std;int main() {// 定义起始年份、结束年份、循环变量以及用于累加的变量&#xff0c;并初始化累加变量为0int start, end, i, sum 0;// 从标准输入读取起始年份和结束年份cin >> start >> end;// 循环遍历从起始年份…

Opencv+ROS实现颜色识别应用

目录 一、工具 二、原理 概念 本质 三、实践 添加发布话题 主要代码 四、成果 五、总结 一、工具 opencvros ubuntu18.04 摄像头 二、原理 概念 彩色图像&#xff1a;RGB&#xff08;红&#xff0c;绿&#xff0c;蓝&#xff09; HSV图像&#xff1a;H&#xff0…

【linux】shell脚本

文章目录 1. jar包启动脚本1.1 方式一1.2 方式二 2. 进程关闭脚本3. 操作mysql4. impala建表语句提取5. 监控磁盘存量6. 清日志脚本7. 替换tomcat的启动端口8. 将一行数据按照空格依次读取 1. jar包启动脚本 1.1 方式一 #!/bin/sh RESOURCE_NAME/usr/local/applications/scre…

Flume和kafka的整合:使用Flume将日志数据抽取到Kafka中

文章目录 1、Kafka作为Source【数据进入到kafka中&#xff0c;抽取出来】2、kafka作为Sink 【数据从别的地方抽取到kafka里面】 1、Kafka作为Source【数据进入到kafka中&#xff0c;抽取出来】 kafka源 --> memory --> 控制台&#xff1a; a1.sources r1 a1.sinks k1…

vue3 reactive响应式实现源码

Vue 3 的 reactive 是基于 JavaScript 的 Proxy 实现的&#xff0c;因此它通过代理机制来拦截对象的操作&#xff0c;从而实现响应式数据的追踪。下面是 Vue 3 的 reactive 源码简化版。 Vue 3 reactive 源码简化版 首先&#xff0c;我们需要了解 reactive 是如何工作的&…

scala模式匹配

object test47 {def main(args: Array[String]): Unit {val id"445646546548858548648"//取出id前两位val provinceid.substring(0,2) // println(province) // if (province"42"){ // println("湖北") // }else if(province&quo…

旋转磁体产生的场 - 实验视频资源下载

先发几个视频&#xff0c;是2019年所作的实验内容 更多视频&#xff0c;到某宝找我吧。注意&#xff1a;是收费的。 20190312-180244-旋转磁体产生的场造成激光功率减小 https://download.csdn.net/download/u014161757/90038058 20190313-090956-旋转磁体产生的场对真空介电…

AI加持,华为全屋智能品牌升级为“鸿蒙智家”

1.传统智能家居的困境&#xff1a;从便利到繁琐 近年来&#xff0c;智能家居因其便捷性和科技感受到消费者的青睐。然而&#xff0c;随着用户需求的多样化&#xff0c;传统智能家居的弊端逐渐显现&#xff1a; 设备连接复杂&#xff0c;品牌间兼容性不足&#xff0c;用户不得不…

【后端面试总结】MySQL索引

数据库索引不只一种实现方法&#xff0c;但是其中最具代表性&#xff0c;也是我们面试中遇到最多的无疑是B树。 索引为什么选择B树 数据量很大的查找&#xff0c;是不能直接放入内存的&#xff0c;而是需要什么数据就通过磁盘IO去获得。 红黑树&#xff0c;AVL树等二叉查找树…

ASP.net WebAPI 上传图片实例(保存显示随机文件名)

[HttpPost]public Task<Hashtable> ImgUpload(){// 检查是否是 multipart/form-dataif (!Request.Content.IsMimeMultipartContent("form-data"))throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);//文件保存目录路径string SaveTempPat…

string类部分(C++)

目录 1. string类 1.1 auto和范围for auto关键词&#xff1a; 范围for&#xff1a; 1.2 string类的常用接口说明 a&#xff09;string类对象的常见构造 b&#xff09; string类对象的容量操作 size与length&#xff1a; capacity: empty: clear: reserve: 1.reserve&am…

Oracle RAC 环境下数据文件误建在本地目录的处理过程

问题描述 在 Oracle RAC 环境中&#xff0c;有时会误将数据文件创建在本地目录&#xff0c;导致其他节点无法访问该数据文件&#xff0c;从而报出 ORA-01157 和 ORA-01110 错误。 问题分析 错误日志 Mon Nov 16 19:02:38 2021 Errors in file /u01/app/oracle/diag/rdbms/orc…

大厂也在用的分布式链路追踪:TraceIdFilter + MDC + Skywalking

痛点 查线上日志时&#xff0c;同一个 Pod 内多线程日志交错&#xff0c;很难追踪每个请求对应的日志信息。 日志收集工具将多个 Pod 的日志收集到同一个数据库中后&#xff0c;情况就更加混乱不堪了。 解决 TraceId MDC 前端每次请求时&#xff0c;添加 X-App-Trace-Id 请…

Dashboard Tactics

1&#xff1a;相关链接Dashboard Tactics :: OpenCPN Dashboard Tactics Plugin rgleason/dashboard_tactics_pi: OpenCPN dashboard built-in plugin merger with external tactics_pi plugin NMEAconverter :: OpenCPN 2&#xff1a;显示样式 3&#xff1a;代码 这个插件…

【leetcode】动态规划

31. 873. 最长的斐波那契子序列的长度 题目&#xff1a; 如果序列 X_1, X_2, ..., X_n 满足下列条件&#xff0c;就说它是 斐波那契式 的&#xff1a; n > 3对于所有 i 2 < n&#xff0c;都有 X_i X_{i1} X_{i2} 给定一个严格递增的正整数数组形成序列 arr &#xff0…

24.11.26 Mybatis2

resultMap 中的标签和属性 如果是主键列 一般用id标签对应 propertyjava对象的属性 column 数据库中的列( javaType实体类数据类型 jdbcType数据库列的数据类型 ) 不需要配置 <id property"empno" column"empno" />如果是普通列 一般用result对…