OpenFeign学习(三):OpenFeign配置生成代理对象

目录结构

  • 说明
  • 正文
    • 工作流程
    • 创建代理
      • ParseHandlerByName.apply(target)
        • contract.parseAndValidateMetadata
        • BuildTemplateByResolvingArgs
        • synchronousMethodHandlerFactory.create
      • InvocationHandler
      • Proxy.newProxyInstance

说明

在之前的两篇博文《OkHttp的高级封装Feign学习(一): Feign注解的使用》和《OpenFeign学习(二):高级用法自定义配置组件HttpClient / SLF4J / RequestInterceptor等》中,我简单介绍了OpenFeign的使用方法。在本篇博文中,我将通过源码来学习记录OpenFeign的工作原理。

正文

工作流程

在阅读源码前,我们先通过流程图来了解OpenFeign的具体工作流程:
OpenFeign学习(三):OpenFeign配置生成代理对象_第1张图片
如图所示,我把OpenFeign的工作流程分为了10个阶段。接下来通过阅读源码来了解每个阶段是如何实现的。

创建代理

在之前的博文中我们已经学习了如何使用OpenFeign来进行服务的调用,在接口方法中使用注释,最后通过Feign的构造器来创建接口的代理。

HelloService service = Feign.builder()
			.logger(new Slf4jLogger())
			.logLevel(Logger.Level.FULL)
			.client(new OkHttpClient())
			.encoder(new JacksonEncoder())
			.decoder(new JacksonDecoder())
			.requestInterceptor(new HeadersInterceptor())
			.errorDecoder(new CustomErrorDecoder())
			.retryer(new MyRetryer(5))
			.exceptionPropagationPolicy(ExceptionPropagationPolicy.UNWRAP)
			.target(HelloService.class, "http://localhost:8080/");

以上示例代码说明了通过Feign的构造器,我们可以自定义配置其中的组件,如client,encoder,decoder等。通过该构造器的源码来了解下未配置组件时的默认值。

public Builder() {
        this.logLevel = Level.NONE;
        this.contract = new Default();
        this.client = new feign.Client.Default((SSLSocketFactory)null, (HostnameVerifier)null);
        this.retryer = new feign.Retryer.Default();
        this.logger = new NoOpLogger();
        this.encoder = new feign.codec.Encoder.Default();
        this.decoder = new feign.codec.Decoder.Default();
        this.queryMapEncoder = new FieldQueryMapEncoder();
        this.errorDecoder = new feign.codec.ErrorDecoder.Default();
        this.options = new Options();
        this.invocationHandlerFactory = new feign.InvocationHandlerFactory.Default();
        this.closeAfterDecode = true;
        this.propagationPolicy = ExceptionPropagationPolicy.NONE;
}

可以看到contract使用的默认的Default实现,client使用的是默认的HttpURLConnection,retryer使用的是默认的Default实现等等。OpenFeign在保证了极大的扩展性的同时也保证了它的简单性。

在Builder的构造函数中,创建了InvocationHandlerFactory对象,使用的默认实现类,该对象在创建代理时,来创建代理对象的InvocationHandler。

public interface InvocationHandlerFactory {
    InvocationHandler create(Target var1, Map var2);

    public static final class Default implements InvocationHandlerFactory {
        public Default() {
        }

        public InvocationHandler create(Target target, Map dispatch) {
            return new FeignInvocationHandler(target, dispatch);
        }
    }

    public interface MethodHandler {
        Object invoke(Object[] var1) throws Throwable;
    }
}

在配置完成后,最后调用了构造器的target方法,设置接口类对象及请求地址URL。在target方法中,我们可以发现它先创建Target对象,在调用了build方法创建ReflectiveFeign对象,最后以Target对象为参数调用Reflective的newInstance(target)方法进行初始化

public  T target(Class apiType, String url) {
    return this.target(new HardCodedTarget(apiType, url));
}

public  T target(Target target) {
    return this.build().newInstance(target);
}

