Polishing in ModelAttributeMethodArgumentResolver

See gh-26721
This commit is contained in:
rstoyanchev 2023-06-22 20:59:05 +01:00
parent ea398d7b7e
commit 801f01e23f
1 changed files with 47 additions and 58 deletions

View File

@ -110,7 +110,7 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
parameter.getGenericParameterType()); parameter.getGenericParameterType());
String name = ModelInitializer.getNameForParameter(parameter); String name = ModelInitializer.getNameForParameter(parameter);
Mono<?> valueMono = prepareAttributeMono(name, valueType, context, exchange); Mono<?> attributeMono = prepareAttributeMono(name, valueType, context, exchange);
// unsafe(): we're intercepting, already serialized Publisher signals // unsafe(): we're intercepting, already serialized Publisher signals
Sinks.One<BindingResult> bindingResultSink = Sinks.unsafe().one(); Sinks.One<BindingResult> bindingResultSink = Sinks.unsafe().one();
@ -118,15 +118,15 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
Map<String, Object> model = context.getModel().asMap(); Map<String, Object> model = context.getModel().asMap();
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultSink.asMono()); model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultSink.asMono());
return valueMono.flatMap(value -> { return attributeMono.flatMap(attribute -> {
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name, parameter); WebExchangeDataBinder binder = context.createDataBinder(exchange, attribute, name, parameter);
return (bindingDisabled(parameter) ? Mono.empty() : bindRequestParameters(binder, exchange)) return (!bindingDisabled(parameter) ? bindRequestParameters(binder, exchange) : Mono.empty())
.doOnError(bindingResultSink::tryEmitError) .doOnError(bindingResultSink::tryEmitError)
.doOnSuccess(aVoid -> { .doOnSuccess(aVoid -> {
validateIfApplicable(binder, parameter, exchange); validateIfApplicable(binder, parameter, exchange);
BindingResult bindingResult = binder.getBindingResult(); BindingResult bindingResult = binder.getBindingResult();
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResult); model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResult);
model.put(name, value); model.put(name, attribute);
// Ignore result: serialized and buffered (should never fail) // Ignore result: serialized and buffered (should never fail)
bindingResultSink.tryEmitValue(bindingResult); bindingResultSink.tryEmitValue(bindingResult);
}) })
@ -134,82 +134,51 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
BindingResult errors = binder.getBindingResult(); BindingResult errors = binder.getBindingResult();
if (adapter != null) { if (adapter != null) {
return adapter.fromPublisher(errors.hasErrors() ? return adapter.fromPublisher(errors.hasErrors() ?
Mono.error(new WebExchangeBindException(parameter, errors)) : valueMono); Mono.error(new WebExchangeBindException(parameter, errors)) : attributeMono);
} }
else { else {
if (errors.hasErrors() && !hasErrorsArgument(parameter)) { if (errors.hasErrors() && !hasErrorsArgument(parameter)) {
throw new WebExchangeBindException(parameter, errors); throw new WebExchangeBindException(parameter, errors);
} }
return value; return attribute;
} }
})); }));
}); });
} }
/** private Mono<?> prepareAttributeMono(
* Determine if binding should be disabled for the supplied {@link MethodParameter}, String name, ResolvableType type, BindingContext context, ServerWebExchange exchange) {
* based on the {@link ModelAttribute#binding} annotation attribute.
* @since 5.2.15
*/
private boolean bindingDisabled(MethodParameter parameter) {
ModelAttribute modelAttribute = parameter.getParameterAnnotation(ModelAttribute.class);
return (modelAttribute != null && !modelAttribute.binding());
}
/** Object attribute = context.getModel().asMap().get(name);
* Extension point to bind the request to the target object.
* @param binder the data binder instance to use for the binding
* @param exchange the current request
* @since 5.2.6
*/
protected Mono<Void> bindRequestParameters(WebExchangeDataBinder binder, ServerWebExchange exchange) {
return binder.bind(exchange);
}
private Mono<?> prepareAttributeMono(String attributeName, ResolvableType attributeType,
BindingContext context, ServerWebExchange exchange) {
Object attribute = context.getModel().asMap().get(attributeName);
if (attribute == null) { if (attribute == null) {
attribute = findAndRemoveReactiveAttribute(context.getModel(), attributeName); attribute = removeReactiveAttribute(context.getModel(), name);
} }
if (attribute == null) { if (attribute == null) {
return createAttribute(attributeName, attributeType.toClass(), context, exchange); return createAttribute(name, type.toClass(), context, exchange);
} }
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, attribute); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, attribute);
if (adapter != null) { Assert.isTrue(adapter == null || !adapter.isMultiValue(), "Model attribute must be single-value publisher");
Assert.isTrue(!adapter.isMultiValue(), "Data binding only supports single-value async types"); return (adapter != null ? Mono.from(adapter.toPublisher(attribute)) : Mono.justOrEmpty(attribute));
return Mono.from(adapter.toPublisher(attribute));
}
else {
return Mono.justOrEmpty(attribute);
}
} }
@Nullable @Nullable
private Object findAndRemoveReactiveAttribute(Model model, String attributeName) { private Object removeReactiveAttribute(Model model, String name) {
return model.asMap().entrySet().stream() for (Map.Entry<String, Object> entry : model.asMap().entrySet()) {
.filter(entry -> { if (entry.getKey().startsWith(name)) {
if (!entry.getKey().startsWith(attributeName)) { ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, entry.getValue());
return false; if (adapter != null) {
if (entry.getKey().equals(name + ClassUtils.getShortName(adapter.getReactiveType()))) {
// Remove since we will be re-inserting the resolved attribute value
model.asMap().remove(entry.getKey());
return entry.getValue();
} }
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, entry.getValue()); }
if (adapter == null) { }
return false; }
} return null;
String name = attributeName + ClassUtils.getShortName(adapter.getReactiveType());
return entry.getKey().equals(name);
})
.findFirst()
.map(entry -> {
// Remove since we will be re-inserting the resolved attribute value
model.asMap().remove(entry.getKey());
return entry.getValue();
})
.orElse(null);
} }
private Mono<?> createAttribute( private Mono<?> createAttribute(
@ -274,6 +243,26 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
return binder.getValuesToBind(exchange); return binder.getValuesToBind(exchange);
} }
/**
* Determine if binding should be disabled for the supplied {@link MethodParameter},
* based on the {@link ModelAttribute#binding} annotation attribute.
* @since 5.2.15
*/
private boolean bindingDisabled(MethodParameter parameter) {
ModelAttribute modelAttribute = parameter.getParameterAnnotation(ModelAttribute.class);
return (modelAttribute != null && !modelAttribute.binding());
}
/**
* Extension point to bind the request to the target object.
* @param binder the data binder instance to use for the binding
* @param exchange the current request
* @since 5.2.6
*/
protected Mono<Void> bindRequestParameters(WebExchangeDataBinder binder, ServerWebExchange exchange) {
return binder.bind(exchange);
}
private boolean hasErrorsArgument(MethodParameter parameter) { private boolean hasErrorsArgument(MethodParameter parameter) {
int i = parameter.getParameterIndex(); int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes(); Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();