Feign源码解析

目录

前言

一、EnableFeignClients

二、FeignClientsRegistrar 

三、FeignClientFactoryBean 

四、FeignInvocationHandler 

五、动态URI参数

总结



前言

       Feign作为调用http请求的工具,在java后端编程中有广泛的应用,能够简化代码书写,但同时也隐藏了大量的处理细节,作为开发者,应当适当了解其源码,解决开发中的问题。


 

一、EnableFeignClients

             @EnableFeignClients注解的主要作用是引入FeignClientsRegistrar类。

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

二、FeignClientsRegistrar 

       FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,能够手动往容器中导入bean定义,其主要逻辑位于registerFeignClients方法中。

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		registerFeignClients(metadata, registry);
	}
}

         registerFeignClients方法中,对文件夹中的类进行扫描,对于有@FeignClient注解的类,作为FeignClient注册到容器中。

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));

					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

三、FeignClientFactoryBean 

       容器中注入的Bean定义为FeignClientFactoryBean,该类实现了FactoryBean接口,所以getBean方法最终调用的是getObject方法,实际就是getTargrt方法。

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}
}

           getTargrt方法中,为所有的FeignClient接口创建代理对象。

	<T> T getTarget() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
		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();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}

         创建代调用到ReflectiveFeign类中的newInstance方法,在该方法中对被代理接口的所有方法创建DefaultMethodHandler对象。

  public <T> T newInstance(Target<T> target) {
    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)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    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;
  }

四、FeignInvocationHandler 

static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

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

         代理对象执行方法的时候执行的是方法对应代理对象的invoke方法,基本就是构造了一个request请求,此处不再过多陈述。

  public Object invoke(Object[] argv) throws Throwable {
    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;
      }
    }
  }

五、动态URI参数

       在有些情况下,我们希望能够动态更改feign请求的地址,而不是固定写死,此时,我们只需要在方法中添加一个URI类型的参数,并且在使用时传入地址,就可以使用,接下来主要从源码角度分析其中的原因。

        ReflectiveFeign类中的apply方法会调用parseAndValidateMetadata方法,该方法会对方法的参数进行解析,保存到MethodMetadata中,并对参数类型为URI的参数进行了特殊处理。

 protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
      MethodMetadata data = new MethodMetadata();
      Class<?>[] parameterTypes = method.getParameterTypes();
      Type[] genericParameterTypes = method.getGenericParameterTypes();
      Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      int count = parameterAnnotations.length;
      for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }
        //解析设置uri
        if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation) {
          checkState(data.formParams().isEmpty(),
              "Body parameters cannot be used with form parameters.");
          checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
          data.bodyIndex(i);
          data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
        }
      }
      return data;
    }

       SynchronousMethodHandler中的create方法中,需要创建RequestTemplateRest,在create方法中,实际会调用到ReflectiveFeign的create方法,基于上文中的MethodMetadata,判断当前method参数中有没有URI类型的,如果有直接将参数的值设置为target。

 @Override
    public RequestTemplate create(Object[] argv) {
      RequestTemplate mutable = RequestTemplate.from(metadata.template());
      //判断有没有URI参数,有这个参数直接设为target,用于动态设置URI
      if (metadata.urlIndex() != null) {
        int urlIndex = metadata.urlIndex();
        checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
        mutable.target(String.valueOf(argv[urlIndex]));
      }

      return template;
    }

       target方法中会对uri参数的值进行校验,uri参数的值为空或者不以“http”开头的时候,会报参数异常。

public RequestTemplate target(String target) {
    /* target can be empty */
    if (Util.isBlank(target)) {
      return this;
    }
    /* verify that the target contains the scheme, host and port */
    if (!UriUtils.isAbsolute(target)) {
      throw new IllegalArgumentException("target values must be absolute.");
    }
    if (target.endsWith("/")) {
      target = target.substring(0, target.length() - 1);
    }
    return this;
  }

  public static boolean isAbsolute(String uri) {
    return uri != null && !uri.isEmpty() && uri.startsWith("http");
  }

         如果传入的url不以“http”开头,不使用RequestTemplate传入的uri,使用Target自带的url,如果传入的url满足要求,直接使用传入的url。

    @Override
    public Request apply(RequestTemplate input) {
      if (input.url().indexOf("http") != 0) {
        input.target(url());
      }
      return input.request();
    }



总结

       本文对Feign的动态代理创建和调用流程进行源码分析,重点分析了URI参数能够实现动态路由的原因,如有不当之处,欢迎大家交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值