openfeign的使用原理(二)

1、前言

在上一篇文章中,我们谈了openfeign借助一系列自动配置类帮我们自动创建feign client对象的一个过程,在文章最后,我们也提到,openfeign帮我们创建的feign client对象其实是一个代理对象,本篇我们将继续探讨下feign client代理对象的生成过程,由于相关代码不算太难理解,本篇文字并不多,主要是以展示源码内容为准。

2、原理讲解

我们从下列代码开始看起,其中build方法返回的是一个ReflectiveFeign对象。

java 复制代码
    //Feign.class
    public <T> T target(Target<T> target) {
      return build().newInstance(target);
    }

ReflectiveFeign类的newInstance方法定义如下:

java 复制代码
// ReflectiveFeign.class 
/**
   * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
   * to cache the result.
   */
  @SuppressWarnings("unchecked")
  @Override
  public <T> T newInstance(Target<T> target) {
    //重点,对接口方法标记了@RequestMapping(GetMapping、PostMapping等等)的方法进行提取转换
    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;
  }

2.1、ParseHandlersByName.apply方法

ParseHandlersByName是ReflectiveFeign的一个静态内部类,它是根据接口方法名来创建对应的转换方法处理器。在创建ReflectiveFeign对象时,ReflectiveFeign的构造函数会接收一个ParseHandlersByName对象。我们来看下它的apply方法。

java 复制代码
//ReflectiveFeign.ParseHandlersByName类    
public Map<String, MethodHandler> apply(Target target) {
        //contract指的是SpringMvcContract对象,此代码获取接口里面所有的表示http请求的方法的元数据
      List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
        //为每个方法生成相应的MethodHandler
      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 || md.alwaysEncodeBody()) {
          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;
    }

我们看下contract.parseAndValidateMetadata方法的实现:

java 复制代码
//Contract类    
@Override
    public List<MethodMetadata> parseAndValidateMetadata(Class<?> targetType) {
      checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s",
          targetType.getSimpleName());
       //这里限制了targetType类对象指代的类或接口最多只能继承一个接口
      checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s",
          targetType.getSimpleName());
      final Map<String, MethodMetadata> result = new LinkedHashMap<String, MethodMetadata>();
      for (final Method method : targetType.getMethods()) {
        if (method.getDeclaringClass() == Object.class ||
            (method.getModifiers() & Modifier.STATIC) != 0 ||
            Util.isDefault(method)) {
          continue;
        }
         /生成方法的元数据信息的关键点
        final MethodMetadata metadata = parseAndValidateMetadata(targetType, method);
        if (result.containsKey(metadata.configKey())) {
          MethodMetadata existingMetadata = result.get(metadata.configKey());
          Type existingReturnType = existingMetadata.returnType();
          Type overridingReturnType = metadata.returnType();
          Type resolvedType = Types.resolveReturnType(existingReturnType, overridingReturnType);
          if (resolvedType.equals(overridingReturnType)) {
            result.put(metadata.configKey(), metadata);
          }
          continue;
        }
        result.put(metadata.configKey(), metadata);
      }
      return new ArrayList<>(result.values());
    }

2.1.1、SpringMvcContract类的parseAndValidateMetadata方法

parseAndValidateMetadata方法完成了对Feign接口内每个方法的元数据信息的提取工作。我们进入SpringMvcContract类看一下parseAndValidateMetadata(targetType, method)的实现:

java 复制代码
	//SpringMvcContract类
	@Override
	public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        //记录下需要处理的接口方法
		processedMethods.put(Feign.configKey(targetType, method), method);
        //调用父类BaseContract中的parseAndValidateMetadata,提取方法的元数据信息
		return super.parseAndValidateMetadata(targetType, method);
	}

BaseContract类,是Contract的子类,也是Contract类的一个内部抽象类,它的parseAndValidateMetadata方法对接口方法进行了剖析,提取出了和Http接口信息相关的信息,其实也就是springmvc中开发controller用到的那些注解。