public Feign build() {
    Factory synchronousMethodHandlerFactory = new Factory(this.client, this.retryer, this.requestInterceptors, this.logger, this.logLevel, this.decode404, this.closeAfterDecode, this.propagationPolicy);
    ParseHandlersByName handlersByName = new ParseHandlersByName(this.contract, this.options, this.encoder, this.decoder, this.queryMapEncoder, this.errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(handlersByName, this.invocationHandlerFactory, this.queryMapEncoder);
}

可以看到在build方法中,创建了synchronousMethodHandlerFactory工厂对象,从其名称可以知道该工厂创建的方法处理类是同步的。同时创建了ParseHandlersByName类对象,也可从名称知道该类是用来通过方法名称来解析获取Handler。

在ReflectiveFeign的newInstance(target)方法中,真正创建代理对象。通过其方法源码,来一步步解析代理对象如何创建。

 public  T newInstance(Target target) {
    // 通过ParseHandlerByName解析接口得到方法名称和对应的MethodHandler
    Map nameToHandler = this.targetToHandlersByName.apply(target);
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();
    Method[] var5 = target.type().getMethods();
    int var6 = var5.length;
    
    // 通过反射过去接口声明的方法
    for(int var7 = 0; var7 < var6; ++var7) {
        Method method = var5[var7];
        if (method.getDeclaringClass() != Object.class) {
            if (Util.isDefault(method)) {
                // 若接口中存在默认方法,则创建DefaultMethodHandler
                DefaultMethodHandler handler = new DefaultMethodHandler(method);
                defaultMethodHandlers.add(handler);
                methodToHandler.put(method, handler);
            } else {
                methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
            }
        }
    }
    
    // 通过默认的InvocationHandlerFactory工厂对象来创建FeignInvocationHandler对象
    InvocationHandler handler = this.factory.create(target, methodToHandler);
    // 再通过JDK动态代理创建代理对象
    T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
    Iterator var12 = defaultMethodHandlers.iterator();

    // 将默认方法绑定到代理对象
    while(var12.hasNext()) {
        DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
        defaultMethodHandler.bindTo(proxy);
    }

    return proxy;
}

通过以上代码注释,我们可以知道创建代理对象分以下几步:

  • 通过ParseHandlerByName的apply()方法获取方法名称对应的MethodHandler
  • 通过反射得到接口声明方法,并得到Method对象对应的MethodHandler生成Map集合nameToHandler,并使用invocationHandlerFactory创建InvocationHandler对象
  • 通过JDK动态代理创建代理对象
  • 将接口的默认方法绑定到代理对象

接下来,分别对每步的代码进行阅读,了解其中原理。

ParseHandlerByName.apply(target)

该方法解析生成接口声明方法对应的MethodHandler。通过源码来认识如何生成方法名对应的MethodHandler

public Map apply(Target target) {
    // 首先通过配置的协议Contract来解析获取方法元数据MethodMetadata
    List metadata = this.contract.parseAndValidateMetadata(target.type());
    Map result = new LinkedHashMap();
    Iterator var4 = metadata.iterator();

    // 再根据方法元数据来分别创建不同的RequestTemplateFactory, 该对象用来resolve对应的RequestTemplate
    while(var4.hasNext()) {
        MethodMetadata md = (MethodMetadata)var4.next();
        Object buildTemplate;
        if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
            buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
        } else if (md.bodyIndex() != null) {
            buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
        } else {
            // 以上两种BuildTemplate都继承自BuildTemplateByResolvingArgs
            buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md, this.queryMapEncoder, target);
        }

        if (md.isIgnored()) {
            result.put(md.configKey(), (args) -> {
                throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
            });
        } else {
            // 使用SynchronousMethodHandlerFactor创建方法对应的MethodHandler
            result.put(md.configKey(), this.factory.create(target, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder));
        }
    }

    return result;
}

通过以上代码注释,我们了解到生成方法名对应的MethodHandler分为以下几个步骤:

  • 通过协议Contract解析获取方法的元数据MethodMetadata
  • 根据MethodMetadata创建每个方法对应的buildTemplate对象,该对象在请求时构造生成不同的requestTemplate
  • 对于每个方法,使用SynchronousMethodHandlerFactor创建对应的MethodHandler。并以方法元数据configKey为键,MethodHandler为值存储返回

