Reactive support for @ModelAttribute argument
Issue: SPR-14542
This commit is contained in:
@@ -43,6 +43,7 @@ import org.springframework.web.reactive.result.method.BindingContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
|
||||
import org.springframework.web.bind.WebExchangeBindException;
|
||||
|
||||
/**
|
||||
* Abstract base class for argument resolvers that resolve method arguments
|
||||
@@ -216,7 +217,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
||||
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name);
|
||||
binder.validate(validationHints);
|
||||
if (binder.getBindingResult().hasErrors()) {
|
||||
throw new ServerWebInputException("Validation failed", param);
|
||||
throw new WebExchangeBindException(param, binder.getBindingResult());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoProcessor;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapter.Descriptor;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.WebExchangeBindException;
|
||||
import org.springframework.web.bind.WebExchangeDataBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.reactive.result.method.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Resolve {@code @ModelAttribute} annotated method arguments.
|
||||
*
|
||||
* <p>Model attributes are sourced from the model, or created using a default
|
||||
* constructor and then added to the model. Once created the attribute is
|
||||
* populated via data binding to the request (form data, query params).
|
||||
* Validation also may be applied if the argument is annotated with
|
||||
* {@code @javax.validation.Valid} or Spring's own
|
||||
* {@code @org.springframework.validation.annotation.Validated}.
|
||||
*
|
||||
* <p>When this handler is created with {@code useDefaultResolution=true}
|
||||
* any non-simple type argument and return value is regarded as a model
|
||||
* attribute with or without the presence of an {@code @ModelAttribute}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ModelAttributeMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final boolean useDefaultResolution;
|
||||
|
||||
private final ReactiveAdapterRegistry adapterRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
* @param useDefaultResolution if "true", non-simple method arguments and
|
||||
* return values are considered model attributes with or without a
|
||||
* {@code @ModelAttribute} annotation present.
|
||||
* @param registry for adapting to other reactive types from and to Mono
|
||||
*/
|
||||
public ModelAttributeMethodArgumentResolver(boolean useDefaultResolution,
|
||||
ReactiveAdapterRegistry registry) {
|
||||
|
||||
Assert.notNull(registry, "'ReactiveAdapterRegistry' is required.");
|
||||
this.useDefaultResolution = useDefaultResolution;
|
||||
this.adapterRegistry = registry;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the configured {@link ReactiveAdapterRegistry}.
|
||||
*/
|
||||
public ReactiveAdapterRegistry getAdapterRegistry() {
|
||||
return this.adapterRegistry;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
|
||||
return true;
|
||||
}
|
||||
if (this.useDefaultResolution) {
|
||||
Class<?> clazz = parameter.getParameterType();
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapterFrom(clazz);
|
||||
if (adapter != null) {
|
||||
Descriptor descriptor = adapter.getDescriptor();
|
||||
if (descriptor.isNoValue() || descriptor.isMultiValue()) {
|
||||
return false;
|
||||
}
|
||||
clazz = ResolvableType.forMethodParameter(parameter).getGeneric(0).getRawClass();
|
||||
}
|
||||
return !BeanUtils.isSimpleProperty(clazz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext context,
|
||||
ServerWebExchange exchange) {
|
||||
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
ReactiveAdapter adapterTo = getAdapterRegistry().getAdapterTo(type.resolve());
|
||||
Class<?> valueType = (adapterTo != null ? type.resolveGeneric(0) : parameter.getParameterType());
|
||||
String name = getAttributeName(valueType, parameter);
|
||||
Mono<?> valueMono = getAttributeMono(name, valueType, parameter, context, exchange);
|
||||
|
||||
Map<String, Object> model = context.getModel().asMap();
|
||||
MonoProcessor<BindingResult> bindingResultMono = MonoProcessor.create();
|
||||
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultMono);
|
||||
|
||||
return valueMono.then(value -> {
|
||||
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name);
|
||||
return binder.bind(exchange)
|
||||
.doOnError(bindingResultMono::onError)
|
||||
.doOnSuccess(aVoid -> {
|
||||
validateIfApplicable(binder, parameter);
|
||||
BindingResult errors = binder.getBindingResult();
|
||||
model.put(BindingResult.MODEL_KEY_PREFIX + name, errors);
|
||||
model.put(name, value);
|
||||
bindingResultMono.onNext(errors);
|
||||
})
|
||||
.then(Mono.fromCallable(() -> {
|
||||
BindingResult errors = binder.getBindingResult();
|
||||
if (adapterTo != null) {
|
||||
return adapterTo.fromPublisher(errors.hasErrors() ?
|
||||
Mono.error(new WebExchangeBindException(parameter, errors)) :
|
||||
Mono.just(value));
|
||||
}
|
||||
else {
|
||||
if (errors.hasErrors() && checkErrorsArgument(parameter)) {
|
||||
throw new WebExchangeBindException(parameter, errors);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private String getAttributeName(Class<?> valueType, MethodParameter parameter) {
|
||||
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
|
||||
String name = (ann != null ? ann.value() : null);
|
||||
// TODO: Conventions does not deal with async wrappers
|
||||
return StringUtils.hasText(name) ? name : ClassUtils.getShortNameAsProperty(valueType);
|
||||
}
|
||||
|
||||
private Mono<?> getAttributeMono(String attributeName, Class<?> attributeType,
|
||||
MethodParameter param, BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
Object attribute = context.getModel().asMap().get(attributeName);
|
||||
if (attribute == null) {
|
||||
attribute = createAttribute(attributeName, attributeType, param, context, exchange);
|
||||
}
|
||||
if (attribute != null) {
|
||||
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapterFrom(null, attribute);
|
||||
if (adapterFrom != null) {
|
||||
return adapterFrom.toMono(attribute);
|
||||
}
|
||||
}
|
||||
return Mono.justOrEmpty(attribute);
|
||||
}
|
||||
|
||||
protected Object createAttribute(String attributeName, Class<?> attributeType,
|
||||
MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
return BeanUtils.instantiateClass(attributeType);
|
||||
}
|
||||
|
||||
protected boolean checkErrorsArgument(MethodParameter methodParam) {
|
||||
int i = methodParam.getParameterIndex();
|
||||
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
|
||||
return paramTypes.length <= (i + 1) || !Errors.class.isAssignableFrom(paramTypes[i + 1]);
|
||||
}
|
||||
|
||||
protected void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
|
||||
Annotation[] annotations = parameter.getParameterAnnotations();
|
||||
for (Annotation ann : annotations) {
|
||||
Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class);
|
||||
if (validAnnot != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = (validAnnot != null ? validAnnot.value() : AnnotationUtils.getValue(ann));
|
||||
Object hintArray = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
|
||||
binder.validate(hintArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import rx.RxReactiveStreams;
|
||||
import rx.Single;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
import org.springframework.web.bind.WebExchangeBindException;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||
import org.springframework.web.reactive.result.ResolvableMethod;
|
||||
import org.springframework.web.reactive.result.method.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.MockWebSessionManager;
|
||||
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.core.ResolvableType.forClass;
|
||||
import static org.springframework.core.ResolvableType.forClassWithGenerics;
|
||||
import static org.springframework.util.Assert.isTrue;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ModelAttributeMethodArgumentResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ModelAttributeMethodArgumentResolverTests {
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private final MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.POST, "/path");
|
||||
|
||||
private final MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
|
||||
private BindingContext bindingContext;
|
||||
|
||||
private ResolvableMethod testMethod = ResolvableMethod.onClass(this.getClass()).name("handle");
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, response, new MockWebSessionManager());
|
||||
this.exchange = this.exchange.mutate().setFormData(Mono.just(this.formData)).build();
|
||||
|
||||
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
|
||||
validator.afterPropertiesSet();
|
||||
ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
|
||||
initializer.setValidator(validator);
|
||||
this.bindingContext = new BindingContext(initializer);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void supports() throws Exception {
|
||||
|
||||
ModelAttributeMethodArgumentResolver resolver =
|
||||
new ModelAttributeMethodArgumentResolver(false, new ReactiveAdapterRegistry());
|
||||
|
||||
ResolvableType type = forClass(Foo.class);
|
||||
assertTrue(resolver.supportsParameter(parameter(type)));
|
||||
|
||||
type = forClassWithGenerics(Mono.class, Foo.class);
|
||||
assertTrue(resolver.supportsParameter(parameter(type)));
|
||||
|
||||
type = forClass(Foo.class);
|
||||
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
|
||||
|
||||
type = forClassWithGenerics(Mono.class, Foo.class);
|
||||
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsWithDefaultResolution() throws Exception {
|
||||
|
||||
ModelAttributeMethodArgumentResolver resolver =
|
||||
new ModelAttributeMethodArgumentResolver(true, new ReactiveAdapterRegistry());
|
||||
|
||||
ResolvableType type = forClass(Foo.class);
|
||||
assertTrue(resolver.supportsParameter(parameterNotAnnotated(type)));
|
||||
|
||||
type = forClassWithGenerics(Mono.class, Foo.class);
|
||||
assertTrue(resolver.supportsParameter(parameterNotAnnotated(type)));
|
||||
|
||||
type = forClass(String.class);
|
||||
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
|
||||
|
||||
type = forClassWithGenerics(Mono.class, String.class);
|
||||
assertFalse(resolver.supportsParameter(parameterNotAnnotated(type)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndBind() throws Exception {
|
||||
testBindFoo(forClass(Foo.class), value -> {
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndBindToMono() throws Exception {
|
||||
testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> {
|
||||
assertTrue(mono.getClass().getName(), mono instanceof Mono);
|
||||
Object value = ((Mono<?>) mono).blockMillis(5000);
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndBindToSingle() throws Exception {
|
||||
testBindFoo(forClassWithGenerics(Single.class, Foo.class), single -> {
|
||||
assertTrue(single.getClass().getName(), single instanceof Single);
|
||||
Object value = ((Single<?>) single).toBlocking().value();
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindExisting() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
foo.setName("Jim");
|
||||
this.bindingContext.getModel().addAttribute(foo);
|
||||
|
||||
testBindFoo(forClass(Foo.class), value -> {
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
|
||||
assertSame(foo, this.bindingContext.getModel().asMap().get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindExistingMono() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
foo.setName("Jim");
|
||||
this.bindingContext.getModel().addAttribute("foo", Mono.just(foo));
|
||||
|
||||
testBindFoo(forClass(Foo.class), value -> {
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
|
||||
assertSame(foo, this.bindingContext.getModel().asMap().get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindExistingSingle() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
foo.setName("Jim");
|
||||
this.bindingContext.getModel().addAttribute("foo", Single.just(foo));
|
||||
|
||||
testBindFoo(forClass(Foo.class), value -> {
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
|
||||
assertSame(foo, this.bindingContext.getModel().asMap().get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindExistingMonoToMono() throws Exception {
|
||||
Foo foo = new Foo();
|
||||
foo.setName("Jim");
|
||||
this.bindingContext.getModel().addAttribute("foo", Mono.just(foo));
|
||||
|
||||
testBindFoo(forClassWithGenerics(Mono.class, Foo.class), mono -> {
|
||||
assertTrue(mono.getClass().getName(), mono instanceof Mono);
|
||||
Object value = ((Mono<?>) mono).blockMillis(5000);
|
||||
assertEquals(Foo.class, value.getClass());
|
||||
return (Foo) value;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validationError() throws Exception {
|
||||
testValidationError(forClass(Foo.class), resolvedArgumentMono -> resolvedArgumentMono);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void validationErrorToMono() throws Exception {
|
||||
testValidationError(forClassWithGenerics(Mono.class, Foo.class),
|
||||
resolvedArgumentMono -> {
|
||||
Object value = resolvedArgumentMono.blockMillis(5000);
|
||||
assertNotNull(value);
|
||||
isTrue(value instanceof Mono);
|
||||
return (Mono<?>) value;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void validationErrorToSingle() throws Exception {
|
||||
testValidationError(forClassWithGenerics(Single.class, Foo.class),
|
||||
resolvedArgumentMono -> {
|
||||
Object value = resolvedArgumentMono.blockMillis(5000);
|
||||
assertNotNull(value);
|
||||
isTrue(value instanceof Single);
|
||||
return Mono.from(RxReactiveStreams.toPublisher((Single) value));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void testBindFoo(ResolvableType type, Function<Object, Foo> valueExtractor) {
|
||||
|
||||
this.formData.add("name", "Robert");
|
||||
this.formData.add("age", "25");
|
||||
|
||||
Object value = createResolver()
|
||||
.resolveArgument(parameter(type), this.bindingContext, this.exchange)
|
||||
.blockMillis(5000);
|
||||
|
||||
Foo foo = valueExtractor.apply(value);
|
||||
assertEquals("Robert", foo.getName());
|
||||
|
||||
String key = "foo";
|
||||
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
|
||||
|
||||
Map<String, Object> map = bindingContext.getModel().asMap();
|
||||
assertEquals(map.toString(), 2, map.size());
|
||||
assertSame(foo, map.get(key));
|
||||
assertNotNull(map.get(bindingResultKey));
|
||||
assertTrue(map.get(bindingResultKey) instanceof BindingResult);
|
||||
}
|
||||
|
||||
private void testValidationError(ResolvableType type, Function<Mono<?>, Mono<?>> valueMonoExtractor) {
|
||||
|
||||
this.formData.add("age", "invalid");
|
||||
|
||||
HandlerMethodArgumentResolver resolver = createResolver();
|
||||
Mono<?> mono = resolver.resolveArgument(parameter(type), this.bindingContext, this.exchange);
|
||||
|
||||
mono = valueMonoExtractor.apply(mono);
|
||||
|
||||
StepVerifier.create(mono)
|
||||
.consumeErrorWith(ex -> {
|
||||
assertTrue(ex instanceof WebExchangeBindException);
|
||||
WebExchangeBindException bindException = (WebExchangeBindException) ex;
|
||||
assertEquals(1, bindException.getErrorCount());
|
||||
assertTrue(bindException.hasFieldErrors("age"));
|
||||
})
|
||||
.verify();
|
||||
}
|
||||
|
||||
|
||||
private ModelAttributeMethodArgumentResolver createResolver() {
|
||||
return new ModelAttributeMethodArgumentResolver(false, new ReactiveAdapterRegistry());
|
||||
}
|
||||
|
||||
private MethodParameter parameter(ResolvableType type) {
|
||||
return this.testMethod.resolveParam(type,
|
||||
parameter -> parameter.hasParameterAnnotation(ModelAttribute.class));
|
||||
}
|
||||
|
||||
private MethodParameter parameterNotAnnotated(ResolvableType type) {
|
||||
return this.testMethod.resolveParam(type,
|
||||
parameter -> !parameter.hasParameterAnnotations());
|
||||
}
|
||||
|
||||
|
||||
private static class Foo {
|
||||
|
||||
private String name;
|
||||
|
||||
private int age;
|
||||
|
||||
public Foo() {
|
||||
}
|
||||
|
||||
public Foo(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return this.age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
void handle(
|
||||
@ModelAttribute @Validated Foo foo,
|
||||
@ModelAttribute @Validated Mono<Foo> mono,
|
||||
@ModelAttribute @Validated Single<Foo> single,
|
||||
Foo fooNotAnnotated,
|
||||
String stringNotAnnotated,
|
||||
Mono<Foo> monoNotAnnotated,
|
||||
Mono<String> monoStringNotAnnotated) {}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user