private Mono<ClientResponse> execute(HttpRequestValues requestValues) {
        final URI uri;
        if (requestValues.getUri() != null) {
            uri = requestValues.getUri();
        } else {
            final String uriTemplate = requestValues.getUriTemplate();
            if (uriTemplate != null) {
                final UriBuilderFactory uriBuilderFactory = requestValues.getUriBuilderFactory();
                final Map<String, String> uriVariables = requestValues.getUriVariables();
                if (uriBuilderFactory != null) {
                    uri = uriBuilderFactory.expand(uriTemplate, uriVariables);
                } else {
                    uri = this.uriBuilderFactory.expand(uriTemplate, uriVariables);
                }
            } else {
                throw new IllegalStateException("Neither full URL nor URI template");
            }
        }

        final Map<String, Object> attributes = requestValues.getAttributes();
        final RequestOptions requestOptions;
        if (attributes.isEmpty()) {
            requestOptions = null;
        } else {
            final RequestOptionsBuilder requestOptionsBuilder = RequestOptions.builder();
            // Don't need to copy the attributes because it is immutable.
            RequestAttributeAccess.set(requestOptionsBuilder, attributes);
            requestOptions = requestOptionsBuilder.build();
        }
        final String path = uri.getRawPath();
        final String query = uri.getRawQuery();
        checkArgument(path != null, "path is undefined: %s", uri);
        final String pathAndQuery = Strings.isNullOrEmpty(query) ? path : path + '?' + query;
        final HttpMethod httpMethod = requestValues.getHttpMethod();
        checkArgument(httpMethod != null, "HTTP method is undefined. requestValues: %s", requestValues);
        final ArmeriaClientHttpRequest request =
                new ArmeriaClientHttpRequest(webClient, httpMethod, pathAndQuery, uri,
                                             DataBufferFactoryWrapper.DEFAULT, requestOptions);

        final Mono<HttpResponse> response = Mono.fromFuture(request.future());
        return toClientRequest(requestValues, httpMethod, uri)
                .writeTo(request, exchangeStrategies)
                .then(response)
                .flatMap(res -> {
                    final ClientRequestContext context = request.context();
                    assert context != null;
                    return toClientResponse(res, context);
                });
    }
Sends the specified {@link HttpRequestValues} to the {@link WebClient} and returns the response.

<p><h4>Implementation note</h4>
In order to encode {@link HttpRequestValues#getBodyValue()} to {@link HttpData}, we need to convert
the {@link HttpRequestValues} to {@link ClientRequest} first. The serialization process is delegated to
the {@link ClientRequest}. After that, the request is written to {@link ArmeriaClientHttpRequest} to send
the request via the {@link WebClient}.

<p>The response handling has to be done in the reverse order. Armeria {@link HttpResponse} is converted
into {@link ArmeriaClientHttpResponse} first and then converted into {@link ClientResponse}.
spring/spring6/src/main/java/com/linecorp/armeria/spring/client/ArmeriaHttpExchangeAdapter.java