OpenFeign-远程调用

新星杯·14天创作挑战营·第13期 10w+人浏览 210人参与

RestTemplate存在的问题

观察之前写的远程调用的代码:

public OrderInfo selectOrderById(Integer orderId){
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        String url = "<http://product-service/product/>" + orderInfo.getProductId();
        log.info("调用的url为:{}",url);
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }

虽说RestTemplate 对HTTP封装后, 已经⽐直接使⽤HTTPClient简单⽅便很多, 但是还存在⼀些问题:

  1. 需要拼接URL, 灵活性⾼, 但是封装臃肿, URL复杂时, 容易出错.
  2. 代码可读性差, ⻛格不统⼀.

微服务之间的通信⽅式, 通常有两种: RPC 和 HTTP.

在SpringCloud中, 默认是使⽤HTTP来进⾏微服务的通信, 最常⽤的实现形式有两种:

  • RestTemplate
  • OpenFeign

RPC(Remote Procedure Call)远程过程调⽤,是⼀种通过⽹络从远程计算机上请求服务,⽽不需要了解底层⽹络通信细节。RPC可以使⽤多种⽹络协议进⾏通信, 如HTTP、TCP、UDP等, 并且在TCP/IP⽹络四层模型中跨越了传输层和应⽤层。简⾔之RPC就是像调⽤本地⽅法⼀样调⽤远程⽅法。

常⻅的RPC框架有:

  1. Dubbo: https://cn.dubbo.apache.org/zh-cn/
  2. Thrift : Apache Thrift - Home
  3. gRPC: gRPC

OpenFeign介绍(不发)

OpenFeign 是⼀个声明式的 Web Service 客⼾端. 它让微服务之间的调⽤变得更简单, 类似controller

调⽤service, 只需要创建⼀个接⼝,然后添加注解即可使⽤OpenFeign.

OpenFeign 的前⾝

Feign 是 Netflix 公司开源的⼀个组件.

  • 2013年6⽉, Netflix发布 Feign的第⼀个版本 1.0.0
  • 2016年7⽉, Netflix发布Feign的最后⼀个版本 8.18.0
  • 2016年,Netflix 将 Feign 捐献给社区
  • 2016年7⽉ OpenFeign 的⾸个版本 9.0.0 发布,之后⼀直持续发布到现在

可以简单理解为 Netflix Feign 是OpenFeign的祖先, 或者说OpenFeign 是Netflix Feign的升级版.

OpenFeign 是Feign的⼀个更强⼤更灵活的实现

<aside> 💡

我们现在⽹络上看到的⽂章, 或者公司使⽤的Feign, ⼤多都是OpenFeign.

本课程后续讲的Feign, 指的是OpenFeign

</aside>

Spring Cloud Feign

Spring Cloud Feign 是 Spring 对 Feign 的封装, 将 Feign 项⽬集成到 Spring Cloud ⽣态系统中.

受 Feign 更名影响,Spring Cloud Feign 也有两个 starter

  • spring-cloud-starter-feign
  • spring-cloud-starter-openfeign

由于Feign的停更维护, 对应的, 我们使⽤的依赖是 spring-cloud-starter-openfeign

OpenFeign 官⽅⽂档:https://github.com/OpenFeign/feign

Spring Cloud Feign⽂档:Spring Cloud OpenFeign

OpenFeign快速上⼿

1.引入依赖(order-service)

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2.通过注解,开启Feign的功能

在order-service的启动类添加注解 @EnableFeignClients , 开启OpenFeign的功能

@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class,args);
    }
}

3.编写客户端

在order-service下创建一个api目录:

基于SpringMVC的注解来声明远程调⽤的信息

@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {

    @RequestMapping("/{productId}")
    ProductInfo getProductInfo(@PathVariable("productId") Integer productId);
}

@FeignClient 注解作⽤在接⼝上, 参数说明:

  • name/value:指定FeignClient的名称, 也就是微服务的名称, ⽤于服务发现, Feign底层会使⽤

    Spring Cloud LoadBalance进⾏负载均衡. 也可以使⽤ url 属性指定⼀个具体的url.

  • path: 定义当前FeignClient的统⼀前缀

