SpringCloud之Feign讲解

1 Feign

1.1 定义

1.1.1 简介

Feign是一个声明式的Web Service客户端,通过声明RESTful请求客户端
Spring Cloud集成了RibbonEureka,可在使用Feign时提供负载均衡的http客户端

微服务直接调用使用RestTemplate进行远程调用,非常方便,那么有了RestTemplate为什么还要有Feign,因为RestTemplate有一个致命的问题:硬编码
点击了解Spring之RestTemplate
RestTemplate 调用中,我们每个调用远程接口的方法,都将远程接口对应的 ip、端口,或 service-id 硬编码到了 URL 中,如果远程接口的 ip、端口、service-id 有修改的话,需要将所有的调用都修改一遍,这样难免会出现漏改、错改等问题,且代码不便于维护。为了解决这个问题,Netflix 推出了 Feign 来统一管理远程调用

1.1.2 属性介绍

@FeignClient标签的常用属性如下:

  • name:指定FeignClient的名称,如果项目使用了Ribbonname属性会作为微服务的名称,用于服务发现
  • value: 调用服务名称,和name属性相同
  • url: url一般用于调试,可以手动指定@FeignClient调用的地址
  • decode404: 当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
  • configuration: Feign配置类,可以自定义FeignEncoder、Decoder、LogLevel、Contract
  • fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
    熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。底层依赖hystrix,启动类要加上@EnableHystrix
  • fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
  • path: 定义当前FeignClient的统一前缀
  • primary: 默认值trueFeign会自动生成FeignClient,也就是接口的jdk代理实现

注意name/valueurl同时存在生效问题

  • name/value属性:这两个的作用是一样的,指定的是调用服务的微服务名称
  • url:指定调用服务的全路径,经常用于本地测试
  • 如果同时指定nameurl属性: 则以url属性为准,name属性指定的值便当做客户端的名称

1.1.3 原理解析

Feign调用步骤:

  • 程序启动时,扫描所有的@FeignClient注解
  • 当接口方法被调用时,通过JDK代理来生成RequestTemplate模板
  • 根据RequestTemplate模板生成Http请求的Request对象
  • Request对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection、HttpClient、OKHttp
  • 最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用

 

1.2 Feign准备工作

1.2.1 引入依赖

Spring Cloud项目中引入Feign依赖,但是因为feign底层是使用了ribbon作为负载均衡的客户端,而ribbon的负载均衡也是依赖于eureka获得各个服务的地址,所以要引入eureka-client

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    随便写的版本号
    <version>2.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    随便写的版本号
    <version>2.0.2.RELEASE</version>
</dependency>

1.2.2 启动类和yml文件

需要在启动类上添加注解@EnableFeignClients以及@EnableDiscoveryClient

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}

server:
  port: 8082

#配置eureka
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    status-page-url-path: /info
    health-check-url-path: /health

#服务名称
spring:
  application:
    name: product
  profiles:
    active: ${boot.profile:dev}
#feign的配置,连接超时及读取超时配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

1.3 使用Feign

1.3.1 简单使用@FeignClient

@FeignClient(value = "CART",fallback=Hysitx.class,configuration = FeignConfiguration.class)
public interface CartFeignClient {

    @PostMapping("/cart/{productId}")
    Long addCart(@PathVariable("productId")Long productId);
}

编写熔断类,发生错误时回调

import java.util.List;
import org.springframework.stereotype.Component;
@Component
public class Hysitx implements IRemoteCallService{
    @Override
    public List<String> test(String[] names) {
        System.out.println("接口调用失败");
        return null;
    }
}

引入FeignAutoConfiguration配置

@Import(FeignAutoConfiguration.class)
@Configuration
public class FeignConfiguration{
    ...
}

上面是最简单的feign client的使用,声明完为feign client后,其他spring管理的类,如service就可以直接注入使用了,例如:

//这里直接注入feign client
@Autowired
private CartFeignClient cartFeignClient;

@PostMapping("/toCart/{productId}")
public ResponseEntity addCart(@PathVariable("productId") Long productId){
    Long result = cartFeignClient.addCart(productId);
    return ResponseEntity.ok(result);
}

1.3.2 @RequestLine

