SpringCloud原理】OpenFeign之FeignClient动态代理生成原理

大家好,前面我已经剖析了OpenFeign的动态代理生成原理和Ribbon的运行原理,这篇文章来继续剖析SpringCloud组件原理,来看一看OpenFeign是如何基于Ribbon来实现负载均衡的,两组件是如何协同工作的。

一、Feign动态代理调用实现rpc流程分析

通过Feign客户端接口的动态代理生成原理讲解,我们可以清楚的知道,Feign客户端接口的动态代理生成是基于JDK的动态代理来实现的,那么在所有的方法调用的时候最终都会走InvocationHandler接口的实现,默认就是ReflectiveFeign.FeignInvocationHandler,那我们接下来就来看看,FeignInvocationHandler是如何实现rpc调用的。

FeignInvocationHandler对于invoke方法的实现。

    private final Map<Method, MethodHandler> dispatch;    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {      if ("equals".equals(method.getName())) {        try {          Object otherHandler =              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;          return equals(otherHandler);        } catch (IllegalArgumentException e) {          return false;        }      } else if ("hashCode".equals(method.getName())) {        return hashCode();      } else if ("toString".equals(method.getName())) {        return toString();      }
      return dispatch.get(method).invoke(args);    }

前几个if判断很简单,就是判断是不是调用的方法是不是equals,hashCode,toString,因为这些方法的调是不需要走rpc调用的。

接下就是从dispatch获取要调用的方法对应的MethodHandler,然后调用MethodHandler的invoke方法。那MethodHandler是什么时候生成的呢?MethodHandler是在构建动态代理的时候生成的,不清楚的同学可以翻一下OpenFeign那篇文章最后关于生成动态代理的那部分源码。那MethodHandler作用是什么呢?你可以理解为最终rpc的调用都是基于这个MethodHandler来实现的,每个方法都有对应MethodHandler来实现rpc调用,接下来我们就来看一下MethodHandler的invoke方法的实现。

MethodHandler是个接口,有两个实现类,一个是DefaultMethodHandler,这个是处理接口中的默认方法的,另一个是SynchronousMethodHandler,这个是实现rpc调用的方法。接下来我们就看看SynchronousMethodHandler关于invoke方法的实现。

  @Override  public Object invoke(Object[] argv) throws Throwable {    RequestTemplate template = buildTemplateFromArgs.create(argv);    Options options = findOptions(argv);    Retryer retryer = this.retryer.clone();    while (true) {      try {        return executeAndDecode(template, options);      } catch (RetryableException e) {        try {          retryer.continueOrPropagate(e);        } catch (RetryableException th) {          Throwable cause = th.getCause();          if (propagationPolicy == UNWRAP && cause != null) {            throw cause;          } else {            throw th;          }        }        if (logLevel != Logger.Level.NONE) {          logger.logRetry(metadata.configKey(), logLevel);        }        continue;      }    }  }

第一行通过方法的参数构建了一个RequestTemplate,RequestTemplate可以看成是组装http请求所需各种参数的封装,比如什么情头,body之类的都放在这里面。

第二行 Options options = findOptions(argv); 这个很有意思,Options主要是封装了发送请求是连接超时时间和读超时时间的配置,findOptions(argv)也就是先从参数里面找有没有Options,没有就返回构造SynchronousMethodHandler的入参时的Options,也就是说,连接超时时间和读超时时间可以从方法入参来传入,不过一般没有人这么玩。

第三行就是搞一个重试的组件,是可以实现重试的,一般不设置。

