Skip to content

Commit 815192f

Browse files
@RequestBody Annotation Support (#1)
* Refactor BinderConfiguration to create default resolvers inside post processor. This will allow me to leverage the message converters in the request body resolver. This means the DefaultPropertyResolverRegistry is no longer needed. * Refactor BinderConfiguration to create default resolvers inside post processor. This will allow me to leverage the message converters in the request body resolver. This means the DefaultPropertyResolverRegistry is no longer needed. * Introduce BindingProperty class. This class represents an abstraction of two different introspection types that represent a java beans property. This class will replace the direct TypeDescriptor usages throughout the library. * Ignore warning. * Add equals/hashCode to BindingProperty. * Add BindingProperty to the ResolvedPropertyData class. The BindingProperty is not yet in use but I've added it where it will be leveraged. * Cut over the introspector to use the BindingProperty object. * Add BindingProperty to AbstractPropertyResolverRegistry. Class is not yet in use. * Remove usages of TypeDescriptor from supports method. * Remove TypeDescriptor from supports method. * Add BindingProperty to the resolve method of the resolver interface. * Remove usages of TypeDescriptor from resolves method. * Remove TypeDescriptor from resolves method signature. * Remove TypeDescriptor from ResolvedPropertyData class. * Cleanup the resolver test warnings. * Add support for the @RequestBody annotation. * Switch to composition for the RequestBodyRequestPropertyResolver. * Fix a couple of code smells. * Add missing headers * Fix code smells. * Fix more code smells. * Fix more code smells. * Add javadoc to annotation. * Add additional integration tests. * Fix the reactive binding get values method so it no longer needs a block() method call. Many thanks to Martin Tarjányi on StackOverflow for helping me figure this out and learn about some reactive programming. * Add assertions. * Add nullability annotations. * Add nullability annotations. * Create release notes file. * Fix typo * Formatting. * Add header.
1 parent 811eb29 commit 815192f

File tree

92 files changed

+3186
-1290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+3186
-1290
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ public class CustomRequestBean {
4242
@RequestContext
4343
private ZoneId timeZone;
4444

45+
// Request body parsed by Spring HTTP MessageReaders
46+
@RequestBody
47+
private JsonBody requestBody;
48+
4549
// A nested Java bean with additional annotated properties
4650
@BeanParameter
4751
private NestedBean nestedBean;

RELEASE_NOTES.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Release Notes
2+
3+
## 0.2.0
4+
In progress
5+
6+
- Add support for binding request payloads using the `@RequestBody` annotation.
7+
8+
## 0.1.0
9+
Released 2020-04-29
10+
11+
- Initial release
12+
- Minimum supported Spring version: 5.2.6

integration-tests/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dependencies {
99
implementation 'javax.servlet:javax.servlet-api'
1010
implementation 'org.hibernate.validator:hibernate-validator:6.0.19.Final'
1111
implementation 'org.glassfish:javax.el:3.0.1-b09'
12+
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.2'
1213

1314
testImplementation 'org.junit.jupiter:junit-jupiter-api'
1415
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessBean.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.mattbertolini.spring.web.bind.annotation.FormParameter;
2121
import com.mattbertolini.spring.web.bind.annotation.HeaderParameter;
2222
import com.mattbertolini.spring.web.bind.annotation.PathParameter;
23+
import com.mattbertolini.spring.web.bind.annotation.RequestBody;
2324
import com.mattbertolini.spring.web.bind.annotation.RequestParameter;
2425
import com.mattbertolini.spring.web.bind.annotation.SessionParameter;
2526

@@ -66,4 +67,13 @@ public String getRequestParameter() {
6667
public String getSessionParameter() {
6768
return sessionParameter;
6869
}
70+
71+
public static class RequestBodyBean {
72+
@RequestBody
73+
private JsonBody requestBody;
74+
75+
public JsonBody getRequestBody() {
76+
return requestBody;
77+
}
78+
}
6979
}

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/DirectFieldAccessController.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ public String requestParameter(@BeanParameter DirectFieldAccessBean directFieldA
5454
public String sessionParameter(@BeanParameter DirectFieldAccessBean directFieldAccessBean) {
5555
return directFieldAccessBean.getSessionParameter();
5656
}
57+
58+
@PostMapping(value = "/requestBody", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
59+
public String requestBody(@BeanParameter DirectFieldAccessBean.RequestBodyBean directFieldAccessBean) {
60+
return directFieldAccessBean.getRequestBody().getProperty();
61+
}
5762
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.mattbertolini.spring.test.web.bind;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
7+
@JsonIgnoreProperties(ignoreUnknown = true)
8+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
9+
public class JsonBody {
10+
@JsonProperty("json_property")
11+
private String property;
12+
13+
public String getProperty() {
14+
return property;
15+
}
16+
17+
public void setProperty(String property) {
18+
this.property = property;
19+
}
20+
}

integration-tests/src/test/java/com/mattbertolini/spring/test/web/bind/NestedBean.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.mattbertolini.spring.web.bind.annotation.FormParameter;
55
import com.mattbertolini.spring.web.bind.annotation.HeaderParameter;
66
import com.mattbertolini.spring.web.bind.annotation.PathParameter;
7+
import com.mattbertolini.spring.web.bind.annotation.RequestBody;
78
import com.mattbertolini.spring.web.bind.annotation.RequestParameter;
89
import com.mattbertolini.spring.web.bind.annotation.SessionParameter;
910

@@ -74,4 +75,17 @@ public String getSessionAttribute() {
7475
public void setSessionAttribute(String sessionAttribute) {
7576
this.sessionAttribute = sessionAttribute;
7677
}
78+
79+
public static class RequestBodyBean {
80+
@RequestBody
81+
private JsonBody requestBody;
82+
83+
public JsonBody getRequestBody() {
84+
return requestBody;
85+
}
86+
87+
public void setRequestBody(JsonBody requestBody) {
88+
this.requestBody = requestBody;
89+
}
90+
}
7791
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.mattbertolini.spring.test.web.bind;
2+
3+
import com.mattbertolini.spring.web.bind.annotation.BeanParameter;
4+
import com.mattbertolini.spring.web.bind.annotation.RequestBody;
5+
6+
import javax.validation.constraints.NotNull;
7+
8+
public class RequestBodyBean {
9+
10+
@SuppressWarnings("unused")
11+
public static class AnnotatedField {
12+
@RequestBody
13+
private JsonBody jsonBody;
14+
15+
public JsonBody getJsonBody() {
16+
return jsonBody;
17+
}
18+
19+
public void setJsonBody(JsonBody jsonBody) {
20+
this.jsonBody = jsonBody;
21+
}
22+
}
23+
24+
@SuppressWarnings("unused")
25+
public static class AnnotatedSetter {
26+
private JsonBody jsonBody;
27+
28+
public JsonBody getJsonBody() {
29+
return jsonBody;
30+
}
31+
32+
@RequestBody
33+
public void setJsonBody(JsonBody jsonBody) {
34+
this.jsonBody = jsonBody;
35+
}
36+
}
37+
38+
@SuppressWarnings("unused")
39+
public static class AnnotatedGetter {
40+
private JsonBody jsonBody;
41+
42+
@RequestBody
43+
public JsonBody getJsonBody() {
44+
return jsonBody;
45+
}
46+
47+
public void setJsonBody(JsonBody jsonBody) {
48+
this.jsonBody = jsonBody;
49+
}
50+
}
51+
52+
@SuppressWarnings("unused")
53+
public static class BindingResult {
54+
@RequestBody
55+
private JsonBody jsonBody;
56+
57+
public JsonBody getJsonBody() {
58+
return jsonBody;
59+
}
60+
61+
public void setJsonBody(JsonBody jsonBody) {
62+
this.jsonBody = jsonBody;
63+
}
64+
}
65+
66+
@SuppressWarnings("unused")
67+
public static class Validation {
68+
@NotNull
69+
@RequestBody
70+
private JsonBody jsonBody;
71+
72+
public JsonBody getJsonBody() {
73+
return jsonBody;
74+
}
75+
76+
public void setJsonBody(JsonBody jsonBody) {
77+
this.jsonBody = jsonBody;
78+
}
79+
}
80+
81+
@SuppressWarnings("unused")
82+
public static class Nested {
83+
@BeanParameter
84+
private NestedBean.RequestBodyBean nestedBean;
85+
86+
public NestedBean.RequestBodyBean getNestedBean() {
87+
return nestedBean;
88+
}
89+
90+
public void setNestedBean(NestedBean.RequestBodyBean nestedBean) {
91+
this.nestedBean = nestedBean;
92+
}
93+
}
94+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.mattbertolini.spring.test.web.bind;
2+
3+
import com.mattbertolini.spring.web.bind.annotation.BeanParameter;
4+
import org.springframework.http.MediaType;
5+
import org.springframework.validation.BindingResult;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
import javax.validation.Valid;
10+
11+
@RestController
12+
public class RequestBodyController {
13+
@PostMapping(value = "/annotatedField", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
14+
public String annotatedField(@BeanParameter RequestBodyBean.AnnotatedField requestBodyBean) {
15+
JsonBody jsonBody = requestBodyBean.getJsonBody();
16+
return jsonBody.getProperty();
17+
}
18+
19+
@PostMapping(value = "/annotatedSetter", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
20+
public String annotatedSetter(@BeanParameter RequestBodyBean.AnnotatedSetter requestBodyBean) {
21+
JsonBody jsonBody = requestBodyBean.getJsonBody();
22+
return jsonBody.getProperty();
23+
}
24+
25+
@PostMapping(value = "/annotatedGetter", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
26+
public String annotatedGetter(@BeanParameter RequestBodyBean.AnnotatedGetter requestBodyBean) {
27+
JsonBody jsonBody = requestBodyBean.getJsonBody();
28+
return jsonBody.getProperty();
29+
}
30+
31+
@PostMapping(value = "/bindingResult", produces = MediaType.TEXT_PLAIN_VALUE)
32+
public String bindingResult(@BeanParameter RequestBodyBean.BindingResult requestBodyBean, BindingResult bindingResult) {
33+
return Integer.toString(bindingResult.getErrorCount());
34+
}
35+
36+
@PostMapping(value = "/validated", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
37+
public String validated(@Valid @BeanParameter RequestBodyBean.Validation requestBodyBean) {
38+
return requestBodyBean.getJsonBody().getProperty();
39+
}
40+
41+
@PostMapping(value = "/validatedWithBindingResult", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
42+
public String validatedWithBindingResult(@Valid @BeanParameter RequestBodyBean.Validation requestBodyBean, BindingResult bindingResult) {
43+
if (bindingResult.hasErrors()) {
44+
return "notValid";
45+
}
46+
return "valid";
47+
}
48+
49+
@PostMapping(value = "/nested", produces = MediaType.TEXT_PLAIN_VALUE)
50+
public String nestedBeanParameter(@BeanParameter RequestBodyBean.Nested requestBodyBean) {
51+
return requestBodyBean.getNestedBean().getRequestBody().getProperty();
52+
}
53+
}

integration-tests/src/test/java/com/mattbertolini/spring/web/reactive/test/DirectFieldAccessIntegrationTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.web.reactive.config.WebFluxConfigurationSupport;
3535

3636
import static org.springframework.web.reactive.function.BodyInserters.fromFormData;
37+
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
3738

3839
@SpringJUnitWebConfig(classes = {DirectFieldAccessIntegrationTest.Context.class})
3940
class DirectFieldAccessIntegrationTest {
@@ -110,6 +111,18 @@ void sessionParameter() {
110111
.expectBody(String.class).isEqualTo("expectedValue");
111112
}
112113

114+
@Test
115+
void requestBody() {
116+
webTestClient.post()
117+
.uri("/requestBody")
118+
.contentType(MediaType.APPLICATION_JSON)
119+
.body(fromValue("{\"json_property\": \"expectedValue\"}"))
120+
.accept(MediaType.TEXT_PLAIN)
121+
.exchange()
122+
.expectStatus().isOk()
123+
.expectBody(String.class).isEqualTo("expectedValue");
124+
}
125+
113126
@Configuration
114127
static class Context extends WebFluxConfigurationSupport {
115128

0 commit comments

Comments
 (0)