RPC远程调用组件-OpenFeign基础知识
概述
OpenFeign介绍
OpenFeign 是⼀个声明式远程调⽤客户端,通过注解的方式可以更方便的发起远程调用,相较于编程式的RestTemplate,OpenFeign能够通过使用SpringMvc注解来声明远程调用的请求,使用了动态代理技术来封装远程服务调用的过程,并且能够与SpringCloud其他组件完美配合。
基础用法
pom依赖
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
启动类
1 2 3 4 5 6 7
| @SpringBootApplication @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
声明Feign客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@FeignClient("stores") public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = "/stores") List<Store> getStores(); @GetMapping("/stores") Page<Store> getStores(Pageable pageable); @PostMapping(value = "/stores/{storeId}", consumes = "application/json",params = "mode=upsert") Store update(@PathVariable("storeId") Long storeId, Store store); @DeleteMapping("/stores/{storeId:\\d+}") void delete(@PathVariable Long storeId); }
|
三方客户端声明
1 2 3 4 5 6 7 8 9 10 11
|
@FeignClient(value = "weather-client", url = "http://aliv18.data.moji.com") public interface WeatherFeignClient { @PostMapping("/whapi/json/alicityweather/condition") String getWeather(@RequestHeader("Authorization") String auth, @RequestParam("token") String token, @RequestParam("cityId") String cityId); }
|
超时配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| spring: cloud: openfeign: client: config: default: logger-level: full connect-timeout: 1000 read-timeout: 2000 stores: logger-level: full connect-timeout: 3000 read-timeout: 5000
|
重试配置
当发生超时后,OpenFeign默认是不进行重试的。可以在yaml中进行配置,或在Spring容器中放入Retryer的实现。OpenFeign提供的重试机制可配置三个参数:
- period:初始超时时间,控制第一次超时的时间(ms)
- maxPeriod:最大超时时间,除第一次请求外,每次重试的超时时间为上一次请求超时时间的1.5倍,如果超过maxPeriod,则取maxPeriod
- maxAttempts:最大重试次数
拦截器
OpenFeign提供了请求拦截器和响应拦截器,请求拦截器可以在发生请求前对请求进行处理。如下代码声明了一个请求拦截器,放入Spring容器的拦截器会自动生效,该拦截器在发生调用前,在请求头里添加了X-Token。
1 2 3 4 5 6 7 8 9 10 11 12
| @Component public class XTokenRequestInterceptor implements RequestInterceptor {
@Override public void apply(RequestTemplate requestTemplate) { requestTemplate.header("X-Token", UUID.randomUUID().toString()); } }
|
兜底返回Fallback
此功能需要搭配SpringCloud组件Sentinel。当请求远程服务失败,未成功获取到响应时,可通过该功能配置兜底返回逻辑。
1 2 3
| feign: sentinel: enabled: true
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class) public interface ProductFeignClient { @GetMapping("/product/{id}") Product getProductById(@PathVariable("id") Long id); }
@Component public class ProductFeignClientFallback implements ProductFeignClient { @Override public Product getProductById(Long id) { System.out.println("兜底回调...."); Product product = new Product(); product.setId(id); product.setPrice(new BigDecimal("0")); product.setProductName("未知商品"); product.setNum(0); return product; } }
|
扩展内容
请求客户端配置
OpenFeign中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,我们可以集成别的组件来替换掉 URLConnection,比如 Apache HttpClient,OkHttp。
配置Apache HttpClient
引入依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.7</version> </dependency>
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> <version>10.1.0</version> </dependency>
|
修改yml配置,将 Feign 的 Apache HttpClient启用。
1 2 3 4
| feign: httpclient: enabled: true
|
关于配置可参考源码: org.springframework.cloud.openfeign.FeignAutoConfiguration,内部有很多@ConditionalOnProperty注解。
配置 OkHttp
引入依赖。
1 2 3 4
| <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
|
修改yml配置,将 Feign 的 HttpClient 禁用,启用 OkHttp,配置如下:
1 2 3 4 5 6 7
| feign: httpclient: enabled: false okhttp: enabled: true
|
手动创建FeignClient
在某些情况下,OpenFeign提供的各类配置可能无法满足你的需求,在这种情况下,你可以使用 Feign Builder API 来创建客户端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Import(FeignClientsConfiguration.class) class FooController {
private FooClient fooClient;
private FooClient adminClient;
@Autowired public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerObservationCapability micrometerObservationCapability) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .addCapability(micrometerObservationCapability) .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) .target(FooClient.class, "https://PROD-SVC");
this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .addCapability(micrometerObservationCapability) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) .target(FooClient.class, "https://PROD-SVC"); } }
|
SpringCache
如果使用了 @EnableCaching 注解,就会创建并注册一个 CachingCapability Bean,这样你的 Feign 客户端就能识别其接口上的 @Cache* 注解。
1 2 3 4 5 6
| public interface DemoClient {
@GetMapping("/demo/{filterParam}") @Cacheable(cacheNames = "demo-cache", key = "#keyParam") String demoEndpoint(String keyParam, @PathVariable String filterParam); }
|
RefreshScope
@RefreshScope 是 Spring Cloud 中用于动态刷新 Bean 配置的重要注解之一。它主要用来解决在 Spring Cloud 项目中通过 Spring Cloud Config 管理配置时,当外部配置发生变化时,如何实时刷新 Bean 中的配置问题。
默认情况下,Feign 客户端的刷新行为处于禁用状态,需要配置开启。
1 2 3 4 5
| spring: cloud: openfeign: client: refresh-enabled: true
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Configuration public class FeignConfiguration { @Value("${feign.client.config.default.connectTimeout:5000}") private int connectTimeout; @Value("${feign.client.config.default.readTimeout:10000}") private int readTimeout; @Value("${feign.retry.maxAttempts:3}") private int maxAttempts; @Value("${feign.retry.period:1000}") private long period; @Bean @RefreshScope public Request.Options options() { return new Request.Options(connectTimeout, readTimeout); } @Bean @RefreshScope public Retryer retryer() { return new Retryer.Default(period, period * 2, maxAttempts); } }
|
结合Apollo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Component public class FeignConfigChangeListener { @Autowired private ApplicationContext applicationContext; @ApolloConfigChangeListener("feign-config") private void onChange(ConfigChangeEvent changeEvent) { refreshFeignConfiguration(); } private void refreshFeignConfiguration() { RefreshScope refreshScope = applicationContext.getBean(RefreshScope.class); refreshScope.refreshAll(); } }
|