【Quarkus】resteasy-client-reactive实现源码解析

发布时间 2024-01-11 17:36:24作者: 小小记录本

[Quarkus] resteasy-client-reactive实现源码解析

resteasy-client-reactive

本文是我为了找到resteasy-client支持的multiform输入参数类型而进行的探索. 如果对resteasy-client-reactive源码感兴趣,可以参考大致的思路.

一、搭建DEMO工程

新建一个项目,引入依赖

依赖

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client-reactive-jackson</artifactId>
        </dependency>

客户端定义

@Path("/upload")
@RegisterRestClient(baseUri = "http://localhost:8081")
public interface UploadClient {

    @POST
    @Path("/one")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    Uni<Boolean> upload(@MultipartForm SingleFile request);

}

SingleFile

public class SingleFile {

    @FormParam("file")
    @PartType(APPLICATION_OCTET_STREAM)
    public File data;

    @FormParam("filename")
    @PartType(TEXT_PLAIN)
    public String fileName;
}

这里就省略了写示例代码了,请参考官方建立一个示例的客户端工程.

怎么发起请求的?

通过翻阅Quarkus的资料,可以知道 resteasy-client-reactive是使用asm技术,在编译期解析待实现接口,然后生成相应的实现类.在运行时直接使用编译好的类来运行.
生成的代码位于target/quarkus-app/quarkus/generated-bytecode.jar,解压后即可看见. 也可以增加参数指定输出的路径: -Dquarkus.debug.generated-classes-dir=dump-classes,增加这个参数会自动输出类到dump-classes目录.
(目前我没找到方法可以在上面打断点,好像每一次运行都会覆盖掉原来的文件,从而使断点失效)(如果想尝试,可以在idea配置Project Structure/Library中增加输出文件夹dump-classes为依赖)

输出的代码有五个类,重点关注UploadClient$$QuarkusRestClientInterface.class

UploadClient$$CDIWrapper.class
UploadClient$$CDIWrapper_Bean.class
UploadClient$$CDIWrapper_ClientProxy.class
UploadClient$$QuarkusRestClientInterface.class
UploadClient$$QuarkusRestClientInterfaceCreator.class

下面就是调用方法时,resteasy-client帮你拼装请求参数的过程.
可以看出是new了一个QuarkusMultipartForm对象用于存放请求参数,然后通过调用org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartForm.binaryFileUpload()
org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartForm.attribute两个方法分别设置了data和filename的请求参数的.
最后的话,就是通过WebTarget发起请求了

UploadClient$$QuarkusRestClientInterface.class

import org.jboss.resteasy.reactive.client.impl.multipart.QuarkusMultipartForm;

// $FF: synthetic class
public class UploadClient$$QuarkusRestClientInterface extends RestClientBase implements Closeable, UploadClient {

    // ...

