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。

  1. 导入依赖(这里使用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>
    
  2. 添加注解@EnableFeignClients

    ​ 在你的服务消费者的启动类或者配置类上添加@EnableFeignClients注解以启用Feign。

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

    相信你应该知道这些是有关Eureka客户端相关注解:

    @EnableEurekaClient
    @EnableDiscoveryClient
    
  3. 声明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客户端接口非常简单,你只需要声明一个接口,把需要服务提供者的接口方法签名在接口中描述出来。注意:

    1. @PostMapping("/todos")是必须的,它规定接口调用的路径和请求方法。

    2. @RequestBody是必须的,它规定参数如何传递。

    3. @FeignClient(name = "TODO-SERVICE")是必须的,它提供要调用的相应服务信息以及配置,其中name表示服务提供者的标识,你可以在Eureka注册中心看到。

    4. 方法返回值为Object与原方法相同,这里会涉及到相应的转换器。如果设置为String那么返回是原接口返回内容字符串形式。

    5. 方法名addTodo不必相同,因为以上信息足以确定调用目标方法。

  4. 使用第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
posted @ 2020-06-15 13:51  Zhifeng_Shen  阅读(232)  评论(0编辑  收藏  举报