文章目录
- 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 {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductApi productApi;@Autowiredprivate 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 {@Autowiredprivate 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);@Autowiredprivate 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>