1、前言
在上一篇文章中,我们谈了openfeign借助一系列自动配置类帮我们自动创建feign client对象的一个过程,在文章最后,我们也提到,openfeign帮我们创建的feign client对象其实是一个代理对象,本篇我们将继续探讨下feign client代理对象的生成过程,由于相关代码不算太难理解,本篇文字并不多,主要是以展示源码内容为准。
2、原理讲解
我们从下列代码开始看起,其中build方法返回的是一个ReflectiveFeign对象。
//Feign.class
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
ReflectiveFeign类的newInstance方法定义如下:
// 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方法。
//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方法的实现:
//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)的实现:
//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用到的那些注解。
//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。
//configkey,用来作为方法元数据对象的一个标识,是“类型#方法名(方法参数类型列表)”的形式
data.configKey(Feign.configKey(targetType, method));
我们看下Feign.configKey方法的内部实现:
//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等也算),不然就会报错,导致系统无法启动。
//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方法,我们来看下它的内部实现:
//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方法,我们来看下它的内部实现:
//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提供的一些默认的注解参数处理器对象。核心代码如下:
//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注解:
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方法,该方法后面有这么一段代码:
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder, errorDecoder));
其中factory.create方法返回的是一个SynchronousMethodHandler对象,它的核心方法如下:
//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对象:
//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实例对象的最后一步,创建代理对象:
//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的一个静态内部类,它的内部实现如下:
// 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接口信息,由于对这些类不够熟悉,所以我们初次看起来可能会觉得有些懵。希望通过本文的讲解,大家都能有所收获~
觉得有收获的朋友,可以点击关注我,这样方便接收我后续的文章,多谢支持~