Feign为什么用的是@RequestLine

  • 这和open-feignContract设计有关系,Contract是一个注解解析接口,它决定了接口可以使用什么注解转换到http请求。open-feign在使用@FeignClient的情况下,使用的是SpringMvcContract,它使得被@FeignClient修饰的接口,可以使用@GetMapping,@PostMapping等Spring Mvc注解。
    如果我们要使用@RequestLine,则需要替换open-FeignMVC解析器

// 在feign上写上配置
@FeignClient(name = "test-center", configuration = TestFeignConfig .class)
@Configuration// 配置类
public class TestFeignConfig {    
    @Bean
    public Contract feignContract() {
    // 配置feign的注释解析器为feign默认解析器而不是mvc解析器
        return new feign.Contract.Default();
    }
}

如果我们不单独配置,则会使用FeignClientsConfiguration中默认配置的SpringMvcContract

@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
    return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}

@RequestLine与其它请求不同,只需要简单写请求方式和路径就能达到请求其它服务的目的

@FeignClient(value = "feign-server",configuration = FeignConfig.class)  //需要一个配置文件
public interface TestService {
    @RequestLine("POST /feign/test")    //对应请求方式和路径
    String feign(@RequestBody UserDO userDO);
}

配置文件

@EnableFeignClients
@SpringBootConfiguration
public class FeignConfig {
    @Bean
    public Contract contract(){
        return new feign.Contract.Default();
    }
}

@SpringBootConfiguration

  • 标注这个类是一个配置类;
  • 它只是@Configuration注解的派生注解;
  • 它与@Configuration注解的功能一致;
  • 只不过@SpringBootConfigurationspringboot的注解,而@Configurationspring的注解

1.4 OpenFeign添加header信息

在微服务间使用Feign进行远程调用时需要在header中添加信息,那么 springcloud open feign如何设置 header 呢?有5种方式可以设置请求头信息:

  • @RequestMapping注解里添加headers属性
  • 在方法参数前面添加@RequestHeader注解
  • 在方法或者类上添加@Headers的注解
  • 在方法参数前面添加@HeaderMap注解
  • 实现RequestInterceptor接口

1.4.1 在@RequestMapping注解里添加headers属性

application.yml中配置

app.secret: appSecretVal

@PostMapping(value = "/book/api", headers = {"Content-Type=application/json;charset=UTF-8", "App-Secret=${app.secret}"})
void saveBook(@RequestBody BookDto condition);

1.4.2 在方法参数前面添加@RequestHeader注解

设置单个header属性

@GetMapping(value = "/getStuDetail")
public StudentVo getStudentDetail(@RequestBody StudentDto condition, @RequestHeader("Authorization") String token);

设置多个header属性

@PostMapping(value = "/card")
public CardVo createCard(@RequestBody CardDto condition, @RequestHeader MultiValueMap<String, String> headers);

1.4.3 在方法或者类上添加@Headers的注解

使用feign自带契约

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }
}

FeignClient使用@RequestLine注解, 而未配置feign自带契约Contract时, @Headers不会起作用, 而且启动项目会报错:

Method xxx not annotated with HTTP method type (ex. GET, POST)
@RequestLine is a core Feign annotation, but you are using the Spring Cloud @FeignClientwhich uses Spring MVC annotations.

配置@Headers注解

@FeignClient(url = "${user.api.url}", name = "user", configuration = FooConfiguration.class)
public interface UserFeignClient {
    @RequestLine("GET /simple/{id}")
    @Headers({"Content-Type: application/json;charset=UTF-8", "Authorization: {token}"})
    public User findById(@Param("id") String id, @Param("token") String token);
}

使用@Param可以动态配置Header属性
网上很多在说 @Headers不起作用,其实@Headers注解没有生效的原因是:官方的Contract没有生效导致的

1.4.4 在方法参数前面添加@HeaderMap注解

使用feign自带契约

配置@HeaderMap注解

@FeignClient(url = "${user.api.url}", name = "user", configuration = FooConfiguration.class)
public interface UserFeignClient {
    @RequestLine("GET /simple/{id}")
    public User findById(@Param("id") String id, @HeaderMap HttpHeaders headers);
}

1.4.5 实现RequestInterceptor接口

