Support for populating model attributes through data class constructors

Includes a new overloaded ModelAndView constructor with an HttpStatus argument, as well as a HandlerMethodArgumentResolverSupport refactoring (revised checkParameterType signature, actually implementing the HandlerMethodArgumentResolver interface).

Issue: SPR-15199
This commit is contained in:
Juergen Hoeller
2017-03-24 12:15:45 +01:00
parent b3154357f0
commit 65ba865d70
30 changed files with 411 additions and 274 deletions

View File

@@ -56,12 +56,9 @@ import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.post;
import static org.junit.Assert.*;
import static org.springframework.core.ResolvableType.*;
import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.*;
/**
* Unit tests for {@link AbstractMessageReaderArgumentResolver}.
@@ -307,7 +304,16 @@ public class MessageReaderArgumentResolverTests {
private AbstractMessageReaderArgumentResolver resolver(Decoder<?>... decoders) {
List<ServerHttpMessageReader<?>> readers = new ArrayList<>();
Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder)));
return new AbstractMessageReaderArgumentResolver(readers) {};
return new AbstractMessageReaderArgumentResolver(readers) {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return false;
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
return null;
}
};
}

View File

@@ -42,16 +42,13 @@ import org.springframework.web.method.ResolvableMethod;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
/**
* Unit tests for {@link ModelAttributeMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
public class ModelAttributeMethodArgumentResolverTests {
@@ -116,7 +113,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
public void createAndBindToMono() throws Exception {
MethodParameter parameter = this.testMethod
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
@@ -130,7 +126,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
public void createAndBindToSingle() throws Exception {
MethodParameter parameter = this.testMethod
.annotPresent(ModelAttribute.class).arg(Single.class, Foo.class);
@@ -211,6 +206,7 @@ public class ModelAttributeMethodArgumentResolverTests {
Foo foo = valueExtractor.apply(value);
assertEquals("Robert", foo.getName());
assertEquals(25, foo.getAge());
String key = "foo";
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
@@ -231,7 +227,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
@SuppressWarnings("unchecked")
public void validationErrorToMono() throws Exception {
MethodParameter parameter = this.testMethod
.annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class);
@@ -246,7 +241,6 @@ public class ModelAttributeMethodArgumentResolverTests {
@Test
public void validationErrorToSingle() throws Exception {
MethodParameter parameter = this.testMethod
.annotPresent(ModelAttribute.class).arg(Single.class, Foo.class);
@@ -264,7 +258,6 @@ public class ModelAttributeMethodArgumentResolverTests {
ServerWebExchange exchange = postForm("age=invalid");
Mono<?> mono = createResolver().resolveArgument(param, this.bindContext, exchange);
mono = valueMonoExtractor.apply(mono);
StepVerifier.create(mono)
@@ -277,6 +270,31 @@ public class ModelAttributeMethodArgumentResolverTests {
.verify();
}
@Test
public void bindDataClass() throws Exception {
testBindBar(this.testMethod.annotNotPresent(ModelAttribute.class).arg(Bar.class));
}
private void testBindBar(MethodParameter param) throws Exception {
Object value = createResolver()
.resolveArgument(param, this.bindContext, postForm("name=Robert&age=25&count=1"))
.block(Duration.ZERO);
Bar bar = (Bar) value;
assertEquals("Robert", bar.getName());
assertEquals(25, bar.getAge());
assertEquals(1, bar.getCount());
String key = "bar";
String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key;
Map<String, Object> map = bindContext.getModel().asMap();
assertEquals(map.toString(), 2, map.size());
assertSame(bar, map.get(key));
assertNotNull(map.get(bindingResultKey));
assertTrue(map.get(bindingResultKey) instanceof BindingResult);
}
private ModelAttributeMethodArgumentResolver createResolver() {
return new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), false);
@@ -298,7 +316,8 @@ public class ModelAttributeMethodArgumentResolverTests {
Foo fooNotAnnotated,
String stringNotAnnotated,
Mono<Foo> monoNotAnnotated,
Mono<String> monoStringNotAnnotated) {
Mono<String> monoStringNotAnnotated,
Bar barNotAnnotated) {
}
@@ -333,4 +352,35 @@ public class ModelAttributeMethodArgumentResolverTests {
}
}
private static class Bar {
private final String name;
private final int age;
private int count;
public Bar(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return this.age;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
}