    /**
     * 主要方法
     */
    public Uni upload(SingleFile var1) {
        WebTarget var15 = (WebTarget)this.target2;
        // 请求参数对象
        QuarkusMultipartForm var8 = new QuarkusMultipartForm();
        if (var1 != null) {
            ClassLoader var2 = Thread.currentThread().getContextClassLoader();
            Class var3 = Class.forName("api.SingleFile", false, var2);
            Object var6 = ReflectionUtil.readField(var1, var3, "data");
            Object var4 = ((Map)beanParamDescriptors1.get()).get("data");
            Type var10000;
            if (var4 == null) {
                var10000 = null;
            } else {
                var10000 = ((ParameterDescriptorFromClassSupplier.ParameterDescriptor)var4).genericType;
            }

            Object var5 = ((Map)beanParamDescriptors1.get()).get("data");
            Annotation[] var29;
            if (var5 == null) {
                var10000 = null;
            } else {
                var29 = ((ParameterDescriptorFromClassSupplier.ParameterDescriptor)var5).annotations;
            }

            if (var6 != null) {
                Path var7 = ((File)var6).toPath();
                String var9 = var7.getFileName().toString();
                String var10 = var7.toString();
                // 设置请求formdata: data
                var8 = var8.binaryFileUpload("file", var9, var10, "application/octet-stream");
            }

            Class var11 = Class.forName("api.SingleFile", false, var2);
            Object var14 = ReflectionUtil.readField(var1, var11, "fileName");
            Object var12 = ((Map)beanParamDescriptors1.get()).get("fileName");
            if (var12 == null) {
                var10000 = null;
            } else {
                var10000 = ((ParameterDescriptorFromClassSupplier.ParameterDescriptor)var12).genericType;
            }

            Object var13 = ((Map)beanParamDescriptors1.get()).get("fileName");
            if (var13 == null) {
                var10000 = null;
            } else {
                var29 = ((ParameterDescriptorFromClassSupplier.ParameterDescriptor)var13).annotations;
            }

            if (var14 != null) {
                // 设置请求formdata: filename
                var8 = var8.attribute("filename", (String)var14, (String)null);
            }
        }

        String[] var16 = new String[]{"application/json"};
        Invocation.Builder var17 = var15.request(var16);
        var17 = this.upload$$2$$handleBeanParam$$0(var17, var1);
        Method var18 = javaMethod2;
        var17 = var17.property("org.eclipse.microprofile.rest.client.invokedMethod", var18);
        HeaderFiller var19 = this.headerFiller2;
        var17 = var17.property("io.quarkus.rest.client.reactive.HeaderFiller", var19);
        Object[] var20 = new Object[]{var1};
        List var21 = Arrays.asList(var20);
        var17 = var17.property("io.quarkus.rest.client.invokedMethodParameters", var21);

        try {
            MediaType var22 = MediaType.valueOf("multipart/form-data");
            Entity var25 = Entity.entity(var8, var22);
            ClassLoader var23 = Thread.currentThread().getContextClassLoader();
            Class var24 = Class.forName("org.jboss.resteasy.reactive.client.impl.UniInvoker", false, var23);
            return ((UniInvoker)var17.rx(var24)).method("POST", var25, Boolean.class);
        } catch (ProcessingException var28) {
            Throwable var27 = ((Throwable)var28).getCause();
            if (!(var27 instanceof RuntimeException)) {
                throw (Throwable)var28;
            } else {
                throw var27;
            }
        }
    }

}

代码怎么生成的?

前面我说过,请求方法的实现是通过asm技术在编译器实现接口的,那么接下来就看看是怎么个asm法的.

通过翻Quarkus的源码,这个工程太庞大了,我是直接搜QuarkusMultipartForm找到了工程的模块(模块都几十个T.T),然后搜索关键字QuarkusRestClientInterface定位到了代码生成的位置:
io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor.generateClientInvoker.
由于quarkus的实现机制,这个maven模块并不是直接引入我们项目中的(具体是为什么请看官方解释了,不展开).所以这里我们手动引入依赖到我们的工程中方便开启debug看源码.

依赖

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jaxrs-client-reactive-deployment</artifactId>
        </dependency>

接下来,在generateClientInvoker打个断点一步步走下去

把生成的方法放这里了,可以慢慢观摩. io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor

package io.quarkus.jaxrs.client.reactive.deployment;


public class JaxrsClientReactiveProcessor {

