Skip to content

Commit c681033

Browse files
committed
Move extended web data binders to common support packages
This resolves cyclic dependencies between function and annotation-based packages. See gh-35800
1 parent 3b90311 commit c681033

File tree

10 files changed

+376
-264
lines changed

10 files changed

+376
-264
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
import org.springframework.web.reactive.function.BodyExtractor;
6262
import org.springframework.web.reactive.function.BodyExtractors;
6363
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
64-
import org.springframework.web.reactive.result.method.annotation.ExtendedWebExchangeDataBinder;
64+
import org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder;
6565
import org.springframework.web.server.ServerWebExchange;
6666
import org.springframework.web.server.ServerWebInputException;
6767
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2002-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.result;
18+
19+
import java.util.List;
20+
import java.util.Locale;
21+
import java.util.Map;
22+
import java.util.Set;
23+
import java.util.function.Predicate;
24+
25+
import org.jspecify.annotations.Nullable;
26+
import reactor.core.publisher.Mono;
27+
28+
import org.springframework.http.HttpHeaders;
29+
import org.springframework.util.CollectionUtils;
30+
import org.springframework.util.StringUtils;
31+
import org.springframework.web.bind.support.WebExchangeDataBinder;
32+
import org.springframework.web.reactive.HandlerMapping;
33+
import org.springframework.web.server.ServerWebExchange;
34+
35+
/**
36+
* Extended variant of {@link WebExchangeDataBinder} that adds URI path variables
37+
* and request headers to the bind values map.
38+
*
39+
* @author Rossen Stoyanchev
40+
* @author Juergen Hoeller
41+
* @since 7.0.2
42+
* @see WebExchangeDataBinder
43+
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
44+
*/
45+
public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
46+
47+
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("accept", "authorization", "connection",
48+
"cookie", "from", "host", "origin", "priority", "range", "referer", "upgrade");
49+
50+
51+
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
52+
53+
54+
/**
55+
* Create a new instance, with default object name.
56+
* @param target the target object to bind onto (or {@code null} if the
57+
* binder is just used to convert a plain parameter value)
58+
* @see #DEFAULT_OBJECT_NAME
59+
*/
60+
public ExtendedWebExchangeDataBinder(@Nullable Object target) {
61+
super(target);
62+
}
63+
64+
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
65+
super(target, objectName);
66+
}
67+
68+
69+
/**
70+
* Add a Predicate that filters the header names to use for data binding.
71+
* Multiple predicates are combined with {@code AND}.
72+
* @param headerPredicate the predicate to add
73+
* @since 6.2.1
74+
*/
75+
public void addHeaderPredicate(Predicate<String> headerPredicate) {
76+
this.headerPredicate = this.headerPredicate.and(headerPredicate);
77+
}
78+
79+
/**
80+
* Set the Predicate that filters the header names to use for data binding.
81+
* <p>Note that this method resets any previous predicates that may have been
82+
* set, including headers excluded by default such as the RFC 9218 defined
83+
* "Priority" header.
84+
* @param headerPredicate the predicate to add
85+
* @since 6.2.1
86+
*/
87+
public void setHeaderPredicate(Predicate<String> headerPredicate) {
88+
this.headerPredicate = headerPredicate;
89+
}
90+
91+
92+
@Override
93+
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
94+
return super.getValuesToBind(exchange).doOnNext(map -> {
95+
Map<String, String> vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
96+
if (!CollectionUtils.isEmpty(vars)) {
97+
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
98+
}
99+
HttpHeaders headers = exchange.getRequest().getHeaders();
100+
for (Map.Entry<String, List<String>> entry : headers.headerSet()) {
101+
String name = entry.getKey();
102+
if (!this.headerPredicate.test(entry.getKey())) {
103+
continue;
104+
}
105+
List<String> values = entry.getValue();
106+
if (!CollectionUtils.isEmpty(values)) {
107+
// For constructor args with @BindParam mapped to the actual header name
108+
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
109+
// Also adapt to Java conventions for setters
110+
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));
111+
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
112+
}
113+
}
114+
});
115+
}
116+
117+
private static void addValueIfNotPresent(
118+
Map<String, Object> map, String label, String name, @Nullable Object value) {
119+
120+
if (value != null) {
121+
if (map.containsKey(name)) {
122+
if (logger.isDebugEnabled()) {
123+
logger.debug(label + " '" + name + "' overridden by request bind value.");
124+
}
125+
}
126+
else {
127+
map.put(name, value);
128+
}
129+
}
130+
}
131+
132+
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExtendedWebExchangeDataBinder.java

Lines changed: 4 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,9 @@
1616

1717
package org.springframework.web.reactive.result.method.annotation;
1818

19-
import java.util.List;
20-
import java.util.Locale;
21-
import java.util.Map;
22-
import java.util.Set;
23-
import java.util.function.Predicate;
24-
2519
import org.jspecify.annotations.Nullable;
26-
import reactor.core.publisher.Mono;
2720

28-
import org.springframework.http.HttpHeaders;
29-
import org.springframework.util.CollectionUtils;
30-
import org.springframework.util.StringUtils;
3121
import org.springframework.web.bind.support.WebExchangeDataBinder;
32-
import org.springframework.web.reactive.HandlerMapping;
33-
import org.springframework.web.server.ServerWebExchange;
3422

