Feign的使用及原理剖析

2023-10-27

feign使用及原理剖析


一、简介

Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了http调用流程。

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,返回给调用者。

二、http client依赖

Feign在默认情况下使用的是JDK原生的URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。

可以通过修改 client 依赖换用底层的 client,不同的 http client 对请求的支持可能有差异。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>

<!--使用Apache HttpClient-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>11.0</version>
</dependency>
#在配置文件中启用ApacheHttpClient
feign.httpclient.enabled=true

三、注解

1、@FeignClient

public @interface FeignClient {
	/**
	*具有可选协议前缀的服务的名称。无论是否提供url,都必须为所有客户端指定名称。
	*/
    @AliasFor("name")
    String value() default "";

    // 过时的
    @Deprecated
    String serviceId() default "";
	
	/**
	*  当存在多个FeignClient调用同一个服务时,需要填写,否则无法启动
	*/
    String contextId() default "";

    // 指定FeignClient的名称
    @AliasFor("value")
    String name() default "";
	
	//返回值:外部客户端的@Qualifier值
    String qualifier() default "";

    // 全路径地址或hostname,http或https可选
    String url() default "";
    
    // 当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException异常
    boolean decode404() default false;
    
    // Feign配置类,可以自定义Feign的LogLevel
    Class<?>[] configuration() default {};
    
    // 容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑
    Class<?> fallback() default void.class;
    
    // 工厂类,用于生成fallback类实例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
    Class<?> fallbackFactory() default void.class;
    
    // 定义当前FeignClient的统一前缀,类似于controller类上的requestMapping
    String path() default "";

	//是否将外部代理标记为主bean
    boolean primary() default true;
}

2、@EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	//等价于basePackages属性,更简洁的方式
    String[] value() default {};
    
    //指定多个包名进行扫描
    String[] basePackages() default {};
    
    //指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫描
    Class<?>[] basePackageClasses() default {};
    
    //为所有的Feign Client设置默认配置类
    Class<?>[] defaultConfiguration() default {};
    
    //指定用@FeignClient注释的类列表。如果该项配置不为空,则不会进行类路径扫描
    Class<?>[] clients() default {};
}

四、原理

  • 启动时,若有@EnableFeignClients注解,则程序会进行包扫描,扫描所有包下所有@FeignClient注解的类,并将这些类注入到spring的IOC容器中。
  • 当定义的@FeignClient中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。RequestTemplate中包含请求的所有信息,如请求参数,请求URL等。
  • RequestTemplate生成Request,然后将Request交给client处理,client默认是JDK的HTTPUrlConnection,也可以是OKhttp、Apache的HTTPClient等。
  • 最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用。

五、流程

流程图如下:feign原理图

从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

1、启用

启动配置上检查是否有@EnableFeignClients注解,如果有该注解,则开启包扫描,扫描被@FeignClient注解的接口。

扫描出该注解后, 通过beanDefinition注入到IOC容器中,方便后续被调用使用。

@EnableFeignClients 是关于注解扫描的配置,使用了@Import(FeignClientsRegistrar.class)。在spring context处理过程中,这个Import会在解析Configuration的时候当做提供了其他的bean definition的扩展,Spring通过调用其registerBeanDefinitions方法来获取其提供的bean definition。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    //在这个重载的方法里面做了两件事情:
    //1.将EnableFeignClients注解对应的配置属性注入
    //2.将FeignClient注解对应的属性注入
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    	//注入EnableFeignClients注解对应的配置属性
        registerDefaultConfiguration(metadata, registry);
        //注入FeignClient注解对应的属性
        registerFeignClients(metadata, registry);
    }

}

FeignClientsRegistrar里重写了spring里ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法。也就是在启动时,处理了EnableFeignClients注解后,registry里面会多出一些关于Feign的BeanDefinition。

BeanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是FeignClientFactoryBean类。

FeignClientFactoryBean作为一个实现了FactoryBean的工厂类,那么每次在Spring Context 创建实体类的时候会调用它的getObject()方法。

这里的getObject()其实就是将@FeinClient中设置value值进行组装起来。