然后执行到executeAndDecode(template, options),进入这个方法

 
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {    Request request = targetRequest(template);
    if (logLevel != Logger.Level.NONE) {      logger.logRequest(metadata.configKey(), logLevel, request);    }
    Response response;    long start = System.nanoTime();    try {      response = client.execute(request, options);    } catch (IOException e) {      if (logLevel != Logger.Level.NONE) {        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));      }      throw errorExecuting(request, e);    }    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    boolean shouldClose = true;    try {      if (logLevel != Logger.Level.NONE) {        response =            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);      }      if (Response.class == metadata.returnType()) {        if (response.body() == null) {          return response;        }        if (response.body().length() == null ||            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {          shouldClose = false;          return response;        }        // Ensure the response body is disconnected        byte[] bodyData = Util.toByteArray(response.body().asInputStream());        return response.toBuilder().body(bodyData).build();      }      if (response.status() >= 200 && response.status() < 300) {        if (void.class == metadata.returnType()) {          return null;        } else {          Object result = decode(response);          shouldClose = closeAfterDecode;          return result;        }      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {        Object result = decode(response);        shouldClose = closeAfterDecode;        return result;      } else {        throw errorDecoder.decode(metadata.configKey(), response);      }    } catch (IOException e) {      if (logLevel != Logger.Level.NONE) {        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);      }      throw errorReading(request, response, e);    } finally {      if (shouldClose) {        ensureClosed(response.body());      }    }  }

首先调用了targetRequest方法,贴出源码

Request targetRequest(RequestTemplate template) {    for (RequestInterceptor interceptor : requestInterceptors) {      interceptor.apply(template);    }    return target.apply(template);  }

这个方法会遍历所有的拦截器RequestInterceptor,这是feign的一个扩展点,也就说再发送请求前,你仍然还有机会对请求的内容进行调整,比如说加个请求头,这也是很常见的一种方式,在微服务之间鉴权的时候使用。RequestInterceptor是在构建Feign.Builder的时候传进来的,Feign.Builder的组件都是通过ioc容器获取的,组件又是通过配置类来的,所以你需要的话就可以在配置类中声明RequestInterceptor对象。配置类有不同的优先级,按照自己的需求,可以在其中一个优先级使用,不过一般这种通用的东西,不是某个微服务特有的功能,一般选择在springboot启动中的容器中配置。

执行完targetRequest,回到executeAndDecode之后,会构建出一个Request,Request很好理解,就是一个请求,里面封装了http请求的东西。接下来就会调用Client的execute方法来执行请求,拿到响应,接下来就是基于处理这个响应,将响应数据封装成需要返回的参数,之后返回给调用方。

到这里,我们已经分析出接口的动态代理是如何运行的。其实就是通过每个方法对应的MethodHandler来实现的,MethodHandler主要就是拼接各种参数,组装成一个请求,随后交由Client接口的实现去发送请求。

二、LoadBalancerFeignClient

通过上面分析整个动态代理调用过程可以看出,Client是发送http请求的关键类。那么Client是什么玩意?还记得我在关于OpenFeign动态代理生成的那篇文章中留下的一个疑问么,当Feign客户端在构建动态代理的时候,填充很多组件到Feign.Builder中,其中有个组件就是Client的实现,我们并没有在FeignClientsConfiguration配置类中找到关于Client的对象的声明。不过当时我就提到了,这个组件的实现是要依赖负载均衡的,也就是这个组件是Feign用来整合Ribbon的入口。

接下来,我们就着重看一下Client的实现,看看Feign是如何通过ribbon实现负载均衡的。

我们先来看一下Feign跟ribbon整合的配置类。

@Import({ HttpClientFeignLoadBalancedConfiguration.class,    OkHttpFeignLoadBalancedConfiguration.class,    DefaultFeignLoadBalancedConfiguration.class })public class FeignRibbonClientAutoConfiguration {
  @Bean  @Primary  @ConditionalOnMissingBean  @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")  public CachingSpringLoadBalancerFactory cachingLBClientFactory(      SpringClientFactory factory) {    return new CachingSpringLoadBalancerFactory(factory);  }
  @Bean  @Primary  @ConditionalOnMissingBean  @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")  public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(      SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {    return new CachingSpringLoadBalancerFactory(factory, retryFactory);  }
  @Bean  @ConditionalOnMissingBean  public Request.Options feignRequestOptions() {    return LoadBalancerFeignClient.DEFAULT_OPTIONS;  }
}

我们来分析一下,首先通过@Impot注解导入了三个配置类。

  • HttpClientFeignLoadBalancedConfiguration:基于HttpClient实现http调用的。

  • OkHttpFeignLoadBalancedConfiguration:基于OkHttp实现http调用的。

  • DefaultFeignLoadBalancedConfiguration:默认的,也就是Feign原生的发送http的实现。

这里我们看一下DefaultFeignLoadBalancedConfiguration配置类,因为默认就是这,HttpClientFeignLoadBalancedConfiguration和OkHttpFeignLoadBalancedConfiguration都需要有引入HttpClient和OkHttp依赖才会有用

@Configuration(proxyBeanMethods = false)class DefaultFeignLoadBalancedConfiguration {
  @Bean  @ConditionalOnMissingBean  public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,      SpringClientFactory clientFactory) {    return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,        clientFactory);  }
}