3523
/**
3624
* Extended variant of {@link WebExchangeDataBinder} that adds URI path variables
@@ -41,93 +29,14 @@
4129
*
4230
* @author Rossen Stoyanchev
4331
* @since 6.2.1
32+
* @deprecated in favor of the relocated
33+
* {@link org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder}
4434
*/
45-
public class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
46-
47-
private static final Set<String> FILTERED_HEADER_NAMES = Set.of("accept", "authorization", "connection",
48-
"cookie", "from", "host", "origin", "priority", "range", "referer", "upgrade");
49-
50-
51-
private Predicate<String> headerPredicate = name -> !FILTERED_HEADER_NAMES.contains(name.toLowerCase(Locale.ROOT));
52-
53-
54-
/**
55-
* Create a new instance, with default object name.
56-
* @param target the target object to bind onto (or {@code null} if the
57-
* binder is just used to convert a plain parameter value)
58-
* @since 7.0.2
59-
* @see #DEFAULT_OBJECT_NAME
60-
*/
61-
public ExtendedWebExchangeDataBinder(@Nullable Object target) {
62-
super(target);
63-
}
35+
@Deprecated(since = "7.0.2", forRemoval = true)
36+
public class ExtendedWebExchangeDataBinder extends org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder {
6437

6538
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
6639
super(target, objectName);
6740
}
6841

69-
70-
/**
71-
* Add a Predicate that filters the header names to use for data binding.
72-
* Multiple predicates are combined with {@code AND}.
73-
* @param headerPredicate the predicate to add
74-
* @since 6.2.1
75-
*/
76-
public void addHeaderPredicate(Predicate<String> headerPredicate) {
77-
this.headerPredicate = this.headerPredicate.and(headerPredicate);
78-
}
79-
80-
/**
81-
* Set the Predicate that filters the header names to use for data binding.
82-
* <p>Note that this method resets any previous predicates that may have been
83-
* set, including headers excluded by default such as the RFC 9218 defined
84-
* "Priority" header.
85-
* @param headerPredicate the predicate to add
86-
* @since 6.2.1
87-
*/
88-
public void setHeaderPredicate(Predicate<String> headerPredicate) {
89-
this.headerPredicate = headerPredicate;
90-
}
91-
92-
93-
@Override
94-
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
95-
return super.getValuesToBind(exchange).doOnNext(map -> {
96-
Map<String, String> vars = exchange.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
97-
if (!CollectionUtils.isEmpty(vars)) {
98-
vars.forEach((key, value) -> addValueIfNotPresent(map, "URI variable", key, value));
99-
}
100-
HttpHeaders headers = exchange.getRequest().getHeaders();
101-
for (Map.Entry<String, List<String>> entry : headers.headerSet()) {
102-
String name = entry.getKey();
103-
if (!this.headerPredicate.test(entry.getKey())) {
104-
continue;
105-
}
106-
List<String> values = entry.getValue();
107-
if (!CollectionUtils.isEmpty(values)) {
108-
// For constructor args with @BindParam mapped to the actual header name
109-
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
110-
// Also adapt to Java conventions for setters
111-
name = StringUtils.uncapitalize(entry.getKey().replace("-", ""));
112-
addValueIfNotPresent(map, "Header", name, (values.size() == 1 ? values.get(0) : values));
113-
}
114-
}
115-
});
116-
}
117-
118-
private static void addValueIfNotPresent(
119-
Map<String, Object> map, String label, String name, @Nullable Object value) {
120-
121-
if (value != null) {
122-
if (map.containsKey(name)) {
123-
if (logger.isDebugEnabled()) {
124-
logger.debug(label + " '" + name + "' overridden by request bind value.");
125-
}
126-
}
127-
else {
128-
map.put(name, value);
129-
}
130-
}
131-
}
132-
13342
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ public SessionStatus getSessionStatus() {
7272

7373

7474
/**
75-
* Returns an instance of {@link ExtendedWebExchangeDataBinder}.
75+
* Returns an instance of {@link org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder}.
7676
* @since 6.2.1
7777
*/
7878
@Override
7979
protected WebExchangeDataBinder createBinderInstance(@Nullable Object target, String name) {
80-
return new ExtendedWebExchangeDataBinder(target, name);
80+
return new org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder(target, name);
8181
}
8282

8383
@Override

spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/InitBinderBindingContextTests.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ void headerPredicate() throws Exception {
215215
MockServerWebExchange exchange = MockServerWebExchange.from(request);
216216

217217
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
218-
ExtendedWebExchangeDataBinder binder = (ExtendedWebExchangeDataBinder) context.createDataBinder(exchange, null, "", null);
218+
org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder binder =
219+
(org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder)
220+
context.createDataBinder(exchange, null, "", null);
219221
binder.addHeaderPredicate(name -> !name.equalsIgnoreCase("Another-Int-Array"));
220222

221223
Map<String, Object> map = binder.getValuesToBind(exchange).block();
@@ -233,7 +235,9 @@ void filteredHeaders(String headerName) throws Exception {
233235
MockServerWebExchange exchange = MockServerWebExchange.from(request);
234236

235237
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
236-
ExtendedWebExchangeDataBinder binder = (ExtendedWebExchangeDataBinder) context.createDataBinder(exchange, null, "", null);
238+
org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder binder =
239+
(org.springframework.web.reactive.result.ExtendedWebExchangeDataBinder)
240+
context.createDataBinder(exchange, null, "", null);
237241

238242
Map<String, Object> map = binder.getValuesToBind(exchange).block();
239243
assertThat(map).isEmpty();

spring-webmvc/src/main/java/org/springframework/web/servlet/function/DefaultServerRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
import org.springframework.web.bind.WebDataBinder;
7777
import org.springframework.web.context.request.ServletWebRequest;
7878
import org.springframework.web.context.request.WebRequest;
79-
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
79+
import org.springframework.web.servlet.support.ExtendedServletRequestDataBinder;
8080
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
8181
import org.springframework.web.util.ServletRequestPathUtils;
8282
import org.springframework.web.util.UriBuilder;

0 commit comments

Comments
 (0)