    private RuntimeValue<BiFunction<WebTarget, List<ParamConverterProvider>, ?>> generateClientInvoker(
            RecorderContext recorderContext,
            RestClientInterface restClientInterface, List<JaxrsClientReactiveEnricherBuildItem> enrichers,
            BuildProducer<GeneratedClassBuildItem> generatedClasses, ClassInfo interfaceClass,
            IndexView index, String defaultMediaType, Map<DotName, String> httpAnnotationToMethod,
            boolean observabilityIntegrationNeeded, Set<ClassInfo> multipartResponseTypes) {

        String creatorName = restClientInterface.getClassName() + "$$QuarkusRestClientInterfaceCreator";
        String name = restClientInterface.getClassName() + "$$QuarkusRestClientInterface";
        MethodDescriptor constructorDesc = MethodDescriptor.ofConstructor(name, WebTarget.class.getName(), List.class);
        try (ClassRestClientContext classContext = new ClassRestClientContext(name, constructorDesc, generatedClasses,
                RestClientBase.class, Closeable.class.getName(), restClientInterface.getClassName())) {
            classContext.constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(RestClientBase.class, List.class),
                    classContext.constructor.getThis(), classContext.constructor.getMethodParam(1));

            AssignableResultHandle baseTarget = classContext.constructor.createVariable(WebTargetImpl.class);
            if (restClientInterface.isEncoded()) {
                classContext.constructor.assign(baseTarget,
                        disableEncodingForWebTarget(classContext.constructor, classContext.constructor.getMethodParam(0)));
            } else {
                classContext.constructor.assign(baseTarget, classContext.constructor.getMethodParam(0));
            }

            classContext.constructor.assign(baseTarget,
                    classContext.constructor.invokeVirtualMethod(
                            MethodDescriptor.ofMethod(WebTargetImpl.class, "path", WebTargetImpl.class, String.class),
                            baseTarget,
                            classContext.constructor.load(restClientInterface.getPath())));
            FieldDescriptor baseTargetField = classContext.classCreator
                    .getFieldCreator("baseTarget", WebTargetImpl.class.getName())
                    .getFieldDescriptor();
            classContext.constructor.writeInstanceField(baseTargetField, classContext.constructor.getThis(), baseTarget);

            for (JaxrsClientReactiveEnricherBuildItem enricher : enrichers) {
                enricher.getEnricher().forClass(classContext.constructor, baseTarget, interfaceClass, index);
            }

            //
            // go through all the methods of the jaxrs interface. Create specific WebTargets (in the constructor) and methods
            //
            int methodIndex = 0;
            for (ResourceMethod method : restClientInterface.getMethods()) {
                methodIndex++;

                // finding corresponding jandex method, used by enricher (MicroProfile enricher stores it in a field
                // to later fill in context with corresponding java.lang.reflect.Method)
                String[] javaMethodParameters = new String[method.getParameters().length];
                for (int i = 0; i < method.getParameters().length; i++) {
                    MethodParameter param = method.getParameters()[i];
                    javaMethodParameters[i] = param.declaredType != null ? param.declaredType : param.type;
                }
                MethodInfo jandexMethod = getJavaMethod(interfaceClass, method, method.getParameters(), index)
                        .orElseThrow(() -> new RuntimeException(
                                "Failed to find matching java method for " + method + " on " + interfaceClass
                                        + ". It may have unresolved parameter types (generics)"));

                if (!Modifier.isAbstract(jandexMethod.flags())) {
                    // ignore default methods, they can be used for Fault Tolerance's @Fallback or filling headers
                    continue;
                }

                if (method.getHttpMethod() == null) {
                    handleSubResourceMethod(enrichers, generatedClasses, interfaceClass, index, defaultMediaType,
                            httpAnnotationToMethod, name, classContext, baseTarget, methodIndex, method,
                            javaMethodParameters, jandexMethod, multipartResponseTypes, Collections.emptyList());
                } else {
                    FieldDescriptor methodField = classContext.createJavaMethodField(interfaceClass, jandexMethod,
                            methodIndex);
                    Supplier<FieldDescriptor> methodParamAnnotationsField = classContext
                            .getLazyJavaMethodParamAnnotationsField(methodIndex);
                    Supplier<FieldDescriptor> methodGenericParametersField = classContext
                            .getLazyJavaMethodGenericParametersField(methodIndex);

                    // if the response is multipart, let's add it's class to the appropriate collection:
                    addResponseTypeIfMultipart(multipartResponseTypes, jandexMethod, index);

                    // constructor: initializing the immutable part of the method-specific web target
                    FieldDescriptor webTargetForMethod = FieldDescriptor.of(name, "target" + methodIndex, WebTargetImpl.class);
                    classContext.classCreator.getFieldCreator(webTargetForMethod).setModifiers(Modifier.FINAL);

                    AssignableResultHandle constructorTarget = createWebTargetForMethod(classContext.constructor, baseTarget,
                            method);
                    classContext.constructor.writeInstanceField(webTargetForMethod, classContext.constructor.getThis(),
                            constructorTarget);
                    if (observabilityIntegrationNeeded) {
                        String templatePath = MULTIPLE_SLASH_PATTERN.matcher(restClientInterface.getPath() + method.getPath())
                                .replaceAll("/");
                        classContext.constructor.invokeVirtualMethod(
                                MethodDescriptor.ofMethod(WebTargetImpl.class, "setPreClientSendHandler", void.class,
                                        ClientRestHandler.class),
                                classContext.constructor.readInstanceField(webTargetForMethod,
                                        classContext.constructor.getThis()),
                                classContext.constructor.newInstance(
                                        MethodDescriptor.ofConstructor(ClientObservabilityHandler.class, String.class),
                                        classContext.constructor.load(templatePath)));
                    }

                    // generate implementation for a method from jaxrs interface:
                    MethodCreator methodCreator = classContext.classCreator.getMethodCreator(method.getName(),
                            method.getSimpleReturnType(),
                            javaMethodParameters);

                    AssignableResultHandle methodTarget = methodCreator.createVariable(WebTarget.class);
                    methodCreator.assign(methodTarget,
                            methodCreator.readInstanceField(webTargetForMethod, methodCreator.getThis()));
                    if (!restClientInterface.isEncoded() && method.isEncoded()) {
                        methodCreator.assign(methodTarget, disableEncodingForWebTarget(methodCreator, methodTarget));
                    }

                    Integer bodyParameterIdx = null;
                    Map<MethodDescriptor, ResultHandle> invocationBuilderEnrichers = new HashMap<>();

                    String[] consumes = extractProducesConsumesValues(
                            jandexMethod.declaringClass().declaredAnnotation(CONSUMES), method.getConsumes());
                    boolean multipart = isMultipart(consumes, method.getParameters());

                    AssignableResultHandle formParams = null;

                    boolean lastEncodingEnabledByParam = true;
                    for (int paramIdx = 0; paramIdx < method.getParameters().length; ++paramIdx) {
                        MethodParameter param = method.getParameters()[paramIdx];
                        if (!restClientInterface.isEncoded() && !method.isEncoded()) {
                            boolean needsDisabling = isParamAlreadyEncoded(param);
                            if (lastEncodingEnabledByParam && needsDisabling) {
                                methodCreator.assign(methodTarget, disableEncodingForWebTarget(methodCreator, methodTarget));
                                lastEncodingEnabledByParam = false;
                            } else if (!lastEncodingEnabledByParam && !needsDisabling) {
                                methodCreator.assign(methodTarget, enableEncodingForWebTarget(methodCreator, methodTarget));
                                lastEncodingEnabledByParam = true;
                            }
                        }

                        if (param.parameterType == ParameterType.QUERY) {
                            //TODO: converters

                            // query params have to be set on a method-level web target (they vary between invocations)
                            methodCreator.assign(methodTarget,
                                    addQueryParam(jandexMethod, methodCreator, methodTarget, param.name,
                                            methodCreator.getMethodParam(paramIdx),
                                            jandexMethod.parameterType(paramIdx), index, methodCreator.getThis(),
                                            getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
                                            getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx)));
                        } else if (param.parameterType == ParameterType.BEAN
                                || param.parameterType == ParameterType.MULTI_PART_FORM) {
                            // bean params require both, web-target and Invocation.Builder, modifications
                            // The web target changes have to be done on the method level.
                            // Invocation.Builder changes are offloaded to a separate method
                            // so that we can generate bytecode for both, web target and invocation builder modifications
                            // at once
                            ClientBeanParamInfo beanParam = (ClientBeanParamInfo) param;
                            MethodDescriptor handleBeanParamDescriptor = MethodDescriptor.ofMethod(name,
                                    method.getName() + "$$" + methodIndex + "$$handleBeanParam$$" + paramIdx,
                                    Invocation.Builder.class,
                                    Invocation.Builder.class, param.type);
                            MethodCreator handleBeanParamMethod = classContext.classCreator.getMethodCreator(
                                    handleBeanParamDescriptor).setModifiers(Modifier.PRIVATE);

                            AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
                                    .createVariable(Invocation.Builder.class);
                            handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));

                            Supplier<FieldDescriptor> beanParamDescriptorsField = classContext
                                    .getLazyBeanParameterDescriptors(beanParam.type);

                            formParams = addBeanParamData(jandexMethod, methodCreator, handleBeanParamMethod,
                                    invocationBuilderRef, classContext, beanParam.getItems(),
                                    methodCreator.getMethodParam(paramIdx), methodTarget, index,
                                    restClientInterface.getClassName(),
                                    methodCreator.getThis(),
                                    handleBeanParamMethod.getThis(),
                                    formParams, beanParamDescriptorsField, multipart,
                                    beanParam.type);

                            handleBeanParamMethod.returnValue(invocationBuilderRef);
                            invocationBuilderEnrichers.put(handleBeanParamDescriptor, methodCreator.getMethodParam(paramIdx));
                        } else if (param.parameterType == ParameterType.PATH) {
                            // methodTarget = methodTarget.resolveTemplate(paramname, paramvalue);
                            addPathParam(methodCreator, methodTarget, param.name, methodCreator.getMethodParam(paramIdx),
                                    param.type, methodCreator.getThis(),
                                    getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
                                    getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx));
                        } else if (param.parameterType == ParameterType.BODY) {
                            // just store the index of parameter used to create the body, we'll use it later
                            bodyParameterIdx = paramIdx;
                        } else if (param.parameterType == ParameterType.HEADER) {
                            // headers are added at the invocation builder level
                            MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(name,
                                    method.getName() + "$$" + methodIndex + "$$handleHeader$$" + paramIdx,
                                    Invocation.Builder.class,
                                    Invocation.Builder.class, param.declaredType);
                            MethodCreator handleHeaderMethod = classContext.classCreator.getMethodCreator(
                                    handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);

                            AssignableResultHandle invocationBuilderRef = handleHeaderMethod
                                    .createVariable(Invocation.Builder.class);
                            handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0));
                            addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name,
                                    handleHeaderMethod.getMethodParam(1), param.type,
                                    handleHeaderMethod.getThis(),
                                    getGenericTypeFromArray(handleHeaderMethod, methodGenericParametersField, paramIdx),
                                    getAnnotationsFromArray(handleHeaderMethod, methodParamAnnotationsField, paramIdx));
                            handleHeaderMethod.returnValue(invocationBuilderRef);
                            invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx));
                        } else if (param.parameterType == ParameterType.COOKIE) {
                            // headers are added at the invocation builder level
                            MethodDescriptor handleHeaderDescriptor = MethodDescriptor.ofMethod(name,
                                    method.getName() + "$$" + methodIndex + "$$handleCookie$$" + paramIdx,
                                    Invocation.Builder.class,
                                    Invocation.Builder.class, param.type);
                            MethodCreator handleCookieMethod = classContext.classCreator.getMethodCreator(
                                    handleHeaderDescriptor).setModifiers(Modifier.PRIVATE);

                            AssignableResultHandle invocationBuilderRef = handleCookieMethod
                                    .createVariable(Invocation.Builder.class);
                            handleCookieMethod.assign(invocationBuilderRef, handleCookieMethod.getMethodParam(0));
                            addCookieParam(handleCookieMethod, invocationBuilderRef, param.name,
                                    handleCookieMethod.getMethodParam(1), param.type,
                                    handleCookieMethod.getThis(),
                                    getGenericTypeFromArray(handleCookieMethod, methodGenericParametersField, paramIdx),
                                    getAnnotationsFromArray(handleCookieMethod, methodParamAnnotationsField, paramIdx));
                            handleCookieMethod.returnValue(invocationBuilderRef);
                            invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx));
                        } else if (param.parameterType == ParameterType.FORM) {
                            formParams = createFormDataIfAbsent(methodCreator, formParams, multipart);
                            // NOTE: don't use type here, because we're not going through the collection converters and stuff
                            addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), param.declaredType,
                                    param.signature,
                                    restClientInterface.getClassName(), methodCreator.getThis(), formParams,
                                    getGenericTypeFromArray(methodCreator, methodGenericParametersField, paramIdx),
                                    getAnnotationsFromArray(methodCreator, methodParamAnnotationsField, paramIdx),
                                    multipart,
                                    param.mimeType, param.partFileName,
                                    jandexMethod.declaringClass().name() + "." + jandexMethod.name());
                        }
                    }

                    for (JaxrsClientReactiveEnricherBuildItem enricher : enrichers) {
                        enricher.getEnricher()
                                .forWebTarget(methodCreator, index, interfaceClass, jandexMethod, methodTarget,
                                        generatedClasses);
                        formParams = enricher.getEnricher().handleFormParams(methodCreator, index, interfaceClass, jandexMethod,
                                generatedClasses, formParams, multipart);
                    }

                    AssignableResultHandle builder = methodCreator.createVariable(Invocation.Builder.class);
                    if (method.getProduces() == null || method.getProduces().length == 0) { // this should never happen!
                        methodCreator.assign(builder, methodCreator.invokeInterfaceMethod(
                                MethodDescriptor.ofMethod(WebTarget.class, "request", Invocation.Builder.class), methodTarget));
                    } else {

                        ResultHandle array = methodCreator.newArray(String.class, method.getProduces().length);
                        for (int i = 0; i < method.getProduces().length; ++i) {
                            methodCreator.writeArrayValue(array, i, methodCreator.load(method.getProduces()[i]));
                        }
                        methodCreator.assign(builder, methodCreator.invokeInterfaceMethod(
                                MethodDescriptor.ofMethod(WebTarget.class, "request", Invocation.Builder.class, String[].class),
                                methodTarget, array));
                    }

                    for (Map.Entry<MethodDescriptor, ResultHandle> invocationBuilderEnricher : invocationBuilderEnrichers
                            .entrySet()) {
                        methodCreator.assign(builder,
                                methodCreator.invokeVirtualMethod(invocationBuilderEnricher.getKey(), methodCreator.getThis(),
                                        builder, invocationBuilderEnricher.getValue()));
                    }

                    for (JaxrsClientReactiveEnricherBuildItem enricher : enrichers) {
                        enricher.getEnricher()
                                .forMethod(classContext.classCreator, classContext.constructor, classContext.clinit,
                                        methodCreator, interfaceClass, jandexMethod, builder, index, generatedClasses,
                                        methodIndex, methodField);
                    }

                    handleReturn(interfaceClass, defaultMediaType, method.getHttpMethod(),
                            method.getConsumes(), jandexMethod, methodCreator, formParams,
                            bodyParameterIdx == null ? null : methodCreator.getMethodParam(bodyParameterIdx), builder,
                            multipart);
                }
            }

            classContext.constructor.returnValue(null);
            classContext.clinit.returnValue(null);

            // create `void close()` method:
            // we only close the RestClient of the base target - all targets share the same one
            MethodCreator closeCreator = classContext.classCreator
                    .getMethodCreator(MethodDescriptor.ofMethod(Closeable.class, "close", void.class));
            ResultHandle webTarget = closeCreator.readInstanceField(baseTargetField, closeCreator.getThis());
            ResultHandle webTargetImpl = closeCreator.checkCast(webTarget, WebTargetImpl.class);
            ResultHandle restClient = closeCreator.invokeVirtualMethod(
                    MethodDescriptor.ofMethod(WebTargetImpl.class, "getRestClient", ClientImpl.class), webTargetImpl);
            closeCreator.invokeVirtualMethod(MethodDescriptor.ofMethod(ClientImpl.class, "close", void.class), restClient);
            closeCreator.returnValue(null);
        }

        try (ClassCreator c = new ClassCreator(new GeneratedClassGizmoAdaptor(generatedClasses, true),
                creatorName, null, Object.class.getName(), BiFunction.class.getName())) {

            MethodCreator apply = c
                    .getMethodCreator(
                            MethodDescriptor.ofMethod(creatorName, "apply", Object.class, Object.class, Object.class));
            apply.returnValue(
                    apply.newInstance(constructorDesc, apply.getMethodParam(0), apply.getMethodParam(1)));
        }

        return recorderContext.newInstance(creatorName);

    }



}

