Skip to content

Commit 4085382

Browse files
committed
Add HttpRequestValues.Processor
Closes gh-34699
1 parent 88e773a commit 4085382

File tree

5 files changed

+92
-8
lines changed

5 files changed

+92
-8
lines changed

Diff for: spring-web/src/main/java/org/springframework/web/service/invoker/HttpRequestValues.java

+29
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.service.invoker;
1818

19+
import java.lang.reflect.Method;
1920
import java.net.URI;
2021
import java.util.Collections;
2122
import java.util.HashMap;
@@ -176,6 +177,9 @@ public Map<String, Object> getAttributes() {
176177
}
177178

178179

180+
/**
181+
* Return a builder for {@link HttpRequestValues}.
182+
*/
179183
public static Builder builder() {
180184
return new Builder();
181185
}
@@ -209,6 +213,28 @@ public interface Metadata {
209213
}
210214

211215

216+
/**
217+
* A contract that allows further customization of {@link HttpRequestValues}
218+
* in addition to those added by argument resolvers.
219+
* <p>Use {@link HttpServiceProxyFactory.Builder#httpRequestValuesProcessor(Processor)}
220+
* to add such a processor.
221+
* @since 7.0
222+
*/
223+
public interface Processor {
224+
225+
/**
226+
* Invoked after argument resolvers have been called, and before the
227+
* {@link HttpRequestValues} is built.
228+
* @param method the {@code @HttpExchange} method
229+
* @param arguments the raw argument values to the method
230+
* @param builder the builder to add request values too; the builder
231+
* also exposes method {@link Metadata} from the {@code HttpExchange} method.
232+
*/
233+
void process(Method method, @Nullable Object[] arguments, Builder builder);
234+
235+
}
236+
237+
212238
/**
213239
* Builder for {@link HttpRequestValues}.
214240
*/
@@ -238,6 +264,9 @@ public static class Builder implements Metadata {
238264

239265
private @Nullable Object bodyValue;
240266

267+
protected Builder() {
268+
}
269+
241270
/**
242271
* Set the HTTP method for the request.
243272
*/

Diff for: spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceMethod.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,22 @@ final class HttpServiceMethod {
7777

7878
private final List<HttpServiceArgumentResolver> argumentResolvers;
7979

80+
private final HttpRequestValues.Processor requestValuesProcessor;
81+
8082
private final HttpRequestValuesInitializer requestValuesInitializer;
8183

8284
private final ResponseFunction responseFunction;
8385

8486

8587
HttpServiceMethod(
8688
Method method, Class<?> containingClass, List<HttpServiceArgumentResolver> argumentResolvers,
87-
HttpExchangeAdapter adapter, @Nullable StringValueResolver embeddedValueResolver) {
89+
HttpRequestValues.Processor valuesProcessor, HttpExchangeAdapter adapter,
90+
@Nullable StringValueResolver embeddedValueResolver) {
8891

8992
this.method = method;
9093
this.parameters = initMethodParameters(method);
9194
this.argumentResolvers = argumentResolvers;
95+
this.requestValuesProcessor = valuesProcessor;
9296

9397
boolean isReactorAdapter = (REACTOR_PRESENT && adapter instanceof ReactorHttpExchangeAdapter);
9498

@@ -129,6 +133,7 @@ public Method getMethod() {
129133
public @Nullable Object invoke(@Nullable Object[] arguments) {
130134
HttpRequestValues.Builder requestValues = this.requestValuesInitializer.initializeRequestValuesBuilder();
131135
applyArguments(requestValues, arguments);
136+
this.requestValuesProcessor.process(this.method, arguments, requestValues);
132137
return this.responseFunction.execute(requestValues.build());
133138
}
134139

Diff for: spring-web/src/main/java/org/springframework/web/service/invoker/HttpServiceProxyFactory.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,19 @@ public final class HttpServiceProxyFactory {
5858

5959
private final List<HttpServiceArgumentResolver> argumentResolvers;
6060

61+
private final HttpRequestValues.Processor requestValuesProcessor;
62+
6163
private final @Nullable StringValueResolver embeddedValueResolver;
6264

6365

6466
private HttpServiceProxyFactory(
6567
HttpExchangeAdapter exchangeAdapter, List<HttpServiceArgumentResolver> argumentResolvers,
68+
List<HttpRequestValues.Processor> requestValuesProcessor,
6669
@Nullable StringValueResolver embeddedValueResolver) {
6770

6871
this.exchangeAdapter = exchangeAdapter;
6972
this.argumentResolvers = argumentResolvers;
73+
this.requestValuesProcessor = new CompositeHttpRequestValuesProcessor(requestValuesProcessor);
7074
this.embeddedValueResolver = embeddedValueResolver;
7175
}
7276

@@ -97,7 +101,8 @@ private <S> HttpServiceMethod createHttpServiceMethod(Class<S> serviceType, Meth
97101
"No argument resolvers: afterPropertiesSet was not called");
98102

99103
return new HttpServiceMethod(
100-
method, serviceType, this.argumentResolvers, this.exchangeAdapter, this.embeddedValueResolver);
104+
method, serviceType, this.argumentResolvers, this.requestValuesProcessor,
105+
this.exchangeAdapter, this.embeddedValueResolver);
101106
}
102107

103108

@@ -126,6 +131,8 @@ public static final class Builder {
126131

127132
private final List<HttpServiceArgumentResolver> customArgumentResolvers = new ArrayList<>();
128133

134+
private final List<HttpRequestValues.Processor> requestValuesProcessors = new ArrayList<>();
135+
129136
private @Nullable ConversionService conversionService;
130137

131138
private @Nullable StringValueResolver embeddedValueResolver;
@@ -154,6 +161,18 @@ public Builder customArgumentResolver(HttpServiceArgumentResolver resolver) {
154161
return this;
155162
}
156163

164+
/**
165+
* Register an {@link HttpRequestValues} processor that can further
166+
* customize request values based on the method and all arguments.
167+
* @param processor the processor to add
168+
* @return this same builder instance
169+
* @since 7.0
170+
*/
171+
public Builder httpRequestValuesProcessor(HttpRequestValues.Processor processor) {
172+
this.requestValuesProcessors.add(processor);
173+
return this;
174+
}
175+
157176
/**
158177
* Set the {@link ConversionService} to use where input values need to
159178
* be formatted as Strings.
@@ -183,7 +202,8 @@ public HttpServiceProxyFactory build() {
183202
Assert.notNull(this.exchangeAdapter, "HttpClientAdapter is required");
184203

185204
return new HttpServiceProxyFactory(
186-
this.exchangeAdapter, initArgumentResolvers(), this.embeddedValueResolver);
205+
this.exchangeAdapter, initArgumentResolvers(), this.requestValuesProcessors,
206+
this.embeddedValueResolver);
187207
}
188208

189209
@SuppressWarnings({"DataFlowIssue", "NullAway"})
@@ -251,7 +271,21 @@ private static Object[] resolveCoroutinesArguments(@Nullable Object[] args) {
251271
System.arraycopy(args, 0, functionArgs, 0, args.length - 1);
252272
return functionArgs;
253273
}
274+
}
275+
276+
277+
/**
278+
* Processor that delegates to a list of other processors.
279+
*/
280+
private record CompositeHttpRequestValuesProcessor(List<HttpRequestValues.Processor> processors)
281+
implements HttpRequestValues.Processor {
254282

283+
@Override
284+
public void process(Method method, @Nullable Object[] arguments, HttpRequestValues.Builder builder) {
285+
for (HttpRequestValues.Processor processor : this.processors) {
286+
processor.process(method, arguments, builder);
287+
}
288+
}
255289
}
256290

257291
}

Diff for: spring-web/src/main/java/org/springframework/web/service/invoker/ReactiveHttpRequestValues.java

+3
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ public static final class Builder extends HttpRequestValues.Builder {
9494

9595
private @Nullable ParameterizedTypeReference<?> bodyElementType;
9696

97+
private Builder() {
98+
}
99+
97100
@Override
98101
public Builder setHttpMethod(HttpMethod httpMethod) {
99102
super.setHttpMethod(httpMethod);

Diff for: spring-web/src/test/java/org/springframework/web/service/invoker/HttpServiceMethodTests.java

+18-5
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,12 @@ void methodAnnotatedService() {
198198

199199
@Test
200200
void typeAndMethodAnnotatedService() {
201-
HttpServiceProxyFactory proxyFactory = HttpServiceProxyFactory.builder()
202-
.exchangeAdapter(this.client)
203-
.embeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
204-
.build();
205201

206-
MethodLevelAnnotatedService service = proxyFactory.createClient(TypeAndMethodLevelAnnotatedService.class);
202+
MethodLevelAnnotatedService service = HttpServiceProxyFactory.builder()
203+
.exchangeAdapter(this.client)
204+
.embeddedValueResolver(value -> (value.equals("${baseUrl}") ? "/base" : value))
205+
.build()
206+
.createClient(TypeAndMethodLevelAnnotatedService.class);
207207

208208
service.performGet();
209209

@@ -222,6 +222,19 @@ void typeAndMethodAnnotatedService() {
222222
assertThat(requestValues.getHeaders().getAccept()).containsOnly(MediaType.APPLICATION_JSON);
223223
}
224224

225+
@Test
226+
void httpRequestValuesProcessor() {
227+
228+
HttpServiceProxyFactory.builder()
229+
.exchangeAdapter(this.client)
230+
.httpRequestValuesProcessor((m, a, builder) -> builder.addAttribute("foo", "a"))
231+
.build()
232+
.createClient(Service.class)
233+
.execute();
234+
235+
assertThat(this.client.getRequestValues().getAttributes().get("foo")).isEqualTo("a");
236+
}
237+
225238
@Test // gh-32049
226239
void multipleAnnotationsAtClassLevel() {
227240
Class<?> serviceInterface = MultipleClassLevelAnnotationsService.class;

0 commit comments

Comments
 (0)