关于Rest和RestFul的个人理解
Rest全称是表述性状态转移,简单说就是将资源状态以最合适的方式从服务端转向客户端或者反过来。——摘自《spring in action》
那RestFul是什么,我个人理解为:Rest是一种编程风格或者说标准,实现了这种风格就叫做RestFul。
rest中的行为是通过http方法来定义的
create:post
read : get
udpate:put、patch
delete:delete
Spring是如何支持rest的
1、控制器可以处理所有的http方法
2、借助@PathVariable注解能够实现参数化url
3、借助@ResponseBody注解和各种资源转换器能够替换基于视图渲染的方式(controller处理完后跳过视图渲染过程),并且将其他类型的数据转化为java对象或者反过来
4、借助RestTemplate,可以在客户端很方便的调用rest资源
先来看一个简单的restful风格的控制器
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@ResponseBody
public User getUser(@PathVariable Integer id){
System.out.println("getUser...");
//访问数据库细节忽略
return user;
}
}
在getUser()方法上使用了@PathVariable注解实现参数化url,并且使用@ResponseBody注解,表示方法处理完后会跳过视图渲染过程,直接将数据以客户端需要的格式发送出去(例如json、xml),这是根据请求的Accept信息来决定的(例如,“application/json”则表示需要json数据),那怎么转换成相应的格式呢,那就需要spring的数据转换器,spring提供了多个数据转换器,并且更为方便的是,大部分常用的转换器只要引入了相应的库,就会自动注册,不需要任何配置,如json和xml。
那么如何在客户端调用rest服务呢,答案是使用RestTemplate
RestTemplate定义了如下11种操作,每一个都有重载,所以一共有36种方法
方法 | 描述 |
---|---|
getForObject() | 发送一个Http GET请求,将请求体映射为一个对象 |
getForEntity() | 发送一个Http GET请求,返回一个ResponseEntity对象,包含了响应踢映射对象和一些其他信息 |
delete() | 发送一个Http DELETE请求,对URL上指定资源做DELETE操作 |
put() | 发送一个Http PUT请求,对指定资源update操作 |
postForObject() | post数据到一个URL,并返回请求体映射的对象 |
postForEntity() | post数据到一个URL,返回一个ResponseEntity对象 |
postForLocation | post数据到一个URL,返回新创建资源的URL |
exchange() | 可以执行指定的http方法,并返回ResponseEntity对象 |
execute() | 可以执行指定的http方法,并返回请求体映射对象 |
headForHeaders() | 发送http HEAD请求,返回特定资源的URL的http头 |
optionsForAllow | 发送http optinos请求,返回特定资源的URL的allow头信息 |
下面通过例子一一介绍
服务端pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
服务端applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.llg"/>
<mvc:annotation-driven/>
</beans>
实体类User:
package com.llg.bean;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
客户端pom.xml
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
好。现在可以开始写测试代码了。
一、GET
在服务端创建如下控制器:
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@ResponseBody
public User getUser(@PathVariable Integer id){
//访问数据库细节忽略
User user = new User();
user.setId(id);
user.setName("zhangsan");
return user;
}
}
没什么好说的,直接启动服务器
客户端调用:
getUser()方法是通过id查询User信息,那么我们应该通过get请求去调用他:先使用getForObject()操作,他的三种重载方式如下:
getForObject(String类型的url,要返回的对象类型,用来填充url中的参数的可变参数列表)
getForObject(String类型的url,要返回的对象类型,用来填充url中的参数的map)
getForObject(java.net.URI类型的url,要返回的对象类型)
其他的操作重载方式雷同,后面将不做介绍
我使用的是我认为最方便的一种如下:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class restfulClient {
@Test
public void testGetObject(){
RestTemplate restFul = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/{id}";
User user = restFul.getForObject(url, User.class,1);
System.out.println(user);
}
}
在这个客户端测试方法中首先创建了一个RestTemplate,然后调用getForObject()方法
第一个参数是请求的url
第二个参数是返回的对象类型
第三个参数是一个可变参数列表,用来填充第一个参数url的占位符
运行截图
如果在获取对象是还想获取一些其他信息,则可以使用getEentity()方法,在这个例子中我获取了请求状态码
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<User> getUser(@PathVariable Integer id){
System.out.println("getUser...");
//访问数据库细节忽略
User user = null;
user.setId(id);
user.setName("zhangsan");
ResponseEntity<User> responseEntity;
responseEntity = new ResponseEntity<User>(user,null, HttpStatus.OK);
return responseEntity;
}
}
在这里我返回了一个ResponseEntity对象,他包含了第一个参数:数据对象,第二个参数:相应体,第三个参数:状态码
客户端调用:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class restfulClient {
@Test
public void testGetEntity(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/{id}";
ResponseEntity<User> responseEntity = template.getForEntity(url,User.class,1);
User user = responseEntity.getBody();
System.out.println(user);
System.out.println(responseEntity.getStatusCode());
}
}
如果服务器返回的是responseEntity 那就可以使用getForEntity()方法来调用,参数和getForObject()方法完全相同。
我们可以通过responseEntity对象的getBody()方法获取相应体中的资源对象
getStatusCode():获取请求状态码
getHeaders():获取相应头
运行截图:
二、PUT
直接看例子:
服务端:
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/update",method = RequestMethod.PUT)
@ResponseBody
public void updateUser(@RequestBody User user){
System.out.println(user);
System.out.println("update......");
}
}
在这里使用@RequestBody表明将请求的数据转换成user对象,与@ResponseBody功能恰好相反
客户端:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class restfulClient {
@Test
public void testPut(){
User user = new User();
user.setName("lisi");
user.setId(2);
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/update";
template.put(url,user);
}
}
这里准备了一个user对象,然后发送给服务端,这里由于url中没有参数,所以put方法的第三个可变参数没有
服务端输出:
三、DELETE
服务端:
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/delete/{id}" ,method = RequestMethod.DELETE)
@ResponseBody
public void deleteUser(@PathVariable Integer id){
System.out.println("delete....." + id);
}
}
没啥好说的
客户端:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class restfulClient {
@Test
public void testDelete(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/delete/{id}";
template.delete(url,3);
}
}
delete方法只有两个参数,一个是url,一个是可变参数列表
运行截图:
四、POST
先来看服务端:
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/save",method = RequestMethod.POST)
@ResponseBody
public User saveUser(@RequestBody User user){
System.out.println(user);
System.out.println("save......");
user.setId(666);
return user;
}
}
在这个方法中接收一个User,然后进行save操作,然后将自动生成的id绑定到User上返回。
客户端的post请求有三种方法。
第一种是如果我只想获取服务端返回的对象,那么就使用getForObject()方法
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
public class restfulClient {
@Test
public void testPost1(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/save";
User user = new User();
user.setName("jack");
User user1 = template.postForObject(url, user, User.class);
System.out.println(user1);
}
}
在这个客户端方法中,首先创建了一个用于保存的user对象,注意这里并没有给id属性赋值,
然后调用getForObject()方法,他有三个参数,请求url、要发送的对象、要返回的对象类型、可变参数列表。
然后打印返回的user对象。
服务端输出:
:
客户端输出:
如果在客户端还想获得更多的信息,可以使用postForEntity()方法:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
public class restfulClient {
@Test
public void testPost2(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/save";
User user = new User();
user.setName("jack");
ResponseEntity<User> responseEntity= template.postForEntity(url, user, User.class);
System.out.println(responseEntity.getStatusCode());
System.out.println(responseEntity.getHeaders());
System.out.println(responseEntity.getBody());
}
}
postForEntity()方法参数和postForObject()一样,但返回的是一个responseEntity,在这个对象中包含了返回对象的信息,响应头和请求状态码。
客户款输出:
还有一个方法postForLocation()是获取响应头中的location信息,列如:
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/save",method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<User> save(@RequestBody User user){
System.out.println(user);
System.out.println("save......");
user.setId(666);
HttpHeaders httpHeaders = new HttpHeaders();
try {
httpHeaders.setLocation(new URI("http://localhost:8080/RestFulServer/user/"+666));
} catch (URISyntaxException e) {
e.printStackTrace();
}
System.out.println(httpHeaders.getLocation());
ResponseEntity<User> entity = new ResponseEntity<>(user,httpHeaders,HttpStatus.OK);
return entity;
}
}
在这个方法中,我不但返回了一个ResponseEntity对象,他里面不但包含了user对象,还包含了httpHeaders 对象,并且修改httpHeaders 对象的location属性为型添加的数据的访问地址。
在实际需求中,我们插入数据之后可能不关心插入的数据,只关心访问地址,列如我们插入一个用户后跳转到用户首页,那么我们就可以使用像上面这样将地址放在请求头的location属性中然后在客户端中使用postForLocation()方法获取
客户端测试代码:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
public class restfulClient {
@Test
public void testPost3(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/save";
User user = new User();
user.setName("jack");
String location = template.postForLocation(url, user).toString();
System.out.println(location);
}
}
客户端输出:
然后在客户端中就可以继续操作这个URL来访问刚插入的数据
五、exchange方法
exchange()方法能够做前面很多方法能做的事,方法声明如下(有三种重载方法,只介绍一种,与get的重载方式一样)
RespnoseEntity<T> exchange(url,请求类型,HttpEntity对象设置请求头,返回对象类型、可变参数列表)
这个方法可以替换上面例子中的get操作,只要吧请求类型设置为GET,他几乎和getForObject()方法一样,只不过他多了一个可以设置请求头的参数,这意味着我们可以修改请求头的各项值,然后发送给服务端。
是否还记得之前说过,服务端会将数据转换成客户端需要的格式,这是通过Accept头信息来决定的。
如果发送请求是不指定的话,Accept信息可能会是这样:
Accpet:application/xml , application/json
当然可能不止上面两个,他表明客户端需要的数据类型是xml或json,那么在服务端发送数据时就会去找这两种类型的数据转换器转换成相应的格式,如果两种转换器都有(上面的例子中,我的项目中中i有json的库,所以只有json转换器,客户端也有json库,所以可以将json转换成java对象),他会转换成accpet信息中靠前的数据,在这里是xml。那我们如果只想要json数据,那么可以在请求时将accpet信息设置成这样: application/json
看例子:
服务端
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
@ResponseBody
public User getUser(@PathVariable Integer id){
System.out.println("getUser...");
//访问数据库细节忽略
User user = new User();
user.setId(id);
user.setName("zhangsan");
return user;
}
}
服务端用之前get方法的例子
客户端:
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class restfulClient {
@Test
public void testExChange(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/{id}";
MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
headers.add("Accept","application/json");
HttpEntity<Object> httpEntity = new HttpEntity<Object>(headers);
ResponseEntity<User> entity = template.exchange(url, HttpMethod.GET,httpEntity,User.class,666);
System.out.println(entity.getBody());
}
}
在客户端中我们通过将请求头参数放在一个MultiValueMap里面,然后用这个map作为httpEntity的构造参数,然后发送请求:
客户端输出:
可以看到和使用getForEntity方法返回的是一样的。
如果我们将accpet信息设置成这样: application/xml
我们推断如果服务端和客户端都有xml的数据转换器的话,那么结果是一样的,但是中间传输数据方式不一样
但是现在我服务端和客户端都没有,应该会抛出异常
package com.llg.test;
import com.llg.bean.User;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class restfulClient {
@Test
public void testExChange2(){
RestTemplate template = new RestTemplate();
String url = "http://localhost:8080/RestFulServer/user/{id}";
MultiValueMap<String,String> headers = new LinkedMultiValueMap<>();
headers.add("Accept","application/xml");
HttpEntity<Object> httpEntity = new HttpEntity<Object>(headers);
ResponseEntity<User> entity = template.exchange(url, HttpMethod.GET,httpEntity,User.class,666);
System.out.println(entity.getBody());
}
}
这里我们修改了请求头信息,将accpet信息设置成这样: application/xml
服务端由于没有xml的数据转换器,抛出了这样一条警告:
31-May-2019 12:07:46.948 警告 [http-nio-8080-exec-7] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.logException Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
客户端:
另外我们发现我之前写的每一个控制器方法上都有@ResponseBody注解,那我们可以使用@RestController替换类上的@Controller注解,这样相当于在该类的每一个方法上都添加了@ResponseBody,就像这样:
package com.llg.controller;
import com.llg.bean.User;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.net.URISyntaxException;
@RestController
@RequestMapping(value = "/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User getUser(@PathVariable Integer id){
System.out.println("getUser...");
//访问数据库细节忽略
User user = new User();
user.setId(id);
user.setName("zhangsan");
return user;
}
}