java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringCloud  OpenFeign使用

SpringCloud  OpenFeign的使用举例详解

作者:程序猿教你打篮球

文章介绍Spring Cloud中使用OpenFeign替代RestTemplate进行微服务通信,通过声明式接口和注解简化调用,建议将Feign客户端抽离为独立模块,统一实体类,解决代码冗余问题,部署时需配置本地jar包依赖,确保服务调用正常,感兴趣的朋友跟随小编一起看看吧

1. OpenFeign

1.1 OpenFeign 的介绍

先来看看咱们之前写的远程方法调用代码:

@RequestMapping("/ok")
public String ok(@PathParam("content")String content) {
    String url = "http://waiter-service/waiter/up/{content}";
    String resp = restTemplate.getForObject(url, String.class, content);
    return "调用成功, 已收到 waiter 的响应: " + resp;
}

虽然说 RestTemplate 对 http 封装后,使用起来还算方便,但是需要拼接 url,如果 url 很复杂呢?而且代码可读性很差,风格也不好统一。

在微服务之间的通信方式通常分为:RPC 和 HTTP,至于 RPC 后期有机会在介绍。

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

RestTemplate 咱们已经见过了,接下来就学习下 OpenFeign,这是一种更优雅的远程方法调用的形式。

OpenFeign 是一个声明式的 Web Service 客户端,它让微服务之间的调用变得更简单了。类似于 Controller 调用 Service,只需要创建一个接口,然后添加注解即可以使用 OpenFeign。

本来是 Feign 是由 Netflix 公司开源的组件,后来呢,在16年7月发布了最后一个版本,就将捐给了社区,16年7月,OpenFeign 首个版本 9.0.0 发布后,就一直发布到现在。

SpringCloud 将 Feign 项目继承到 SpringCloud 的生态中,但是受到 Feign 更名的影响,所以 SpringCloudFeign 有两个 starter。

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

由于 Feign 停止维护,咱们的项目中使用的是后者 OpenFeign。

1.2 快速使用 OpenFeign

在 cook-service 引入 OpenFeign 依赖

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

cook-service 启动类添加注解:@EnableFeignClients 开启 OpenFeign 功能。

package com.zlcode.cook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class CookServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CookServiceApplication.class, args);
    }
}

编写 OpenFeign 客户端,基于 SpringMVC 注解来声明远程调用的信息。

在 cook 目录下创建 api 目录,在这个 api 目录中创建 WaiterApi 接口。

package com.zlcode.cook.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "waiter-service", path = "/waiter")
public interface WaiterApi {
    @RequestMapping("/up/{content}")
    String up(@PathVariable("content") String content);
}

上述 @FeignClient 注解中的 value 表示注册的服务名,用于服务发现,Feign底层会使用 Spring Cloud Load Balance 进行负载均衡,如果使用了 Nacos 负载均衡策略则使用的是 Nacos 的负载均衡。path 表示统一接口前缀,与咱们的 WaiterController 对应的。

WaiterController

package com.zlcode.waiter.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/waiter")
@Slf4j
public class WaiterController {
    @RequestMapping("/up/{content}")
    public String up(@PathVariable("content") String content) {
        log.info("正在执行: " + content);
        return "执行: " + content + "成功!";
    }
}

修改 CookController 的远程方法调用代码:

package com.zlcode.cook.controller;
import com.zlcode.cook.api.WaiterApi;
import jakarta.websocket.server.PathParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cook")
public class CookController {
    @Autowired
    private WaiterApi waiterApi;
    @RequestMapping("/ok")
    public String ok(@PathParam("content")String content) {
        String resp = waiterApi.up(content);
        return "调用成功, 已收到 waiter 的响应: " + resp;
    }
}

重启 cook-service,再去浏览器中访问:http://127.0.0.1:8080/cook/up?content=给25桌上红烧肉

1.3 接口返回的是自定义对象该怎么办?

咱们例子中,waiter-service 的 waiter/up/ 接口返回值是 String,每个 Java 项目都可以用 String 这个对象,如果新增一个接口,获取服务员信息呢?返回的是一个 WaiterInfo 对象,那么在 cook-service 调用方该如何接收呢?

其实方法很简单,咱们先在 WatierController 新增一个 get-info 接口:

package com.zlcode.waiter.controller;
import com.zlcode.waiter.model.WaiterInfo;
import jakarta.websocket.server.PathParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/waiter")
@Slf4j
public class WaiterController {
    @RequestMapping("/up/{content}")
    public String up(@PathVariable("content") String content) {
        log.info("正在执行: " + content);
        return "执行: " + content + "成功!";
    }
    @RequestMapping("/get-info")
    public WaiterInfo getInfo(String name) {
        WaiterInfo waiterInfo = new WaiterInfo();
        waiterInfo.setWaiterId(1111);
        waiterInfo.setWaiterName(name);
        return waiterInfo;
    }
}

