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…

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…

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…

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

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

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…

大厂也在用的分布式链路追踪: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对…

第六届国际科技创新学术交流大会暨新能源科学与电力工程国际(NESEE 2024)

重要信息 会议官网&#xff1a;nesee.iaecst.org 会议时间&#xff1a;2024年12月6-8日 会议地点&#xff1a; 中国-广州&#xff08;越秀国际会议中心) 大会简介 新能源科学与电力工程国际学术会议&#xff08;NESEE 2024&#xff09;作为第六届国际科技创新学术交流大会分…

【es6】原生js在页面上画矩形添加选中状态高亮及显示调整大小控制框(三)

接上篇文章&#xff0c;这篇实现下选中当前元素显示调整大小的控制框&#xff0c;点击document取消元素的选中高亮状态效果。 实现效果 代码逻辑 动态生成控制按钮矩形,并设置响应的css // 动态添加一个调整位置的按钮addScaleBtn(target) {const w target.offsetWidth;con…

文心一言与千帆大模型平台的区别:探索百度AI生态的双子星

随着人工智能技术的迅猛发展&#xff0c;越来越多的公司开始投入资源开发自己的AI解决方案。在中国&#xff0c;百度作为互联网巨头之一&#xff0c;不仅在搜索引擎领域占据重要位置&#xff0c;还在AI领域取得了显著成就。其中&#xff0c;“文心一言”和“千帆大模型平台”便…

【西瓜书】神经网络-MP神经元、感知机和多层网络

神经网络&#xff08;neural networks&#xff09;的定义&#xff1a;神经网络是由具有适应性的简单单元组成的广泛并行互联的网络&#xff0c;它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。&#xff08;T. Kohonen 1988年在Neural Networks创刊号上给出的定义…

《基于FPGA的便携式PWM方波信号发生器》论文分析(三)——数码管稳定显示与系统调试

一、论文概述 基于FPGA的便携式PWM方波信号发生器是一篇由任青颖、庹忠曜、黄洵桢、李智禺和张贤宇 等人发表的一篇期刊论文。该论文主要研究了一种新型的信号发生器&#xff0c;旨在解决传统PWM信号发生器在移动设备信号调控中存在的精准度低和便携性差的问题 。其基于现场可编…

一个专为云原生环境设计的高性能分布式文件系统

大家好&#xff0c;今天给大家分享一款开源创新的分布式 POSIX 文件系统JuiceFS&#xff0c;旨在解决海量云存储与各类应用平台&#xff08;如大数据、机器学习、人工智能等&#xff09;之间高效对接的问题。 项目介绍 JuiceFS 是一款面向云原生设计的高性能分布式文件系统&am…

【JavaScript】图解JS中的字符串方法

&#x1f4af; 欢迎光临清清ww的博客小天地&#x1f4af; &#x1f525; 个人主页:【清清ww】&#x1f525; &#x1f4da; 系列专栏:vue3 | TypeScript &#x1f4da; &#x1f31f; 学习本无底&#xff0c;前进莫徬徨。&#x1f31f; 目录 一.字符串查找 1.length属性 2. i…

ffmpeg视频滤镜:替换部分帧-freezeframes

滤镜描述 freezeframes 官网地址 > FFmpeg Filters Documentation 这个滤镜接收两个输入&#xff0c;然后会将第一个视频中的部分帧替换为第二个视频的某一帧。 滤镜使用 参数 freezeframes AVOptions:first <int64> ..FV....... set first fra…

云计算-华为HCIA-学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第三章&#xff1a;常见设备 交换机 二层交换机和三层交换机&#xff0c;所谓二层交换机…