Skip to content

Commit 23160a4

Browse files
committed
Conditional use of URI vars for data binding in WebFlux
This commit aligns Spring WebFlux with WebMvc in adding URI variables conditionally if not already in the map. The idea is that data binding is primarily through form data and query params, with URI variables, and request headers (to be implemented next) also made available but without shadowing existing values. See gh-32676
1 parent 398aae2 commit 23160a4

File tree

2 files changed

+49
-5
lines changed

2 files changed

+49
-5
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.util.Collection;
21-
import java.util.Collections;
2221
import java.util.Map;
2322

2423
import reactor.core.publisher.Mono;
@@ -29,6 +28,7 @@
2928
import org.springframework.core.ResolvableType;
3029
import org.springframework.lang.Nullable;
3130
import org.springframework.ui.Model;
31+
import org.springframework.util.CollectionUtils;
3232
import org.springframework.validation.BindingResult;
3333
import org.springframework.validation.DataBinder;
3434
import org.springframework.validation.SmartValidator;
@@ -209,10 +209,29 @@ public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName)
209209

210210
@Override
211211
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
212-
return super.getValuesToBind(exchange).doOnNext(map ->
213-
map.putAll(exchange.<Map<String, String>>getAttributeOrDefault(
214-
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap())));
212+
return super.getValuesToBind(exchange).doOnNext(map -> {
213+
Map<String, String> vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
214+
if (!CollectionUtils.isEmpty(vars)) {
215+
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
216+
}
217+
});
215218
}
219+
220+
private static void addValueIfNotPresent(
221+
Map<String, Object> map, String label, String name, @Nullable Object value) {
222+
223+
if (value != null) {
224+
if (map.containsKey(name)) {
225+
if (logger.isDebugEnabled()) {
226+
logger.debug(label + " '" + name + "' overridden by request bind value.");
227+
}
228+
}
229+
else {
230+
map.put(name, value);
231+
}
232+
}
233+
}
234+
216235
}
217236

218237

spring-webflux/src/test/java/org/springframework/web/reactive/BindingContextTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@
1717
package org.springframework.web.reactive;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.Map;
2021

2122
import jakarta.validation.Valid;
2223
import org.junit.jupiter.api.Test;
2324

25+
import org.springframework.beans.testfixture.beans.TestBean;
2426
import org.springframework.core.ResolvableType;
27+
import org.springframework.http.MediaType;
2528
import org.springframework.validation.Errors;
2629
import org.springframework.validation.SmartValidator;
2730
import org.springframework.validation.Validator;
2831
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
2932
import org.springframework.web.bind.WebDataBinder;
33+
import org.springframework.web.bind.support.WebExchangeDataBinder;
3034
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
3135
import org.springframework.web.testfixture.server.MockServerWebExchange;
3236

@@ -64,6 +68,27 @@ void jakartaValidatorExcludedWhenMethodValidationApplicable() throws Exception {
6468
assertThat(binder.getValidatorsToApply()).containsExactly(springValidator);
6569
}
6670

71+
@Test
72+
void uriVariablesAddedConditionally() {
73+
74+
MockServerHttpRequest request = MockServerHttpRequest.post("/path")
75+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
76+
.body("name=John&age=25");
77+
78+
MockServerWebExchange exchange = MockServerWebExchange.from(request);
79+
exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Map.of("age", "26"));
80+
81+
TestBean testBean = new TestBean();
82+
83+
BindingContext bindingContext = new BindingContext(null);
84+
WebExchangeDataBinder binder = bindingContext.createDataBinder(exchange, testBean, "testBean", null);
85+
86+
binder.bind(exchange).block();
87+
88+
assertThat(testBean.getName()).isEqualTo("John");
89+
assertThat(testBean.getAge()).isEqualTo(25);
90+
}
91+
6792

6893
@SuppressWarnings("unused")
6994
private void handleValidObject(@Valid Foo foo) {

0 commit comments

Comments
 (0)