在以上步骤中,前两步十分重要,直接关系到后续请求是否能成功进行。接下来对这两步中的源码进行了解

contract.parseAndValidateMetadata

通过配置的Contract协议来解析方法或类上的注解,生成方法的元数据,在之前的Builder构造函数代码示例中,OpenFeign使用的是默认协议Default。通过类图我们可以了解协议类Contract之间的关系。
OpenFeign学习(三):OpenFeign配置生成代理对象_第2张图片

通过类图可以看到,Default类实现了DeclarativeContract抽象类,DeclarativeContract继承了BaseContract抽象类,BaseContract抽象类实现了Contract接口。

在Contract接口中声明了parseAndValidateMetadata方法,在BaseContract抽象类中实现了该方法,并且BaseContract抽象类声明了模板方法processAnnotationOnClass,processAnnotationOnMethod,processAnnotationsOnParameter。从名称可以看出这三个方法是分别用来处理类,方法和参数上的注解的。

在BaseContract类的parseAndValidateMetadata方法实现中,我们可以看到也是通过反射获取声明的方法Method对象,再对注解进行处理生成MethodMetadata。

public List parseAndValidateMetadata(Class targetType) {
    Util.checkState(targetType.getTypeParameters().length == 0, "Parameterized types unsupported: %s", new Object[]{targetType.getSimpleName()});
    Util.checkState(targetType.getInterfaces().length <= 1, "Only single inheritance supported: %s", new Object[]{targetType.getSimpleName()});
    if (targetType.getInterfaces().length == 1) {
        Util.checkState(targetType.getInterfaces()[0].getInterfaces().length == 0, "Only single-level inheritance supported: %s", new Object[]{targetType.getSimpleName()});
    }

    Map result = new LinkedHashMap();
    Method[] var3 = targetType.getMethods();
    int var4 = var3.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        Method method = var3[var5];
        if (method.getDeclaringClass() != Object.class && (method.getModifiers() & 8) == 0 && !Util.isDefault(method)) {
            MethodMetadata metadata = this.parseAndValidateMetadata(targetType, method);
            Util.checkState(!result.containsKey(metadata.configKey()), "Overrides unsupported: %s", new Object[]{metadata.configKey()});
            result.put(metadata.configKey(), metadata);
        }
    }

    return new ArrayList(result.values());
}

在parseAndValidateMetadata(class, Method)方法中别调用了以上提到三个方法的实现,来处理类,方法和参数上的注解。

此时,我们可以看DefaultContract类做了什么。
首先,在该类中声明了三个集合:

public abstract class DeclarativeContract extends BaseContract {
    private List classAnnotationProcessors = new ArrayList();
    private List methodAnnotationProcessors = new ArrayList();
    Map, DeclarativeContract.ParameterAnnotationProcessor> parameterAnnotationProcessors = new HashMap();
    ...
}

这三个集合分别存储类,方法和参数的注解处理器,而这些注解器,正是通过Default类在其构造函数中进行注册的。通过源码我们可以看到,Default中注册的注释处理器,正是我们在使用OpenFeign声明接口方法时使用的注解。

 public static class Default extends DeclarativeContract {
        // 该正则表达式用来匹配@RequestLine注解中声明的HTTP请求方式
        static final Pattern REQUEST_LINE_PATTERN = Pattern.compile("^([A-Z]+)[ ]*(.*)$");

        public Default() {
            super.registerClassAnnotation(Headers.class, (header, data) -> {
                String[] headersOnType = header.value();
                Util.checkState(headersOnType.length > 0, "Headers annotation was empty on type %s.", new Object[]{data.configKey()});
                Map> headers = toMap(headersOnType);
                headers.putAll(data.template().headers());
                data.template().headers((Map)null);
                data.template().headers(headers);
            });
            super.registerMethodAnnotation(RequestLine.class, (ann, data) -> {
                String requestLine = ann.value();
                Util.checkState(Util.emptyToNull(requestLine) != null, "RequestLine annotation was empty on method %s.", new Object[]{data.configKey()});
                Matcher requestLineMatcher = REQUEST_LINE_PATTERN.matcher(requestLine);
                if (!requestLineMatcher.find()) {
                    throw new IllegalStateException(String.format("RequestLine annotation didn't start with an HTTP verb on method %s", data.configKey()));
                } else {
                    data.template().method(HttpMethod.valueOf(requestLineMatcher.group(1)));
                    data.template().uri(requestLineMatcher.group(2));
                    data.template().decodeSlash(ann.decodeSlash());
                    data.template().collectionFormat(ann.collectionFormat());
                }
            });
            // 省略其他处理器注册代码
            .... 
        }
}