这个配置类很简单,声明了LoadBalancerFeignClient到spring容器,传入了三个参数,一个Client的实现,一个CachingSpringLoadBalancerFactory和一个SpringClientFactory。LoadBalancerFeignClient这个类实现了Client接口,也就数说我们在构建Feign.Builder填充的就是这个对象,也就是上面说feign的执行流程最后用来执行请求的Client的实现。

接下来我说一下入参的三个参数是什么意思。

  • Client.Default:就是Feign自己实现的Client,里面封装了真正发送http发送请求的功能,LoadBalancerFeignClient虽然也实现了Client接口,但是这个实现其实是为了整合Ribbon用的,并没有发送http的功能,所以需要有个可以发送http功能的实现。

  • CachingSpringLoadBalancerFactory:后面会说这个类的作用

  • SpringClientFactory:这个跟Feign里面的FeignContext的作用差不多,用来实现配置隔离的,当然,这个也在关于Ribbon的那篇文章有剖析过。

其实大家可以自行去看OkHttpFeignLoadBalancedConfiguration和HttpClientFeignLoadBalancedConfiguration,其实他们配置跟DefaultFeignLoadBalancedConfiguration是一样的,声明的对象都是LoadBalancerFeignClient,只不过将Client.Default换成了基于HttpClient和OkHttp的实现,也就是发送http请求使用的工具不一样。

FeignRibbonClientAutoConfiguration除了导入配置类还声明了CachingSpringLoadBalancerFactory,只不过一种是带基于spring实现的重试功能的,一种是不带的,主要看有没有引入spring重试功能的包,所以上面构建LoadBalancerFeignClient注入的CachingSpringLoadBalancerFactory就是在这声明的。

这里就说完了Feign整合ribbon的配置类FeignRibbonClientAutoConfiguration,我们也找到了构造Feign.Builder的实现LoadBalancerFeignClient,接下来就来剖析LoadBalancerFeignClient的实现。

