Spring Cloud系列—OpenFeign远程调用
上篇文章:
Spring Cloud系列—Nacos配置中心https://blog.csdn.net/sniper_fandc/article/details/149939799?fromshare=blogdetail&sharetype=blogdetail&sharerId=149939799&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link
目录
1 OpenFeign的使用
1.1 引入依赖
1.2 在启动类添加注解
1.3 编写客户端
1.4 远程调用代码
1.5 运行结果
2 OpenFeign参数传递
3 OpenFeign继承
3.1 公共接口的jar包
3.2 服务提供方实现公共接口ProductInterface
3.3 服务调用方继承公共接口ProductInterface
4 OpenFeign抽取
4.1 公共接口的jar包
4.2 服务调用方代码修改
如果使用RestTemplate来进行远程调用,会发现拼接URL还是有点复杂,并且代码可读性差。
Spring Cloud的OpenFeign(Feign的升级版)提供了更高级的远程服务调用方式。
1 OpenFeign的使用
注意:本文的实现基于前面的文章。
1.1 引入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
谁要远程调用别的服务,就给谁添加依赖。
1.2 在启动类添加注解
在要进行远程调用的服务的启动类添加注解:
@EnableFeignClients
1.3 编写客户端
该客户端类似于被远程调用的服务对应的方法的声明,在远程调用服务中编写该客户端,就可以让我们像对象的方法调用一样进行简洁的远程调用:
@FeignClient(value = "product-service", path = "/product")public interface ProductApi {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId") Integer productId);}
@FeignClient(value = "product-service", path = "/product")注解声明该接口是远程调用product-service服务的接口,path表示该接口的所有方法的访问路径都具有统一前缀/product。
@RequestMapping("/{productId}")注解定义了具体的方法实现路径(该实现路径是path+@RequestMapping注解内的值),即/product/{productId}(上述写法可以去掉path,@RequestMapping注解内填上完整的接口路径)。
方法的声明格式就类似于product-service的方法实现一样,不一样的是productId是从路径中获取的,因此需要添加@PathVariable("productId")注解。
1.4 远程调用代码
@Servicepublic class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate ProductApi productApi;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//OpenFeign远程调用方式ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId());orderInfo.setProductInfo(productInfo);return orderInfo;}}
1.5 运行结果
运行程序,访问接口,结果如下:
2 OpenFeign参数传递
OpenFeign同样也支持类似SpringMVC的请求中参数传递,以下分别演示单个参数、多个参数、对象、JSON的参数传递效果:
首先需要在服务提供方创建相关的接口供服务调用方调用,这部分就是SpringMVC的相关知识:
@RequestMapping("/product")@RestControllerpublic class ProductController {//请求中传递单个参数@RequestMapping("/param1")public String param1(Integer productId){return "product-service收到请求中的参数为:Id:"+productId;}//请求中传递多个参数@RequestMapping("/param2")public String param2(Integer productId,String name){return "product-service收到请求中的参数为Id:"+productId+" name:"+name;}//请求中传递对象@RequestMapping("/object")public String object(ProductInfo productInfo){return "product-service收到请求中的参数为productInfo:"+productInfo.toString();}//请求中传递JSON(JSON格式是放在请求Body中传递的)@RequestMapping("/json")public String json(@RequestBody ProductInfo productInfo){return "product-service收到请求中的参数为productInfo的JSON格式:"+productInfo;}}
然后在服务调用方定义OpenFeign的客户端,声明服务提供方的相关方法和url:
@FeignClient(value = "product-service", path = "/product")public interface ProductApi {//请求中传递单个参数(注意参数绑定)@RequestMapping("/param1")String param1(@RequestParam("productId") Integer productId);//请求中传递多个参数(注意参数绑定)@RequestMapping("/param2")String param2(@RequestParam("productId")Integer productId,@RequestParam("name")String name);//请求中传递对象(注意@SpringQueryMap是做对象绑定的)@RequestMapping("/object")String object(@SpringQueryMap ProductInfo productInfo);//请求中传递JSON(JSON格式是放在请求Body中传递的)@RequestMapping("/json")String json(@RequestBody ProductInfo productInfo);}
最后服务调用方可以像调用对象的方法一样调用OpenFeign声明的方法,从而实现请求中传参的远程调用:
@RequestMapping("/order")@RestControllerpublic class OrderController {@Autowiredprivate ProductApi productApi;//order-service远程调用product-service的接口并传单个参数@RequestMapping("/feign-param1")public String feignParam1(@RequestParam("productId") Integer productId){return productApi.param1(productId);}//order-service远程调用product-service的接口并传多个参数@RequestMapping("/feign-param2")public String feignParam2(@RequestParam("productId") Integer productId, @RequestParam("name") String name){return productApi.param2(productId,name);}////order-service远程调用product-service的接口并传对象@RequestMapping("/feign-object")public String feignObject(ProductInfo productInfo){return productApi.object(productInfo);}////order-service远程调用product-service的接口并传JSON@RequestMapping("/feign-json")public String feignJson(@RequestBody ProductInfo productInfo){return productApi.json(productInfo);}}
启动服务,各个方法的运行结果如下:
3 OpenFeign继承
上述代码可以发现,在服务调用方实现OpenFeign客户端的代码和服务提供方实现的接口非常相似,那么我们就可以定义一个公共的接口,让服务提供方实现该接口,让服务调用方的OpenFeign客户端接口继承该接口,再把公共接口以一个单独公共的jar包导入项目中,实现项目的解耦。
3.1 公共接口的jar包
该jar包内部结构如下:
ProductInterface接口声明了远程调用的方法:
public interface ProductInterface {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId") Integer productId);//请求中传递单个参数(注意参数绑定)@RequestMapping("/param1")String param1(@RequestParam("productId") Integer productId);//请求中传递多个参数(注意参数绑定)@RequestMapping("/param2")String param2(@RequestParam("productId")Integer productId,@RequestParam("name")String name);//请求中传递对象(注意@SpringQueryMap是做对象绑定的)@RequestMapping("/object")String object(@SpringQueryMap ProductInfo productInfo);//请求中传递JSON(JSON格式是放在请求Body中传递的)@RequestMapping("/json")String json(@RequestBody ProductInfo productInfo);}
ProductInfo类的内容就是order-service和product-service用到的公共产品信息类,路径建议和order-service和product-service中的ProductInfo类保持一致(当然不同项目中肯定无法100%一致),这样导入的jar后其它代码改动就较小。
同时注意引入依赖,主要是SpringMVC和OpenFeign的依赖:
<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>
最后就是通过Maven将该模块打成jar包导入本地Maven仓库:
3.2 服务提供方实现公共接口ProductInterface
在服务提供方引入依赖,该依赖刚刚打包的依赖,可以直接先在要实现接口的类输入implements ProductInterface,然后用快捷键alt+Enter快捷引入pom文件中:
<dependency><groupId>org.example</groupId><artifactId>product_api</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency>
修改服务提供方代码实现该接口:
@RequestMapping("/product")@RestControllerpublic class ProductController implements ProductInterface {@Autowiredprivate ProductService productService;@RequestMapping("/{productId}")public ProductInfo getProductById(@PathVariable("productId") Integer productId){System.out.println("收到请求,Id:"+productId);return productService.selectProductById(productId);}//请求中传递单个参数@RequestMapping("/param1")public String param1(Integer productId){return "product-service收到请求中的参数为:Id:"+productId;}//请求中传递多个参数@RequestMapping("/param2")public String param2(Integer productId,String name){return "product-service收到请求中的参数为Id:"+productId+" name:"+name;}//请求中传递对象@RequestMapping("/object")public String object(ProductInfo productInfo){return "product-service收到请求中的参数为productInfo:"+productInfo.toString();}//请求中传递JSON(JSON格式是放在请求Body中传递的)@RequestMapping("/json")public String json(@RequestBody ProductInfo productInfo){return "product-service收到请求中的参数为productInfo的JSON格式:"+productInfo;}}
3.3 服务调用方继承公共接口ProductInterface
在服务调用方的OpenFeign客户端(接口)继承ProductInterface接口,也就继承了其中声明的方法:
@FeignClient(value = "product-service", path = "/product")public interface ProductApi extends ProductInterface {}
注意:此时由于ProductInfo包也是通过jar引入,因此原服务中相关的包路径都需要修改。
重启服务,测试远程调用接口均可以正常使用:
4 OpenFeign抽取
通过继承的方式,实际上还是需要在服务调用方写OpenFeign客户端接口,服务调用方越多,越麻烦。OpenFeign还提供了抽取的方式,可以直接把整个OpenFeign客户端接口都抽取成公共的模块,其它服务调用方只需要注入该模块即可使用。
4.1 公共接口的jar包
该jar包通常是由服务提供方制作,jar包的结构也类似继承方式时jar包的结构:
不同之处在于公共接口中的内容比继承方式的接口中的内容多了OpenFeign的注解表明这是OpenFeign客户端:
@FeignClient(value = "product-service", path = "/product")public interface ProductInterface {@RequestMapping("/{productId}")ProductInfo getProductById(@PathVariable("productId") Integer productId);//请求中传递单个参数(注意参数绑定)@RequestMapping("/param1")String param1(@RequestParam("productId") Integer productId);//请求中传递多个参数(注意参数绑定)@RequestMapping("/param2")String param2(@RequestParam("productId")Integer productId,@RequestParam("name")String name);//请求中传递对象(注意@SpringQueryMap是做对象绑定的)@RequestMapping("/object")String object(@SpringQueryMap ProductInfo productInfo);//请求中传递JSON(JSON格式是放在请求Body中传递的)@RequestMapping("/json")String json(@RequestBody ProductInfo productInfo);}
其它部分都同继承方式的内容,将该模块打包成jar包存入本地Maven仓库:
4.2 服务调用方代码修改
同样也需要引入该jar包的依赖:
<dependency><groupId>org.example</groupId><artifactId>product_api2</artifactId><version>1.0-SNAPSHOT</version><scope>compile</scope></dependency>
采用注入的方式来注入抽取的OpenFeign客户端接口(注意路径是新引入的jar包):
@RequestMapping("/order")@RestControllerpublic class OrderController {@Autowiredprivate OrderService orderService;@Autowiredprivate ProductInterface productInterface;@RequestMapping("/{orderId}")public OrderInfo getOrderById(@PathVariable("orderId") Integer orderId) {return orderService.selectOrderById(orderId);}//order-service远程调用product-service的接口并传单个参数@RequestMapping("/feign-param1")public String feignParam1(@RequestParam("productId") Integer productId){return productInterface.param1(productId);}//order-service远程调用product-service的接口并传多个参数@RequestMapping("/feign-param2")public String feignParam2(@RequestParam("productId") Integer productId, @RequestParam("name") String name){return productInterface.param2(productId,name);}////order-service远程调用product-service的接口并传对象@RequestMapping("/feign-object")public String feignObject(ProductInfo productInfo){return productInterface.object(productInfo);}////order-service远程调用product-service的接口并传JSON@RequestMapping("/feign-json")public String feignJson(@RequestBody ProductInfo productInfo){return productInterface.json(productInfo);}}
原先继承方式@FeignClient注解的OpenFeign客户端位于order-service的启动类同级目录下,因此该注解会在启动时被Spring扫描到从而作为Bean存储到Spring容器中。
而抽取方式由于直接引入OpenFeign客户端的依赖,jar包不再启动类同级目录下,因此该Bean无法被扫描到,因此需要手动声明扫描该Bean的路径:
//方式1:扫描路径下所有的Bean,即用@FeignClient注释的类(更耗时)@EnableFeignClients(basePackages = {"com.demo.product.api2"})//方式2:指定OpenFeign客户端,只扫描@FeignClient注解注释的类//@EnableFeignClients(clients = {ProductInterface.class})@SpringBootApplicationpublic class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}}
重启服务,测试远程调用接口:
下篇文章: