(一).前言

这是我们远程调用的代码,虽然RestTemplate对HTTP封装后,已经比直接使用HTTPClient简单方便很多,但是还存在一些问题,例如,拼接URL,当URL复杂时容易出错;代码可读性差;如果传递多个参数的时候,会很麻烦。
微服务之间的通信方式,通常有两种:RPC和HTTP
在SpringCloud中,默认是使用HTTP来进行微服务的通信,最常用的实现方式形式有两种:RestTemplate和OpenFeign,下面就开始介绍OpenFeign
(二).OpenFeign介绍
OpenFeign是一个声明式的Web Service客户端。它能让微服务之间的调用变得更简单,类似于Controller调用Service,只需要创建一个接口,然后添加注解即可使用OpenFeign
OpenFeign的前身的Feign,Feign是Netflix公司开源的一个组件。可以简单的理解为Netflix Feign是OpenFeign的祖先,或者说OpenFeign是Netflix Feign的升级版,OpenFeign是Feign的一个更强大更灵活的实现。
Spring Cloud Feign,Spring Cloud Feign 是Spring对Feign的封装,将Feign项目集成到Spring Cloud生态系统中。这里我们使用的Spring Cloud Feign的依赖是 spring-cloud-starter-openfeign
(三).使用OpenFeign
1.引入依赖
在order-service中引入OpenFeign的依赖
XML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.通过注解,开启Feign的功能
在order-service的启动类上添加**@EnableFeignClients**,开启OpenFeign
java
package org.com.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class,args);
}
}
3.编写客户端
通过Spring MVC的注解来声明远程调用的信息
java
package org.com.spring.api;
import org.com.spring.mapper.doobject.ProductDetailDO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(name = "product-service",path = "/product")
public interface ProductApi {
@RequestMapping("/getDetail")
ProductDetailDO getDatail(Integer id);
}

4.修改远程调用
java
@Override
public OrderDetailDTO getOrderDetail(Integer id) {
//1.校验非空
if (null == id || id < 0) {
log.error("订单不存在");
}
//2.调用Mapper
OrderDetailDO orderDetail = orderMapper.getOrderDetail(id);
if (null==orderDetail){
log.error("订单不存在");
}
// String url="http://product-service/product/getDetail?id="+orderDetail.getProductId();
// ProductDetailDO productDetailDO = restTemplate.getForObject(url, ProductDetailDO.class);
/**
* 调用ProductApi中的接口方法
*/
ProductDetailDO productDetailDO = productApi.getDatail(orderDetail.getProductId());
if (null==productDetailDO){
log.error("商品不存在");
}
//3.返回结果
return convertToOrderDetailDTO(orderDetail,productDetailDO);
}
5.启动项目进行测试
当启动的时候

发现,报错了。这是因为对于OpenFeign来说,如果要进行参数的传递,则必须使用 @RequestParam注解做参数绑定,不能省略
java
package org.com.spring.api;
import org.com.spring.mapper.doobject.ProductDetailDO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "product-service",path = "/product")
public interface ProductApi {
@RequestMapping("/getDetail")
ProductDetailDO getDatail(@RequestParam("id") Integer id);
}

可以发现,测试成功了。同时也可以看出来,使用Feign也可以实现远程调用。Feign简化了与HTTP服务交互的过程,把REST客户端的定义转换成Java接口,并通过注解的方式来声明请求参数,请求方式等信息,使远程调用更加方便和简洁。
(四).OpenFeign参数传递
1.传递单个参数
服务方product-service
java
@RequestMapping("get1")
public String get1(Integer id){
return "接收一个参数:"+id;
}
Feign客户端
java
@RequestMapping("get1")
String get1(@RequestParam("id") Integer id);
服务消费方order-service
java
@RequestMapping("get1")
public String get1(Integer id){
return productApi.get1(id);
}

可以发现,可以成功的
2.传递多个参数
服务方product-service
java
@RequestMapping("get2")
public String get2(Integer id,String name){
return "接收多个参数,id: "+id+",name: "+name;
}
Feign客户端
java
@RequestMapping("get2")
String get2(@RequestParam("id") Integer id,@RequestParam("name") String name);
服务消费方order-service
java
@RequestMapping("get2")
public String get2(Integer id,String name){
return productApi.get2(id,name);
}

可以发现,也是可以成功的
3.传递对象
服务方product-service
java
@RequestMapping("get3")
public String get3(ProductDetailDTO productDetailDTO){
return "接收一个对象:"+productDetailDTO.toString();
}
Feign客户端
java
@RequestMapping("get3")
String get3(@SpringQueryMap ProductDetailDTO productDetailDTO);
这里要使用@SpringQueryMap注解,进行参数的绑定
服务消费方order-service
java
@RequestMapping("get3")
public String get3(ProductDetailDTO productDetailDTO){
return productApi.get3(productDetailDTO);
}

可以发现,也是可以成功的
4.传递JSON
服务方product-service
java
@RequestMapping("get4")
public String get4(@RequestBody ProductDetailDTO productDetailDTO){
return "接收一个JSON对象:"+productDetailDTO.toString();
}
Feign客户端
java
@RequestMapping("get4")
String get4(@RequestBody ProductDetailDTO productDetailDTO);
服务消费方order-service
java
@RequestMapping("get4")
public String get4(@RequestBody ProductDetailDTO productDetailDTO){
return productApi.get4(productDetailDTO);
}

可以看到,也是可以测试成功的
(五).最佳实践
最佳实践,指的是,经过历史的迭代,在项目的实践过程中,总结出来的最好的使用方式。事实上,也不难发现,Feign的客户端与服务提供者的controller代码十分相似


此时,就可以引入Feign的最佳实践了。Feign的最佳实践有两种,第一种是Feign的继承方式,第二种是Feign的抽取方式
1.Feign的继承方式
Feign的继承方式,指的是把一些常见的操作封装到接口中。我们可以定义好一个接口,服务提供方实现这个接口,服务消费方编写Feign接口的时候,直接继承这个接口
(1).创建一个Module
这里我们定义的接口,可以放在一个公共的Jar包里,供服务提供方和服务消费方使用

(2).引入依赖
在新的Jar包中引入对应的依赖
XML
<dependencies>
<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>
</dependencies>
(3).编写接口
这里,直接将ProductApi和用到的返回值ProductDetailDO,ProductDetailDTO复制到接口中即可

(4).打Jar包
这里,打Jar包指的是,把当前这个接口工程打成Jar包,放到maven本地仓库,不是远程仓库

选择install

可以看到,已经打包成功了
(5).服务提供方
服务提供方实现接口ProductApi

注意:在实现的时候,会有很多指向性的问题,例如,返回值,接口调用等问题,统统使用新的接口中返回值和方法
(6).服务消费方
服务消费方直接继承ProductApi这个接口

(7).进行测试

可以看到,测试成功了
2.Feign的抽取方式
官方推荐Feign的使用方式为继承的方式,但是在开发中,更多是把Feign接口抽取为独立的模块,抽取的方式和继承相似,但是理念不同
(1).创建一个Module

(2).引入依赖
XML
<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>
(3).编写API
复制前面的ProductApi,ProductDetailDTO到product-api模块中

(4).打Jar包
还是选择install
(5).服务消费方使用
Ⅰ.将order-service中的ProductApi和ProductDTO注释到

Ⅱ.引入依赖
XML
<dependency>
<groupId>org.com.spring</groupId>
<artifactId>product-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
这个依赖是刚才打包的那个接口的依赖
Ⅲ.指定扫描类ProductApi

在启动类上添加**@EnableFeignClients**注解,如何指定扫描类为ProductApi