java 复制代码
//BaseContract类   
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
      final MethodMetadata data = new MethodMetadata();
      data.targetType(targetType);
      data.method(method);
      data.returnType(
          Types.resolve(targetType, targetType, method.getGenericReturnType()));
      //configkey,用来作为方法元数据对象的一个标识,是"类型#方法名(方法参数类型列表)"的形式
      data.configKey(Feign.configKey(targetType, method));
      if (AlwaysEncodeBodyContract.class.isAssignableFrom(this.getClass())) {
        data.alwaysEncodeBody(true);
      }
      //对targetType继承的接口进行处理,前提是只继承了一个接口
      if (targetType.getInterfaces().length == 1) {
        processAnnotationOnClass(data, targetType.getInterfaces()[0]);
      }
     //处理当前接口上面的注解,也就是targetType类对象所属的接口
      processAnnotationOnClass(data, targetType);

     //对方法上的注解进行遍历处理
      for (final Annotation methodAnnotation : method.getAnnotations()) {
        processAnnotationOnMethod(data, methodAnnotation, method);
      }
      if (data.isIgnored()) {
        return data;
      }
      checkState(data.template().method() != null,
          "Method %s not annotated with HTTP method type (ex. GET, POST)%s",
          data.configKey(), data.warnings());
      final Class<?>[] parameterTypes = method.getParameterTypes();
      final Type[] genericParameterTypes = method.getGenericParameterTypes();
	 //处理方法参数上面的注解
      final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
      final int count = parameterAnnotations.length;
      for (int i = 0; i < count; i++) {
        boolean isHttpAnnotation = false;
        if (parameterAnnotations[i] != null) {
          isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
        }

        if (isHttpAnnotation) {
          data.ignoreParamater(i);
        }

        if (parameterTypes[i] == URI.class) {
          data.urlIndex(i);
        } else if (!isHttpAnnotation && parameterTypes[i] != Request.Options.class) {
          if (data.isAlreadyProcessed(i)) {
            checkState(data.formParams().isEmpty() || data.bodyIndex() == null,
                "Body parameters cannot be used with form parameters.%s", data.warnings());
          } else if (!data.alwaysEncodeBody()) {
            checkState(data.formParams().isEmpty(),
                "Body parameters cannot be used with form parameters.%s", data.warnings());
            checkState(data.bodyIndex() == null,
                "Method has too many Body parameters: %s%s", method, data.warnings());
            data.bodyIndex(i);
            data.bodyType(
                Types.resolve(targetType, targetType, genericParameterTypes[i]));
          }
        }
      }

      if (data.headerMapIndex() != null) {
        checkMapString("HeaderMap", parameterTypes[data.headerMapIndex()],
            genericParameterTypes[data.headerMapIndex()]);
      }

      if (data.queryMapIndex() != null) {
        if (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {
          checkMapKeys("QueryMap", genericParameterTypes[data.queryMapIndex()]);
        }
      }

      return data;
    }
2.1.1.1、MethodMetaData中的configkey

每个接口方法都会有一个configKey属性,方便后续根据它来获取到MethodMetaData。

java 复制代码
  //configkey,用来作为方法元数据对象的一个标识,是"类型#方法名(方法参数类型列表)"的形式
      data.configKey(Feign.configKey(targetType, method));

我们看下Feign.configKey方法的内部实现:

java 复制代码
//Feign类
public static String configKey(Class targetType, Method method) {
  StringBuilder builder = new StringBuilder();
  builder.append(targetType.getSimpleName());
  builder.append('#').append(method.getName()).append('(');
  for (Type param : method.getGenericParameterTypes()) {
    param = Types.resolve(targetType, targetType, param);
    builder.append(Types.getRawType(param).getSimpleName()).append(',');
  }
  if (method.getParameterTypes().length > 0) {
    builder.deleteCharAt(builder.length() - 1);
  }
  return builder.append(')').toString();
}

由上面代码可知,每个方法对应的configKey的值就是类似"类型#方法名(方法参数类型列表)"的形式,拿我们写的OrderClient接口中的findById方法来说,它的configKey的值就是"OrderClient#findById(Long)"。

2.1.1.2、processAnnotationOnClass方法

processAnnotationOnClass方法的主要作用只有一个,就是不允许@FeignClient所在的接口(包括父接口)上面存在@RequestMapping注解(GetMapping、PostMapping等也算),不然就会报错,导致系统无法启动。

