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.rhkb.cn/news/479006.html

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

相关文章

51单片机从入门到精通:理论与实践指南入门篇(二)

续51单片机从入门到精通&#xff1a;理论与实践指南&#xff08;一&#xff09;https://blog.csdn.net/speaking_me/article/details/144067372 第一篇总体给大家在&#xff08;全局&#xff09;总体上讲解了一下51单片机&#xff0c;那么接下来几天结束详细讲解&#xff0c;从…

STM32C011开发(3)----Flash操作

STM32C011开发----3.Flash操作 概述硬件准备视频教学样品申请源码下载参考程序生成STM32CUBEMX串口配置堆栈设置串口重定向FLASH数据初始化FLASH 读写演示 概述 STM32C011 系列微控制器内置 Flash 存储器&#xff0c;支持程序存储与数据保存&#xff0c;具备页面擦除、双字写入…

IDEA无法创建java8、11项目创建出的pom.xml为空

主要是由于Spring3.X版本不支持JDK8&#xff0c;JDK11&#xff0c;最低支持JDK17 解决的话要不就换成JDK17以上的版本&#xff0c;但是不太现实 另外可以参考以下方式解决 修改spring初始化服务器地址为阿里云的 https://start.aliyun.com/

【Unity3D】创建自定义字体

前置准备 如图所示&#xff0c;项目工程中需要用文件夹存储0-9的Sprite图片。 使用流程 直接右键存放图片的文件夹&#xff0c;选择【创建自定义字体】&#xff0c;之后会在脚本定义的FontOutputPath中生成材质球和字体。 源码 using System; using System.Collections.Gene…

logminer挖掘日志归档查找问题

--根据发生问题时间点查找归档文件 select first_time,NAME from gv$archived_log where first_time>2016-03-15 17:00:00 and first_time<2016-03-15 21:00:00; 2016-03-15 17:23:55 ARCH/jxdb/archivelog/2016_03_15/thread_1_seq_41588.4060.906577337 2016-03-15 17:…

电商项目高级篇06-缓存

电商项目高级篇06-缓存 1、docker下启动redis2、项目整合redis 缓存 流程图&#xff1a; data cache.load(id);//从缓存加载数据 If(data null){ data db.load(id);//从数据库加载数据 cache.put(id,data);//保存到 cache 中 } return data;在我们的单体项目中可以用Map作…

如何使用GCC手动编译stm32程序

如何不使用任何IDE&#xff08;集成开发环境&#xff09;编译stm32程序? 集成开发环境将编辑器、编译器、链接器、调试器等开发工具集成在一个统一的软件中&#xff0c;使得开发人员可以更加简单、高效地完成软件开发过程。如果我们不使用KEIL,IAR等集成开发环境&#xff0c;…

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

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

Vue-TreeSelect组件最下级隐藏No sub-options

问题&#xff1a;最下级没有数据的话&#xff0c;去除No sub-options信息 为什么没下级&#xff0c;会展示这个&#xff1f; 整个树形结构数据都是由后端构造好返回给前端的。默认子类没数据的话&#xff0c;children是一个空数组。也就是因为这最下级的空数组&#xff0c;导致…

k8s集群增加nfs-subdir-external-provisioner存储类

文章目录 前言一、版本信息二、本机安装nfs组件包三、下载nfs-subdir-external-provisioner配置文件并进行配置1.下载文件2.修改配置 三、进行部署备注&#xff1a;关于镜像无法拉取问题的处理 前言 手里的一台服务器搭建一个单点的k8s集群&#xff0c;然后在本机上使用nfs-su…

C语言数据结构-链表

C语言数据结构-链表 1.单链表1.1概念与结构1.2结点3.2 链表性质1.3链表的打印1.4实现单链表1.4.1 插入1.4.2删除1.4.3查找1.4.4在指定位置之前插入或删除1.4.5在指定位置之后插入或删除1.4.6删除指定位置1.4.7销毁链表 2.链表的分类3.双向链表3.1实现双向链表3.1.1尾插3.1.2头插…

【SpringCloud详细教程】-04-服务容错--Sentinel

精品专题&#xff1a; 01.《C语言从不挂科到高绩点》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12753294.html?spm1001.2014.3001.5482 02. 《SpringBoot详细教程》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12789841.html?spm1001.20…

【Python中while循环】

一、深拷贝、浅拷贝 1、需求 1&#xff09;拷贝原列表产生一个新列表 2&#xff09;想让两个列表完全独立开&#xff08;针对改操作&#xff0c;读的操作不改变&#xff09; 要满足上述的条件&#xff0c;只能使用深拷贝 2、如何拷贝列表 1&#xff09;直接赋值 # 定义一个…

在 Mac(ARM 架构)上安装 JDK 8 环境

文章目录 步骤 1&#xff1a;检查系统版本步骤 2&#xff1a;下载支持 ARM 的 JDK 8步骤 3&#xff1a;安装 JDK步骤 4&#xff1a;配置环境变量步骤 5&#xff1a;验证安装步骤 6&#xff1a;注意事项步骤7&#xff1a;查看Java的安装路径 在 Mac&#xff08;ARM 架构&#xf…

对比C++,Rust在内存安全上做的努力

简介 近年来&#xff0c;越来越多的组织表示&#xff0c;如果新项目在技术选型时需要使用系统级开发语言&#xff0c;那么不要选择使用C/C这种内存不安全的系统语言&#xff0c;推荐使用内存安全的Rust作为替代。 谷歌也声称&#xff0c;Android 的安全漏洞&#xff0c;从 20…

小程序基础:流程。

一、前言 该文章是个人的学习笔记&#xff0c;是学习了黑马程序的微信小程序开发视频后写的笔记&#xff0c;将老师所讲的内容重点汇总起来&#xff0c;目的是为了方便自己日后回顾&#xff0c;同时也方便大家学习和了解小程序的开发 想要入门小程序&#xff0c;那么看这一篇文…

【漏洞复现】CVE-2020-13925

漏洞信息 NVD - CVE-2020-13925 Similar to CVE-2020-1956, Kylin has one more restful API which concatenates the API inputs into OS commands and then executes them on the server; while the reported API misses necessary input validation, which causes the hac…

数据结构 (11)串的基本概念

一、串的定义 1.串是由一个或者多个字符组成的有限序列&#xff0c;一般记为&#xff1a;sa1a2…an&#xff08;n≥0&#xff09;。其中&#xff0c;s是串的名称&#xff0c;用单括号括起来的字符序列是串的值&#xff1b;ai&#xff08;1≤i≤n&#xff09;可以是字母、数字或…

LLM PPT Translator

LLM PPT Translator 引言Github 地址UI PreviewTranslated Result Samples 引言 周末开发了1个PowerPoint文档翻译工具&#xff0c;上传PowerPoint文档&#xff0c;指定想翻译的目标语言&#xff0c;通过LLM的能力将文档翻译成目标语言的文档。 Github 地址 https://github.…

Python数据分析实例五、US 大选捐款数据分析

美国联邦选举委员会 (FEC) 公布了对政治竞选活动的贡献数据。这包括投稿人姓名、职业和雇主、地址和投款金额。2012 年美国总统大选的贡献数据以单个 150 MB 的 CSV 文件P00000001-ALL.csv形式提供,该文件可以通过以下pandas.read_csv加载: import pandas as pdfec = pd.r…