public Object getObject() throws Exception {
    FeignContext context = applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        String url;
        if (!this.name.startsWith("http")) {
            url = "http://" + this.name;
        }
        else {
            url = this.name;
        }
        url += cleanPath();
        return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.url;
    }
    String url = this.url + cleanPath();
    Client client = getOptional(context, Client.class);
    if (client != null) {
        if (client instanceof LoadBalancerFeignClient) {
            // not lod balancing because we have a url,
            // but ribbon is on the classpath, so unwrap
            client = ((LoadBalancerFeignClient)client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return targeter.target(this, builder, context, new HardCodedTarget<>(
        this.type, this.name, url));
}

2、发起请求

ReflectiveFeign内部使用了jdk的动态代理为目标接口生成了一个动态代理类,这里会生成一个InvocationHandler统一的方法拦截器,同时为接口的每个方法生成一个SynchronousMethodHandler拦截器,并解析方法上的元数据,生成一个http请求模板RequestTemplate。

查看ReflectiveFeign类中newInstance方法是返回一个代理对象:

这个方法大概的逻辑是:

  1. 根据target,解析生成MethodHandler对象;
  2. 对MethodHandler对象进行分类整理,整理成两类:default 方法和 SynchronousMethodHandler 方法;
  3. 通过jdk动态代理生成代理对象,这里是最关键的地方;
  4. 将DefaultMethodHandler绑定到代理对象。
public class ReflectiveFeign extends Feign {

    @Override
    public <T> T newInstance(Target<T> target) {
    //为每个方法创建一个SynchronousMethodHandler对象,并放在 Map 里面
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        //如果是 default 方法,说明已经有实现了,用 DefaultHandler
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        //否则就用上面的 SynchronousMethodHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    /** 
    * 设置拦截器
    * 创建动态代理,factory 是 InvocationHandlerFactory.Default,创建出来的是 
    * ReflectiveFeign.FeignInvocationHanlder,也就是说后续对方法的调用都会进入到该对象的 inovke 方
    * 法
    */ 
    InvocationHandler handler = factory.create(target, methodToHandler);
    // 创建动态代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class<?>[] {target.type()}, handler);
    
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
    }

}

最终都是执行了SynchronousMethodHandler拦截器中的invoke方法:

final class SynchronousMethodHandler implements MethodHandler {

    @Override
    public Object invoke(Object[] argv) throws Throwable {
        // 根据输入参数,构造Http请求
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        // 克隆出一份重试器
        Retryer retryer = this.retryer.clone();
        // 尝试最大次数,如果中间有结果,直接返回
        while (true) {
          try {
            return executeAndDecode(template);
          } 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;
          }
        }
    }
}

invoke方法方法首先生成 RequestTemplate 对象,应用 encoder,decoder 以及 retry 等配置,下面有一个死循环调用:executeAndDecode,从名字上看就是执行调用逻辑并对返回结果解析。

Object executeAndDecode(RequestTemplate template) throws Throwable {
    //根据  RequestTemplate生成Request对象
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
        logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
        // 调用client对象的execute()方法执行http调用逻辑,
        //execute()内部可能设置request对象,也可能不设置,所以需要response.toBuilder().request(request).build();这一行代码
        response = client.execute(request, options);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
    } catch (IOException e) {
        if (logLevel != Logger.Level.NONE) {
            logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
        }
        // IOException的时候,包装成 RetryableException异常,上面的while循环 catch里捕捉的就是这个异常
        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);
            // ensure the request is set. TODO: remove in Feign 10
            response.toBuilder().request(request).build();
        }
        //如果元数据返回类型是 Response,直接返回回去即可,不需要decode()解码
        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();
        }
        //主要对2xx和404等进行解码,404需要特别的开关控制。其他情况,使用errorDecoder进行解码,以异常的方式返回
        if (response.status() >= 200 && response.status() < 300) {
            if (void.class == metadata.returnType()) {
                return null;
            } else {
                return decode(response);
            }
        } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
            return decode(response);
        } 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());
        }
    }
}

Feign真正发送HTTP请求是委托给feign.Client的execute方法来做的:

public interface Client {
    Response execute(Request request, Options options) throws IOException;
    class Default implements Client {
        @Override
        public Response execute(Request request, Options options) throws IOException {
            HttpURLConnection connection = convertAndSend(request, options);
            return convertResponse(connection, request);
        }
    }
}

注意:SynchronousMethodHandler并不是直接完成远程URL的请求,而是通过负载均衡机制,定位到合适的远程server服务器,然后再完成真正的远程URL请求。即:SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是LoadBalancerFeignClient客户端负载均衡类型。

3、性能分析

Feign框架比较小巧,在处理请求转换和消息解析的过程中,基本上没什么时间消耗。真正影响性能的,是处理Http请求的环节。可以从这个方面着手分析系统的性能提升点。

六、总结

1、调用接口为什么会直接发送请求?