java 复制代码
//SpringMvcContract类
@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
		if (classAnnotation != null) {
			LOG.error("Cannot process class: " + clz.getName()
					+ ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");
			throw new IllegalArgumentException("@RequestMapping annotation not allowed on @FeignClient interfaces");
		}
		CollectionFormat collectionFormat = findMergedAnnotation(clz, CollectionFormat.class);
		if (collectionFormat != null) {
			data.template().collectionFormat(collectionFormat.value());
		}
	}
2.1.1.3、processAnnotationOnMethod方法

处理完接口上面的注解之后,接下来就要遍历处理接口内部方法上面所有的注解,完成这一功能的就是processAnnotationOnMethod方法,我们来看下它的内部实现:

java 复制代码
//SpringMvcContract类
	@Override
	protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
		if (CollectionFormat.class.isInstance(methodAnnotation)) {
			CollectionFormat collectionFormat = findMergedAnnotation(method, CollectionFormat.class);
			data.template().collectionFormat(collectionFormat.value());
		}

		if (!RequestMapping.class.isInstance(methodAnnotation)
				&& !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
			return;
		}

		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		// HTTP Method
		RequestMethod[] methods = methodMapping.method();
		if (methods.length == 0) {
			methods = new RequestMethod[] { RequestMethod.GET };
		}
		checkOne(method, methods, "method");
		data.template().method(Request.HttpMethod.valueOf(methods[0].name()));

		// path
		checkAtMostOne(method, methodMapping.value(), "value");
		if (methodMapping.value().length > 0) {
			String pathValue = emptyToNull(methodMapping.value()[0]);
			if (pathValue != null) {
				pathValue = resolve(pathValue);
				// Append path from @RequestMapping if value is present on method
				if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) {
					pathValue = "/" + pathValue;
				}
				data.template().uri(pathValue, true);
				if (data.template().decodeSlash() != decodeSlash) {
					data.template().decodeSlash(decodeSlash);
				}
			}
		}

		// produces
		parseProduces(data, method, methodMapping);

		// consumes
		parseConsumes(data, method, methodMapping);

		// headers
		parseHeaders(data, method, methodMapping);

		data.indexToExpander(new LinkedHashMap<>());
	}

上面的方法很简单,主要就是处理接口方法上面的RequestMapping注解,包括提取path、header等信息。如果当前注解不是RequestMapping的实例,则会跳过当前注解,转而处理方法上面的下一个注解。

2.1.1.4、processAnnotationsOnParameter方法

处理完方法上面的所有注解之后,就是处理该方法的参数上面的注解,完成这一功能的是processAnnotationsOnParameter方法,我们来看下它的内部实现:

java 复制代码
//SpringMvcContract类
@Override
	protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
		boolean isHttpAnnotation = false;

		AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data,
				paramIndex);
		Method method = processedMethods.get(data.configKey());
		for (Annotation parameterAnnotation : annotations) {
            //根据当前的参数注解获取对应的注解参数处理器对象
			AnnotatedParameterProcessor processor = annotatedArgumentProcessors
					.get(parameterAnnotation.annotationType());
			if (processor != null) {
				Annotation processParameterAnnotation;
				// synthesize, handling @AliasFor, while falling back to parameter name on
				// missing String #value():
				processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation,
						method, paramIndex);
				isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);
			}
		}

		if (!isMultipartFormData(data) && isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {
			TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);
			if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {
				Param.Expander expander = convertingExpanderFactory.getExpander(typeDescriptor);
				if (expander != null) {
					data.indexToExpander().put(paramIndex, expander);
				}
			}
		}
		return isHttpAnnotation;
	}

上面方法对方法参数注解处理的过程中用到了annotatedArgumentProcessors对象,它是map类型的,是在创建

SpringMvcContract对象(由FeignClientsConfiguration配置类创建)的时候生成的,map中存储的是openfeign提供的一些默认的注解参数处理器对象。核心代码如下:

java 复制代码
//SpringMvcContract类
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
			ConversionService conversionService, boolean decodeSlash) {
		Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
		Assert.notNull(conversionService, "ConversionService can not be null.");
		//获取默认的注解参数处理器
		List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
		processors.addAll(annotatedParameterProcessors);
        //将list格式的注解参数处理器转换为map形式
		annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
		this.conversionService = conversionService;
		convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
		this.decodeSlash = decodeSlash;
	}