4.修改远程调用

修改远程调⽤的⽅法

@Autowired
    private ProductApi productApi;

    public OrderInfo selectOrderById(Integer orderId){
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        /*String url = "<http://product-service/product/>" + orderInfo.getProductId();
        log.info("调用的url为:{}",url);
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);*/
        ProductInfo productInfo = productApi.getProductInfo(orderInfo.getProductId());
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }

5.测试

启动服务, 访问接⼝, 测试远程调⽤:127.0.0.1:8080/order/1

可以看出来, 使⽤Feign也可以实现远程调⽤.

Feign 简化了与HTTP服务交互的过程, 把REST客⼾端的定义转换为Java接⼝, 并通过注解的⽅式来声

明请求参数,请求⽅式等信息, 使远程调⽤更加⽅便和间接.

OpenFeign参数传递

上面演示了Feign从url中获取参数,既然是会传递参数,那参数的类型就会有很多,接下来演⽰下Feign参数传递的其他⽅式。

传递单个参数

  1. 服务端需要提供相应的接口(product-service 的controller)

    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
        @RequestMapping("/p1")
        public String p1(Integer id){
            return "product-service 接收到的参数,id" + id;
        }
    }
    
  2. Feign客户端声明

    @FeignClient(value = "product-service" , path = "/product")
    public interface ProductApi {
    
        @RequestMapping("/p1")
        String p1(@RequestParam("id") Integer id);
    }
    

    <aside> 💡

    注意: @RequestParam 做参数绑定, 不能省略

    </aside>

  3. 服务消费⽅order-service

    @RestController
    @RequestMapping("/feign")
    public class FeignController {
    
        @Autowired
        private ProductApi productApi;
    
        @RequestMapping("/o1")
        public String o1(Integer id){
            System.out.println("参数o1="+ id);
            return productApi.p1(id);
        }
    }
    
  4. 测试远程调用:127.0.0.1:8080/feign/o1?id=23

传递多个参数

使⽤多个@RequestParam 进⾏参数绑定即可

  1. 服务提供⽅ product-service

    
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
        @RequestMapping("/p2")
        public String p2(Integer id,String name){
            return "product-service 接收到的参数,id=" + id + "name = " + name;
        }
    }
    
  2. Feign客户端声明

    @FeignClient(value = "product-service" , path = "/product")
    public interface ProductApi {
        @RequestMapping("/p2")
        String p2(@RequestParam("id") Integer id,@RequestParam("name") String name);
    }
    
  3. 服务消费⽅order-service

    @RestController
    @RequestMapping("/feign")
    public class FeignController {
    
        @Autowired
        private ProductApi productApi;
    
        @RequestMapping("/o2")
        public String o2(@RequestParam("id")Integer id, @RequestParam("name")String name){
            return productApi.p2(id,name);
        }
    
    }
    
  4. 测试远程调用:127.0.0.1:8080/feign/o2?id=23&name=zhangsan

传递对象(模拟)

  1. 服务提供⽅ product-service

    		@RequestMapping("/p3")
        public String p3(ProductInfo productInfo){
            return "product-service 接收到的参数 productInfo =  " + productInfo.toString();
        }
    
  2. Feign客户端声明

        @RequestMapping("/p3")
        String p3(@SpringQueryMap ProductInfo productInfo);
    
  3. 服务消费⽅order-service

        @RequestMapping("/o3")
        public String o3(){
            ProductInfo productInfo = new ProductInfo();
            productInfo.setId(23);
            productInfo.setProductName("衣服");
            return productApi.p3(productInfo);
        }
    
  4. 测试远程调用:127.0.0.1:8080/feign/o3