原因就是Spring扫描了@FeignClient注解,并且根据配置的信息生成代理类,调用的接口实际上调用的是生成的代理类。

2、请求是如何被Feign接管的?

  • Feign通过扫描@EnableFeignClients注解中配置包路径,扫描@FeignClient注解并将注解配置的信息注入到Spring容器中,类型为FeignClientFactoryBean;
  • 然后通过FeignClientFactoryBean的getObject()方法得到不同动态代理的类并为每个方法创建一个SynchronousMethodHandler对象;
  • 为每一个方法创建一个动态代理对象, 动态代理的实现是 ReflectiveFeign.FeignInvocationHanlder,代理被调用的时候,会根据当前调用的方法,转到对应的 SynchronousMethodHandler;
  • 这样我们发出的请求就能够被已经配置好各种参数的Feign handler进行处理,从而被Feign托管。

七、简单入门


一、示例

1.引入依赖

<!-- spring-cloud-starter-openfeign 支持负载均衡、重试、断路器等 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>
<!--使用Apache HttpClient-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
	<version>11.0</version>
</dependency>

2.添加配置

#配置文件启用ApacheHttpClient
feign.httpclient.enabled=true

3.开启支持

@SpringBootApplication
@EnableFeignClients
public class FeignProjectApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignProjectApplication.class, args);
    }

}

4.编写接口

①单服务项目

@FeignClient(value = "demo", url = "http://localhost:8081/")
public interface UserCenter {

    /**
     * 获取用户信息
     * @param uid
     * @return
     */
    @PostMapping("/user/getUser/{uid}")
    User getUser(@PathVariable(value = "uid") Integer uid);
}

②微服务项目

@FeignClient(
        name="user-center",  // 微服务名称
        url="${feign.service.user:user-center}",  // 微服务的服务名,用来定位到要调用哪个服务,可在配置文件中填写
        contextId = "user-center",  // 当有多个FeignClient调用同一个微服务时需要填写,否则会无法启动
        path="/user", // 固定的一个path,用于拼接整个url
    	fallback = UserCenterImpl.class // 熔断类,当请求超时或其他原因时,会调用熔断类里的方法,防止调用方请求过久
)
public interface UserCenter {

    /**
     * 获取用户信息
     * @param uid
     * @return
     */
    @PostMapping("/getUser/{uid}")
    User getUser(@PathVariable(value = "uid") Integer uid);
}

③熔断类

@Service
public class UserCenterImpl implements UserCenter {
    @Override
    public User getUser(Integer uid) {
        // 直接返回null
        return null;
    }
}

5.配置自动添加token

@Configuration
public class FeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        requestTemplate.header("Authorization", request.getHeader("Authorization"));
//        requestTemplate.header("Authorization", "1kmhZwomS6LSQKXQKNjRibRORrCZnsnrTU9CcBGkQJ3DGL1soxIWegq/vF3UXdEm");
    }
}

6.编写controller接口

@CrossOrigin
@RequestMapping("/usercenter")
@RestController
public class UserController {

    @Autowired
    private UserCenter userCenter;

    @PostMapping("/getUser/{uid}")
    public User getUser(@PathVariable(value = "uid") Integer uid){
        return userCenter.getUser(uid);
    }
}

7.接口请求结果

{
    "data": {
        "uid": 1001,
        "name": "张三",
    },
    "statusText": "查找成功",
    "status": 200
}

二、一些其他配置

1.FormEncoder支持

@Configuration
public class FeignFormConfiguration {
    
    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    @Primary
    public Encoder feignFormEncoder() {
        return new FormEncoder(new SpringEncoder(this.messageConverters));
    }
}

2.拦截器: 自动添加header或者token等

@Configuration
public class FeignInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        requestTemplate.header("Authorization", request.getHeader("Authorization"));
        
//        requestTemplate.header("Authorization", "1kmhZwomS6LSQKXQKNjRibRORrCZnsnrTU9CcBGkQJ3DGL1soxIWegq/vF3UXdEm");
    }
}

3.ErrorCode: 可以自定义错误响应码的处理

1.引入依赖

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.2</version>
</dependency>

2.配置自定义异常

@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TyaleErrorException extends Exception{
    /**
     * example: "./api/{service-name}/{problem-id}"
     */
    private String type;

    /**
     * example: {title}
     */
    private String title;

    /**
     * example: https://api/docs/index.html#error-handling
     */
    private String documentation;

    /**
     * example: {code}
     */
    private String status;
}

