SpringCloud——OpenFeign 微服务远程调用组件使用教程
目录一. Feign 的基本使用1.1 第一步引入 OpenFeign 的依赖1.2 第二步在调用者的启动类上添加注解1.3 第三步编写Feign的客户端二. 自定义Feign的配置三. Feign的性能优化四. Feign的最佳实践如何使用Feign4.1 第一种继承4.2 第二种抽取五. 最佳实践实际操作(方式二)5.1 创建 feign-api 模块5.2 定义 UserClient其它微服务组件文章合集有需要的小伙伴可跳转观看SpringCloud——Eureka 微服务注册中心组件使用教程SpringCloudAlibaba——Nacos 微服务注册配置功能组件使用教程SpringCloud——OpenFeign 微服务远程调用组件使用教程SpringCloud——gateway 微服务网关组件使用教程SpringCloud——Ribbon 微服务负载均衡原理解析一. Feign 的基本使用Feign 是一个声明式的 http 客户端我们知道在不使用 Feign 之前在微服务中一个模块如果想要调用另一个模块中的某个功能需要向其发起请求 http 请求如果不使用 Feign我们就需要通过硬编码的形式去编写构建 http 请求例如使用 RestTemplate 发起远程调用。可以看到这样的编码明显是不太友好的代码可读性差而且编程体验也不好参数复杂时URL难以维护。因此就要引出我们的 FeignFeign 可以帮助我们发送 http 请求解决上面的这些问题而在 spring cloud 微服务组件中OpenFeign是使用率较高的一个组件我们就来了解下 OpenFeign 的基本使用。1.1 第一步引入 OpenFeign 的依赖这里要注意当模块A要调用模块B的方法时要在调用者中加入Feign的依赖依赖如下dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency1.2 第二步在调用者的启动类上添加注解// 开启 openFeign 远程调用组件功能 EnableFeignClients public class XXXServerApplication { // 代码省略...... }开启Feign功能不加注解是不启动的1.3 第三步编写Feign的客户端假设在 orderservice 层查询订单信息需要调用 userservice 层查询用户信息的方法在 orderservice 模块中新建一个包 client需要声明的客户端如下其实就是一个接口因为要查询 User 信息所以定义为 UserClientFeignClient(value userservice) public interface UserClient { GetMapping(/user/{id}) User findById(PathVariable(id) Long id); }这里声明的客户端需要和上述中的方法体保持一致都要使用GetMapping路径也要保持一致。FeignClient(value userservice) 这里面因为我写的这个方法是 userservice 业务模块的所以指定 value 值就是 userservice。完成上述三步之后在 orderservice 业务模块中查询订单的方法中我们就不需要像之前那样采用硬编码的形式采用自动装配userClient那么方法及可以改成下面这种形式1 用户传入要查询的订单信息通过 orderId 查询到订单2然后我们直接就可以使用注入的 userClient 远程调用其实就是调用了我们定义的接口。此外这里多提一句Feign 不仅可以帮助我们发送请求还可以帮助我们实现负载均衡因为在Feign 内部已经集成了 Ribbon二. 自定义Feign的配置Spring Boot已经自动装配了Feign的配置但是我们自己是可以修改或者自定义Feign的配置的可以修改的配置如下上面是我们最常用到的当然也还有其他的这里就不说了简单来说一下日志日志一共分为四个级别NONE没有任何日志也是默认配置BASIC可以记录 http 请求什么时候发送什么时候结束耗时多久等基本信息HEADERS除了BASIC中含有的以外还包含请求头和响应头FULL是最全面的除了HEADERS中包含的以外还包含请求体信息和响应体信息。自定义 Feign 配置是有两种方式的方式一配置文件方式这里又可分为全局生效和局部生效在 application.yml 文件中的全局配置如下其实 default 默认的就是全局配置如果想要局部生效即对某个微服务生效将 default 改成对应的服务名称即可。这里 loggerLevel 我定义为FULL我先清空一下控制台然后打开浏览器查询一下102订单查询成功如下再来看控制台打印的信息可以看到打印了一大堆日志。方式二就是采用Java代码的形式需要先申明一个Bean在Bean中定义日志的等级public class DefaultFeignConfiguration { Bean public Logger.Level logLevel(){ return Logger.Level.BASIC; } }当然了这里也分为全局生效和局部生效想要局部生效时就在Feign的具体的接口上的注解上添加configuration FeignConfiguration.classFeignClient(value userservice,configuration DefaultFeignConfiguration.class) public interface UserClient { GetMapping(/user/{id}) User findById(PathVariable(id) Long id); }想要全局生效时就在功能模块的启动类上的启动Feign的注解上添加值 defaultConfiguration FeignConfiguration.classEnableFeignClients(defaultConfiguration DefaultFeignConfiguration.class)这里我也设置为全局生效重启服务器打开浏览器仍然查询订单102打开idea重新观察如下所示可以看到BASIC相比于FULL明显少了许多。总结得出如下图这里我建议如果是调试错误的时候可以使用FULL但如果日常开发建议使用NONE或BASIC。因为记录日志也是会消耗一定的性能的。三. Feign的性能优化说起性能调优大家都不陌生不管开发什么用什么工具都会有调优这一说例如jvm调优mysql调优。其实Feign的性能本身已经很好了但是还是有优化的余地我们先来了解一下Feign的底层实现第一种URLConnection是默认实现是JDK自带的它的性能是不太好的而且不支持连接池。连接池可以避免我们创建和销毁的一个性能损耗而且连接需要三次握手断开需要四次挥手是比较浪费性能的。所以我们可以使用另外两个所以对Feign性能的优化就是对底层实现的改变。还有就是我们上述的日志的级别也是对Feign性能稍微有影响的这也是为什么Feign日志默认是NONE。这里我就采用Apache HttpClient来实现第一步引入HttpClient的依赖maven依赖如下dependency groupIdio.github.openfeign/groupId artifactIdfeign-httpclient/artifactId /dependency第二步配置连接池feign: httpclient: enabled: true # 支持HttpClient的开关 max-connections: 200 # 最大连接数 max-connections-per-route: 50 # 单个路径的最大连接数这里enable要设置为true打开开关下边的连接参数和我们之前学的数据库连接池有些相似很好理解这两个数量设置多少合适没有具体数值可以在具体的业务中进行测试测试设置为多少位最好因项目而异。总结如下图四. Feign的最佳实践如何使用Feign关于Feign如何使用大概可以分为以下两种4.1 第一种继承给消费者的 FeignCilent 和提供者的 Controller 定义统一的父接口作为标准因为 orderservice 业务中要向 userservice 发起请求要通过 FeignCilentFeignClient 又需要定义和 userservice 业务中控制层完全一样的接口所以我们可以定义一个父接口让 UserClient 接口去继承这个父接口让 userservice 业务中 Controller 层去实现这个父接口之前我们的Controller 层都是自己定义方法这样一来就不需要了我们直接实现父接口UserAPI也算是一种规范算是对子类型为的一个约束给 Feign 客户端和 Controller 定义统一的标准。但是这种方式确有一定的问题下面这段话是Spring官方给出的一个说明翻译过来就是“一般情况下不推荐在服务端和客户端共享一个接口因为它会造成紧耦合而且这种继承方案对SpringMVC是不起作用的”。我来简单解释几个问题第一紧耦合因为我们的服务端 FeignClient 和客户端 Controller 都实现了同一个接口一旦后期我们的接口发生了改变那么两端都需要发生改变需要付出巨大的成本。我们做项目都希望高内聚低耦合方便后期对业务的扩展所以这个方法是有点缺点的。第二“这种继承方案对SpringMVC是不起作用的”这句话的意思也就是说我们在Controller层继承此接口时参数是不能继承下来的例如我们所写的PathVariable这个是需要我们自己补充的。尽管上述两点是此方法的一些缺点但是 这个方法实现了面向契约编程而且在一些小型微服务项目中特别合适因此仍然也是比较流行的。4.2 第二种抽取将 FeignClient 抽取成一个独立模块并且把有关的POJO默认的 Feign 配置都可以放在这个模块当中提供给所有消费者使用。这样说可能有些抽象我来简单举例说明有三个模块user-serviceorde-rservicepay-service假设我们的orde-rservicepay-service模块都需要 user-service 模块中的功能按照我们的第一种做法就是各自都实现UserClient如果后期模块越来越多相互调用越来越复杂那么这个项目的耦合度是很高的就背离了我们的微服务项目的初衷。由此就引出了第二种方法这里我们把 Feign 也当作微服务的一个模块专门用来存放各种的POJO类和默认配置这样的话我们的所有业务模块只需要引入我们的feign-api依赖即可直接使用。这样我们就大大的降低了项目的耦合度。但是这种方案也有一定的问题。例如我的 order-service 模块只需要 feign-api 模块中的一个功能但是我又必须要用我就必须将 feign-api 所有的东西都通过依赖拿过来不管用不用其他模块也是如此这样就造成了一定资源的浪费但这种资源浪费感觉也无可厚非。综上所述两种方案都是有利有弊。看如何选择了如果不想耦合就可以选方式二如果模块与模块之间暴漏的交互接口很少不在意耦合想省事就可以使用方式一没有完美的解决方案根据需要选择即可。五. 最佳实践实际操作(方式二)刚才我们说了继承和抽取两种方案这里的话我实际演示一下我选择的是方式二(抽取)。步骤主要分为三步新建 feign-api 模块引入 feign 的依赖将所需要的 Client 定义到 feign-api 模块中在所需要的业务模块中引入我们创建的 feign-api 依赖完成上述操作之后我们都可以重启测试了5.1 创建 feign-api 模块打开 pom 文件继承父工程 cloud-demo。引入 openfeign 依赖如下?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd parent artifactIdcloud-demo/artifactId groupIdcn.itcast.demo/groupId version1.0/version /parent modelVersion4.0.0/modelVersion artifactIdfeign-api/artifactId properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency /dependencies /project5.2 定义 UserClient这里我只是做举例所以实体类只写了 User之前 orderservice 业务中的 User 类就可以删掉了。Data public class User { private Long id; private String username; private String address; }实际项目中用到那个写那个。这里我只定义了关于 User的Client 接口实际开发也一样有别的业务在定义别的业务的接口FeignClient(value userservice) public interface UserClient { GetMapping(/user/{id}) User findById(PathVariable(id) Long id); }这里我单独创建 config 包定义了日志的等级public class DefaultFeignConfiguration { Bean public Logger.Level logLevel(){ return Logger.Level.BASIC; } }和上面第二节我说的一样全局生效就在对应模块的启动类上配置局部生效就在上面定义的UserClient 接口上的 FeignClient 中配置这里我是全局生效就不再展示了。3在对应的业务模块中引入我们定义的feign-api依赖这里就是 orderservice 业务模块这里我直接把 orderservice 的 pom 文件全展示一下吧?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd parent artifactIdcloud-demo/artifactId groupIdcn.itcast.demo/groupId version1.0/version /parent modelVersion4.0.0/modelVersion artifactIdorder-service/artifactId dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency !--mybatis-- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId /dependency !-- nacos客户端依赖包 -- dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency !--feign客户端依赖-- dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency !--优化feign引入依赖-- dependency groupIdio.github.openfeign/groupId artifactIdfeign-httpclient/artifactId /dependency !--引入我们的feign-api依赖-- dependency groupIdcn.itcast.demo/groupId artifactIdfeign-api/artifactId version1.0/version /dependency /dependencies build finalNameapp/finalName plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build /project这里还有一点非常重要千万千万要注意当我们将 feign 独立成一个模块之后我们的Spring容器再去注入Client时是会报错的。因为我们在独立出来之前orderservice 模块启动类默认扫描的是整个 orderservice 模块当时我们把 feign 写在 orderservice 模块中肯定是能扫描到的。但现在独立出来了它就扫不到了会报错。这里有两种解决方案方案一在启动类EnableFeignClients注解上指定要扫描的client包如下所示方案二仍然是在启动类上的 EnableFeignClients 注解上指定要扫描的特定的字节码文件也就是你用哪个扫哪个如果某个模块有多个 client 要用时可以扫包更快捷如果是某个模块只有一个client要用时可以扫指定的文件。4完成上述操作之后我们重启项目在浏览器输入localhost:8088/order/102 查询订单id为102的订单信息如下所示可以看到我们已经查询成功了。所以项目应该是没有问题的。