public class LoadBalancerFeignClient implements Client {
  static final Request.Options DEFAULT_OPTIONS = new Request.Options();
  private final Client delegate;
  private CachingSpringLoadBalancerFactory lbClientFactory;
  private SpringClientFactory clientFactory;
  public LoadBalancerFeignClient(Client delegate,      CachingSpringLoadBalancerFactory lbClientFactory,      SpringClientFactory clientFactory) {    this.delegate = delegate;    this.lbClientFactory = lbClientFactory;    this.clientFactory = clientFactory;  }
  static URI cleanUrl(String originalUrl, String host) {    String newUrl = originalUrl;    if (originalUrl.startsWith("https://")) {      newUrl = originalUrl.substring(0, 8)          + originalUrl.substring(8 + host.length());    }    else if (originalUrl.startsWith("http")) {      newUrl = originalUrl.substring(0, 7)          + originalUrl.substring(7 + host.length());    }    StringBuffer buffer = new StringBuffer(newUrl);    if ((newUrl.startsWith("https://") && newUrl.length() == 8)        || (newUrl.startsWith("http://") && newUrl.length() == 7)) {      buffer.append("/");    }    return URI.create(buffer.toString());  }
  @Override  public Response execute(Request request, Request.Options options) throws IOException {    try {      URI asUri = URI.create(request.url());      String clientName = asUri.getHost();      URI uriWithoutHost = cleanUrl(request.url(), clientName);      FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(          this.delegate, request, uriWithoutHost);
      IClientConfig requestConfig = getClientConfig(options, clientName);      return lbClient(clientName)          .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();    }    catch (ClientException e) {      IOException io = findIOException(e);      if (io != null) {        throw io;      }      throw new RuntimeException(e);    }  }
  IClientConfig getClientConfig(Request.Options options, String clientName) {    IClientConfig requestConfig;    if (options == DEFAULT_OPTIONS) {      requestConfig = this.clientFactory.getClientConfig(clientName);    }    else {      requestConfig = new FeignOptionsClientConfig(options);    }    return requestConfig;  }
  protected IOException findIOException(Throwable t) {    if (t == null) {      return null;    }    if (t instanceof IOException) {      return (IOException) t;    }    return findIOException(t.getCause());  }
  public Client getDelegate() {    return this.delegate;  }
  private FeignLoadBalancer lbClient(String clientName) {    return this.lbClientFactory.create(clientName);  }
  static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
    FeignOptionsClientConfig(Request.Options options) {      setProperty(CommonClientConfigKey.ConnectTimeout,          options.connectTimeoutMillis());      setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());    }
    @Override    public void loadProperties(String clientName) {
    }
    @Override    public void loadDefaultValues() {
    }
  }
}

在动态代理调用的那里我们得出一个结论,那就是最后会调用Client接口的execute方法的实现,所以我们就看一下execute方法的实现,这里就是一堆操作,从请求的URL中拿到了clientName,也就是服务名。

为什么可以拿到服务名?

其实很简单,OpenFeign构建动态代理的时候,传入了一个HardCodedTarget,当时说在构建HardCodedTarget的时候传入了一个url,那个url当时说了其实就是http://服务名,所以到这里,虽然有具体的请求接口的路径,但是还是类似 http://服务名/api/sayHello这种,所以可以通过路径拿到你锁请求的服务名。

拿到服务名之后,再拿到了一个配置类IClientConfig,最后调用lbClient,我们看一下lbClient的方法实现。

private FeignLoadBalancer lbClient(String clientName) {    return this.lbClientFactory.create(clientName);}

就是调用CachingSpringLoadBalancerFactory的create方法

public FeignLoadBalancer create(String clientName) {    FeignLoadBalancer client = this.cache.get(clientName);    if (client != null) {      return client;    }    IClientConfig config = this.factory.getClientConfig(clientName);    ILoadBalancer lb = this.factory.getLoadBalancer(clientName);    ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,        ServerIntrospector.class);    client = this.loadBalancedRetryFactory != null        ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,            this.loadBalancedRetryFactory)        : new FeignLoadBalancer(lb, config, serverIntrospector);    this.cache.put(clientName, client);    return client;  }

这个方法先根据服务名从缓存中获取一个FeignLoadBalancer,获取不到就创建一个。

创建的过程就是从每个服务对应的容器中获取到IClientConfig和ILoadBalancer。Ribbon那篇文章都讲过这些核心类,这里不再赘述。

默认就是创建不带spring重试功能的FeignLoadBalancer,放入缓存,最后返回这个FeignLoadBalancer。所以第一次来肯定没有,需要构建,也就是最终一定会返回FeignLoadBalancer,所以我们通过lbClient方法拿到的是FeignLoadBalancer。从这里可以看出CachingSpringLoadBalancerFactory是构建FeignLoadBalancer的工厂类,只不过先从缓存中查找,找不到再创建FeignLoadBalancer。