3.配置工具类

public class GsonUtil {
    private static Gson filterNullGson;
    private static Gson nullableGson;
    static {
        nullableGson = new GsonBuilder()
                .enableComplexMapKeySerialization()
                .serializeNulls()
                .setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
                .create();
        filterNullGson = new GsonBuilder()
                .enableComplexMapKeySerialization()
                .setDateFormat("yyyy-MM-dd HH:mm:ss:SSS")
                .create();
    }

    protected GsonUtil() {
    }

    /**
     * 根据对象返回json   不过滤空值字段
     */
    public static String toJsonWtihNullField(Object obj){
        return nullableGson.toJson(obj);
    }

    /**
     * 根据对象返回json  过滤空值字段
     */
    public static String toJsonFilterNullField(Object obj){
        return filterNullGson.toJson(obj);
    }

    /**
     * 将json转化为对应的实体对象
     * new TypeToken<HashMap<String, Object>>(){}.getType()
     */
    public static <T>  T fromJson(String json, Type type){
        return nullableGson.fromJson(json, type);
    }

    /**
     * 将对象值赋值给目标对象
     * @param source 源对象
     * @param <T> 目标对象类型
     * @return 目标对象实例
     */
    public static <T> T convert(Object source, Class<T> clz){
        String json = GsonUtil.toJsonFilterNullField(source);
        return GsonUtil.fromJson(json, clz);
    }
}

4.自定义错误码

@Configuration
public class TyaleErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String s, Response response) {
        TyaleErrorException errorException = null;
        try {
            if (response.body() != null) {
                Charset utf8 = StandardCharsets.UTF_8;
                var body = Util.toString(response.body().asReader(utf8));
                errorException = GsonUtil.fromJson(body, TyaleErrorException.class);
            } else {
                errorException = new TyaleErrorException();
            }
        } catch (IOException ignored) {

        }
        return errorException;
    }
}

4.自定义错误码

@Configuration
public class TyaleErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String s, Response response) {
        TyaleErrorException errorException = null;
        try {
            if (response.body() != null) {
                Charset utf8 = StandardCharsets.UTF_8;
                var body = Util.toString(response.body().asReader(utf8));
                errorException = GsonUtil.fromJson(body, TyaleErrorException.class);
            } else {
                errorException = new TyaleErrorException();
            }
        } catch (IOException ignored) {

        }
        return errorException;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Feign的使用及原理剖析 的相关文章

随机推荐

  • 【漏洞复现】CVE-2021-45232 Apache-apisix-dashboard

    靶场搭建 修改版本号 启动环境 查看端口号为9090 用主机登录ip 9000 默认用户名 admin 默认密码 admin 点击 路由 进入路由页面 打开POC所在文件夹 cmd执行 刷新路由页面 查看路由配置 成功注入恶意 script
  • Oracle各种进程功能一览表

    在安装Oracle数据库的时候 我检查进程 发现了以下进程 功能如下 Ora pmon 是进程监视器 Process Monitor 的缩写 当取消当前的事务 或者释放进程占用的锁以及其它资源的时候 这个进程清空那些失败的进程 Ora vk
  • 谷歌Chrome小恐龙代码(自动跳,高跳,无敌,加速)

    目录 自动跳代码 无敌代码 高跳代码 可以改括号内参数 疾跑代码 可以改括号内参数 大多数浏览器都有自己的彩蛋 而今天我们分享的是谷歌Chrome 谷歌小恐龙游戏是一个浏览器自带的小游戏 断网联网状态都是可以玩的 那么如何在联网的状态下进行
  • Flask 增删改查

    基本操作 目录 基本操作 添加 查看数据 分页 详情 删除 修改 路由配置 创建模型类models from app extensions import db class Books db Model tablename tb books
  • Feign远程调用丢失请求头问题

    在业务中 需要使用A B两个模块 这些模块使用了SpringSession共享Session数据 在B模块中的业务需要用户登录后才能操作 当A调用B的业务时 在B模块中获取不到用户的Session信息 导致B模块判定该请求用户没有登录导致A
  • java基础错题总结

    1 解析 首先乘法的优先级高于加法 所以先进行y z 然后在这里 是连接符 因为头一个是字符串所以系统就认为是连接符 就成 10202 0 输出这个字符串 但如果第一个不是字符串类型的 就像 10 20 a 这个会输出 30 a 2 解析
  • gis中的加权求和工具在哪里_因果推理初探(5)——干预工具(上)