值得注意的一点是:
如果FeignRequestInterceptor注入到spring容器的话就会全局生效, 就是说即使在没有指定configuration属性的FeignClient该配置也会生效
配置@Component@Service@Configuration就可以将该配置注入spring容器中, 即可实现全局配置, 从而该项目中的所有FeignClientfeign接口都可以使用该配置.
如果只想给指定FeignClientfeign接口使用该配置, 请勿将该类配置注入spring中.

@Configuration
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        template.header(HttpHeaders.AUTHORIZATION, "tokenVal");
    }

}

1.5 其他操作

1.5.1 使用OKhttp

Feign底层默认是使用jdk中的HttpURLConnection发送HTTP请求,feign也提供了OKhttp来发送请求,具体配置如下

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic
  okhttp:
    enabled: true
  hystrix:
    enabled: true

1.5.2 开启GZIP压缩

Spring Cloud Feign支持对请求和响应进行GZIP压缩,以提高通信效率。
application.yml配置信息如下:

feign:
  compression:
    request: #请求
      enabled: true #开启
      mime-types: text/xml,application/xml,application/json #开启支持压缩的MIME TYPE
      min-request-size: 2048 #配置压缩数据大小的下限
    response: #响应
      enabled: true #开启响应GZIP压缩

注意
由于开启GZIP压缩之后,Feign之间的调用数据通过二进制协议进行传输,返回值需要修改为ResponseEntity<byte[]>才可以正常显示,否则会导致服务之间的调用乱码。
示例如下:

@PostMapping("/order/{productId}")
ResponseEntity<byte[]> addCart(@PathVariable("productId") Long productId);

2 Feign拦截器RequestInterceptor

2.1 定义

在使用feign做服务间调用的时候,如何修改请求的头部或编码信息呢,可以通过实现RequestInterceptor接口的apply方法,feign在发送请求之前都会调用该接口的apply方法,所以我们也可以通过实现该接口来记录请求发出去的时间点

2.2 RequestInterceptor


public interface RequestInterceptor {
 
  /**
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

RequestInterceptor接口定义了apply方法,其参数为RequestTemplate;它有一个抽象类为BaseRequestInterceptor,还有几个实现类分别为BasicAuthRequestInterceptor、FeignAcceptGzipEncodingInterceptor、FeignContentGzipEncodingInterceptor

  • BasicAuthRequestInterceptor : 实现了RequestInterceptor接口,其apply方法往RequestTemplate添加名为Authorizationheader
  • BaseRequestInterceptor : 定义了addHeader方法,往requestTemplate添加非重名的header
  • FeignAcceptGzipEncodingInterceptor : 继承了BaseRequestInterceptor,它的apply方法往RequestTemplate添加了名为Accept-Encoding,值为gzip,deflateheader
  • FeignContentGzipEncodingInterceptor : 继承了BaseRequestInterceptor,其apply方法先判断是否需要compression,即mimeType是否符合要求以及content大小是否超出阈值,需要compress的话则添加名为Content-Encoding,值为gzip,deflateheader

2.3 配置RequestInterceptor

可以通过配置类和配置文件两种方式注册RequestInterceptor

  • 通过配置类配置时,通过FeignContext获取RequestInterceptor bean
  • 通过配置文件注册时,通过ApplicationContext获取RequestInterceptor bean
    通过配置类进行配置
    设置配置类有两种方式。

2.3.1 通过@EnableFeignClients的defaultConfiguration属性配置

@EnableFeignClients注解的 defaultConfiguration 属性值为 @Configuration 注解的配置类,配置类内定义的 bean 会注册为所有 @FeignClient 的默认配置。
配置信息会保存为FeignClientSpecification对象。

2.3.2 通过@FeignClient的configuration属性配置

@FeignClient注解的 configuration 属性值为 @Configuration 注解的配置类,其定义的 bean 为每个FeignClient 的专有 bean

FeignContext 为每一个 @FeignClient 注解的接口提供独立的AnnotationConfigApplicationContext,其中包含 FeignClient 的私有beanEnableFeignClient 的默认 bean

2.3.3 通过配置文件配置

@Component
public class DpAuthFeignReqInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String jwtToken = request.getHeader(JWTTokenConstant.TOKEN_NAME);
            //传递token  给下游 
            template.header(JWTTokenConstant.TOKEN_NAME, jwtToken);
            
        }
    }
}


转载
作者:景宗会
链接:https://www.jianshu.com/p/92d0537de737
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值