拿到FeignLoadBalancer之后就会调用executeWithLoadBalancer,接收到Response之后直接返回。

三、FeignLoadBalancer

那么这个FeignLoadBalancer又是啥呢?这里放上FeignLoadBalancer核心源码。

public class FeignLoadBalancer extends    AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
  private final RibbonProperties ribbon;
  protected int connectTimeout;
  protected int readTimeout;
  protected IClientConfig clientConfig;
  protected ServerIntrospector serverIntrospector;
  public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,      ServerIntrospector serverIntrospector) {    super(lb, clientConfig);    this.setRetryHandler(RetryHandler.DEFAULT);    this.clientConfig = clientConfig;    this.ribbon = RibbonProperties.from(clientConfig);    RibbonProperties ribbon = this.ribbon;    this.connectTimeout = ribbon.getConnectTimeout();    this.readTimeout = ribbon.getReadTimeout();    this.serverIntrospector = serverIntrospector;  }
  @Override  public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)      throws IOException {    Request.Options options;    if (configOverride != null) {      RibbonProperties override = RibbonProperties.from(configOverride);      options = new Request.Options(override.connectTimeout(this.connectTimeout),          override.readTimeout(this.readTimeout));    }    else {      options = new Request.Options(this.connectTimeout, this.readTimeout);    }    Response response = request.client().execute(request.toRequest(), options);    return new RibbonResponse(request.getUri(), response);  } } 

FeignLoadBalancer继承自AbstractLoadBalancerAwareClient,AbstractLoadBalancerAwareClient又是啥玩意?看过我写的关于Ribbon核心组件已经运行原理的那篇文章小伙伴肯定知道,AbstractLoadBalancerAwareClient类主要作用是通过ILoadBalancer组件获取一个Server,然后基于这个Server重构了URI,也就是将你的请求路径http://服务名/api/sayHello转换成类似http://192.168.1.101:8088/api/sayHello这种路径,也就是将原服务名替换成服务所在的某一台机器ip和端口,替换之后就交由子类实现的exceut方法来发送http请求。

所以我们知道调用executeWithLoadBalancer之后,就会重构请求路径,将服务名替换成某个具体的服务器所在的ip和端口,之后交给子类execute来处理,对于这里来说,也就是FeignLoadBalancer的execute方法,因为FeignLoadBalancer继承AbstractLoadBalancerAwareClient。

直接定位到execute方法最核心的一行代码

Response response = request.client().execute(request.toRequest(), options);

request.client()就会拿到构建LoadBalancerFeignClient传入的那个Client的实现,我提到过,这个Client的实现是具体发送请求的实现,默认的就是Client.Default类(不是默认就有可能是基于HttpClient或者是OkHttp的实现)。所以这行代码就是基于这个Client就成功的发送了Http请求,拿到响应,然后将这个Response 封装成一个RibbonResponse返回,最后就返回给MethodHandler,然后解析响应,封装成方法的返回值返回给调用者。

好了,其实到这里就完全知道Feign是如何整合Ribbon的,LoadBalancerFeignClient其实是OpenFeign适配Ribbon的入口,FeignLoadBalancer才是真正实现选择负载均衡,发送http请求的组件,因为他继承了AbstractLoadBalancerAwareClient。

为了大家能够清楚的知道整个动态代理的调用过程,我在Ribbon的那张图的基础上,加上Feign的调用链路。

图片

通过这张图,我们可以清楚地看出OpenFeign、Ribbon以及注册中心之间的协同关系。

四、总结

到这里,我通过三篇文章,算上Nacos那两篇,总共五篇文章完整的讲述了在微服务架构中,OpenFeign、Ribbon、Nacos(当然其它注册中心也可以)这三个组件协同工作的核心源码和流程。这里我再用简洁的话来总结一下他们的协同工作原理,OpenFeign在进行rpc调用的时候,由于不知道服务具体在哪台机器上,所以需要Ribbon这个负载均衡组件从服务所在的机器列表中选择一个,Ribbon中服务所在的机器列表是从注册中心拉取的,Ribbon提供了一个ServerList接口,注册中心实现之后,Ribbon就可以获取到服务所在的机器列表,这就是这三个组件最基本的原理。希望通过这五篇文章,小伙伴们可以对微服务架构的最基本的原理有一定的了解,同时也对OpenFeign、Ribbon、Nacos源码有一定的认识。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