一顿折腾找到了位置,调用栈如下

addFile:1869, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        handleMultipartField:1792, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        addFormParam:2799, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        addSubBeanParamData:2525, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        addBeanParamData:2435, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        generateClientInvoker:969, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        setupClientProxies:392, JaxrsClientReactiveProcessor (io.quarkus.jaxrs.client.reactive.deployment)
        invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
        invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
        invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
        invoke:568, Method (java.lang.reflect)
        execute:849, ExtensionLoader$3 (io.quarkus.deployment)
        run:256, BuildContext (io.quarkus.builder)
        run:-1, BuildContext$$Lambda$599/0x0000000800eab628 (io.quarkus.builder)
        runWith:18, ContextHandler$1 (org.jboss.threads)
        run:2513, EnhancedQueueExecutor$Task (org.jboss.threads)
        run:1538, EnhancedQueueExecutor$ThreadBody (org.jboss.threads)
        run:833, Thread (java.lang)
        run:501, JBossThread (org.jboss.threads)

这里就是生成ASM代码的方法了,可以看到这里就是根据反射判断到请求参数有一个File类型,所以这里生成了使用binaryFileUpload增加请求体的方法,从而生成了前面的提到的类: UploadClient$$QuarkusRestClientInterface.class.

package io.quarkus.jaxrs.client.reactive.deployment;
public class JaxrsClientReactiveProcessor {
    private void addFile(BytecodeCreator methodCreator, AssignableResultHandle multipartForm, String formParamName,
                         String partType, String partFilename, ResultHandle filePath) {
        // ...
        if (partType.equalsIgnoreCase(MediaType.APPLICATION_OCTET_STREAM)) {
            methodCreator.assign(multipartForm,
                    // MultipartForm#binaryFileUpload(String name, String filename, String pathname, String mediaType);
                    // filename = name
                    // 这里就是根据反射判断到请求参数有一个File类型,所以这里生成了使用binaryFileUpload增加请求体的方法,对应前面提到的UploadClient$$QuarkusRestClientInterface.class
                    methodCreator.invokeVirtualMethod(
                            MethodDescriptor.ofMethod(QuarkusMultipartForm.class, "binaryFileUpload",
                                    QuarkusMultipartForm.class, String.class, String.class, String.class,
                                    String.class),
                            multipartForm, methodCreator.load(formParamName), fileName,
                            pathString, methodCreator.load(partType)));
        } else {
            // ...
        }
    }
}

