SpringCloud OpenFeign(二)

SpringCloud OpenFeign(二),第1张

上接:

SpringCloud OpenFeign_钱多多_qdd的博客-CSDN博客

前言

源码图:

一、Bean的动态装载

在spring系列里bean的动态装载是很常见的,感兴趣可以看看我的这篇文章:

Bean的动态装载_钱多多_qdd的博客-CSDN博客_动态加载bean

二、@FeignClient解析

从下面这个类开始切入,这个注解开启了FeignClient的解析过程。

@EnableFeignClients(basePackages = "com.gupaoedu.example.clients")
2.1 FeignClientsRegistrar

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

 这里面需要重点分析的就是 registerFeignClients 方法,这个方法主要是扫描类路径下所有的
@FeignClient注解,然后进行动态Bean的注入。它最终会调用 registerFeignClient 方法。

由上图我们可以看到此方法大概有5个步骤:

  1. 获取@EnableFeignClient的相关属性;
  2. 获取服务端类所在的路径basePackages(@EnableFeignClient("xx.xx.xx")配置的)。后续会遍历这些路径,并找到@FeignClient注解的类,将相关的类注册;
  3. 扫描basePackage下的所有类;
  4. 获取@FeignClient注解的相关属性;
  5. 注册;

下面我们来着重看第5步注册:

private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。

我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过
genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。
我们可以发现,FeignClient被动态注册成了一个FactoryBean,之前听过tom老师的源码的同学应该知道什么是FactoryBean吧。

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 逻辑返回的示例。

public static BeanDefinitionBuilder genericBeanDefinition(Class beanClass) {
    BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
    builder.beanDefinition.setBeanClass(beanClass);
    return builder;
}

         简单来说,FeignClient标注的这个接口,会通过FeignClientFactoryBean.getObject()这个方法获得一个代理对象。

2.2 FeignClientFactoryBean.getObject 

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

FeignContext注册到容器是在FeignAutoConfiguration上完成的:

在初始化FeignContext时,会把configurations在容器中放入FeignContext中。configurations的来源就是在前面registerFeignClients方法中将@FeignClient的配置configuration。

  1. 接着,构建feign.builder,在构建时会向FeignContext获取配置的Encoder,Decoder等各种信息。
  2. FeignContext在上篇中已经提到会为每个Feign客户端分配了一个容器,它们的父容器就是spring容器配置完Feign.Builder之后,再判断是否需要LoadBalance,如果需要,则通过LoadBalance的方法来设置。
  3. 实际上他们最终调用的是Target.target()方法。 
2.3 loadBalance

生成具备负载均衡能力的feign客户端,为feign客户端构建起绑定负载均衡客户端Client client = (Client)this.getOptional(context, Client.class);从上下文中获取一个Client,默认是LoadBalancerFeignClient。

——绿色②代码。

它是在FeignRibbonClientAutoConfiguration这个自动装配类中,通过Import实现的:

 绿色①代码:

protected  T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget 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?");
	}
2.4 DefaultTarget.target

 

2.5 ReflectiveFeign.newInstance

        这个方法是用来创建一个动态代理的方法,在生成动态代理之前,会根据Contract协议(协议解析规则,解析接口类的注解信息,解析成内部的MethodHandler的处理方式。

        从实现的代码中可以看到熟悉的Proxy.newProxyInstance方法产生代理类。而这里需要对每个定义的接口方法进行特定的处理实现,所以这里会出现一个MethodHandler的概念,就是对应方法级别的InvocationHandler。

三、接口定义的参数解析

根据FeignClient接口的描述解析出对应的请求数据。

3.1 targetToHandlersByName.apply(target)

还是在ReflectiveFeign.newInstance方法里:

 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

        targetToHandlersByName.apply(target);会解析接口方法上的注解,从而解析出方法粒度的特定的配置信息,然后生产一个SynchronousMethodHandler 然后需要维护一个的map,放入InvocationHandler的实现FeignInvocationHandler中。

3.2 SpringMvcContract

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

扩展:Feign详解4-Contract 源码_zhangyingchengqi的博客-CSDN博客

四、OpenFeign调用过程

        在前面的分析中,我们知道OpenFeign最终返回的是一个#ReflectiveFeign.FeignInvocationHandler的对象。

        那么当客户端发起请求时,会进入到FeignInvocationHandler.invoke方法中,这个大家都知道,它是一个动态代理的实现。

而接着,在invoke方法中,会调用 this.dispatch.get(method)).invoke(args) 。
this.dispatch.get(method) 会返回一个SynchronousMethodHandler,进行拦截处理。

这个方法会根据参数生成完成的RequestTemplate对象,这个对象是Http请求的模版,代码如下:

4.1 动态生成Request 4.2 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);
      // ensure the request is set. TODO: remove in Feign 12
      //获取返回结果
      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);


    if (decoder != null)
      return decoder.decode(response, metadata.returnType());

    CompletableFuture resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");

      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  } 
 4.3 Client.execute 

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

  

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/736473.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)