在 cook-service 的 WatierApi 接口中新增远程调用 waiter-service 提供的 get-info 的接口:

此时发现咱们的 cook-service 项目中没有 WaiterInfo 这个类,这也好办,去 waiter-service 中把 WaiterInfo 类复制到 cook-service 的 model 目录中。

package com.zlcode.cook.api;
import com.zlcode.cook.model.WaiterInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "waiter-service", path = "/waiter")
public interface WaiterApi {
    @RequestMapping("/up/{content}")
    String up(@PathVariable("content") String content);
    @RequestMapping("/get-info")
    WaiterInfo getInfo(@RequestParam("name") String name);
}

果然解决了这个问题,再来到 CookController 中新增接口去远程调用 get-info 方法:

package com.zlcode.cook.controller;
import com.zlcode.cook.api.WaiterApi;
import com.zlcode.cook.model.WaiterInfo;
import jakarta.websocket.server.PathParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/cook")
public class CookController {
    @Autowired
    private WaiterApi waiterApi;
    @RequestMapping("/ok")
    public String ok(@PathParam("content")String content) {
        String resp = waiterApi.up(content);
        return "调用成功, 已收到 waiter 的响应: " + resp;
    }
    @RequestMapping("/get")
    public String get(@RequestParam("name")String name) {
        WaiterInfo waiterInfo = waiterApi.getInfo(name);
        return "调用成功, 已收到 waiter 的响应: " + waiterInfo;
    }
}

重启 cook-service 和 waiter-service,在浏览器中访问:http://127.0.0.1:8080/cook/get?name=张三

调用成功,拿到了 waiter-service get-info 接口返回的 WaiterInfo 对象。

但是咱们仔细想想这段代码,在 cook-service 项目中存在 WaiterInfo 实体类,在 waiter-service 项目中也存在 WaiterInfo 实体类。那随着业务的增多,这样的冗余代码也会越来越多。而且咱们的 WaiterAPI 和 WaiterController 的代码也十分相似。

所以上述这样的写法是不行的,不是最佳的写法,更好的办法是把把 OpenFeign 抽离出来,作为一个独立的模块,服务方把提供的 API 都封装到这个独立的模块中,供消费方使用。

1.4 OpenFeign 的最佳实践

官方推荐的方式是继承,但是企业中用的不多,咱们此处只介绍抽离的方式,就像 6.3 最后说的,把 OpenFeign 提取出来成为一个独立的模块即可,这个模块由服务方去提供。

简单来说,将 OpenFeign 的 Client 抽取为⼀个独⽴的模块,并把涉及到的实体类等都放在这个模块中,打成⼀个 jar。消费方只需要依赖该 jar 包即可。

步骤如下:

创建一个 Model

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-loadbalancer</artifactId>
    </dependency>
</dependencies>

编写 API

将 cook-service 的 WaiterApi 和 WatierInfo 直接复制进来:

将这个 waiter-service-api 打包成 jar 包,单击 maven 选项中的 install:

将 cook-service 的 WaiterApi 和 WatierInfo 删除掉,然后引入咱们自己打包的 waiter-service-api 依赖

<dependency>
    <groupId>org.example</groupId>
    <artifactId>waiter-service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>compile</scope>
</dependency>

在启动类添加扫描路径:

package com.zlcode.cook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(basePackages = "com.zlcode.api")
public class CookServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CookServiceApplication.class, args);
    }
}

也可以指定需要加载的 Feign客户端:

@EnableFeignClients(basePackageClasses = WaiterApi.class)
@EnableFeignClients(basePackages = "com.zlcode.api")

接着重新启动 cook-service 项目,然后在浏览器中访问:http://127.0.0.1:8080/cook/get?name=张三

这种更优雅的远程方法调用也就成功了!

1.5 部署时需要注意事项

由于当前项目中使用到了自己封装的 jar 包,但是 maven 打包默认会从中央仓库中去获取,但是咱们的 waiter-service-api 是在本地,这就比较麻烦,目前有三种解决方案:

观先获取 waiter-service-api 执行 install 后的本地 jar 包路径(通过控制台查看):

把之前引入的依赖替换成本地目录(记得将 \ 换成 /):

<dependency>
    <groupId>org.example</groupId>
    <artifactId>waiter-service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>system</scope>
    <systemPath>
        C:/xxx/xxx/.m2/repository/org/example/waiter-service-api/1.0-SNAPSHOT/waiter-service-api-1.0-SNAPSHOT.jar
    </systemPath>
</dependency>

把 build 配置项换成:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <includeSystemScope>true</includeSystemScope>
            </configuration>
        </plugin>
    </plugins>
</build>

includeSystemScope 允许包括系统本地的 jar 包。

接下来就可以进行快乐的部署到 Linux 服务器啦,剩下部署的操作这里就不再赘述了。

到此这篇关于SpringCloud OpenFeign的使用的文章就介绍到这了,更多相关SpringCloud OpenFeign使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文