    本节将延续上一节学习的干预的有关概念 开始深入介绍几种干预的工具 后门调整 前门调整 逆概率加权等 本节将有大量公式来袭 请准备草稿纸或提前绕道 在上一节最后 我们推导出有关干预的重要公式 调整公式 它的形式如下 这个公式让我们可以通过观测
  • 为什么使用代理后不能上网了?

    在使用完代理服务器之后 有的用户可能会遇到这样的问题 明明网络正常 为什么我的浏览器不能打开网页了 今天就给大家说下具体解决方法 这里我们以IE浏览器为例 1 先打开浏览器 点击右上角的 工具 图标 然后点击下拉中的 Internet选项
  • elasticsearch中文分词器插件elasticsearch-analysis-ik远程自定义词典热更新

    IK简介 IK分词器基于词库进行分词 analysis ik内置了一些词典 主词典main dic 姓氏词典surname dic 量词词典quantifier dic 后缀词典suffix dic 介词词典preposition dic
  • vue项目创建

    默认3 默认2 自定义配置 js语法编辑器 ts 渐进式web应用程序 路由 状态管理器 css处理器 代码检查 单元测试 端对端测试 选择版本 路由是否选择历史模式 选择css预处理器 配置放在哪里 保存这个项目作为一个模版使用 npm
  • 【网络】Linux网络问题汇总(一)

    网卡设置了静态获取 仍然获取动态IP的解决方法 问题展示 网卡配置静态方式获取 仍然通过dhcp获取到了ip 且每次分配的ip都一样 root senlian cat etc sysconfig network scripts ifcfg
  • OAUTH之 钉钉第三方授权登录

    文章目录 OAUTH之钉钉第三方授权登录 前期用到的工具 获取access token 请求地址 请求方法 响应 扫码 使用账号密码 获取 临时 code 参数重要说明 直接访问 扫码登录 使用账号密码登录第三方网站 根据 sns 临时授权
  • 性能测试度量指标

    1 响应时间 响应时间指从用户或事务在客户端发起一个请求开始 到客户端接收到从服务器端返回的响应结束 这整个过程所消耗的时间 在性能测试实践中 为了使响应时间更具代表性 响应时间通常是指事务的平均响应时间ART 在实践中要注意 不同行业 不
  • node+koa2+mongodb搭建RESTful API风格后台

    RESTful API风格 在开发之前先回顾一下 RESTful API 是什么 RESTful 是一种 API 设计风格 并不是一种强制规范和标准 它的特点在于请求和响应都简洁清晰 可读性强 不管 API 属于哪种风格 只要能够满足需要
  • Unity之URP开启PostProcessing后使用RenderTexture渲染模型背景为不透明

    项目需要在UI界面显示角色模型 使用一个模型相机投射到RT上然后放在Raw Image上 现在这个模型相机需要开启后处理Post Processing 只针对模型添加了后处理效果 问题是开启后 Raw Image背景变了 把UI背景图遮住了
  • TensorFlow找不到cudart64_110.dll not found的解决方案

    问题描述 当我写了两句小程序准备开启我的TensorFlow之路时候 import tensorflow as tf hello tf constant hello tensorflow print Hello python sess tf
  • 安防监控视频云存储平台EasyNVR对接EasyNVS时,一直不上线该如何解决?

    视频安防监控平台EasyNVR可支持设备通过RTSP Onvif协议接入 并能对接入的视频流进行处理与多端分发 包括RTSP RTMP HTTP FLV WS FLV HLS WebRTC等多种格式 近期有用户在使用安防视频平台EasyNV
  • STLvector源码——实现框架、具体实现的详细分段剖析(重点是insert_aux在指定位置插入元素和在指定位置插入n个元素的源码)、vector实现的基本函数总结

    VS2010的源码真的让人放弃 还是安安稳稳看侯捷老师的SGI 源码 SGI vector 实现框架 include
  • Vue语言基础——ECMAScript 6.0

    ECMAScript 1ES6基础 1 1ES6简介 1 2let命令 1 3const命令 2 变量的结构赋值 2 1数组的解构赋值 2 2对象的解构赋值 2 3解构赋值的主要用途 3 箭头函数 3 1箭头函数的定义 3 2箭头函数与解构
  • Feign的使用及原理剖析

    feign使用及原理剖析 一 简介 Feign是一个http请求调用的轻量级框架 可以以Java接口注解的方式调用Http请求 Feign通过处理注解 将请求模板化 当实际调用的时候 传入参数 根据参数再应用到请求上 进而转化成真正的请求