通过以上源码可以看到,在使用@RequestLine注解时,必须声明HTTP的请求方式,并且为大写字母。

同时,DeclarativeContract类实现了BaseContract类的三个抽象方法 processAnnotationOnClass,processAnnotationOnMethod,processAnnotationsOnParameter。并且这些方法都以final修饰,不可被子类重写修改。在这三个方法中,使用Default注册的注解处理器,对类,方法和参数上的注解进行处理。

protected final void processAnnotationOnClass(MethodMetadata data, Class targetType) {
    Arrays.stream(targetType.getAnnotations()).forEach((annotation) -> {
        this.classAnnotationProcessors.stream().filter((processor) -> {
            return processor.test(annotation);
        }).forEach((processor) -> {
            processor.process(annotation, data);
        });
    });
}

protected final void processAnnotationOnMethod(MethodMetadata data, Annotation annotation, Method method) {
    this.methodAnnotationProcessors.stream().filter((processor) -> {
        return processor.test(annotation);
    }).forEach((processor) -> {
        processor.process(annotation, data);
    });
}

protected final boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
    Arrays.stream(annotations).filter((annotation) -> {
        return this.parameterAnnotationProcessors.containsKey(annotation.annotationType());
    }).forEach((annotation) -> {
        ((DeclarativeContract.ParameterAnnotationProcessor)this.parameterAnnotationProcessors.getOrDefault(annotation.annotationType(), DeclarativeContract.ParameterAnnotationProcessor.DO_NOTHING)).process(annotation, data, paramIndex);
    });
    return false;
}

至此,我们可以清晰的知道,Contract接口声明了解析方法元数据的方法parseValidateMetadata;抽象类BaseContract实现了该接口方法,并声明了处理类,方法和参数上的注解的模板方法;抽象类DeclarativeContract实现了BaseContract的模板方法,并设置了注解处理器的注册方法;Default类继承DeclarativeContract抽象类,并使用注册方法注册OpenFeign的注解处理器。在解析方法元数据时,通过Default注册的注解处理器来处理注解生成方法的元数据。

由此可以看出,OpenFeign有极大的扩展性,我们可以定制自己的注解,并重新实现DeclarativeContract抽象类来注册自己的处理器,就可以扩展注解的使用。同时,在官方文档中也说明了可以支持多种协议Contract,如Spring4,SOAP等等。
OpenFeign学习(三):OpenFeign配置生成代理对象_第3张图片

在完成方法元数据解析后,我们再回到MethodHandler创建的步骤中,接下来就是根据方法的元数据来创建不同的buildTempalte。

BuildTemplateByResolvingArgs
while(var4.hasNext()) {
    MethodMetadata md = (MethodMetadata)var4.next();
    Object buildTemplate;
    if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
        buildTemplate = new ReflectiveFeign.BuildFormEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
    } else if (md.bodyIndex() != null) {
        buildTemplate = new ReflectiveFeign.BuildEncodedTemplateFromArgs(md, this.encoder, this.queryMapEncoder, target);
    } else {
        buildTemplate = new ReflectiveFeign.BuildTemplateByResolvingArgs(md, this.queryMapEncoder, target);
    }
    
    ....
}

以上源码说明了通过解析到的方法的元数据,为每个方法创建不同的buildTemplate对象,该对象实际是个factory对象,用来创建RequestTemplate。其中,BuildFormEncodedTemplateFromArgs和BuildEncodedTemplateFromArgs都继承自BuildTemplateByResolvingArgs类。在解析时生成RequestTemplate时,对请求参数或者请求体使用配置的Encoder进行编码。