private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {

   List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();

   annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());
   annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
   annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
   annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
   annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
   annotatedArgumentResolvers.add(new RequestPartParameterProcessor());
   annotatedArgumentResolvers.add(new CookieValueParameterProcessor());

   return annotatedArgumentResolvers;
}

private Map<Class<? extends Annotation>, AnnotatedParameterProcessor> toAnnotatedArgumentProcessorMap(
			List<AnnotatedParameterProcessor> processors) {
		Map<Class<? extends Annotation>, AnnotatedParameterProcessor> result = new HashMap<>();
		for (AnnotatedParameterProcessor processor : processors) {
			result.put(processor.getAnnotationType(), processor);
		}
		return result;
	}

每个处理器都声明了自己能处理的注解类型,比如PathVariableParameterProcessor只能处理@PathVariable注解:

java 复制代码
public class PathVariableParameterProcessor implements AnnotatedParameterProcessor {
    private static final Class<PathVariable> ANNOTATION = PathVariable.class;

    public PathVariableParameterProcessor() {
    }
    public Class<? extends Annotation> getAnnotationType() {
        return ANNOTATION;
    }
    。。。
}

2.2.1、为每个方法生成对应的MethodHandler

我们继续看apply方法,该方法后面有这么一段代码:

less 复制代码
result.put(md.configKey(),
              factory.create(target, md, buildTemplate, options, decoder, errorDecoder));

其中factory.create方法返回的是一个SynchronousMethodHandler对象,它的核心方法如下:

java 复制代码
//SynchronousMethodHandler类  
@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 {
          //执行http接口调用并对返回结果进行解码
        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;
      }
    }
  }

我们调用Feign接口,最终都会交由SynchronousMethodHandler的invoke来执行。

2.2、生成Method->MethodHandler的map对象

2.1部分我们得到了以configKey为key,MethodHandler为value的map对象,我们通过反射,可以直接拿到Method对象,因此为方便查找,openfeign又生成了一个Method作为key,MethodHandler作为value的map对象:

java 复制代码
//ReflectiveFeign类
 @Override
  public <T> T newInstance(Target<T> target) {
    ...
    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)));
      }
    }
    ...
  }

2.3、为当前Feign接口生成代理对象

现在走到了创建Feign实例对象的最后一步,创建代理对象:

java 复制代码
//ReflectiveFeign类
 @Override
  public <T> T newInstance(Target<T> target) {
    ...
    //ReflectiveFeign.FeignInvocationHandler类型的对象
    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;
  }

factory.create方法返回的是ReflectiveFeign.FeignInvocationHandler类型的对象,FeignInvocationHandler实现了InvocationHandler接口,是ReflectiveFeign的一个静态内部类,它的内部实现如下:

java 复制代码
// ReflectiveFeign类
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);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof FeignInvocationHandler) {
        FeignInvocationHandler other = (FeignInvocationHandler) obj;
        return target.equals(other.target);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return target.hashCode();
    }

    @Override
    public String toString() {
      return target.toString();
    }
  }

Proxy.newProxyInstance方法通过jdk动态代理来创建Feign接口的代理对象,当我们调用Feign实例对象的某个方法时,都会交由上面的FeignInvocationHandler对象进行处理,然后选择相应的MethodHandler,发起http接口调用。

3、小结

本节我们主要看了openfeign根据Feign接口创建Feign代理对象的过程,其实内部的逻辑还是比较简单的,无非就是解析方法上面的RequestMapping注解,提取请求接口的路径信息,还有解析接口方法的参数上的注解,只不过openfeign自己定义了一些类来处理常用的http接口信息,由于对这些类不够熟悉,所以我们初次看起来可能会觉得有些懵。希望通过本文的讲解,大家都能有所收获~

觉得有收获的朋友,可以点击关注我,这样方便接收我后续的文章,多谢支持~

相关推荐
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
Stringzhua1 小时前
【SpringCloud】Kafka消息中间件
spring·spring cloud·kafka
小码编匠2 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
AskHarries2 小时前
Java字节码增强库ByteBuddy
java·后端
佳佳_2 小时前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
许野平4 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
BiteCode_咬一口代码5 小时前
信息泄露!默认密码的危害,记一次网络安全研究
后端
齐 飞5 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb