6.3 RestTemplate
- RestTemplate 是 Spring 3.0 之后支持 http 请求的一个工具,跟Spring Boot 无关,更跟 Spring Cloud 无关,提供了常见的 Rest 模板,如 GET、POST、PUT、DELETE,以及一些通用的 EXCHANG、EXCUTE方法。
RestTemplate 实现了 RestOperation 接口,并实现了其中的方法。RestOperation 接口定义了常见的 Restful 操作,
6.3.1 GET
- 在 provider 服务中定义一个 hello2 接口:
@GetMapping("/hello2")
public String hello2(String name){
return "hello" + name;
}
重新启动 provider 服务
- 在 consumer 中访问 hello2 接口,是 GET 请求,就调用RestTemplate 中的 getXXX() 方法
- 主要分为 getForObject 和 getForEntity 两大类方法,返回值不一致,getForObject 就是服务端返回的具体值,getForEntity 返回 ReponseEntity ,除了具体值,还有响应头的数据
- 代码编写
- @GetMapping("hello4")
public void hello4(){
String s = balanceRestTemplate.getForObject("http://provider/hello2?name={1}", String.class, "javaboy");
System.out.println(s);
ResponseEntity<String> responseEntity = balanceRestTemplate.getForEntity("http://provider/hello2?name={1}", String.class, "javaboy");
String body = responseEntity.getBody();
System.out.println("body:" + body);
HttpStatus statusCode = responseEntity.getStatusCode();
System.out.println("HttpStatus:" + statusCode);
int statusCodeValue = responseEntity.getStatusCodeValue();
System.out.println("statusCodeValue:" + statusCodeValue);
System.out.println("----------------header------------------");
HttpHeaders headers = responseEntity.getHeaders();
Set<Map.Entry<String, List<String>>> entries = headers.entrySet();
for (Map.Entry<String, List<String>> entry:entries) {
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
}
-
代码执行结果
-
看清楚区别后,接下来看各自的重载方法,getForObject 和 getForEntity 分别有三个重载方法,两者的三个重载方法基本是一致的,所以,这里只看其中一种。三个重载方法,其实代表了三种传参方式。
-
编写代码
@GetMapping("/hello5")
public void hello5() throws UnsupportedEncodingException, URISyntaxException {
//多个参数的传参方式
// balanceRestTemplate.getForObject("http://provider/hello2?name={1}&age={2}", String.class, "javaboy", 32);
String s = balanceRestTemplate.getForObject("http://provider/hello2?name={1}", String.class, "javaboy");
System.out.println(s);
HashMap<String, Object> map = new HashMap<>();
map.put("name", "zhangsan");
s = balanceRestTemplate.getForObject("http://provider/hello2?name={name}", String.class, map);
System.out.println(s);
// URI 形式中文必须转码
String url = "http://provider/hello2?name=" + URLEncoder.encode("张三", "UTF-8");
s = balanceRestTemplate.getForObject(new URI(url), String.class);
System.out.println(s);
}
- 代码执行结果
6.3.2 POST
- 首先在 provider 中提供两个 POST接口,同时因为 POST 请求可能需要传递 JSON,所以这里我们创建一个普通的 maven 项目作为 commons 模块,然后这个 commons 模块被 provider 和 consumer 共同引用,这样我们就可以方便的传递 JSON 了
- 分别在 provider 和 consumer 中添加 commons 模块的依赖
<dependency>
<groupId>org.javaboy</groupId>
<artifactId>commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 然后再 provider 中提供两个 POST 接口,两个接口代表了 POST 请求的两种传参方式,第一种方式是以 Key-Value 形式传参,第二种是以 JSON 的形式传参
@PostMapping("/user1")
public User addUser1(User user){
return user;
}
@PostMapping("/user2")
public User addUser2(@RequestBody User user){
return user;
}
重启 provider 服务
- 接下来,我们在 consumer 中调用这两个 POST 方法
首先来看一下 POST 的所有方法:
可以看到多出来三个 postForLocation 方法,postForObject 和 postForEntity 与前面的 GET 基本一致,主要看postForObject ,看完之后再来看额外的 postForLocation - 在 consumer 中再定义一个方法测试 postForObject
@GetMapping("/hello6")
public void hello6(){
MultiValueMap map = new LinkedMultiValueMap<String,Object>();
map.add("username","javaboy");
map.add("password","123");
map.add("id",99);
User user = balanceRestTemplate.postForObject("http://provider/user1", map, User.class);
System.out.println(user);
user.setId(98);
user = balanceRestTemplate.postForObject("http://provider/user2", user, User.class);
System.out.println(user);
}
- 执行结果
- 额外的 postForLocation
postForLocation 只对重定向的请求有左右,即响应头中有 location 返回头,返回状态码是 302 的请求
在 provider 中定义一个两个接口,其中一个是另一个的重定向结果
@Controller
public class UserController {
@PostMapping("/register")
public String register(User user){
return "redirect:http://provider/loginPage?username=" + user.getUsername();
}
@GetMapping("/loginPage")
@ResponseBody
public String loginPage(String username){
return "login " + username;
}
}
在 consumer 中调用第一个接口,使用返回的 URI 调用第二个接口
@GetMapping("/hello7")
public void hello7(){
MultiValueMap map = new LinkedMultiValueMap<String,Object>();
map.add("username","javaboy");
map.add("password","123");
map.add("id",99);
URI uri = balanceRestTemplate.postForLocation("http://provider/register", map, User.class);
System.out.println(uri);
String s = balanceRestTemplate.getForObject(uri, String.class);
System.out.println(s);
}
访问 hello7
注意在 proivder 中为 postForLocation 的接口,重定向的地址必须为绝对路径,如果是相对路径,会补上 provider 服务的 ip 和端口,但是我们使用的是 ribbon 的 restTemplate ,他会把 ip 和端口,当作服务的名字去 服务注册表中查询,导致服务失败。
- postForObject 失败的例子
@Controller
public class UserController {
@PostMapping("/register")
public String register(User user){
return "redirect:/loginPage?username=" + user.getUsername();
}
@GetMapping("/loginPage")
@ResponseBody
public String loginPage(String username){
return "login " + username;
}
}
单独拿出来重定向地址是可以请求成功的:
6.3.3 PUT
-
没有返回值
-
在 provider 中定义两个 PUT 接口,与 POST 请求类似,也是有 Key - Value 和 JSON 两种形式
@PutMapping("/user1")
@ResponseBody
public void updateUser1(User user){
System.out.println(user);
}
@PutMapping("/user2")
@ResponseBody
public void updateUser2(@RequestBody User user){
System.out.println(user);
}
然后再 consumer 服务中调用,也是 Key - Value 和 JSON 形式两种方式请求
@GetMapping("/hello8")
public void hello8(){
MultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
map.add("id",99);
map.add("username","javaboy");
map.add("password","123");
balanceRestTemplate.put("http://provider/user1",map);
User user = new User();
user.setId(100);
user.setUsername("xuan");
user.setPassword("456");
balanceRestTemplate.put("http://provider/user2", user);
}
在 provider 服务控制台显示:
6.3.4 DELET
-
没有返回值
-
delete 请求也有两种方式,分别是 Key - Value 和 PathVariable 形式
首先在 provider 中定义两种类型的接口
@DeleteMapping("/user1")
@ResponseBody
public void deleteUser1(Integer id){
System.out.println(id);
}
@DeleteMapping("/user2/{id}")
@ResponseBody
public void deleteUser2(@PathVariable Integer id){
System.out.println(id);
}
在 consumer 中调用这两种请求:
@GetMapping("/hello9")
public void hello9(){
balanceRestTemplate.delete("http://provider/user1?id=11");
balanceRestTemplate.delete("http://provider/user1?id={1}",22);
balanceRestTemplate.delete("http://provider/user2/{1}",33);
}
在 provider 服务的控制台:
至此,RestTemplate 的 基本用法介绍完了
6.4 RestTemplate 负载均衡
- RestTemplate 负载均衡是客户端负载均衡,客户端负载均衡是相对服务端负载均衡而言的
- 服务端负载均衡就是传统的 Nginx 方式,用 Nginx 做负载均衡,我们称之为服务端负债均衡
这种负载均衡,我们称之为服务端负债均衡,它的一个特点是,调用的客户端不知道具体哪一个服务端提供服务,它也不关心,就是客户端发给 Nginx,Nginx 再将请求发给 Tomcat ,客户端只需要记住 Nginx 地址就行了 - 服务端负载均衡则是另外一种形式:
客户端负载均衡,就是客户端本身知道所有 Server 的详细信息,当需要调用 Server 上的接口时,客户端从所维护的 Server 列表中,根据已配置好的负债均衡策略,自己挑选一个 Server 来调用,此时,客户端知道它所调用的 是哪一个 Tomcat - 在 RestTemplate 中,要使用这种客户端负载均衡,只需在 RestTemplate 实例上加上 @ReloadBalanced 注解即可,默认是线性负载均衡