Spring Cloud OpenFeign使用介绍
文章目录
- Spring Cloud OpenFeign使用介绍
- 导引
- 1. 简单介绍
- 2. 操作方式
- 3. 参数传递
- 3.1 传递单个参数
- 3.2 传递多个参数
- 3.3 传递对象
- 3.4 传递JSON类型参数
 
- 4. 最佳实践
 
导引
在之前的文章中,我们使用过RestTemplate来进行远程调用:
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private RestTemplate restTemplate;
    @Override
    public OrderInfo selectOrderById(Integer orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        String url = "http://product-service/product/" + orderInfo.getProductId();
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}
虽说RestTemplate对HTTP封装后已经比直接使用HTTPClient简单方便很多,但还是存在这一些问题:
- 需要拼接URL,灵活性高,但是封装臃肿,当URL复杂时则容易出错
- 代码可读性差,风格不统一
于是,我们可以使用OpenFeign来更”优雅“的实现远程通信!
1. 简单介绍
OpenFeign是一个声明式的Web Service 客户端,它让微服务之间的调用变得更加简单,类似于controller调用service,只需要创建一个接口,然后添加注解即可使用OpenFeign:
OpenFeign 官方文档:GitHub - OpenFeign/feign: Feign makes writing java http clients easier
2. 操作方式
当前代码基于Nacos配置完成的基础上进行编写【代码获取】:
-  先给 order-service(哪个实例需要进行远程调用就给谁加)引入依赖<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-  在 order-service的启动类上添加注解@EnableFeignClients,开启OpenFeign功能package com.order; 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); } }
-  编写OpenFeign的客户端(接口),且只需进行方法声明,基于SpringMVC的注解来声明远程调用的信息 package com.order.api; import com.order.model.ProductInfo; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.Date; @FeignClient(value = "product-service" , path = "/product") public interface ProductApi { @RequestMapping("/{productId}") ProductInfo getProductById(@PathVariable("productId") Integer productId); }注: - value:指定FeignClient的名称,也就是微服务的名称,用于服务发现
- path:定义当前FeignClient的统一前缀
 
-  修改远程调用的方法 package com.order.service.Impl; import com.order.api.ProductApi; import com.order.mapper.OrderMapper; import com.order.model.OrderInfo; import com.order.model.ProductInfo; import com.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; @Autowired private ProductApi productApi; @Override public OrderInfo selectOrderById(Integer orderId) { OrderInfo orderInfo = orderMapper.selectOrderById(orderId); // String url = "http://product-service/product/" + orderInfo.getProductId(); // ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class); ProductInfo productInfo = productApi.getProductById(orderInfo.getProductId()); orderInfo.setProductInfo(productInfo); return orderInfo; } }
完成上述配置后,启动服务,访问接口测试远程调用:

远程调用测试成功,同时远程调用的代码也变的更加简洁了!
3. 参数传递
在上述例子中,介绍了从Feign从URL中获取参数,接下来介绍Feign参数传递的其它方式.
3.1 传递单个参数
服务提供方 product-service (ProductController)
package com.product.controller;
import com.product.service.model.ProductInfo;
import com.product.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@Slf4j
@RequestMapping("/product")
@RestController
public class ProductController {
    @RequestMapping("p1")
    public String p1(Integer id) {
        return "p1接受单个参数Id = " + id;
    }
}
Feign客户端
package com.order.api;
import com.order.model.ProductInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Date;
@FeignClient(value = "product-service" , path = "/product")
public interface ProductApi {
    
    @RequestMapping("/p1")
    String p1(@RequestParam("id") Integer id);
    
}
注:@RequestParam作参数绑定,不能省略
服务消费方 order-service (FeignController)
package com.order.controller;
import com.order.api.ProductApi;
import com.order.model.ProductInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/feign")
@RestController
public class FeignController {
    @Autowired
    private ProductApi productApi;
    @RequestMapping("/o1")
    public String o1(Integer id) {
        log.info("调用o1");
        return productApi.p1(id);
    }
}
测试远程调用:http://127.0.0.1:8080/feign/o1?id=5

3.2 传递多个参数
使用多个@RequestParam进行参数绑定即可
服务提供方 product-service (ProductController)
@RequestMapping("p2")
public String p2(Integer id, String productName) {
    return "p2接收多个参数:id = " + id + " productName = " + productName;
}
Feign客户端
@RequestMapping("p2")
String p2(@RequestParam("id") Integer id, @RequestParam("productName") String productName);
服务消费方 order-service (FeignController)
@RequestMapping("/o2")
public String o2(Integer id, String productName) {
    log.info("调用o2");
    return productApi.p2(id, productName);
}
这里我们使用Postman进行远程调用测试:

3.3 传递对象
服务提供方 product-service (ProductController)
@RequestMapping("/p3")
public String p3(ProductInfo productInfo) {
    // 这里我们只给对象传入 id 和 productName,剩下的由内部封装如下
    productInfo.setProductPrice(99);
    productInfo.setState(0);
    productInfo.setCreateTime(new Date(System.currentTimeMillis()));
    productInfo.setUpdateTime(new Date(System.currentTimeMillis()));
    return productInfo.toString();
}
Feign客户端
@RequestMapping("/p3")
String p3(@SpringQueryMap ProductInfo productInfo);
注:传入对象参数的话需要加上注解 @SpringQueryMap
服务消费方 order-service (FeignController)
@RequestMapping("/o3")
public String o3(ProductInfo productInfo) {
    log.info("调用o3");
    return productApi.p3(productInfo);
}
进行远程调用测试:

3.4 传递JSON类型参数
服务提供方 product-service (ProductController)
@RequestMapping("/p4")
public String p4(@RequestBody ProductInfo productInfo) {
    productInfo.setProductPrice(99);
    productInfo.setState(0);
    productInfo.setCreateTime(new Date(System.currentTimeMillis()));
    productInfo.setUpdateTime(new Date(System.currentTimeMillis()));
    return productInfo.toString();
}
Feign客户端
@RequestMapping("/p4")
String p4(@RequestBody ProductInfo productInfo);
服务消费方 order-service (FeignController)
@RequestMapping("/o4")
    public String o4(@RequestBody ProductInfo productInfo) {
        log.info("调用o4");
        return productApi.p4(productInfo);
    }
}
进行远程调用测试:

4. 最佳实践
最佳实践,其实就是经过历史的迭代,在项目的实际过程中总结出来的最好的使用方式。
在上文配置Feign客户端时也能看出,它与服务提供方的代码非常相似:

如果需要在每个模块中都配置重复的代码,一旦数量上来则会造成一定的代码冗余!
对此我们可以对其做出简化,将Feign的客户端抽取为一个独立的模块,并把涉及到的实体类等都放在这个模块中,打成一个jar包,消费方只需要依赖该jar包即可(jar包通常由服务提供方来实现)
具体操作如下:
-  先创建一个模块 product-api 
-  给刚创建的模块 product-api引入依赖<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>
-  复制ProductApi、ProductInfo 到 product-api模块中 
-  通过Maven将该模块打包到本地Maven仓库    
-  删除 order-service中的ProductApi与ProductInfo
-  为服务消费方 order-service引入依赖<dependency> <groupId>org.example</groupId> <artifactId>product-api</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency>会出现报错信息,此时需要修改项目中ProductApi与ProductInfod 路径为 product-api中的路径
-  给服务消费方 order-service启动类添加扫描路径@EnableFeignClients(basePackages = {“com.api”}, clients = {ProductApi.class}) 其中basePackages为扫描路径,clients为需要加载的Feign客户端 package com.order; import com.api.ProductApi; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableFeignClients(basePackages = {"com.api"}, clients = {ProductApi.class}) @SpringBootApplication public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
完成上述配置后即可进行远程测试调用:

注:若需要将服务部署到服务器上,需要修改服务消费方的pom文件,因为其使用了部署在本地Maven仓库的jar包:
<dependency>
     <groupId>org.example</groupId>
     <artifactId>product-api</artifactId>
     <version>1.0-SNAPSHOT</version>
<!--  <scope>compile</scope>-->
     <scope>system</scope>
     <systemPath>D:/maven_test/.m2/repository/org/example/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>
以上便是对OpenFeign的使用介绍了!!如果内容对大家有帮助的话请给这篇文章一个三连关注吧💕( •̀ ω •́ )✧( •̀ ω •́ )✧✨



















