1. Understand REST
Representational State Transfer (REST) is Chinese translation of Representational State Transfer. Roy Thomas Fielding in his PhD thesis in 2000. It differs from traditional SOAP Web services in that REST focuses on the data to be processed, while SOAP focuses on behavior and processing. To understand REST well, it is easier to understand the first letters of REST in English.
Representational: With REST, URI resources on our network can be represented in a variety of formats, such as XML, JSON, or HTML.
State: REST is more concerned with the State of a resource than with the behavior it takes.
Transfer: REST enables a resource to be expressively transferred from one application to another (e.g. from a server to a client) during network transport.
Specifically, there are behaviors in REST, which are defined by the way HTTP represents operations: GET, POST, PUT, DELETE, PATCH; GET is used to obtain resources, POST is used to create resources (or update resources), PUT is used to update resources, DELETE is used to DELETE resources, and PATCH is used to update resources. With a view like REST, we need to avoid terms like REST services and REST Web services, which are somewhat behavior-oriented.
Error in using RESTful architecture design
**RESTful architecture: ** is a popular Internet software architecture based on REST. It has a clear structure, meets standards, is easy to understand, easy to expand, so more and more sites are using it.
Without knowing enough about REST, it’s easy to mistakenly think of it as “URL-based Web services,” meaning that REST, like SOAP, is a mechanism for remote Procedure Calls (RPC). But REST has almost nothing to do with RPC, which is service-oriented, while REST is resource-oriented, emphasizing things and nouns that describe applications. One easy result of this is to use verbs in urIs when designing RESTful apis. For example, GET /user/getUser/123. The correct way to write this is GET /user/123.
Third, springMVC supports RESTful
Since Spring 3.0, some of spring’s enhancements to springMVC have provided good support for RESTful features. In release 4.0, Spring supports the creation of REST resources in the following ways:
-
The controller handles all HTTP methods, including the main REST methods: GET, POST, PUT, DELETE, and PATCH.
-
With The help of Spring’s View parser, resources can be represented in a variety of ways, including rendering model data as XML, JSON, Atom, and already RSS View implementations;
-
You can use ContentNegotiatingViewResolver to choose the most suitable for client presentation;
-
Can replace view-based rendering with the @responseBody annotation and various HttpMethodConverter implementations;
-
Similarly, the @RequestBody annotation and the HttpMethodConverter implementation convert incoming HTTP data into Java objects that pass in controller handling methods;
-
With RestTemplate, Spring applications can easily use REST resources.
4. Rest-based Controller
Our REST API:
- GET request/API /user/ Returns the user list
- GET request/API /user/1 Returns the user whose ID is 1
- POST request/API /user/ Creates a new user object using the JSON parameter of the user object
- PUT request/API /user/3 Update the user object whose ID is 3 in JSON format
- DELETE requests/API /user/4 to DELETE the user object whose ID is 4
- DELETE request/API /user/ DELETE all users
package com.websystique.springmvc.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import com.websystique.springmvc.model.User;
import com.websystique.springmvc.service.UserService;
@RestController
public class HelloWorldRestController {
@Autowired
UserService userService; //Service which will do all data retrieval/manipulation work
//-------------------Retrieve All Users--------------------------------------------------------
@RequestMapping(value = "/user/", method = RequestMethod.GET)
public ResponseEntity<List<User>> listAllUsers() {
List<User> users = userService.findAllUsers();
if(users.isEmpty()){
returnnew ResponseEntity<List<User>>(HttpStatus.NO_CONTENT); //You many decide toreturn HttpStatus.NOT_FOUND
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}
//-------------------Retrieve Single User--------------------------------------------------------
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(@PathVariable("id") long id) {
System.out.println("Fetching User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
//-------------------Create a User--------------------------------------------------------
@RequestMapping(value = "/user/", method = RequestMethod.POST)
public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
System.out.println("Creating User " + user.getName());
if (userService.isUserExist(user)) {
System.out.println("A User with name " + user.getName() + " already exist");
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
userService.saveUser(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
//------------------- Update a User --------------------------------------------------------
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {
System.out.println("Updating User " + id);
User currentUser = userService.findById(id);
if (currentUser==null) {
System.out.println("User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
currentUser.setName(user.getName());
currentUser.setAge(user.getAge());
currentUser.setSalary(user.getSalary());
userService.updateUser(currentUser);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}
//------------------- Delete a User --------------------------------------------------------
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {
System.out.println("Fetching & Deleting User with id " + id);
User user = userService.findById(id);
if (user == null) {
System.out.println("Unable to delete. User with id " + id + " not found");
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
userService.deleteUserById(id);
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}
//------------------- Delete All Users --------------------------------------------------------
@RequestMapping(value = "/user/", method = RequestMethod.DELETE)
public ResponseEntity<User> deleteAllUsers() {
System.out.println("Deleting All Users");
userService.deleteAllUsers();
returnnew ResponseEntity<User>(HttpStatus.NO_CONTENT); }}Copy the code
Springmvc annotations in detail
RestController: First we use the new Spring 4 annotation @restController annotation.
This annotation avoids the @responseBody annotation for every method. So @restController itself is wearing the @responseBody annotation, so look at that
@requestbody: if the method parameter is annotated by @requestbody, Spring will bind the HTTP RequestBody to that parameter. If you do that, Spring will use HTTP Message Converters to convert the HTTP request body to a Domain object based on the ACCEPT or Content-Type headers in the request (privately).
@responseBody: If the method annotates @responseBody, Spring returns the value to the ResponseBody. If you do, Spring uses HTTP Message Converters (privately) to convert the Domain object into the response body based on the Content-Type header in the request.
ResponseEntity: is a real data. It represents the entire HTTP response. The nice thing about it is that you can control any object you put inside it.
You can specify the status code, header information, and response body. It contains the information that you want to build the HTTP Response.
@pathVariable: This annotation means that a method parameter should be bound to a URL template variable [one in ‘{}’]
In general, you need to understand @RestController, @RequestBody, ResponseEntity, and @PathVariable annotations to implement the REST API in Spring 4. In addition, Spring provides support classes to help you implement things that are customizable.
MediaType: With the @requestMapping annotation, you can specify additional MediaType production or consumption via a special controller method.
Release and test the API
To test the API, I’ll use POSTMAN, an external client, and then we’ll write our own.
1. Obtain all users
Open the POSTMAN tool, select the request type GET, and specify the URI
Note: We did not specify any HTTP headers. Click Send to receive a list of all users
Also note the HTTP 200 response.
You may be wondering why this response is sent as a JSON string, and the Content-Type header in the response indicates this. Because we added JACKSON
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> The < version > 2.5.3 < / version > < / dependencyCopy the code
Because Spring found this library in the class path, it calls the built-in MappingJackson2HttpMessageConverter converter converts response (a collection of objects) to the JSON format. The nice thing about Spring’s built-in converters is that most of the time you just need to put the library on the classpath to do the conversion. Of course, sometimes we need to use our API. For example, if we want to provide XML as well, we need to annotate the User class with JAXB annotations.
2. Obtain a user
GET specifies /user/1
Now try sending a GET request with an error identifier and you will receive an HTTP 404
3. Create a User
Select the POST method, specify the URI /user/ Specify the POSTMAN Body TAB, and select the Application/JSON type
Content-Type
Remember: The Accept header contains the types that the client can recognize. The Content-type header indicates the actual data Type.
When I hit Send, I’ll get HTTP 200 with no response body.
You can query for newly created users
This is a common implementation of REST. But there’s nothing stopping you from sending content to the body of a POST or PUT response. But this is still REST API, right? Doubtful. Anyway, when we try to create the same user, you’ll get HTTP conflicting responses.
4. Update users
Send an HTTP PUT request to update the user.
5. Delete the user
6 Delete all users
7. Verify that the user is deleted
6. Write REST clients based on the RestTemplate
Postman is an excellent tool for testing Rest apis, but if you want to fully digest Rest, try writing your own. The best known Htpp client is HttpClient (Apache HttpComponents). But using it to access REST services is relatively rare. Spring’s RestTemplate appears. RestTemplate provides advanced methods that respond to the six main HTTP methods.
HTTP method and corresponding RestTemplate method:
- HTTP GET : getForObject, getForEntity
- HTTP PUT: PUT (String URL, Object Request, String… urlVariables)
- HTTP DELETE : delete
- HTTP POST: postForLocation(String URL, Object Request, String… PostForObject (String URL, Object Request, ClassresponseType, String… uriVariables)
- HTTP HEAD: headForHeaders(String URL, String… urlVariables)
- HTTP OPTIONS: optionsForAllow(String URL, String… urlVariables)
- HTTP PATCH and others : exchange execute
Define Rest Clients and Rest services
package com.websystique.springmvc;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.web.client.RestTemplate;
import com.websystique.springmvc.model.User;
public class SpringRestTestClient {
public static final String REST_SERVICE_URI = "http://localhost:8080/Spring4MVCCRUDRestService";
/* GET */
@SuppressWarnings("unchecked")
private static void listAllUsers(){
System.out.println("Testing listAllUsers API-----------");
RestTemplate restTemplate = new RestTemplate();
List<LinkedHashMap<String, Object>> usersMap = restTemplate.getForObject(REST_SERVICE_URI+"/user/", List.class);
if(usersMap! =null){for(LinkedHashMap<String, Object> map : usersMap){
System.out.println("User : id="+map.get("id") +", Name="+map.get("name") +", Age="+map.get("age") +", Salary="+map.get("salary")); }}else{
System.out.println("No user exist----------");
}
}
/* GET */
private static void getUser(){
System.out.println("Testing getUser API----------");
RestTemplate restTemplate = new RestTemplate();
User user = restTemplate.getForObject(REST_SERVICE_URI+"/user/1", User.class);
System.out.println(user);
}
/* POST */
private static void createUser() {
System.out.println("Testing create User API----------");
RestTemplate restTemplate = new RestTemplate();
User user = new User(0,"Sarah",51,134);
URI uri = restTemplate.postForLocation(REST_SERVICE_URI+"/user/", user, User.class);
System.out.println("Location : "+uri.toASCIIString());
}
/* PUT */
private static void updateUser() {
System.out.println("Testing update User API----------");
RestTemplate restTemplate = new RestTemplate();
User user = new User(1,"Tomy", 33, 70000); restTemplate.put(REST_SERVICE_URI+"/user/1", user);
System.out.println(user);
}
/* DELETE */
private static void deleteUser() {
System.out.println("Testing delete User API----------");
RestTemplate restTemplate = new RestTemplate();
restTemplate.delete(REST_SERVICE_URI+"/user/3");
}
/* DELETE */
private static void deleteAllUsers() {
System.out.println("Testing all delete Users API----------");
RestTemplate restTemplate = new RestTemplate();
restTemplate.delete(REST_SERVICE_URI+"/user/"); } public static void main(String args[]){ listAllUsers(); getUser(); createUser(); listAllUsers(); updateUser(); listAllUsers(); deleteUser(); listAllUsers(); deleteAllUsers(); listAllUsers(); }}Copy the code
Restart the server and run the above program.
Here is the output:
Testing listAllUsers API----------- User: ID =1, Name=Sam, Age=30, Salary=70000.0 User: Id =2, Name=Tom, Age=40, Salary=50000.0 User: id=3, Name=Jerome, Age=45, Salary=30000.0 User: Id =4, Name=Silvia, Age=50, Salary=40000.0 Testing getUser API---------- User [id=1, Name= Sam, Age= 30, Salary =70000.0] Testing create User API---------- Location: http://localhost:8080/Spring4MVCCRUDRestService/user/5 Testing listAllUsers API----------- User : Id =1, Name=Sam, Age=30, Salary=70000.0 User: id=2, Name=Tom, Age=40, Salary=50000.0 User: Id =3, Name=Jerome, Age=45, Salary=30000.0 User: id=4, Name=Silvia, Age=50, Salary=40000.0 User: Id =5, Name=Sarah, Age=51, Salary=134.0 Testing update User API---------- User [id=1, Name= Tomy, Age= 33, Salary=70000.0] Testing listAllUsers API----------- User: id=1, Name=Tomy, Age=33, salary=70000.0 User: Id =2, Name=Tom, Age=40, Salary=50000.0 User: id=3, Name=Jerome, Age=45, Salary=30000.0 User: Id =4, Name=Silvia, Age=50, Salary=40000.0 User: Id =5, Name=Sarah, Age=51, Salary=134.0 Testing delete User API---------- Testing listAllUsers API----------- User: Id =1, Name=Tomy, Age=33, Salary=70000.0 User: ID =2, Name=Tom, Age=40, Salary=50000.0 User: Id =4, Name=Silvia, Age=50, Salary=40000.0 User: id=5, Name=Sarah, Age=51, Salary=134.0 Testing all delete Users API---------- Testing listAllUsers API----------- No user exist----------Copy the code
7. Complete examples
1. Project structure
Add project dependencies to POM.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> < modelVersion > 4.0.0 < / modelVersion > < groupId > com. Websystique. For springmvc < / groupId > The < artifactId > Spring4MVCCRUDRestService < / artifactId > < packaging > war < / packaging > < version > 1.0.0 < / version > <name>Spring4MVCCRUDRestService Maven Webapp</name> <properties> < springframework version > 4.2.0. RELEASE < / springframework version > < Jackson. Version > 2.5.3 < / Jackson version > < / properties > <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> <build> <pluginManagement> . < groupId > org, apache maven plugins < / groupId > < artifactId > maven - compiler - plugin < / artifactId > < version > 3.2 < / version > <configuration> <source> 1.7 < /source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> The < artifactId > maven - war - the plugin < / artifactId > < version > 2.4 < / version > < configuration > <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>Spring4MVCCRUDRestService</warName> <failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<finalName>Spring4MVCCRUDRestService</finalName>
</build>
</project>
Copy the code
3, the User Service
package com.websystique.springmvc.service;
import java.util.List;
import com.websystique.springmvc.model.User;
public interface UserService {
User findById(long id);
User findByName(String name);
void saveUser(User user);
void updateUser(User user);
void deleteUserById(long id);
List<User> findAllUsers();
void deleteAllUsers();
public boolean isUserExist(User user);
}
Copy the code
package com.websystique.springmvc.service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.websystique.springmvc.model.User;
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{
private static final AtomicLong counter = new AtomicLong();
private static List<User> users;
static{
users= populateDummyUsers();
}
public List<User> findAllUsers() {
return users;
}
public User findById(long id) {
for(User user : users){
if(user.getId() == id){
returnuser; }}return null;
}
public User findByName(String name) {
for(User user : users){
if(user.getName().equalsIgnoreCase(name)){
returnuser; }}return null;
}
public void saveUser(User user) {
user.setId(counter.incrementAndGet());
users.add(user);
}
public void updateUser(User user) {
int index = users.indexOf(user);
users.set(index, user);
}
public void deleteUserById(long id) {
for (Iterator<User> iterator = users.iterator(); iterator.hasNext(); ) {
User user = iterator.next();
if (user.getId() == id) {
iterator.remove();
}
}
}
public boolean isUserExist(User user) {
returnfindByName(user.getName())! =null; } private static List<User>populateDummyUsers(){
List<User> users = new ArrayList<User>();
users.add(new User(counter.incrementAndGet(),"Sam", 30, 70000)); users.add(new User(counter.incrementAndGet(),"Tom", 40, 50000)); users.add(new User(counter.incrementAndGet(),"Jerome", 45, 30000)); users.add(new User(counter.incrementAndGet(),"Silvia", 50, 40000));return users;
}
public void deleteAllUsers() { users.clear(); }}Copy the code
4. The Model class
package com.websystique.springmvc.model;
public class User {
private long id;
private String name;
private int age;
private double salary;
public User(){
id=0;
}
public User(long id, String name, int age, double salary){
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (id ^ (id >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if(getClass() ! = obj.getClass())return false;
User other = (User) obj;
if(id ! = other.id)return false;
return true;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age
+ ", salary=" + salary + "]"; }}Copy the code
5. Configuration classes
** Note: ** The following configuration is equivalent to the applicationContext-SpringMVC.xml configuration file. This is just a Java class configuration for SpringMVC, which is a configuration saving approach.
package com.websystique.springmvc.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.websystique.springmvc")
public class HelloWorldConfiguration {
}
Copy the code
Since the restful approach does not require view configuration, no implementation is required.
6. Initialize classes (equivalent to web.xml files)
** Note: ** This initialization class is equivalent to the web.xml file, thus eliminating the need to configure web.xml.
package com.websystique.springmvc.configuration; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class HelloWorldInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<? > []getRootConfigClasses() {
returnnew Class[] { HelloWorldConfiguration.class }; } @Override protected Class<? > []getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/"}; }}Copy the code
Add CORS support to REST API
When accessing REST apis, you may face the same origin policy problem.
The error is as follows:
“No ‘access-Control-allow-origin’ header is present on the requested resource. Origin ‘http://127.0.0.1:8080’ is Therefore not allowed access. “OR” XMLHttpRequest cannot load http://abc.com/bla. Origin http://localhost:12345 is not Allowed by the Access Control – Allow – Origin.”
In general, on the server side, we return additional CORS access control headers in the response, implementing cross-domain chaining.
With Spring, we can write a simple filter to add CORS signature headers to each response.
package com.websystique.springmvc.configuration;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
public class CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
System.out.println("Filtering on...........................................................");
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin"."*");
response.setHeader("Access-Control-Allow-Methods"."POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age"."3600");
response.setHeader("Access-Control-Allow-Headers"."x-requested-with");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}}Copy the code
You need to add it to your Spring configuration:
package com.websystique.springmvc.configuration; import javax.servlet.Filter; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class HelloWorldInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<? > []getRootConfigClasses() {
returnnew Class[] { HelloWorldConfiguration.class }; } @Override protected Class<? > []getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Filter[] getServletFilters() {
Filter [] singleton = { new CORSFilter()};
returnsingleton; }}Copy the code
-
Download the source code: http://websystique.com/?smd_process_download=1&download_id=1689
-
Download the source code (with CORS) : http://websystique.com/?smd_process_download=1&download_id=1890
The resources
- https://blog.csdn.net/w605283073/article/details/51338765#commentsedit
- https://blog.csdn.net/bhuds/article/details/73865745
- http://www.ruanyifeng.com/blog/2011/09/restful.html
- http://www.ruanyifeng.com/blog/2014/05/restful_api.html
- http://blog.jobbole.com/41233/
If there are any improper articles, please correct them. You can also follow my wechat official account: Learn Java well and get quality learning resources.