回归我自己的主题吧,resteasy-client-reactive支持哪些form-data输入类型

通过前面的一顿分析,其实在中间已经经过了输入类型的判断分支了,就是下面的handleMultipartField方法,进行if-else-fi判断,可见支持
String/File/Path/InputStream/Buffer/[B.*]/.... 其中partType不为空时,会转成pojo发送. 最后都不支持的,就进入convertParamToString进行转换了.

io.quarkus.jaxrs.client.reactive.deployment.JaxrsClientReactiveProcessor.handleMultipartField

package io.quarkus.jaxrs.client.reactive.deployment;


public class JaxrsClientReactiveProcessor {
    private void handleMultipartField(String formParamName, String partType, String partFilename,
                                      String type,
                                      String parameterGenericType, ResultHandle fieldValue, AssignableResultHandle multipartForm,
                                      BytecodeCreator methodCreator,
                                      ResultHandle client, String restClientInterfaceClassName, ResultHandle parameterAnnotations,
                                      ResultHandle genericType, String errorLocation) {

        // ...
        // we support string, and send it as an attribute unconverted
        if (type.equals(String.class.getName())) {
            addString(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue);
        } else if (type.equals(File.class.getName())) {
            // file is sent as file :)
            ResultHandle filePath = ifValueNotNull.invokeVirtualMethod(
                    MethodDescriptor.ofMethod(File.class, "toPath", Path.class), fieldValue);
            addFile(ifValueNotNull, multipartForm, formParamName, partType, partFilename, filePath);
        } else if (type.equals(Path.class.getName())) {
            // and so is path
            addFile(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue);
        } else if (type.equals(InputStream.class.getName())) {
            // and so is path
            addInputStream(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, type);
        } else if (type.equals(Buffer.class.getName())) {
            // and buffer
            addBuffer(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, errorLocation);
        } else if (type.startsWith("[")) {
            // byte[] can be sent as file too
            if (!type.equals("[B")) {
                throw new IllegalArgumentException("Array of unsupported type: " + type
                        + " on " + errorLocation);
            }
            ResultHandle buffer = ifValueNotNull.invokeStaticInterfaceMethod(
                    MethodDescriptor.ofMethod(Buffer.class, "buffer", Buffer.class, byte[].class),
                    fieldValue);
            addBuffer(ifValueNotNull, multipartForm, formParamName, partType, partFilename, buffer, errorLocation);
        } else if (parameterGenericType.equals(MULTI_BYTE_SIGNATURE)) {
            addMultiAsFile(ifValueNotNull, multipartForm, formParamName, partType, partFilename, fieldValue, errorLocation);
        } else if (partType != null) {
            if (partFilename != null) {
                log.warnf("Using the @PartFilename annotation is unsupported on the type '%s'. Problematic field is: '%s'",
                        partType, formParamName);
            }
            // assume POJO:
            addPojo(ifValueNotNull, multipartForm, formParamName, partType, fieldValue, type);
        } else {
            // go via converter
            ResultHandle convertedFormParam = convertParamToString(ifValueNotNull, client, fieldValue, type, genericType,
                    parameterAnnotations);
            BytecodeCreator parameterIsStringBranch = checkStringParam(ifValueNotNull, convertedFormParam,
                    restClientInterfaceClassName, errorLocation);
            addString(parameterIsStringBranch, multipartForm, formParamName, null, partFilename, convertedFormParam);
        }
    }
}