Add HttpExchangeAdapter decoration

Closes gh-35059
This commit is contained in:
rstoyanchev
2025-06-18 15:32:40 +01:00
parent 0e84761871
commit a2b90d9e9a
4 changed files with 291 additions and 1 deletions
@@ -0,0 +1,79 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.service.invoker;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
/**
* {@link HttpExchangeAdapter} that wraps and delegates to another adapter instance.
*
* @author Rossen Stoyanchev
* @since 7.0
*/
public class HttpExchangeAdapterDecorator implements HttpExchangeAdapter {
private final HttpExchangeAdapter delegate;
public HttpExchangeAdapterDecorator(HttpExchangeAdapter delegate) {
this.delegate = delegate;
}
/**
* Return the wrapped delgate {@code HttpExchangeAdapter}.
*/
public HttpExchangeAdapter getHttpExchangeAdapter() {
return this.delegate;
}
@Override
public boolean supportsRequestAttributes() {
return this.delegate.supportsRequestAttributes();
}
@Override
public void exchange(HttpRequestValues requestValues) {
this.delegate.exchange(requestValues);
}
@Override
public HttpHeaders exchangeForHeaders(HttpRequestValues requestValues) {
return this.delegate.exchangeForHeaders(requestValues);
}
@Override
public <T> @Nullable T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return this.delegate.exchangeForBody(requestValues, bodyType);
}
@Override
public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues requestValues) {
return this.delegate.exchangeForBodilessEntity(requestValues);
}
@Override
public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return this.delegate.exchangeForEntity(requestValues, bodyType);
}
}
@@ -136,6 +136,8 @@ public final class HttpServiceProxyFactory {
private @Nullable HttpExchangeAdapter exchangeAdapter;
private Function<HttpExchangeAdapter, HttpExchangeAdapter> exchangeAdapterDecorator = Function.identity();
private final List<HttpServiceArgumentResolver> customArgumentResolvers = new ArrayList<>();
private final List<HttpRequestValues.Processor> requestValuesProcessors = new ArrayList<>();
@@ -158,6 +160,17 @@ public final class HttpServiceProxyFactory {
return this;
}
/**
* Provide a function to wrap the configured {@code HttpExchangeAdapter}.
* @param decorator a client adapted to {@link HttpExchangeAdapter}
* @return this same builder instance
* @since 7.0
*/
public Builder exchangeAdapterDecorator(Function<HttpExchangeAdapter, HttpExchangeAdapter> decorator) {
this.exchangeAdapterDecorator = this.exchangeAdapterDecorator.andThen(decorator);
return this;
}
/**
* Register a custom argument resolver, invoked ahead of default resolvers.
* @param resolver the resolver to add
@@ -207,9 +220,10 @@ public final class HttpServiceProxyFactory {
*/
public HttpServiceProxyFactory build() {
Assert.notNull(this.exchangeAdapter, "HttpClientAdapter is required");
HttpExchangeAdapter adapterToUse = this.exchangeAdapterDecorator.apply(this.exchangeAdapter);
return new HttpServiceProxyFactory(
this.exchangeAdapter, initArgumentResolvers(), this.requestValuesProcessors,
adapterToUse, initArgumentResolvers(), this.requestValuesProcessors,
this.embeddedValueResolver);
}
@@ -0,0 +1,129 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.service.invoker;
import java.time.Duration;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
/**
* {@link ReactorHttpExchangeAdapter} that wraps and delegates to another adapter instance.
*
* @author Rossen Stoyanchev
* @since 7.0
*/
public class ReactorHttpExchangeAdapterDecorator extends HttpExchangeAdapterDecorator
implements ReactorHttpExchangeAdapter {
public ReactorHttpExchangeAdapterDecorator(HttpExchangeAdapter delegate) {
super(delegate);
}
/**
* Return the wrapped delgate {@code HttpExchangeAdapter}.
*/
@Override
public ReactorHttpExchangeAdapter getHttpExchangeAdapter() {
return (ReactorHttpExchangeAdapter) super.getHttpExchangeAdapter();
}
@Override
public boolean supportsRequestAttributes() {
return getHttpExchangeAdapter().supportsRequestAttributes();
}
@Override
public void exchange(HttpRequestValues requestValues) {
getHttpExchangeAdapter().exchange(requestValues);
}
@Override
public HttpHeaders exchangeForHeaders(HttpRequestValues requestValues) {
return getHttpExchangeAdapter().exchangeForHeaders(requestValues);
}
@Override
public <T> @Nullable T exchangeForBody(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return getHttpExchangeAdapter().exchangeForBody(requestValues, bodyType);
}
@Override
public ResponseEntity<Void> exchangeForBodilessEntity(HttpRequestValues requestValues) {
return getHttpExchangeAdapter().exchangeForBodilessEntity(requestValues);
}
@Override
public <T> ResponseEntity<T> exchangeForEntity(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return getHttpExchangeAdapter().exchangeForEntity(requestValues, bodyType);
}
@Override
public ReactiveAdapterRegistry getReactiveAdapterRegistry() {
return getHttpExchangeAdapter().getReactiveAdapterRegistry();
}
@Override
public @Nullable Duration getBlockTimeout() {
return getHttpExchangeAdapter().getBlockTimeout();
}
@Override
public Mono<Void> exchangeForMono(HttpRequestValues requestValues) {
return getHttpExchangeAdapter().exchangeForMono(requestValues);
}
@Override
public Mono<HttpHeaders> exchangeForHeadersMono(HttpRequestValues requestValues) {
return getHttpExchangeAdapter().exchangeForHeadersMono(requestValues);
}
@Override
public <T> Mono<T> exchangeForBodyMono(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return getHttpExchangeAdapter().exchangeForBodyMono(requestValues, bodyType);
}
@Override
public <T> Flux<T> exchangeForBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) {
return getHttpExchangeAdapter().exchangeForBodyFlux(requestValues, bodyType);
}
@Override
public Mono<ResponseEntity<Void>> exchangeForBodilessEntityMono(HttpRequestValues values) {
return getHttpExchangeAdapter().exchangeForBodilessEntityMono(values);
}
@Override
public <T> Mono<ResponseEntity<T>> exchangeForEntityMono(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) {
return getHttpExchangeAdapter().exchangeForEntityMono(values, bodyType);
}
@Override
public <T> Mono<ResponseEntity<Flux<T>>> exchangeForEntityFlux(HttpRequestValues values, ParameterizedTypeReference<T> bodyType) {
return getHttpExchangeAdapter().exchangeForEntityFlux(values, bodyType);
}
}