传递JSON(模拟)

  1. 服务提供⽅ product-service

        @RequestMapping("/p4")
        public String p4(@RequestBody ProductInfo productInfo){
            return  "product-service 接收到的参数 productInfo =  " + productInfo.toString();
        }
    
  2. Feign客户端声明

        @RequestMapping("/p4")
        String p4(@RequestBody ProductInfo productInfo);
    
  3. 服务消费⽅order-service

        @RequestMapping("/o4")
        public String o4(){
            ProductInfo productInfo = new ProductInfo();
            productInfo.setId(23);
            productInfo.setProductName("衣服");
            return productApi.p4(productInfo);
        }
    
    
  4. 测试远程调用:127.0.0.1:8080/feign/o4

### OpenFeign动态远程调用实现方式 OpenFeign 是一种声明式的 HTTP 客户端工具,它允许开发者像调用本地方法一样调用远程服务[^1]。然而,默认情况下,OpenFeign 的接口定义是静态的,在开发阶段就已经固定下来。如果需要支持 **动态远程调用**,可以通过一些扩展机制来实现。 以下是通过 OpenFeign 实现动态远程调用的一种常见方案: #### 1. 使用 Feign.Builder 手动构建 Feign 客户端 `Feign.Builder` 提供了一种灵活的方式来创建动态的 Feign 客户端实例。这种方式不需要提前定义固定的接口,而是可以在运行时指定目标 URL 和请求参数。 ```java import feign.Feign; import feign.RequestLine; public class DynamicFeignExample { public static void main(String[] args) { String targetUrl = "http://example.com/api"; // 动态设置的目标URL MyDynamicInterface dynamicClient = Feign.builder() .target(MyDynamicInterface.class, targetUrl); // 调用远程方法 String response = dynamicClient.getSomeData(); System.out.println(response); } interface MyDynamicInterface { @RequestLine("GET /data") String getSomeData(); // 动态调用的方法 } } ``` 上述代码展示了如何使用 `Feign.Builder` 创建一个动态的 Feign 客户端,并将其指向不同的目标地址[^4]。 --- #### 2. 结合 Spring Cloud 自定义配置 Spring Cloud 中集成了 OpenFeign 支持,因此也可以利用其灵活性来自定义动态调用逻辑。具体来说,可以基于以下步骤完成: ##### (1) 配置动态目标地址 在实际应用中,可能需要根据业务需求动态调整目标服务地址。这可以通过自定义 Bean 来实现。 ```java import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; @FeignClient(name = "dynamic-client", url = "${dynamic.url}") // 动态url可以从配置文件或者环境变量读取 public interface DynamicFeignClient { @GetMapping("/api/data") String getData(); } ``` 在此示例中,`${dynamic.url}` 可以从配置文件或程序启动时传入的参数中获取,从而实现动态切换目标地址的功能[^5]。 --- ##### (2) 运行时修改目标地址 除了通过配置文件设定外,还可以在运行期间动态更改目标地址。例如,借助于 `Targeter` 或者手动替换 `LoadBalancerClient` 的行为。 ```java import org.springframework.beans.factory.ObjectFactory; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.context.ApplicationContext; import feign.Contract; import feign.Feign; import feign.codec.Decoder; import feign.codec.Encoder; public class CustomFeignBuilder { public Object createDynamicClient(Class<?> type, String url) { Encoder encoder = new SpringEncoder(new HttpMessageConverters()); Decoder decoder = new ResponseEntityDecoder(new SpringDecoder((ObjectFactory<HttpMessageConverters>) () -> null)); return Feign.builder() .encoder(encoder) .decoder(decoder) .contract(new Contract.Default()) .target(type, url); // 动态传递目标URL } } ``` 此代码片段展示了一个通用的工厂类,用于按需生成针对不同 URL 的 Feign 客户端实例[^3]。 --- #### 3. 利用反射技术增强动态能力 对于更加复杂的场景,比如完全未知的服务名和方法签名,可以考虑结合 Java 反射以及动态代理技术。不过需要注意的是,这种方法会增加一定的复杂度并可能导致性能下降。 --- ### 总结 以上介绍了几种常见的 OpenFeign 动态远程调用实现方式,包括但不限于使用 `Feign.Builder` 构建动态客户端、结合 Spring Cloud 自定义配置以及引入反射技术等手段[^2]。每种方法都有各自的适用范围和优缺点,应根据具体的业务需求选择合适的解决方案。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值