目录
一、前言
二、微服务接口之间的调用问题
2.1 Httpclient
2.2 Okhttp
2.3 HttpURLConnection
2.4 RestTemplate
三、openfeign介绍
3.1 什么是 openfeign
3.2 openfeign优势
四、Spring Cloud Alibaba整合OpenFeign
4.1 前置准备
4.2 完整整合步骤
4.2.1 order模块添加feign依赖
4.2.2 添加feign接口类
4.2.3 调整调用的方法
4.2.4 核心配置文件
4.2.5 接口模拟测试
五、openfeign自定义配置及使用
5.1 扩展日志配置 — 全局配置
5.1.1 添加一个日志配置类
5.1.2 配置文件调整日志级别
5.1.3 接口测试
5.2 扩展日志配置 — 局部配置
5.2.1 自定义日志配置类
5.2.2 在目标Feign接口指定日志配置类
5.2.3 配置文件方式实现局部日志扩展
六、openfeign契约配置
6.1 操作步骤
6.1.1 使用配置类的方式
6.1.2 修改Feign接口的方法调用
6.1.3 模拟测试
6.1.3 通过yml配置文件配置契约
七、openfeign超时时间配置
7.1 通过代码方式配置
7.2 通过配置文件方式配置
7.2.1 添加配置文件
7.2.2 修改被调用方接口
7.2.3 模拟测试调用
八、openfeign 自定义拦截器
8.1 openfeign 拦截器概述
8.2 操作过程
8.2.1 自定义 FeignInterceptor
8.2.2 使自定义的拦截器生效
8.2.3 改造stock中的get接口
8.2.4 模拟测试
8.2.5 自定义拦截器小结
九、写在文末
随着微服务治理手段越来越多,对开发者来说,不同微服务之间的接口调用是日常开发中非常普遍的事情,以springcloud生态体系的微服务治理方案,或者dubbo解决方案,各自都提供了不同的接口处理方式,需要根据自身的情况进行合理的选择,本篇以springcloud中提供的远程接口调用组件openfeign为例,来聊聊openfeign的详细使用。
在正式学习openfeign之前,大家是否记得在日常项目的开发中,是如何实现不同微服务之间的接口调用的呢?下面列举几种在Java项目中常用到的几种组件。
HttpClient是Apache 的子项目,用以提供高效、最新、功能丰富的支持http协议的客户端编程工具包,并且支持 HTTP协议最新版本。
相比传统 JDK自带的 URLConnection,提升了易用性和灵活性,使客户端发送 HTTP 请求变得容易,提高了开发的效率。
一个用以处理网络请求的开源项目,是安卓端最火的轻量级框架,由 Square 公司贡献,用于替代
HttpUrlConnection 和 Apache HttpClient。
OkHttp 拥有简洁的 API、高效的性能,并支持多种协议(HTTP/2 和 SPDY)。
HttpURLConnection是Java 的标准类,它继承自URLConnection,可用于向指定网站发送
get、post请求,HttpURLConnection 相比HttpClient使用比较复杂。
RestTemplate 是 Spring 提供的用于访问 Rest 服务的客户端,RestTemplate 提供了多种便
捷访问远程 HTTP 服务的方法,能够大大提升客户端编写效率。
上面介绍了常见的几种接口调用方法,接下来要介绍的方法比上面更简单方便,它就是Feign。
Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不
到这是远程方法,更感知不到这是个 HTTP 请求。具体来说:
- 它像 Dubbo 一样,consumer 直接调用接口方法调用 provider,而不需要通过常规的 Http Client 构造请求再解析返回数据;
- 它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发;
接下来,通过实际的操作,演示如何基于Spring Cloud Alibaba实现与OpenFeign的整合
创建一个新的order模块,工程结构和目录如下
添加如下依赖,主要是open-feign的依赖
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-openfeign
使用过dubbo的同学应该对此不陌生,使用服务端提供的接口时只需要注入就可以像调用本地方法一样使用了,feign的使用也是类似,只需在调用方定义一个接口类,里面添加被调用的接口中的完整的方法名,参数等,即保持与接口中的方法体参数一致即可;
比如在order模块调用stock模块时,stock的接口类提供了下面的方法
@GetMapping("/reduct")
public String reduct(){
System.out.println("扣减库存 : 8021");
return "扣减库存 : 8021";
}
那么在order模块中,自定义一个接口类,如下
@FeignClient(name = "stock-service",path = "/stock")
public interface StockFeignService {
@GetMapping("/reduct")
public String reduct();
}
参数说明:
在之前的调用中,我们一直使用的是restTemplate的方式调用,如果使用了feign,调用看起来就变得简单了,做一下简单的改造即可;
@RestController
@RequestMapping("/order")
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String serverURL;
@Autowired
private StockFeignService stockFeignService;
//localhost:8083/order/add
@GetMapping("/add")
public String add(){
System.out.println("下单成功");
String reduct = stockFeignService.reduct();
return "add order :" + reduct;
}
}
这里调整一下端口即可
server:
port: 8040
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
启动stock-service的两个工程,再启动order模块,启动完成后,浏览器进行接口调用
反复调用,默认情况下,呈现轮询的效果
Feign 提供了很多的扩展机制,让开发者可以更灵活的使用这些机制扩展系统的功能。
使用了Feign之后,有时候开发过程中遇到Bug,比如接口调用失败、参数没收到等问题,或者想看看调用性能,这就需要配置Feign的日志了,以便让Feign把请求的完整信息输出来定位和分析问题。配置过程也很简单,只需要添加一个配置类, 定制Feign提供的Logger级别即可。
添加如下配置类,指定level的级别为FULL,表示输出完整的日志信息
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
也可以根据自身的需求配置其他的日志级别,从源码中可以看到提供了多种级别的配置
默认情况下,服务工程以info级别输出,但Feign的日志级别为debug,两者不匹配,所以还需要额外做一下配置,在配置文件中补充下面的配置即可,即针对
logging:
level:
com.congge.feign: debug
在order模块的Feign中添加一个新的接口进行调用
@GetMapping("/get")
public String get(@RequestParam("id") Integer id);
接口调用
//localhost:8040/order/get
@GetMapping("/get")
public String get(){
String stockInfo = stockFeignService.get(1);
return stockInfo;
}
浏览器执行调用:localhost:8040/order/get,说明调用成功
此时再看控制台,发现输出了详细的调用日志信息
上面配置的这种方式是针对全局生效的,即Feign包路径下的所有类都生效,如果仅仅是针对某个具体的类生效,该怎么做呢?其实也很简单,只需要下面的两步;
与上面的想必,去掉那个@Configuration注解即可
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
在@FeignClient注解中有一个configuration的熟悉,使用上面的配置类即可
@FeignClient(name = "stock-service",path = "/stock",configuration = FeignConfig.class)
public interface StockFeignService {
@GetMapping("/reduct")
public String reduct();
@GetMapping("/get")
public String get(@RequestParam("id") Integer id);
}
在5.2.1的基础上,也可以不用通过5.2.2的方式通过指定配置类添加,而是在配置文件中指定,只需要在配置文件中添加下面的配置即可;
feign:
client:
config:
stock-service:
loggerLevel: FULL
配置完成后调用一下,控制台仍然可以输出完整的调用日志信息
Spring Cloud 在 Feign 的基础上做了扩展,使用 Spring MVC 的注解来完成Feign的功
能,以减少使用者的学习成本。
而原生的 Feign 是不支持 Spring MVC 注解的,如果你想在 Spring Cloud 中使用原生的
注解方式来定义客户端也是可以的,通过配置契约来改变这个配置,Spring Cloud 中默认的
是 SpringMvcContract。
Spring Cloud 1 早期版本就是用的原生Fegin. 随着netflix的停更替换成了Open feign
接下来看具体的操作过程
在配置类中添加Contract 的bean
@SpringBootApplication
//@EnableDiscoveryClient
@EnableFeignClients
public class FeignOrderApp {
public static void main(String[] args) {
SpringApplication.run(FeignOrderApp.class, args);
}
@Bean
public Contract feignContract() {
return new Contract.Default();
}
}
注意:修改契约配置后,OrderFeignService 不再支持springmvc的注解,需要使用Feign原
生的注解
接口中的调用改为如下(更多语法配置可以参阅相关资料)
@FeignClient(name = "stock-service",path = "/stock")
public interface StockFeignServiceV2 {
@RequestLine("GET /reduct")
public String reduct();
@RequestLine("GET /get")
public String get(@Param("id") Integer id);
}
再次启动order模块服务,然后在浏览器调用
在6.1.1 基础上,将契约的配置配置到配置文件中也可以实现相同的效果,具体如下
feign:
client:
config:
stock-service:
loggerLevel: FULL
#配置契约
concontract: feign.Contract.Default
客户端微服务通过Fiegn调用其他微服务接口时,可能因为某些原因导致接口响应超时达到默认的设置时间而调用失败,如果业务允许超时时长更长一点的话,就可以考虑手动配置openfeign的超时时间;
在上述的配置类中添加下面的超时配置bean;
@Bean
public Request.Options options() {
return new Request.Options(5000, 10000);
}
核心配置如下
feign:
client:
config:
stock-service:
loggerLevel: FULL
#配置契约
#concontract: feign.Contract.Default
connectTimeout: 5000
readTimeout: 3000
补充说明: Feign的底层用的是Ribbon,但超时时间以Feign配置为准
为了模拟效果,将调用的stock-service中的接口加一个sleep时间
@GetMapping("/get")
public String get(@RequestParam("id") Integer id) throws InterruptedException {
Thread.sleep(4000);
return "查询到的库存商品ID : " + id;
}
启动两个模块的工程,浏览器调用接口:localhost:8040/order/get,将会看到下面的效果
同时观察控制台输出日志,可以看到输出了 超时的信息;
使用过springmvc拦截器的同学对拦截器应该不陌生,拦截器可以在请求真正到达后端接口之前做一下预处理工作,比如非法请求拦截、参数校验过滤、全局token的认证、加解密、审计等等,在openfeign 中也提供了类似的可以自定义的拦截器,其存在的目的主要是针对服务调用端在调用服务接口时可以做的一些预处理工作。
接下来演示如何在openfeign 中自定义拦截器;
主要是实现RequestInterceptor 接口,重写里面的apply方法即可,比如在下面的apply方法中,给请求头中添加了两个参数,这样的话,服务提供方就可以解析到header中参数的值;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
public class FeignInterceptor implements RequestInterceptor {
static final Logger logger = LoggerFactory.getLogger(FeignInterceptor.class);
@Override
public void apply(RequestTemplate requestTemplate) {
logger.info("enter FeignInterceptor ...");
String accessToken=UUID.randomUUID().toString();
requestTemplate.header("accessToken",accessToken);
requestTemplate.header("username","jerry");
}
}
可以通过配置全局bean的方式或者在yaml中配置都可以
配置bean的方式
@Bean
public FeignInterceptor feignInterceptor() {
return new FeignInterceptor();
}
配置文件方式
feign:
client:
config:
stock-service:
loggerLevel: FULL
connectTimeout: 5000
readTimeout: 3000
#自定义拦截器
requestInterceptors: com.congge.config.FeignInterceptor
这里将请求中的参数进行解析,并打印输出结果
@GetMapping("/get")
public String get(@RequestParam("id") Integer id) throws InterruptedException {
String accessToken = request.getHeader("accessToken");
System.out.println(accessToken);
String username = request.getHeader("username");
System.out.println(username);
//Thread.sleep(4000);
return "查询到的库存商品ID : " + id;
}
分别启动stock模块的工程和order模块的工程,然后浏览器调用:localhost:8040/order/get
接口可以正常返回结果;
观察stock服务控制台,header中的参数值也能正确的被解析出来;
这是一种很有用的技巧,尤其是内部各个微服务之间进行调用的时候,为了确认对方的身份是否授信或者互信,可以通过这种方式传递一些可以加解密的参数进行身份确认。
Feign在springcloud微服务生态体系中具有承上启下非常重要的作用,对于习惯了使用dubbo服务调用方式的同学来说,系统在微服务架构演进过程中,可以采用同时兼容dubbo和feign的方式,而且只需要在现用的架构中做少量的适配和改造即可,这是一个很好的尝试,因此有必要深入了解Feign的技术。