7 微服务间通信
7.1 RestTemplate+Ribbon
7.1.1 微服务间通信的目的
在微服务架构下,服务和服务之间必不可少的会产生一些依赖,导致彼此之间会产生一些调用。
目前Spring Cloud提供了两种解决方案,其中一种就是“RestTemplate+Ribbon”。
微服务项目,会员服务就依赖于图书管理服务:
7.1.2 创建被调用服务接口
在book应用服务中:
在包com.marshal.springcloudsctsjybook.service.exception中创建BookNotFoundException类
package com.marshal.springcloudsctsjybook.service.exception;
public class BookNotFoundException extends RuntimeException{
public BookNotFoundException(String msg){
super();
}
}
在包com.marshal.springcloudsctsjybook.service中创建BookService的服务方法
public Book getInfo(Long bid){
Optional<Book> optional = bookRepository.findById(bid);
Book book = null;
if(optional.isPresent()){
book = optional.get();
}else{
throw new BookNotFoundException("BOOKID:" + bid + " not found");
}
return book;
}
在包com.marshal.springcloudsctsjybook.controller中编写BookController的方法
@GetMapping("/info")
private Map info(Long bid){
Map result = new HashMap();
try{
Book book = bookService.getInfo(bid);
result.put("code" , "0");
result.put("message" , "success");
result.put("data" , book);
}catch (Exception e){
e.printStackTrace();
result.put("code" , e.getClass().getSimpleName());
result.put("message" , e.getMessage());
}
return result;
}
7.1.3 创建调用服务接口服务
7.1.3.1 第一种方式:使用Spring Cloud内置的RestTemplate对象
在包com.marshal.springcloudsctsjymember.controller的控制类MemberController中编写调用接口测试方法。
这是一种最简单的方式,其实RestTemplate底层进行的http传输就是使用的Apache的HttpClient组件。
@GetMapping
@ResponseBody
public String test(Long bid){
RestTemplate restTemplate = new RestTemplate();
String json = restTemplate.getForObject("http://localhost:8100/info?bid="+bid, String.class);
System.out.println(json);
return json;
}
该种方式由于访问地址及端口号被写在代码中,维护不便。因此该种方式不推荐使用。
7.1.3.2 第二种方式:采用Ribbon进行客户端负载均衡
在包com.marshal.springcloudsctsjymember.controller的控制类MemberController中添加负载均衡客户端
@Resource
private LoadBalancerClient loadBalancerClient;
LoadBalancerClient是Ribbon的核心组件。
@GetMapping
@ResponseBody
public String test2(Long bid){
RestTemplate restTemplate = new RestTemplate();
// 获取服务列表
ServiceInstance serviceInstance = loadBalancerClient.choose("book");
// 获取主机名
String host = serviceInstance.getHost();
// 获取端口号
Integer port = serviceInstance.getPort();
String json = restTemplate.getForObject("http://"+ host + ":" + port + "/info?bid="+bid, String.class);
System.out.println(json);
return json;
}
对于member这个应用服务来说,在程序运行之初通过运行程序
loadBalancerClient.choose("book")
向注册中心Eureka发送与“book”相关的请求,获取服务相关的服务器地址。对于LoadBalance来说就会向获取的两个应用服务发送请求,进行两个被调用接口服务的应用的负载均衡服务。
但是这种方式并不优雅,主要目的是讲解原理,在实际开发中并不常用。所以可以采用注解的方式进行,则有第三种方式。
7.1.3.3 第三种方式:利用注解简化URL通信
在入口类com.marshal.springcloudsctsjymember.SpringCloudSctsjyMemberApplication中增加类RestTemplate的注册方法。
@Bean // 将返回的RestTemplate对象注入到IOC容器中
@LoadBalanced // 对RestTemplate进行负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
入口类全部代码:
package com.marshal.springcloudsctsjymember;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudSctsjyMemberApplication {
@Bean // 将返回的RestTemplate对象注入到IOC容器中
@LoadBalanced // 对RestTemplate进行负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringCloudSctsjyMemberApplication.class, args);
}
}
在类com.marshal.springcloudsctsjymember.controller.MemberController中将
@Resource
private LoadBalancerClient loadBalancerClient;
进行注解掉,同时新增在入口类中注入的restTemplate对象
@Resource
private RestTemplate restTemplate;
并在该类中进行应用接口的注解方式调用
@GetMapping
@ResponseBody
public String test3(Long bid){
String json = restTemplate.getForObject("http://book/info?bid=" + bid, String.class);
return json;
}
利用注解开发的时候,URL主机名和端口号都要换成serviceid。
7.1.4 微服务间的通信组件Feign
7.1.4.1 Feign的概念解释
- Feign是一个声明web服务客户端,它出现唯一的目的就是为了简化微服务的通信过程
- 使用Feign开发创建一个接口并使用Spring MVC注解
- Feign集成Ribbon内置负载均衡
7.1.4.2 进行Feign的实现
1:首先需要在依赖包中增加对Feign的依赖支持
在pom.xml配置文件中增加依赖
<!-- 增加OpenFeign的dep -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2:在入口类com.marshal.springcloudsctsjymember.SpringCloudSctsjyMemberApplication中增加注解来启用Feign客户端。
@EnableFeignClients
入口类代码被修改为
package com.marshal.springcloudsctsjymember;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 启用Feign客户端
public class SpringCloudSctsjyMemberApplication {
@Bean // 将返回的RestTemplate对象注入到IOC容器中
@LoadBalanced // 对RestTemplate进行负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(SpringCloudSctsjyMemberApplication.class, args);
}
}
3:在member应用中新建包bookclient及接口BookClient
在包com.marshal.springcloudsctsjymember.bookclient中创建接口BookClient
package com.marshal.springcloudsctsjymember.bookclient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
// book是service-id
// @FeignClient(name="book") 指明这是Book微服务的调用客户端
@FeignClient(name="book")
public interface BookClient {
// 当调用getInfo方法的时候,自动向book微服务的/info发起请求
@GetMapping("/info")
// 调用时自动会将?bid=XXX附加到url中
public String getInfo(@RequestParam("bid") Long bid);
}
4:在微服务member中增加使用Feign的调用方法
在包com.marshal.springcloudsctsjymember.controller中的类MemberController中增加测试方法
首先在MemberController中将接口BookClient进行注入
@Resource
private BookClient bookClient;
编写测试方法
@GetMapping
@ResponseBody
public String test(Long bid){
String json = bookClient.getInfo(bid);
return json;
}
举例来说,如果使用post请求体访问则在BookClient接口中进行如下代码编写
@PostMapping("/create")
public String create(@RequestBody Map rcc);
注意:
- Get请求对应GetMapping
Get请求使用“@RequestParam”注解发送参数 - Post请求对应PostMapping
Post请求发送请求体使用“@RequestBody”注解发送
7.1.5 对实际返回数据结果的加工处理
在进行远程微服务的接口方法调用时,往往的实际结果为
{"code":"0",
"data":
{"bid":3,
"sn":"9787121331084",
"name":"疯狂Java讲义(第4版)",
"author":"李刚",
"publishing":"电子工业出版社",
"bprice":36.0,
"sprice":109.0,
"btype":"计算机与互联网",
"stock":99},
"message":"success"}
可是真正需要的是中间的那个部分Book实体类的相关属性。
解决方法是只需要按照以上结构创建对象就可以了。
在包com.marshal.springcloudsctsjymember.bookclient中新建实体类。
该类与微服务book中的实体类是一样的。
package com.marshal.springcloudsctsjymember.bookclient;
public class BookDTO {
private Long bid;
private String sn;
private String name;
private String author;
private String publishing;
private Float bprice;
private Float sprice;
private String btype;
private Integer stock;
public Long getBid() {
return bid;
}
public void setBid(Long bid) {
this.bid = bid;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPublishing() {
return publishing;
}
public void setPublishing(String publishing) {
this.publishing = publishing;
}
public Float getBprice() {
return bprice;
}
public void setBprice(Float bprice) {
this.bprice = bprice;
}
public Float getSprice() {
return sprice;
}
public void setSprice(Float sprice) {
this.sprice = sprice;
}
public String getBtype() {
return btype;
}
public void setBtype(String btype) {
this.btype = btype;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
}
然后在相同的包中再创建一个RestResult类。
package com.marshal.springcloudsctsjymember.bookclient;
public class RestResult {
private String code;
private String message;
private BookDTO data;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public BookDTO getData() {
return data;
}
public void setData(BookDTO data) {
this.data = data;
}
}
接下来只需要将BookClient接口返回的结果修改为RestResult就可以了
@GetMapping("/info")
public RestResult getInfo(@RequestParam("bid") Long bid);
而对于调用服务的MemberController中的方法则可以修改为
@GetMapping
@ResponseBody
public String test(Long bid){
RestResult restResult = bookClient.getInfo(bid);
return restResult.getData().getName();
}