private static class BuildEncodedTemplateFromArgs extends ReflectiveFeign.BuildTemplateByResolvingArgs {
    private final Encoder encoder;
    ....
}

private static class BuildFormEncodedTemplateFromArgs extends ReflectiveFeign.BuildTemplateByResolvingArgs {
    private final Encoder encoder;
    ....
}

private static class BuildTemplateByResolvingArgs implements Factory {
    private final QueryMapEncoder queryMapEncoder;
    protected final MethodMetadata metadata;
    protected final Target target;
    private final Map indexToExpander;
    ....
}

interface Factory {
    RequestTemplate create(Object[] var1);
}

在创建完buildTemplate对象后,以此为参数使用synchronousMethodHandlerFactory创建方法对应的MethodHandler。

synchronousMethodHandlerFactory.create
result.put(md.configKey(), this.factory.create(target, md, (Factory)buildTemplate, this.options, this.decoder, this.errorDecoder));

方法的configKey在解析元数据时生成,其形式为 classSimpleName#methodName(paramTypeSimpleName,…)

在得到方法对应的MethodHandler的Map集合后,我们回到创建代理的newInstance方法中,第二步是通过反射获取接口中的方法对象,并以之前获取的Map map为依据,获取Map nameToHandler的map集合。生成代理对象使用的InvocationHandler对象。

InvocationHandler

在该步骤中,我们重点关注的是接口中默认方法是如何处理的。

在java8中,允许接口中声明实现默认方法Default Methods,针对默认方法如何创建其对应的MethodHandler?

 for(int var7 = 0; var7 < var6; ++var7) {
    Method method = var5[var7];
    if (method.getDeclaringClass() != Object.class) {
        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)));
        }
    }
}

通过源码可以看出,当判断方法为默认方法时,创建DefaultMethodHandler对象,在上面提到正常方法的methodHandler是通过解析的方法元数据创建的,而默认方法则是通过方法句柄来创建MethodHandler。

public DefaultMethodHandler(Method defaultMethod) {
    try {
        Class declaringClass = defaultMethod.getDeclaringClass();
        Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP");
        field.setAccessible(true);
        Lookup lookup = (Lookup)field.get((Object)null);
        // 通过Lookup的unreflectSpecial创建默认方法的methodHandler
        this.unboundHandle = lookup.unreflectSpecial(defaultMethod, declaringClass);
    } catch (NoSuchFieldException var5) {
        throw new IllegalStateException(var5);
    } catch (IllegalAccessException var6) {
        throw new IllegalStateException(var6);
    }
}

关于什么是方法句柄,请看以下文章进行学习了解:
《JDK1.7方法句柄学习》
《Method Handles in Java》

如何通过方法句柄调用默认方法,请看《How to explicitly invoke default method from a dynamic Proxy?》。

在创建完InvocationHandler后,进行第三步使用JDK动态代理创建代理对象。

Proxy.newProxyInstance

通过JDK动态代理创建代理对象,这也是为什么要使用接口声明方法的原因。在创建完代理对象后,将默认方法的MethodHandler绑定到代理对象上。

T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
Iterator var12 = defaultMethodHandlers.iterator();

while(var12.hasNext()) {
    DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
    defaultMethodHandler.bindTo(proxy);
}

至此,OpenFeign配置生成代理过程的源码已经阅读完毕,可以发现框架的根本就是反射和代理,这涉及到java的基础知识,反射,动态代理,方法句柄,设计模式等等。OpenFeign以其出色的设计,支持了极强的扩展性并保持了使用的简单性,非常值得我们去研究学习。

本篇博文我对OpenFeign代理对象过程中涉及到的组件扩展配置,协议解析方法元数据,MethodHandler对象的生成及最后代理对象的生成的代码进行了阅读总结,后续我将继续对执行过程中的请求生成,参数编码,结果解码,重试控制等源码进行学习总结。

参考资料:
https://blog.csdn.net/luanlouis/article/details/82821294

你可能感兴趣的:(Feign学习,OpenFeign,JDK,Proxy,默认方法,反射)