Spring 4.x/3.x (Web MVC) REST API and JSON2 Post requests, how to get it right once for all?
Before going into details, I know there has been lots of conversations and related questions on Stackoverflow. All of them kind of help me in different ways so I thought I put my findings all together as a single organized FAQ to summarize my findings.
Related Concepts
Surely you know about these but I just write them as a quick review. Feel free to edit in case I am missing something.
HTTP POST Request:
A post request is used when you are willing to send an object to a web service or a your server side application.
Serialization:
Is the process of getting the object from your web browser through to your server side application. A jQuery Ajax call or a Curl post request can be used.
Serialization protocols:
The most popular ones theses days are JSON and XML. XML is becoming less popular as serialized xml objects are relatively bigger in size due the the nature of XML tagging. In this FAQ the main focus is JSON2 serialization.
Spring:
Spring framework and its powerful annotation makes it possible to expose web service in an efficient way. There are a lot of different libraries in Spring. The one that is our focus here is Spring web MVC.
Curl vs JQuery:
These are the tools you can use to make a post request in your client side. Even if you are planning to use JQuery ajax call, I suggest you use Curl for debugging purposes as it provides you with a detailed response after making the post request.
@RequestBody vs @RequestParam/@PathVariable vs @ModelAttribute:
In cases where you have a web service that is not depending on your Java EE model, @RequestBody must be used. If you are using the model and your JSON object is added to the model, you can access the object through @ModelAttribute. Only for cases where your request is either a GET request or a GET and POST request combination you will need to use @RequestParam/@PathVariable.
@RequestBody vs @ResposeBody:
As you can see from the name it as simple as that, you only need the @ResponseBody if you are sending a response the the client after the server side method processed the request.
RequestMappingHandlerAdapter vs AnnotationMethodHandlerAdapter:
RequestMappingHandlerAdapter is the new mapping handler for Spring framework that replaced AnnotationMethodHandlerAdapter since Spring 3.1. If your existing configuration is still in AnnotationMethodHandlerAdapter you might find this post useful. The config provided in my post will give you an idea on how to set up the RequestMappingHandlerAdapter.
Setup
You will need to setup a message convertor. This is how your serialized JSON message body is converted into a local java object at your server side.
Basic Configuration from here. The convertors were MarshallingHttpMessageConverter and CastorMarshaller in the basic configuration sample, I have replaced them with MappingJackson2HttpMessageConverter and MappingJacksonHttpMessageConverter.
Where to put the configuration
The way my project is set up, I have two config files:
hadlerAdapter bean has to be located in the later that is the MVC Dispatcher XML file.
<bean name="handlerAdapter"
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>
<ref bean="jsonConverter"/>
</list>
</property>
<property name="requireSession" value="false"/>
</bean>
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="supportedMediaTypes" value="application/json"/>
</bean>
You can have multiple message convertors. here, I have created a normal JSON as well as a JSON 2 message convertor. Both Ref and normal bean format in the XML file have been used (personally I prefer the ref tag as its neater).
REST API
Here is a sample controller that is exposing the REST API.
The controller
This is where your REST API for a HTTP post request is exposed.
@Component
@Controller
@RequestMapping("/api/user")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String insertUser(@RequestBody final User user) {
System.out.println(user.toString());
userService.insertUser(user);
String userAdded = "User-> {" + user.toString() + "} is added";
System.out.println(userAdded);
return userAdded;
}
}
The Java Object
@JsonAutoDetect
public class User {
private int id;
private String username;
private String name;
private String lastName;
private String email;
public int getId() {
return externalId;
}
public void setId(final int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(final String username) {
this.username = username;
}
public String getLastName() {
return lastName;
}
public void setLastName(final String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return this.getName() + " | " + this.getLastName() + " | " + this.getEmail()
+ " | " + this.getUsername() + " | " + this.getId() + " | ";
}
}
CURL Post call
curl -i -H "Content-Type: application/json" -X POST -d '{"id":100,"username":"JohnBlog","name":"John","lastName":"Blog","email":"JohnBlog@user.com"}' http://localhost:8080/[YOURWEBAPP]/api/user/add
Related posts and questions
This FAQ was not possible if it wasn't for all the people who provided the following posts and questions (this list will expand if I come across useful related posts/questions):
CURL Post call
curl -i -H "Content-Type: application/json" -X POST -d '{"id":100,"username":"JohnBlog","name":"John","lastName":"Blog","email":"JohnBlog@user.com"}' http://localhost:8080/[YOURWEBAPP]/api/user/add
Different Error Scenarios:
Here I explore different errors you might come across after you have made a curl call and what might have possibly gone wrong.
Scenario One:
HTTP/1.1 404 Not Found
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 949
Date: Tue, 04 Jun 2013 02:59:35 GMT
This implies that the REST API does not exist in the URL you have provide.
Root cause:After you have made sure that everything is done perfectly right and nothing is wrong with your Configuration nor you URL: - Run a maven clean. - Undeploy your web app or simply delete it. - Redeploy the web app - Make sure to use only one version of Spring in your maven/gradle
Scenario Two:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 968
Date: Tue, 04 Jun 2013 03:08:05 GMT
Connection: close
The only reason behind this is that fact that your request is not formatted correctly. If you checkout the detailed curl response you should be able to see "The request sent by the client was syntactically incorrect.".
Root cause:Either your JSON format is not right or you are missing a mandatory parameter for the JAVA object.
Actions:Make sure you provide the JSON object in correct format and with the right number of parameters. Nullable properties are not mandatory but you do have to provide data for all NotNullable properties. It is VERY important to remember that Spring is using Java reflection to turn yours JSON file into Java objects, what does this mean? it means that variable and method names are CasE SensItiVe. If your JSON file is sending the variable "userName", than your matching variable in your Java object MUST also be named "userName". If you have getters and setters, they also have to follow the same rule. getUserName and setUserName to match our previous example.
Senario Three:
HTTP/1.1 415 Unsupported Media Type
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 1051
Date: Wed, 24 Aug 2011 08:50:17 GMT
Root cause: The Json media type is not supported by your web service. This could be due to your annotation not specifying the media type or you not specifying the media type in Curl post command.
Actions:Check your message convertor is set up correctly and make sure the web service annotation matches the example above. If these were fine, make sure you specify the content-type in your Curl post request.
The json media type is not supported by your web service.
Senario N(!):
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 04 Jun 2013 03:06:16 GMT
Congrats the user is actually send to your server side REST API.
For further details on how to set up your spring checkout the spring mvc guide.
Related posts and questions
This FAQ was not possible if it wasn't for all the people who provided the following posts and questions (this list will expand if I come across useful related posts/questions):
Should be good to notice that a bean class can NOT be handled if it has 2 or more setter for one field without @JsonIgnore
on optional ones. Spring/Jackson throw HttpMediaTypeNotSupportedException
and http status 415 Unsupported Media Type.
Example :
@JsonGetter
public String getStatus() {
return this.status;
}
@JsonSetter
public void setStatus(String status) {
this.status = status;
}
@JsonIgnore
public void setStatus(StatusEnum status) {
if (status == null) {
throw new NullPointerException();
}
this.status = status.toString();
}
Update : We also have to specify @JsonGetter
and @JsonSetter
in this case, not to have issues when used as return type.
Just tested it with Spring 3.2.2 and Jackson 2.2. It works fine as parameter ( @RequestBody
) and/or as return type ( @ResponseBody
).
Update 2 :
If @JsonGetter
and @JsonSetter
are specified, @JsonIgnore
seems not to be required.
上一篇: CouchDB cURL Windows命令行无效的JSON
下一篇: Spring 4.x / 3.x(Web MVC)REST API和JSON2 Post请求,如何正确使用它?