Browse Source

Add HttpRequestValues.Processor

Closes gh-34699
pull/34769/head
rstoyanchev 9 months ago
parent
commit
40853825dc
  1. 29
      spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java
  2. 7
      spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java
  3. 38
      spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java
  4. 3
      spring-web/src/main/java/org/springframework/web/service/invoker/ReactiveHttpRequestValues.java
  5. 23
      spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java

29
spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.web.service.invoker;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
@ -176,6 +177,9 @@ public class HttpRequestValues { @@ -176,6 +177,9 @@ public class HttpRequestValues {
}
/**
* Return a builder for {@link HttpRequestValues}.
*/
public static Builder builder() {
return new Builder();
}
@ -209,6 +213,28 @@ public class HttpRequestValues { @@ -209,6 +213,28 @@ public class HttpRequestValues {
}
/**
* A contract that allows further customization of {@link HttpRequestValues}
* in addition to those added by argument resolvers.
* <p>Use {@link HttpServiceProxyFactory.Builder#httpRequestValuesProcessor(Processor)}
* to add such a processor.
* @since 7.0
*/
public interface Processor {
/**
* Invoked after argument resolvers have been called, and before the
* {@link HttpRequestValues} is built.
* @param method the {@code @HttpExchange} method
* @param arguments the raw argument values to the method
* @param builder the builder to add request values too; the builder
* also exposes method {@link Metadata} from the {@code HttpExchange} method.
*/
void process(Method method, @Nullable Object[] arguments, Builder builder);
}
/**
* Builder for {@link HttpRequestValues}.
*/
@ -238,6 +264,9 @@ public class HttpRequestValues { @@ -238,6 +264,9 @@ public class HttpRequestValues {
private @Nullable Object bodyValue;
protected Builder() {
}
/**
* Set the HTTP method for the request.
*/

7
spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java

@ -77,6 +77,8 @@ final class HttpServiceMethod { @@ -77,6 +77,8 @@ final class HttpServiceMethod {
private final List<HttpServiceArgumentResolver> argumentResolvers;
private final HttpRequestValues.Processor requestValuesProcessor;
private final HttpRequestValuesInitializer requestValuesInitializer;
private final ResponseFunction responseFunction;
@ -84,11 +86,13 @@ final class HttpServiceMethod { @@ -84,11 +86,13 @@ final class HttpServiceMethod {
HttpServiceMethod(
Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers,
HttpExchangeAdapter adapter, @Nullable StringValueResolver embeddedValueResolver) {
HttpRequestValues.Processor valuesProcessor, HttpExchangeAdapter adapter,
@Nullable StringValueResolver embeddedValueResolver) {
this.method = method;
this.parameters = initMethodParameters(method);
this.argumentResolvers = argumentResolvers;
this.requestValuesProcessor = valuesProcessor;
boolean isReactorAdapter = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter);
@ -129,6 +133,7 @@ final class HttpServiceMethod { @@ -129,6 +133,7 @@ final class HttpServiceMethod {
public @Nullable Object invoke(@Nullable Object[] arguments) {
HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder();
applyArguments(requestValues, arguments);
this.requestValuesProcessor.process(this.method, arguments, requestValues);
return this.responseFunction.execute(requestValues.build());
}

38
spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java

@ -58,15 +58,19 @@ public final class HttpServiceProxyFactory { @@ -58,15 +58,19 @@ public final class HttpServiceProxyFactory {
private final List<HttpServiceArgumentResolver> argumentResolvers;
private final HttpRequestValues.Processor requestValuesProcessor;
private final @Nullable StringValueResolver embeddedValueResolver;
private HttpServiceProxyFactory(
HttpExchangeAdapter exchangeAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
List<HttpRequestValues.Processor> requestValuesProcessor,
@Nullable StringValueResolver embeddedValueResolver) {
this.exchangeAdapter = exchangeAdapter;
this.argumentResolvers = argumentResolvers;
this.requestValuesProcessor = new CompositeHttpRequestValuesProcessor(requestValuesProcessor);
this.embeddedValueResolver = embeddedValueResolver;
}
@ -97,7 +101,8 @@ public final class HttpServiceProxyFactory { @@ -97,7 +101,8 @@ public final class HttpServiceProxyFactory {
"No argument resolvers: afterPropertiesSet was not called");
return new HttpServiceMethod(
method, serviceType, this.argumentResolvers, this.exchangeAdapter, this.embeddedValueResolver);
method, serviceType, this.argumentResolvers, this.requestValuesProcessor,
this.exchangeAdapter, this.embeddedValueResolver);
}
@ -126,6 +131,8 @@ public final class HttpServiceProxyFactory { @@ -126,6 +131,8 @@ public final class HttpServiceProxyFactory {
private final List<HttpServiceArgumentResolver> customArgumentResolvers = new ArrayList<>();
private final List<HttpRequestValues.Processor> requestValuesProcessors = new ArrayList<>();
private @Nullable ConversionService conversionService;
private @Nullable StringValueResolver embeddedValueResolver;
@ -154,6 +161,18 @@ public final class HttpServiceProxyFactory { @@ -154,6 +161,18 @@ public final class HttpServiceProxyFactory {
return this;
}
/**
* Register an {@link HttpRequestValues} processor that can further
* customize request values based on the method and all arguments.
* @param processor the processor to add
* @return this same builder instance
* @since 7.0
*/
public Builder httpRequestValuesProcessor(HttpRequestValues.Processor processor) {
this.requestValuesProcessors.add(processor);
return this;
}
/**
* Set the {@link ConversionService} to use where input values need to
* be formatted as Strings.
@ -183,7 +202,8 @@ public final class HttpServiceProxyFactory { @@ -183,7 +202,8 @@ public final class HttpServiceProxyFactory {
Assert.notNull(this.exchangeAdapter, "HttpClientAdapter is required");
return new HttpServiceProxyFactory(
this.exchangeAdapter, initArgumentResolvers(), this.embeddedValueResolver);
this.exchangeAdapter, initArgumentResolvers(), this.requestValuesProcessors,
this.embeddedValueResolver);
}
@SuppressWarnings({"DataFlowIssue", "NullAway"})
@ -251,7 +271,21 @@ public final class HttpServiceProxyFactory { @@ -251,7 +271,21 @@ public final class HttpServiceProxyFactory {
System.arraycopy(args, 0, functionArgs, 0, args.length - 1);
return functionArgs;
}
}
/**
* Processor that delegates to a list of other processors.
*/
private record CompositeHttpRequestValuesProcessor(List<HttpRequestValues.Processor> processors)
implements HttpRequestValues.Processor {
@Override
public void process(Method method, @Nullable Object[] arguments, HttpRequestValues.Builder builder) {
for (HttpRequestValues.Processor processor : this.processors) {
processor.process(method, arguments, builder);
}
}
}
}

3
spring-web/src/main/java/org/springframework/web/service/invoker/ReactiveHttpRequestValues.java

@ -94,6 +94,9 @@ public final class ReactiveHttpRequestValues extends HttpRequestValues { @@ -94,6 +94,9 @@ public final class ReactiveHttpRequestValues extends HttpRequestValues {
private @Nullable ParameterizedTypeReference<?> bodyElementType;
private Builder() {
}
@Override
public Builder setHttpMethod(HttpMethod httpMethod) {
super.setHttpMethod(httpMethod);

23
spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java

@ -198,12 +198,12 @@ class HttpServiceMethodTests { @@ -198,12 +198,12 @@ class HttpServiceMethodTests {
@Test
void typeAndMethodAnnotatedService() {
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder()
.exchangeAdapter(this.client)
.embeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
.build();
MethodLevelAnnotatedService service = proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
MethodLevelAnnotatedService service = HttpServiceProxyFactory.builder()
.exchangeAdapter(this.client)
.embeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
.build()
.createClient(TypeAndMethodLevelAnnotatedService.class);
service.performGet();
@ -222,6 +222,19 @@ class HttpServiceMethodTests { @@ -222,6 +222,19 @@ class HttpServiceMethodTests {
assertThat(requestValues.getHeaders().getAccept()).containsOnly(MediaType.APPLICATION_JSON);
}
@Test
void httpRequestValuesProcessor() {
HttpServiceProxyFactory.builder()
.exchangeAdapter(this.client)
.httpRequestValuesProcessor((m, a, builder) -> builder.addAttribute("foo", "a"))
.build()
.createClient(Service.class)
.execute();
assertThat(this.client.getRequestValues().getAttributes().get("foo")).isEqualTo("a");
}
@Test // gh-32049
void multipleAnnotationsAtClassLevel() {
Class<?> serviceInterface = MultipleClassLevelAnnotationsService.class;

Loading…
Cancel
Save