zoukankan      html  css  js  c++  java
  • Fegin源码解析

    OpenFegin源码解析图

    ① BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
      Spring Cloud FengnClient实际上是利用Spring的代理工厂来生成代理类,所以在这里地方才会把所有的FeignClient的BeanDefinition设置为FeignClientFactoryBean类型,而
      FeignClientFactoryBean继承自FactoryBean,它是一个工厂Bean。在Spring中,FactoryBean是一个工厂Bean,用来创建代理Bean。工厂 Bean 是一种特殊的 Bean, 对于 Bean 的消费者来说, 
      他逻辑上是感知不到这个 Bean 是普通的 Bean 还是工厂 Bean, 只是按照正常的获取 Bean 方式去调用, 但工厂bean 最后返回的实例不是工厂Bean 本身, 而是执行工厂 Bean 的 getObject 逻辑返回的示例。
    ② ## ReflectiveFeign.newInstance  
       Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
       Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
       List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    

    FeignClientsRegistrar

    • registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册
    • registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition
      将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中.
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
          //注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
          //在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
          //所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
          registerDefaultConfiguration(metadata, registry);
          registerFeignClients(metadata, registry);
    }
    

    FeignClientFactoryBean.getObject

    getObject调用的是getTarget方法,它从applicationContext取出FeignContext,FeignContext继承了NamedContextFactory,它是用来来统一维护feign中各个feign客户端相互隔离的上下文。

    // FeignContext注册到容器是在FeignAutoConfiguration上完成的
    @Autowired(required = false)
    private List<FeignClientSpecification> configurations = new ArrayList<>();
    @Bean
    public FeignContext feignContext() {
          FeignContext context = new FeignContext();
          context.setConfigurations(this.configurations);
          return context;
    }
    //在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。
    

    Target.target()

    //配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。实际上他们最终调用的是Target.target()方法。
    
    @Override
    public Object getObject() throws Exception {
    	return getTarget();
    }	    
    
    <T> T getTarget() {
    	FeignContext context = this.applicationContext.getBean(FeignContext.class);
    	Feign.Builder builder = feign(context);
          //如果url为空,则走负载均衡,生成有负载均衡功能的代理类
    	if (!StringUtils.hasText(this.url)) {
    		if (!this.name.startsWith("http")) {
    			this.url = "http://" + this.name;
    		}
    		else {
    			this.url = this.name;
    		}
    		this.url += cleanPath();
    		return (T) loadBalance(builder, context,
    				new HardCodedTarget<>(this.type, this.name, this.url));
    	}
            //如果指定了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 load balancing because we have a url,
    			// but ribbon is on the classpath, so unwrap
    			client = ((LoadBalancerFeignClient) client).getDelegate();
    		}
    		if (client instanceof FeignBlockingLoadBalancerClient) {
    			// not load balancing because we have a url,
    			// but Spring Cloud LoadBalancer is on the classpath, so unwrap
    			client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
    		}
    		builder.client(client);
    	}
            //生成默认代理类
    	Targeter targeter = get(context, Targeter.class);
    	return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
    }
    

    loadBalance

    生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端Client client = (Client)this.getOptional(context, Client.class);
    从上下文中获取一个Client,默认是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的

    @Import({ HttpClientFeignLoadBalancedConfiguration.class,
          OkHttpFeignLoadBalancedConfiguration.class,
          DefaultFeignLoadBalancedConfiguration.class })
    
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
    	Client client = getOptional(context, Client.class);
    	if (client != null) {
    		builder.client(client);
    		Targeter targeter = get(context, Targeter.class);
    		return targeter.target(this, builder, context, target);
    	}
    	throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
    }
    

    targetToHandlersByName.apply(target)

    根据Contract协议规则,解析接口类的注解信息,解析成内部表现:
    targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个<method,MethodHandler>的map,放入InvocationHandler的实现FeignInvocationHandler中。

    public Map<String, MethodHandler> apply(Target target) {
          List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
          Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();
          for (MethodMetadata md : metadata) {
            BuildTemplateByResolvingArgs buildTemplate;
            if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
              buildTemplate =
                  new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
            } else if (md.bodyIndex() != null) {
              buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
            } else {
              buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
            }
            if (md.isIgnored()) {
              result.put(md.configKey(), args -> {
                throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
              });
            } else {
              result.put(md.configKey(),
                  factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
            }
          }
          return result;
        }
      }
    

    SpringMvcContract

    当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务。

    OpenFeign调用过程

    OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。
    那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,它是一个动态代理的实现。

    而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。
    这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下。

    @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;
          }
        }
      }
    

    动态生成Request

    executeAndDecode

    我们已经将restTemplate拼装完成,上面的代码中有一个 executeAndDecode() 方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。

    Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
        //转化为http请求报文
        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);
          // 返回结果
          response = response.toBuilder()
              .request(request)
              .requestTemplate(template)
              .build();
        } 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());
          }
        }
      }
    

    Client.execute

    默认采用JDK的 HttpURLConnection 发起远程调用

     @Override
        public Response execute(Request request, Options options) throws IOException {
          HttpURLConnection connection = convertAndSend(request, options);
          return convertResponse(connection, request);
        }
    
        Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
          int status = connection.getResponseCode();
          String reason = connection.getResponseMessage();
    
          if (status < 0) {
            throw new IOException(format("Invalid status(%s) executing %s %s", status,
                connection.getRequestMethod(), connection.getURL()));
          }
    
          Map<String, Collection<String>> headers = new LinkedHashMap<>();
          for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
            // response message
            if (field.getKey() != null) {
              headers.put(field.getKey(), field.getValue());
            }
          }
    
          Integer length = connection.getContentLength();
          if (length == -1) {
            length = null;
          }
          InputStream stream;
          if (status >= 400) {
            stream = connection.getErrorStream();
          } else {
            stream = connection.getInputStream();
          }
          return Response.builder()
              .status(status)
              .reason(reason)
              .headers(headers)
              .request(request)
              .body(stream, length)
              .build();
        }
    
  • 相关阅读:
    常用正则
    css换行与不换
    数组Array.prototype方法的使用
    你真的知道css三种存在样式(外联样式、内部样式、内联样式)的区别吗?
    js中批量处理样式——cssText的使用
    CSS的定位属性实现text-shadow属性的文本下产生阴影效果
    深入js的面向对象学习篇(继承篇)——温故知新(三)
    深入js的面向对象学习篇(封装是一门技术和艺术)——温故知新(二)
    深入js的面向对象学习篇——温故知新(一)
    【转链接】Handlebars模板引擎以及浅谈模板引擎的实现原理
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14096306.html
Copyright © 2011-2022 走看看