Spring Cloud微服务调用Feign初探
1、技术概述
Spring Cloud Feign
是声明式、模块化的Http客户端,可以快捷优雅的调用HTTP接口。由于用到了需要调用其他的微服务,因此需要通过HTTP方式进行调用。之前试过了RestTemplate
,感觉用起来不太方便,因此选择了Fegin。这次技术难点主要涉及到接口如何声明、以及在调用过程如何传递Cookies,设置Header。
2、技术详述
这是Feign的Github地址:https://github.com/OpenFeign/feign
这里已经假设你的Spring Boot项目拥有了一个Eureka注册中心,多个微服务的客户端,同时你已经明白他们之间的工作流程。这个不是本篇重点,因为使用Eureka进行服务注册和发现在网上已经有很多资料。这里使用到了Maven,并且假设你已经知道如何使用Maven。
使用Feign步骤如下:
1.导入依赖
2.添加注解@EnableFeignClients
3.声明Feign客户端接口
4.使用第3步声明的接口
接着我们一步步来使用Feign。
-
导入依赖(这里使用Maven进行依赖管理)
在你的pom.xml文件中,添加如下依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
你要注意的是Spring Cloud版本和Spring Boot 之间对应关系(这里以Spring Boot 2.3.0.RELEASE为例,因此Spring Cloud对应版本应该为Hoxton.SR5,有关Spring Cloud版本和Spring Boot 之间对应关系可以参见:https://start.spring.io/actuator/info或者通过https://start.spring.io/自动生成Spring Boot项目而不必关心他们之间的对应关系),这里列出部分pom.xml有关Spring Cloud示例:
... <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> ... <properties> ... <spring-cloud.version>Hoxton.SR5</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencyManagement>
-
添加注解@EnableFeignClients
在你的服务消费者的启动类或者配置类上添加
@EnableFeignClients
注解以启用Feign。@SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient @EnableFeignClients public class NotificationCenterApplication { public static void main(String[] args) { SpringApplication.run(NotificationCenterApplication.class, args); } }
相信你应该知道这些是有关Eureka客户端相关注解:
@EnableEurekaClient @EnableDiscoveryClient
-
声明Feign客户端接口
假设我们需要调用某个服务提供者的接口,因此我们先来到服务提供提供者相应的接口代码。我们声明Feign客户端接口将以它为蓝图。
@PostMapping("/todos") @ResponseBody public Object addTodo(@RequestBody @Valid TodoAddResource resource) { //... }
接者在我们服务消费者这里声明Feign客户端接口,它看起来是这样的:
@FeignClient(name = "TODO-SERVICE") public interface TodoRemoteService { @PostMapping("/todos") Object addTodo(@RequestBody TodoAddResource resource); }
声明Feign客户端接口非常简单,你只需要声明一个接口,把需要服务提供者的接口方法签名在接口中描述出来。注意:
-
@PostMapping("/todos")是必须的,它规定接口调用的路径和请求方法。
-
@RequestBody是必须的,它规定参数如何传递。
-
@FeignClient(name = "TODO-SERVICE")是必须的,它提供要调用的相应服务信息以及配置,其中
name
表示服务提供者的标识,你可以在Eureka注册中心看到。 -
方法返回值为
Object
与原方法相同,这里会涉及到相应的转换器。如果设置为String
那么返回是原接口返回内容字符串形式。 -
方法名
addTodo
不必相同,因为以上信息足以确定调用目标方法。
-
-
使用第3步声明的接口
最后一步,在你需要调用远程微服务地方注入刚才我们声明的Feign客户端接口:
@Autowired TodoRemoteService todoService; public void foo() { //... TodoAddResource resource = new TodoAddResource(); todoService.addTodo(resource); }
3、技术使用中遇到的问题和解决过程
在调试远程微服务调用过程中,发现出现了调用超时异常。
可能是连接超时或者调用方法执行时间超时,可以在application.yml
这样配置(连接超时ConnectTimeout
和读超时ReadTimeout
单位均为毫秒ms):
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 600000
这是默认全局配置,也可以根据相应调用的服务进行配置,更多信息可以参考文末的链接:Feign超时配置。配置的时间设置可以结合业务需求,同时使用Hystrix 熔断器进行相应处理。
在调用远程服务中,如何将原来请求中Cookies信息转发到调用请求,或者设置Header?
在使用@FeignClient
注解中,其实我们还有一个configuration
属性没有用到,这里可以指定相应Feign客户端配置。我们可以配置一个拦截器,把原来请求中信息复制到调用远程服务的请求上,这里我们转发相应的Cookies和设置一个X-XSRF-TOKEN
请求头信息,这个X-XSRF-TOKEN
的值从原本Cookies中一个名为XSRF-TOKEN
的Cookie获取。
我们首先可以新建一个类:
public class FeignAuthRequestInterceptor implements RequestInterceptor {
private static final String XSRF_TOKEN_HEADER_NAME = "X-XSRF-TOKEN";
private static final String XSRF_TOKEN_COOKIE_NAME = "XSRF-TOKEN";
private static final String COOKIE_HEADER_NAME = "Cookie";
private final Logger logger = LoggerFactory.getLogger(FeignAuthRequestInterceptor.class);
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Cookie[] cks = request.getCookies();
if (cks.length > 0) {
List<String> cookies = new ArrayList<>(cks.length);
for (int i = 0; i < cks.length; i++) {
cookies.add(cks[i].getName() + "=" + cks[i].getValue());
logger.info("cookie found:{}", cks[i].getName());
if (XSRF_TOKEN_COOKIE_NAME.equals(cks[i].getName())) {
requestTemplate.header(XSRF_TOKEN_HEADER_NAME, cks[i].getValue());
}
}
requestTemplate.header(COOKIE_HEADER_NAME, cookies);
}
}
}
之后,在需要转发原来请求信息的Feign客户端接口上,使用@FeignClient
注解的configuration
属性指定改配置类即可(配置类还可以配置Feign客户端的Encoder、Decoder等):
@FeignClient(value = "TODO-SERVICE", configuration = FeignAuthRequestInterceptor.class)
public interface TodoRemoteService {
@PostMapping("/todos")
String addTodo(@RequestBody TodoAddResource resource);
}
4、进行总结
总的来说Feign使用起来较为简洁,同时也支持强大的机制支撑。这里提供博客只是较为简单介绍和使用Feign部分功能,同时列举一些常见问题的解决方案,更多关于Feign还需要自行查找相关资料,由于自己对于Feign了解不是很充分,错误在所难免,还望大家多多指教。学无止境,多实践才能加深印象。
5、参考文献、参考博客
标题 | 作者 | 链接 |
---|---|---|
Feign接口方法返回值设置 | CoderYin | https://blog.csdn.net/CoderYin/article/details/90754547 |
Spring Cloud 进阶之路 -- Feign 的使用 | 老麻在此 | https://blog.csdn.net/antma/article/details/81317707 |
SpringBoot和SpringCloud对应的关系 | 代码拯救不了世界 | https://www.cnblogs.com/kaifaxiaoliu/p/11980114.html |
Feign超时配置 | 妙蛙大种子 | https://blog.csdn.net/zhang7495826/article/details/96002377 |