17
17
package org .springframework .web .reactive .result .method .annotation ;
18
18
19
19
import java .lang .annotation .Annotation ;
20
- import java .lang .reflect .Constructor ;
21
- import java .util .List ;
22
20
import java .util .Map ;
23
- import java .util .Optional ;
24
21
25
22
import reactor .core .publisher .Mono ;
26
23
import reactor .core .publisher .Sinks ;
31
28
import org .springframework .core .MethodParameter ;
32
29
import org .springframework .core .ReactiveAdapter ;
33
30
import org .springframework .core .ReactiveAdapterRegistry ;
34
- import org .springframework .core .ResolvableType ;
35
31
import org .springframework .lang .Nullable ;
36
32
import org .springframework .ui .Model ;
37
33
import org .springframework .util .Assert ;
@@ -100,72 +96,74 @@ else if (this.useDefaultResolution) {
100
96
public Mono <Object > resolveArgument (
101
97
MethodParameter parameter , BindingContext context , ServerWebExchange exchange ) {
102
98
103
- ResolvableType type = ResolvableType .forMethodParameter (parameter );
104
- Class <?> resolvedType = type .resolve ();
105
- ReactiveAdapter adapter = (resolvedType != null ? getAdapterRegistry ().getAdapter (resolvedType ) : null );
106
- ResolvableType valueType = (adapter != null ? type .getGeneric () : type );
107
-
108
- Assert .state (adapter == null || !adapter .isMultiValue (),
109
- () -> getClass ().getSimpleName () + " does not support multi-value reactive type wrapper: " +
110
- parameter .getGenericParameterType ());
99
+ Class <?> resolvedType = parameter .getParameterType ();
100
+ ReactiveAdapter adapter = getAdapterRegistry ().getAdapter (resolvedType );
101
+ Assert .state (adapter == null || !adapter .isMultiValue (), "Multi-value publisher is not supported" );
111
102
112
103
String name = ModelInitializer .getNameForParameter (parameter );
113
- Mono <?> attributeMono = prepareAttributeMono (name , valueType , context , exchange );
114
104
115
- // unsafe(): we're intercepting, already serialized Publisher signals
105
+ Mono <WebExchangeDataBinder > dataBinderMono = initDataBinder (
106
+ name , (adapter != null ? parameter .nested () : parameter ), context , exchange );
107
+
108
+ // unsafe() is OK: source is Reactive Streams Publisher
116
109
Sinks .One <BindingResult > bindingResultSink = Sinks .unsafe ().one ();
117
110
118
111
Map <String , Object > model = context .getModel ().asMap ();
119
112
model .put (BindingResult .MODEL_KEY_PREFIX + name , bindingResultSink .asMono ());
120
113
121
- return attributeMono .flatMap (attribute -> {
122
- WebExchangeDataBinder binder = context .createDataBinder (exchange , attribute , name , parameter );
123
- return (!bindingDisabled (parameter ) ? bindRequestParameters (binder , exchange ) : Mono .empty ())
124
- .doOnError (bindingResultSink ::tryEmitError )
125
- .doOnSuccess (aVoid -> {
126
- validateIfApplicable (binder , parameter , exchange );
127
- BindingResult bindingResult = binder .getBindingResult ();
128
- model .put (BindingResult .MODEL_KEY_PREFIX + name , bindingResult );
129
- model .put (name , attribute );
130
- // Ignore result: serialized and buffered (should never fail)
131
- bindingResultSink .tryEmitValue (bindingResult );
132
- })
133
- .then (Mono .fromCallable (() -> {
134
- BindingResult errors = binder .getBindingResult ();
135
- if (adapter != null ) {
136
- return adapter .fromPublisher (errors .hasErrors () ?
137
- Mono .error (new WebExchangeBindException (parameter , errors )) : attributeMono );
138
- }
139
- else {
140
- if (errors .hasErrors () && !hasErrorsArgument (parameter )) {
141
- throw new WebExchangeBindException (parameter , errors );
142
- }
143
- return attribute ;
144
- }
145
- }));
146
- });
114
+ return dataBinderMono
115
+ .flatMap (binder -> {
116
+ Object attribute = binder .getTarget ();
117
+ Assert .state (attribute != null , "Expected model attribute instance" );
118
+ return (!bindingDisabled (parameter ) ? bindRequestParameters (binder , exchange ) : Mono .empty ())
119
+ .doOnError (bindingResultSink ::tryEmitError )
120
+ .doOnSuccess (aVoid -> {
121
+ validateIfApplicable (binder , parameter , exchange );
122
+ BindingResult bindingResult = binder .getBindingResult ();
123
+ model .put (BindingResult .MODEL_KEY_PREFIX + name , bindingResult );
124
+ model .put (name , attribute );
125
+ // Ignore result: serialized and buffered (should never fail)
126
+ bindingResultSink .tryEmitValue (bindingResult );
127
+ })
128
+ .then (Mono .fromCallable (() -> {
129
+ BindingResult errors = binder .getBindingResult ();
130
+ if (adapter != null ) {
131
+ Mono <Object > mono = (errors .hasErrors () ?
132
+ Mono .error (new WebExchangeBindException (parameter , errors )) :
133
+ Mono .just (attribute ));
134
+ return adapter .fromPublisher (mono );
135
+ }
136
+ else {
137
+ if (errors .hasErrors () && !hasErrorsArgument (parameter )) {
138
+ throw new WebExchangeBindException (parameter , errors );
139
+ }
140
+ return attribute ;
141
+ }
142
+ }));
143
+ });
147
144
}
148
145
149
- private Mono <?> prepareAttributeMono (
150
- String name , ResolvableType type , BindingContext context , ServerWebExchange exchange ) {
151
-
152
- Object attribute = context .getModel ().asMap ().get (name );
146
+ private Mono <WebExchangeDataBinder > initDataBinder (
147
+ String name , MethodParameter parameter , BindingContext context , ServerWebExchange exchange ) {
153
148
154
- if (attribute == null ) {
155
- attribute = removeReactiveAttribute (context .getModel (), name );
149
+ Object value = context .getModel ().asMap ().get (name );
150
+ if (value == null ) {
151
+ value = removeReactiveAttribute (name , context .getModel ());
156
152
}
157
-
158
- if (attribute == null ) {
159
- return createAttribute (name , type .toClass (), context , exchange );
153
+ if (value != null ) {
154
+ ReactiveAdapter adapter = getAdapterRegistry ().getAdapter (null , value );
155
+ Assert .isTrue (adapter == null || !adapter .isMultiValue (), "Multi-value publisher is not supported" );
156
+ return (adapter != null ? Mono .from (adapter .toPublisher (value )) : Mono .just (value ))
157
+ .map (attr -> context .createDataBinder (exchange , attr , name , parameter ));
158
+ }
159
+ else {
160
+ WebExchangeDataBinder binder = context .createDataBinder (exchange , null , name , parameter );
161
+ return constructAttribute (binder , exchange ).thenReturn (binder );
160
162
}
161
-
162
- ReactiveAdapter adapter = getAdapterRegistry ().getAdapter (null , attribute );
163
- Assert .isTrue (adapter == null || !adapter .isMultiValue (), "Model attribute must be single-value publisher" );
164
- return (adapter != null ? Mono .from (adapter .toPublisher (attribute )) : Mono .justOrEmpty (attribute ));
165
163
}
166
164
167
165
@ Nullable
168
- private Object removeReactiveAttribute (Model model , String name ) {
166
+ private Object removeReactiveAttribute (String name , Model model ) {
169
167
for (Map .Entry <String , Object > entry : model .asMap ().entrySet ()) {
170
168
if (entry .getKey ().startsWith (name )) {
171
169
ReactiveAdapter adapter = getAdapterRegistry ().getAdapter (null , entry .getValue ());
@@ -181,66 +179,24 @@ private Object removeReactiveAttribute(Model model, String name) {
181
179
return null ;
182
180
}
183
181
184
- private Mono <?> createAttribute (
185
- String attributeName , Class <?> clazz , BindingContext context , ServerWebExchange exchange ) {
186
-
187
- Constructor <?> ctor = BeanUtils .getResolvableConstructor (clazz );
188
- return constructAttribute (ctor , attributeName , context , exchange );
189
- }
190
-
191
- private Mono <?> constructAttribute (Constructor <?> ctor , String attributeName ,
192
- BindingContext context , ServerWebExchange exchange ) {
193
-
194
- if (ctor .getParameterCount () == 0 ) {
195
- // A single default constructor -> clearly a standard JavaBeans arrangement.
196
- return Mono .just (BeanUtils .instantiateClass (ctor ));
197
- }
198
-
199
- // A single data class constructor -> resolve constructor arguments from request parameters.
200
- WebExchangeDataBinder binder = context .createDataBinder (exchange , null , attributeName );
201
- return getValuesToBind (binder , exchange ).map (bindValues -> {
202
- String [] paramNames = BeanUtils .getParameterNames (ctor );
203
- Class <?>[] paramTypes = ctor .getParameterTypes ();
204
- Object [] args = new Object [paramTypes .length ];
205
- String fieldDefaultPrefix = binder .getFieldDefaultPrefix ();
206
- String fieldMarkerPrefix = binder .getFieldMarkerPrefix ();
207
- for (int i = 0 ; i < paramNames .length ; i ++) {
208
- String paramName = paramNames [i ];
209
- Class <?> paramType = paramTypes [i ];
210
- Object value = bindValues .get (paramName );
211
- if (value == null ) {
212
- if (fieldDefaultPrefix != null ) {
213
- value = bindValues .get (fieldDefaultPrefix + paramName );
214
- }
215
- if (value == null && fieldMarkerPrefix != null ) {
216
- if (bindValues .get (fieldMarkerPrefix + paramName ) != null ) {
217
- value = binder .getEmptyValue (paramType );
218
- }
219
- }
220
- }
221
- value = (value instanceof List <?> list ? list .toArray () : value );
222
- MethodParameter methodParam = MethodParameter .forFieldAwareConstructor (ctor , i , paramName );
223
- if (value == null && methodParam .isOptional ()) {
224
- args [i ] = (methodParam .getParameterType () == Optional .class ? Optional .empty () : null );
225
- }
226
- else {
227
- args [i ] = binder .convertIfNecessary (value , paramTypes [i ], methodParam );
228
- }
229
- }
230
- return BeanUtils .instantiateClass (ctor , args );
231
- });
182
+ /**
183
+ * Protected method to obtain the values for data binding.
184
+ * @deprecated and not called; replaced by built-in support for
185
+ * constructor initialization in {@link org.springframework.validation.DataBinder}
186
+ */
187
+ @ Deprecated (since = "6.1" , forRemoval = true )
188
+ public Mono <Map <String , Object >> getValuesToBind (WebExchangeDataBinder binder , ServerWebExchange exchange ) {
189
+ throw new UnsupportedOperationException ();
232
190
}
233
191
234
192
/**
235
- * Protected method to obtain the values for data binding. By default this
236
- * method delegates to {@link WebExchangeDataBinder#getValuesToBind}.
237
- * @param binder the data binder in use
193
+ * Extension point to create the attribute, binding the request to constructor args.
194
+ * @param binder the data binder instance to use for the binding
238
195
* @param exchange the current exchange
239
- * @return a map of bind values
240
- * @since 5.3
196
+ * @since 6.1
241
197
*/
242
- public Mono <Map < String , Object >> getValuesToBind (WebExchangeDataBinder binder , ServerWebExchange exchange ) {
243
- return binder .getValuesToBind (exchange );
198
+ protected Mono <Void > constructAttribute (WebExchangeDataBinder binder , ServerWebExchange exchange ) {
199
+ return binder .construct (exchange );
244
200
}
245
201
246
202
/**
0 commit comments