文章目录
- 1. RestTemplate存在问题
- 2. OpenFeign介绍
- 3. 快速上手
- 引入依赖
- 添加注解
- 编写OpenFeign的客户端
- 远程调用
- 4. OpenFeign参数传递
- 从URL中获取参数
- 传递单个参数
- 传递多个参数
- 传递对象
- 传递JSON
- 5. 最佳实践
- Feign继承方式
- 创建一个新的模块
- 引入依赖
- 编写接口
- 打jar包
- 服务实现方实现ProductApi接口
- 服务消费方继承接口
- Feign抽取方式
- 创建一个module
- 引入依赖
- 编写API
- 打jar包
- 服务消费方使用接口
- 测试
- 6. 服务部署到Linux注意事项
- 修改pow.xml文件
1. RestTemplate存在问题
之前我们写过的远程调用的代码:
@Autowired
private RestTemplate restTemplate;
public OrderInfo getOrderById(int id) {
OrderInfo orderInfo = orderMapper.getOrderById(id);
String url = "http://product-service/product/getProductById?id=" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
- URL需要拼接,灵活性高,但是封装臃肿,容易出错
- 代码可读性差,风格不统一
微服务之间的通信,通常有两种:RCP和HTTP
在Spring Cloud中,默认是使用HTTP来进行微服务的通信,最常用的实现形式有两种:
- RestTemplate
- OpenFeign
2. OpenFeign介绍
OpenFeign是一个声明式的Web Service 客户端.它让微服务之间的调用变得更简单,类似于controller调用service,只需要创建一个接口,然后添加注解即可使用OpenFeign
Feign是Netflix公司的一个组件,16年Netflix将Feign捐献给社区,2016年7月OpenFeign的首个版本9.0.0发布
可以理解为 Netflix Feign是Open Feign的祖先,或者说OpenFeign是NetFlix Feign的升级版
Spring Cloud Feign是Spring 对 Feign的封装,将Feign项目集成到Spring Cloud生态系统中
3. 快速上手
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
添加注解
在order-service的启动类添加注解@EnableFeignClients
,开启OpenFeign的功能
@EnableFeignClients
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
编写OpenFeign的客户端
基于SpringMVC的注解来声明远程调用的信息
@FeignClient(value = "product-service",path = "/product/getProductBuId")
public interface ProductApi {
@RequestMapping("/{id}")
ProductInfo getProductBuId(@PathVariable("id") Integer id);
}
参数说明:
- name/value:指定FeignClient的名称,也就是微服务的名称,用于服务发现.Feign底层会使用Spring Cloud LoadBalance进行负载均衡,也可以使用
url
属性指定一个具体的url - path:定义当前FeignClient的统一前缀
远程调用
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductApi productApi;
@Autowired
private RestTemplate restTemplate;
public OrderInfo getOrderById(int id) {
OrderInfo orderInfo = orderMapper.getOrderById(id);
// String url = "http://product-service/product/getProductById?id=" + orderInfo.getProductId();
ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
Feign 简化了与HTTP服务交互的过程, 把REST客户端的定义转换为Java接⼝, 并通过注解的⽅式来声
明请求参数,请求⽅式等信息, 使远程调⽤更加方便和间接.
4. OpenFeign参数传递
从URL中获取参数
服务提供方product-service
@RequestMapping("product")
@RestController
public class ProductController {
@RequestMapping("/p1/{id}")
public String p1(@PathVariable("id") Integer id) {
return "p1接收到参数" + id;
}
}
OpenFeign客户端
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi {
@RequestMapping("/p1/{id}")
String p1 (@PathVariable("id") Integer id);
}
服务消费方order-service
@RestController
@RequestMapping("testOpenFeign")
public class TestFeignController {
@Autowired
private ProductApi productApi;
@RequestMapping("/o1")
public String o1(Integer id) {
return productApi.p1(id);
}
}
测试:
传递单个参数
服务提供方product-service
@RequestMapping("/p2")
public String p2(@RequestParam("id") int id) {
return "p2接受到参数" + id;
}
OpenFeign客户端
@RequestMapping("/p2")
String p2 (@RequestParam("id") Integer id);// 这里的@RequestParam做参数绑定,不能省略
服务消费方order-service
@RequestMapping("/o2")
public String o2(Integer id) {
return productApi.p2(id);
}
测试:
传递多个参数
需要使用多个@RequestParam进行参数绑定
服务提供方product-service
@RequestMapping("/p3")
public String p3(Integer id,String name) {
return "p3接受到参数" + id + ": " + name;
}
OpenFeign客户端
@RequestMapping("/p3")
String o3 (@RequestParam("id")Integer id, @RequestParam("name")String name);
服务消费方order-service
@RequestMapping("/o3")
public String o3(Integer id,String name) {
return productApi.o3(id,name);
}
测试:
传递对象
服务提供方product-service
@RequestMapping("/p4")
public String p4(ProductInfo productInfo) {
return "接收到参数" + productInfo;
}
OpenFeign客户端
@RequestMapping("/p4")
String p4 (@SpringQueryMap ProductInfo productInfo);
服务消费方order-service
@RequestMapping("/o4")
public String o4(ProductInfo productInfo) {
return productApi.p4(productInfo);
}
测试:
传递JSON
服务提供方product-service
@RequestMapping("/p5")
public String p5(@RequestBody ProductInfo productInfo) {
return "接收到参数" + productInfo;
}
OpenFeign客户端
@RequestMapping("/p5")
String p5 (@RequestBody ProductInfo productInfo);
服务消费方order-service
@RequestMapping("/o5")
public String o5(@RequestBody ProductInfo productInfo) {
return productApi.p5(productInfo);
}
测试:
5. 最佳实践
不难发现,Feign的客户端代码和服务提供者的代码有很大的相似性
提出两种解决方法
Feign继承方式
我们可以将一些常见的操作封装到接口里,服务提供方实现这个接口,服务消费方编写Feign接口的时候,直接继承这个接口
创建一个新的模块
将接口放在一个公共的jar包里面,供服务提供方和服务消费方使用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编写接口
public interface ProductInterface {
@RequestMapping("/getProductById")
ProductInfo getProductById(@RequestParam("id") Integer id);
@RequestMapping("/p1/{id}")
String p1 (@PathVariable("id") Integer id);
@RequestMapping("/p2")
String p2 (@RequestParam("id") Integer id);
@RequestMapping("/p3")
String p3 (@RequestParam("id")Integer id, @RequestParam("name")String name);
@RequestMapping("/p4")
String p4 (@SpringQueryMap ProductInfo productInfo);
@RequestMapping("/p5")
String p5 (@RequestBody ProductInfo productInfo);
}
注意:这里的ProductInfo
需要在product-api抽取出来,因此可以将order-service以及product-service里面的ProductInfo删除
打jar包
此时就会将product-api的jar包放到本地的maven仓库,供product-service以及order-service使用
服务实现方实现ProductApi接口
package org.JWCB.controller;
import org.JWCB.api.ProductInterface;
import org.JWCB.model.ProductInfo;
import org.JWCB.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RequestMapping("product")
@RestController
public class ProductController implements ProductInterface {
private static final Logger log = LoggerFactory.getLogger(ProductController.class);
@Autowired
private ProductService productService;
@RequestMapping("getProductById")
public ProductInfo getProductById(@RequestParam Integer id) {
log.info("getProductById");
return productService.getProductById(id);
}
@RequestMapping("/p1/{id}")
public String p1(@PathVariable("id") Integer id) {
return "p1接收到参数" + id;
}
@RequestMapping("/p2")
public String p2(@RequestParam("id") Integer id) {
return "p2接受到参数" + id;
}
@RequestMapping("/p3")
public String p3(Integer id,String name) {
return "p3接受到参数" + id + ": " + name;
}
@RequestMapping("/p4")
public String p4(ProductInfo productInfo) {
return "接收到参数" + productInfo;
}
@RequestMapping("/p5")
public String p5(@RequestBody ProductInfo productInfo) {
return "接收到参数" + productInfo;
}
}
此时需要将本地Maven仓库的ProductApi导入,可以先将接口名完整打出来,由idea自己导入
<dependency>
<groupId>org.JWCB</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
注意:这里的ProductInfo是来自于product-api这个包
服务消费方继承接口
package org.JWCB.api;
import org.JWCB.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.*;
@FeignClient(value = "product-service",path = "/product")
public interface ProductApi extends ProductInterface{
@RequestMapping("/getProductById")
ProductInfo getProductById(@RequestParam("id") Integer id);
@RequestMapping("/p1/{id}")
String p1 (@PathVariable("id") Integer id);
@RequestMapping("/p2")
String p2 (@RequestParam("id") Integer id);
@RequestMapping("/p3")
String o3 (@RequestParam("id")Integer id, @RequestParam("name")String name);
@RequestMapping("/p4")
String p4 (@SpringQueryMap ProductInfo productInfo);
@RequestMapping("/p5")
String p5 (@RequestBody ProductInfo productInfo);
}
测试:
Feign抽取方式
做法实际上与继承的方式相似,但是理念不同,这种方式是将Feign抽取成一个独立的模块
就是将Feign的Client抽取为一个独立的模块,并把涉及的实体类都放入这个模块里面,打成一个jar包. 服务消费方只需要依赖该jar包即可
创建一个module
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编写API
@FeignClient(value = "product-service",path = "/product")
public interface ProductInterface {
@RequestMapping("/getProductById")
ProductInfo getProductById(@RequestParam("id") Integer id);
@RequestMapping("/p1/{id}")
String p1 (@PathVariable("id") Integer id);
@RequestMapping("/p2")
String p2 (@RequestParam("id") Integer id);
@RequestMapping("/p3")
String o3 (@RequestParam("id")Integer id, @RequestParam("name")String name);
@RequestMapping("/p4")
String p4 (@SpringQueryMap ProductInfo productInfo);
@RequestMapping("/p5")
String p5 (@RequestBody ProductInfo productInfo);
}
打jar包
服务消费方使用接口
删除ProductApi以及ProductInfo
引入依赖:
<dependency>
<groupId>org.JWCB</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
修改order-service代码
指定扫描类
测试
6. 服务部署到Linux注意事项
使用Maven进行打包的时候.默认是从远程中央仓库进行下载的,但是product-api这个包在本地,我们可以制定从本地读取jar包
修改pow.xml文件
<dependency>
<groupId>org.JWCB</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>system</scope>
<systemPath>C:/Users/31127/.m2/repository/org/JWCB/product-api/1.0-SNAPSHOT/product-api-1.0-SNAPSHOT.jar</systemPath>
</dependency>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>