并查集带权并查集

定义 : 并查集 : 一种数据结构&#xff0c;用于处理一些不相交集合的合并与查询问题&#xff1b; 例题 : 如 : 有n种元素&#xff0c;分属于不同的n个集合&#xff1b; 有两种操作 : 1.给出两个元素的亲属关系&#xff0c;合并两个集合(x与y是亲戚&#xff0c;亲戚的亲戚…

基于Java SSM框架+Vue实现实现大学生企业推荐网站项目【项目源码+论文说明】

基于java的SSM框架Vue实现大学生企业推荐网站演示 摘要 大学生企业推荐系统采用B/S结构、java开发语言、以及Mysql数据库等技术。系统主要分为管理员和学生、企业三部分&#xff0c;管理员主要功能包括&#xff1a;首页、个人中心、学生管理、企业管理、招聘信息管理、个人简历…

【探索Linux】—— 强大的命令行工具 P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )

阅读导航 引言一、信号捕捉1. 内核实现信号捕捉过程2. sigaction() 函数&#xff08;1&#xff09;函数原型&#xff08;2&#xff09;参数说明&#xff08;3&#xff09;返回值&#xff08;4&#xff09;函数使用 二、可重入函数与不可重入函数1. 可重入函数条件2. 不可重入函…

MQTT发布_订阅架构(Pub_Sub)

MQTT发布/订阅架构&#xff08;Pub/Sub&#xff09; 本文中&#xff0c;将深入研究Pub/Sub架构&#xff0c;在软件架构中一个消息模式&#xff0c;它支持不同组件或系统之间以解耦的方式进行通信。 在前一片文章[MQTT简介]http://t.csdnimg.cn/6lNeZ中&#xff0c;对MQTT有一个…

Gitee-PicGo-Typora

Gitee-PicGo-Typora 问题引出 问题1&#xff1a;根据相关法律法规和政策&#xff0c;您的部分文件因存在敏感信息而无法显示 就在昨晚&#xff0c; 我在记笔记的时候&#xff0c;发现之前配置的七牛云图床出了问题&#xff1a; 1、根据相关法律法规和政策&#xff0c;您的部…

RabbitMQ消息模型之Routing-Topic

Routing Topic Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符&#xff01;这种模型Routingkey一般都是由一个或多个单词组成&#xff0c;多个单词之间以”…

Mysql安全之基础合规

一、背景 某次某平台进行安全性符合型评估时&#xff0c;列出了数据库相关安全选项&#xff0c;本文特对此记录&#xff0c;以供备忘参考。 二、安全配置 2.1、数据库系统登录时的用户进行身份标识和鉴别&#xff1b; 1&#xff09;对登录Mysql系统用户的密码复杂度是否有要…

Stream API练习题

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 考虑到Stream API在实际…

2023-11-30 事业-代号s-资质-香港公司-带注册服务商-盛森国际-分析

摘要: 基于合法避税及其他因素&#xff0c;考虑在香港注册公司. 选择的服务商为盛森国际&#xff0c;对该公司做彻底的背调和服务分析, 以规避潜在的风险. 并分析该公司在香港代注册的服务商中的行业竞争力, 以保证其服务的质量及成本的控制. 盛森国际官方资料: 官网: 注册香港…

Nuxt.js:下一代Web开发框架的革命性力量

