1,服务间通信方式
接下来在整个微服务架构中,我们比较关心的就是服务间的服务改如何调用,有哪些调用方式?
总结:在springcloud中服务间调用方式主要是使用 http restful方式进行服务间调用
2,RestTemplate
2.1 RestTemplate说明
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。RestTemplate是一种更优雅的调用RESTful服务的方式。
2.2.RestTemplate 服务调用
2.2.1 ,创建两个服务
- users 代表用户服务 端口为 9999
- products 代表商品服务 端口为 9998
`注意:这里服务仅仅用来测试,没有实际业务意义
2.2.2 ,注册到eureka注册中心中
2.2.2.1,修改users和products的pom文件
<dependencies>
<!-- 引入springboot的外部依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
</dependencies>
2.2.2.1,完成users和products的启动类
· products服务
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class Products9998Application {
public static void main(String[] args) {
SpringApplication.run(Products9998Application.class, args);
}
}
·users 服务
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class Users9999Application {
public static void main(String[] args) {
SpringApplication.run(Users9999Application.class, args);
}
}
2.2.2.1,完成users和products的配置文件
· products服务
#服务端口号
server.port=9998
#服务名称唯一标识
spring.application.name=products9998
#eureka注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
· users服务
#服务端口号
server.port=9999
#服务名称唯一标识
spring.application.name=users9999
#eureka注册中心地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
2.2.3 ,在商品服务( products)中提供服务方法
package com.study.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class ProductController {
public static final Logger LOGGER = LoggerFactory.getLogger(ProductController.class);
@Value("${server.port}")
private int port;
@GetMapping("/product/findAll")
public Map<String,Object> findAll(){
LOGGER.info("商品服务查询所有调用成功,当前服务端口:[{}]",port);
Map<String, Object> map = new HashMap<>();
map.put("msg","服务调用成功,服务提供端口为: "+port);
map.put("status",true);
return map;
}
}
2.2.4,在用户服务(users)中使用restTemplate进行调用
package com.study.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController {
public static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
@GetMapping("/user/findAll")
public String findAll(){
LOGGER.info("调用用户服务...");
//1.使用restTemplate调用商品服务
RestTemplate restTemplate = new RestTemplate();
String forObject = restTemplate.getForObject("http://localhost:9998/product/findAll",
String.class);
return forObject;
}
}
2.2.5,测试服务调用
浏览器访问用户服务 http://localhost:9999/user/findAll
2.2.6,总结
rest Template是直接基于服务地址调用没有在服务注册中心获取服务,也没有办法完成服务的负载均衡如果需要实现服务的负载均衡需要自己书写服务负载均衡策略。
3,Ribbon
3.1,Ribbon说明
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。
3.2,Ribbon 服务调用
使用restTemplate + ribbon进行服务调用
- 使用discovery client 进行客户端调用
- 使用loadBalanceClient 进行客户端调用
- 使用@loadBalanced 进行客户端调用
3.2.1,在创建一个products服务端口为9997
配置文件,pom文件,启动类,对外提供controller和9998端口的products服务一致
注意 :两个products的配置文件中服务名称改为相同的名称
spring.application.name=products
3.2.2 在users服务中添加ribbon的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
3.2.3,使用discovery Client形式调用
修改UserController
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/user/discoveryClient")
public String discoveryClient(){
List<ServiceInstance> products = discoveryClient.getInstances("PRODUCTS");
for (ServiceInstance product : products) {
LOGGER.info("服务主机:[{}]",product.getHost());
LOGGER.info("服务端口:[{}]",product.getPort());
LOGGER.info("服务地址:[{}]",product.getUri());
LOGGER.info("====================================");
}
RestTemplate restTemplate = new RestTemplate();
String forObject = restTemplate.getForObject(products.get(0).getUri()+"/product/findAll",
String.class);
return forObject;
}
3.2.4,使用loadBalance Client形式调用
修改UserController
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/user/loadBalanceClient")
public String loadBalanceClient(){
//根据负载均衡策略选取某一个服务调用
ServiceInstance product = loadBalancerClient.choose("PRODUCTS");
LOGGER.info("服务主机:[{}]",product.getHost());
LOGGER.info("服务端口:[{}]",product.getPort());
LOGGER.info("服务地址:[{}]",product.getUri());
RestTemplate restTemplate = new RestTemplate();
String forObject = restTemplate.getForObject(product.getUri()+"/product/findAll",
String.class);
return forObject;
}
3.2.5,使用@loadBalanced
3.2.5.1,创建 RestTemplateConfig
package com.study.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
//1.整合restTemplate + ribbon
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
3.2.5.2 修改UserController
@Autowired
private RestTemplate restTemplate;
@GetMapping("/user/restTemplateConfig")
public String restTemplateConfig(){
String forObject = restTemplate.getForObject("http://PRODUCTS/product/findAll", String.class);
return forObject;
}
4,OpenFeign
4.1,OpenFeign介绍
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性(可以使用springmvc的注解),可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,默认实现了负载均衡的效果并且springcloud为feign添加了springmvc注解的支持。
4.2,OpenFeign服务调用
4.2.1 在users服务中添加OpenFeign依赖
<!--Open Feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
4.2.2 修改users启动类
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // 开启openfeign
@EnableEurekaClient
public class Users9999Application {
public static void main(String[] args) {
SpringApplication.run(Users9999Application.class, args);
}
}
4.2.3 创建一个客户端调用接口
package com.study.fegin;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
//value属性用来指定:调用服务名称
@FeignClient("PRODUCTS")
public interface ProductClient {
@GetMapping("/product/findAll") //书写服务调用路径
String findAll();
}
4.2.4 使用feignClient客户端对象调用服务
@Autowired
private ProductClient productClient;
@GetMapping("/user/productClient")
public String productClient(){
String forObject = productClient.findAll();
return forObject;
}
4.3,调用服务并传参
服务和服务之间通信,不仅仅是调用,往往在调用过程中还伴随着参数传递,接下来重点来看看OpenFeign在调用服务时如何传递参数
4.3.1 GET方式调用服务传递参数
- 在商品服务中加入需要传递参数的服务方法来进行测试
- 在用户服务中进行调用商品服务中需要传递参数的服务方法进行测试
4.3.1.1,商品服务中添加如下方法
@GetMapping("/product/findOne")
public Map<String,Object> findOne(String productId){
LOGGER.info("商品服务查询商品信息调用成功,当前服务端口:[{}]",port);
LOGGER.info("当前接收商品信息的id:[{}]",productId);
Map<String, Object> map = new HashMap<String,Object>();
map.put("msg","商品服务查询商品信息调用成功,当前服务端口: "+port);
map.put("status",true);
map.put("productId",productId);
return map;
}
4.3.1.2,用户服务中在product客户端中声明方法
@FeignClient("PRODUCTS")
public interface ProductClient {
@GetMapping("/product/findOne")
String findOne(@RequestParam("productId") String productId);
}
4.3.1.3,用户服务中调用并传递参数
@GetMapping("/user/findOne/{productId}")
public String findOne(@PathVariable("productId") String productId){
LOGGER.info("通过使用OpenFeign组件调用商品服务...");
String msg = productClient.findOne(productId);
return msg;
}
4.3.2 post方式调用服务传递参数
- 在商品服务中加入需要传递参数的服务方法来进行测试
- 在用户服务中进行调用商品服务中需要传递参数的服务方法进行测试
4.3.2.1 商品服务加入post方式请求并接受name
@PostMapping("/product/save")
public Map<String,Object> save(String name){
LOGGER.info("商品服务保存商品调用成功,当前服务端口:[{}]",port);
LOGGER.info("当前接收商品名称:[{}]",name);
Map<String, Object> map = new HashMap<String,Object>();
map.put("msg","商品服务保存商品信息调用成功,当前服务端口: "+port);
map.put("status",true);
map.put("name",name);
return map;
}
4.3.2.2 用户服务中在product客户端中声明方法
@PostMapping("/product/save")
String save(@RequestParam("name") String name);
4.3.2.3 用户服务中调用并传递参数
@PostMapping("/user/save")
public String save(String productName){
LOGGER.info("接收到的商品信息名称:[{}]",productName);
String save = productClient.save(productName);
LOGGER.info("调用成功返回结果: "+save);
return save;
}
4.3.2.4 测试
4.3.3 传递对象类型参数
- 商品服务定义对象
- 商品服务定义对象接收方法
- 用户服务调用商品服务定义对象参数方法进行参数传递
4.3.3.1 商品服务定义对象
package com.study.entity;
import java.util.Date;
public class Product {
private Integer id;
private String name;
private Date bir;
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;
}
public Date getBir() {
return bir;
}
public void setBir(Date bir) {
this.bir = bir;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", bir=" + bir +
'}';
}
}
4.3.3.2 商品服务定义接收对象的方法
@PostMapping("/product/saveProduct")
public Map<String,Object> saveProduct(@RequestBody Product product){
LOGGER.info("商品服务保存商品信息调用成功,当前服务端口:[{}]",port);
LOGGER.info("当前接收商品名称:[{}]",product);
Map<String, Object> map = new HashMap<String,Object>();
map.put("msg","商品服务查询商品信息调用成功,当前服务端口: "+port);
map.put("status",true);
map.put("product",product);
return map;
}
4.3.3.3 将商品对象复制到用户服务中
@PostMapping("/product/saveProduct")
String saveProduct(@RequestBody Product product);
4.3.3.4 在用户服务中调用保存商品信息服务
@PostMapping("/user/saveProduct")
public String saveProduct(@RequestBody Product product){
LOGGER.info("接收到的商品信息:[{}]",product);
String save = productClient.saveProduct(product);
LOGGER.info("调用成功返回结果: "+save);
return save;
}