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 @@
package org.springframework.web.service.invoker; package org.springframework.web.service.invoker;
import java.lang.reflect.Method;
import java.net.URI; import java.net.URI;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -176,6 +177,9 @@ public class HttpRequestValues {
} }
/**
* Return a builder for {@link HttpRequestValues}.
*/
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
} }
@ -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}. * Builder for {@link HttpRequestValues}.
*/ */
@ -238,6 +264,9 @@ public class HttpRequestValues {
private @Nullable Object bodyValue; private @Nullable Object bodyValue;
protected Builder() {
}
/** /**
* Set the HTTP method for the request. * 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 {
private final List<HttpServiceArgumentResolver> argumentResolvers; private final List<HttpServiceArgumentResolver> argumentResolvers;
private final HttpRequestValues.Processor requestValuesProcessor;
private final HttpRequestValuesInitializer requestValuesInitializer; private final HttpRequestValuesInitializer requestValuesInitializer;
private final ResponseFunction responseFunction; private final ResponseFunction responseFunction;
@ -84,11 +86,13 @@ final class HttpServiceMethod {
HttpServiceMethod( HttpServiceMethod(
Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers, Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers,
HttpExchangeAdapter adapter, @Nullable StringValueResolver embeddedValueResolver) { HttpRequestValues.Processor valuesProcessor, HttpExchangeAdapter adapter,
@Nullable StringValueResolver embeddedValueResolver) {
this.method = method; this.method = method;
this.parameters = initMethodParameters(method); this.parameters = initMethodParameters(method);
this.argumentResolvers = argumentResolvers; this.argumentResolvers = argumentResolvers;
this.requestValuesProcessor = valuesProcessor;
boolean isReactorAdapter = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter); boolean isReactorAdapter = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter);
@ -129,6 +133,7 @@ final class HttpServiceMethod {
public @Nullable Object invoke(@Nullable Object[] arguments) { public @Nullable Object invoke(@Nullable Object[] arguments) {
HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder(); HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder();
applyArguments(requestValues, arguments); applyArguments(requestValues, arguments);
this.requestValuesProcessor.process(this.method, arguments, requestValues);
return this.responseFunction.execute(requestValues.build()); 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 {
private final List<HttpServiceArgumentResolver> argumentResolvers; private final List<HttpServiceArgumentResolver> argumentResolvers;
private final HttpRequestValues.Processor requestValuesProcessor;
private final @Nullable StringValueResolver embeddedValueResolver; private final @Nullable StringValueResolver embeddedValueResolver;
private HttpServiceProxyFactory( private HttpServiceProxyFactory(
HttpExchangeAdapter exchangeAdapter, List<HttpServiceArgumentResolver> argumentResolvers, HttpExchangeAdapter exchangeAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
List<HttpRequestValues.Processor> requestValuesProcessor,
@Nullable StringValueResolver embeddedValueResolver) { @Nullable StringValueResolver embeddedValueResolver) {
this.exchangeAdapter = exchangeAdapter; this.exchangeAdapter = exchangeAdapter;
this.argumentResolvers = argumentResolvers; this.argumentResolvers = argumentResolvers;
this.requestValuesProcessor = new CompositeHttpRequestValuesProcessor(requestValuesProcessor);
this.embeddedValueResolver = embeddedValueResolver; this.embeddedValueResolver = embeddedValueResolver;
} }
@ -97,7 +101,8 @@ public final class HttpServiceProxyFactory {
"No argument resolvers: afterPropertiesSet was not called"); "No argument resolvers: afterPropertiesSet was not called");
return new HttpServiceMethod( 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 {
private final List<HttpServiceArgumentResolver> customArgumentResolvers = new ArrayList<>(); private final List<HttpServiceArgumentResolver> customArgumentResolvers = new ArrayList<>();
private final List<HttpRequestValues.Processor> requestValuesProcessors = new ArrayList<>();
private @Nullable ConversionService conversionService; private @Nullable ConversionService conversionService;
private @Nullable StringValueResolver embeddedValueResolver; private @Nullable StringValueResolver embeddedValueResolver;
@ -154,6 +161,18 @@ public final class HttpServiceProxyFactory {
return this; 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 * Set the {@link ConversionService} to use where input values need to
* be formatted as Strings. * be formatted as Strings.
@ -183,7 +202,8 @@ public final class HttpServiceProxyFactory {
Assert.notNull(this.exchangeAdapter, "HttpClientAdapter is required"); Assert.notNull(this.exchangeAdapter, "HttpClientAdapter is required");
return new HttpServiceProxyFactory( return new HttpServiceProxyFactory(
this.exchangeAdapter, initArgumentResolvers(), this.embeddedValueResolver); this.exchangeAdapter, initArgumentResolvers(), this.requestValuesProcessors,
this.embeddedValueResolver);
} }
@SuppressWarnings({"DataFlowIssue", "NullAway"}) @SuppressWarnings({"DataFlowIssue", "NullAway"})
@ -251,7 +271,21 @@ public final class HttpServiceProxyFactory {
System.arraycopy(args, 0, functionArgs, 0, args.length - 1); System.arraycopy(args, 0, functionArgs, 0, args.length - 1);
return functionArgs; 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 {
private @Nullable ParameterizedTypeReference<?> bodyElementType; private @Nullable ParameterizedTypeReference<?> bodyElementType;
private Builder() {
}
@Override @Override
public Builder setHttpMethod(HttpMethod httpMethod) { public Builder setHttpMethod(HttpMethod httpMethod) {
super.setHttpMethod(httpMethod); super.setHttpMethod(httpMethod);

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

@ -198,12 +198,12 @@ class HttpServiceMethodTests {
@Test @Test
void typeAndMethodAnnotatedService() { 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(); service.performGet();
@ -222,6 +222,19 @@ class HttpServiceMethodTests {
assertThat(requestValues.getHeaders().getAccept()).containsOnly(MediaType.APPLICATION_JSON); 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 @Test // gh-32049
void multipleAnnotationsAtClassLevel() { void multipleAnnotationsAtClassLevel() {
Class<?> serviceInterface = MultipleClassLevelAnnotationsService.class; Class<?> serviceInterface = MultipleClassLevelAnnotationsService.class;

Loading…
Cancel
Save