文章目录 一、Nuxt.js简介二、Nuxt.js的特点1. 集成Vue.js和Node.js2. 自动代码分割和优化3. 服务端渲染&#xff08;SSR&#xff09;4. 强大的路由管理5. 丰富的插件系统 三、Nuxt.js的优势1. 提高开发效率2. 降低维护成本3. 提高用户体验 四、Nuxt.js在实际应用中的案例1. 电…

YOLOv5独家原创改进:自研独家创新FT_Conv,卷积高效结合傅里叶分数阶变换

💡💡💡本文自研创新改进:卷积如何有效地和频域结合,引入分数阶傅里叶变换(FrFT)和分数阶Gabor变换(FrGT),最终创新到YOLOv5。 使用方法:1)直接替换原来的C2f;2)放在backbone SPPF后使用;等 推荐指数:五星 在道路缺陷检测任务中,原始map为0.8,FT_Conv为0.82 …

linux用户组_创建_删除_修改

2.2.2 用户组 每个用户都有一个用户组&#xff0c;系统可以对一个用户组中的所有用户进行集中管理。不同Linux系统对用户组的规定有所不同&#xff0c;如Linux下的用户属于与它同名的用户组&#xff0c;这个用户组在创建用户时同时创建。 组的类型&#xff1a; 基本组&#x…

基于STM32+定时器中断和定时器外部时钟(标准库函数讲解)

前言 本篇博客主要学习了解定时器的标准库函数&#xff0c;以及定时器中断进行LED灯的反转&#xff0c;还有定时器外部时钟获取脉冲计数功能。本篇博客大部分是自己收集和整理&#xff0c;如有侵权请联系我删除。 本篇博客主要是对通用定时器来讲解&#xff0c;功能适中比较常…

判断数组中每个元素是否为负数 numpy.signbit()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 判断数组中每个元素是否为负数 numpy.signbit() [太阳]选择题 请问以下代码中最后输出结果是&#xff1f; import numpy as np a np.array([-1, 0, 1]) print("【显示】a ",a) pr…

智能优化算法应用:基于帝国主义竞争算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于帝国主义竞争算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于帝国主义竞争算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.帝国主义竞争算法4.实验参数设定5.算…

运维知识点-PostgreSql

PostgreSql 下载安装地址安装组件数据目录设置superuser密码 端口安装语言安装完成&#xff0c;是否安装Stack Builder 下载 https://www.postgresql.org/download/windows/ https://get.enterprisedb.com/postgresql/postgresql-13.7-1-windows-x64.exe 我下载的 13.7 安装…

【MySQL数据库】SQL查询语句总结

目录 一、查询数据 1.1 基本查询语句 1.2 表单查询 1.3 WHERE子句 1.3.1 IN关键字查询 1.3.2 Between查询范围 1.3.3 Like匹配查询 1.3.4 AND多条件查询&#xff08;等同于&&&#xff09; 1.3.5 OR多条件查询&#xff08;等同于||&#xff09; 1.3.6 LIMIT子句 1.3.7 对…

基于Java SSM框架实现母婴儿用品网站系统项目【项目源码+论文说明】

基于java的SSM框架实现母婴儿用品网站系统演示 摘要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 母婴用品网站&#xff0c;主要的模块包括管理员&#xff1b;主页、个人中心、用户管理、商品分…

架构图是什么,该怎么制作?

架构图是指可视化展示软件、系统、应用程序、网络等各种体系结构的一类图表或图形&#xff0c;它能够形象地展示体系结构中各个组成部分和它们之间的关系。 架构图的类型 架构图的种类比较多&#xff0c;逐一列举不太合适&#xff0c;这里只列举一些常见的架构图类型&#…

Oracle E-Business Suite软件 任意文件上传漏洞(CVE-2022-21587)

0x01 产品简介 Oracle E-Business Suite&#xff08;电子商务套件&#xff09;是美国甲骨文&#xff08;Oracle&#xff09;公司的一套全面集成式的全球业务管理软件。该软件提供了客户关系管理、服务管理、财务管理等功能。 0x02 漏洞概述 Oracle E-Business Suite 的 Oracle…