当前位置 : 主页 > 编程语言 > java >

Spring Cloud Feign组件

来源:互联网 收集:自由互联 发布时间:2023-02-04
概述 Feign 是Netflix开源的一个声明式的Http 客户端,它的目的就是让Web Service基于Http的远程调用变得更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好

概述

Feign 是Netflix开源的一个声明式的Http 客户端,它的目的就是让Web Service基于Http的远程调用变得更加简单。 Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign 默认集成了 Ribbon,Nacos 也很好的兼容了 Feign,默认实现了负载均衡的效果。

  • Feign 采用的是基于接口的注解
  • Feign 整合了 ribbon

快速入门

1、引入maven依赖:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <!--整合Spring Cloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

2、通过@EnableFeignClients注解开启Feign功能

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

3、创建Feign接口,通过 @FeignClient("服务名") 注解来指定调用哪个服务

@FeignClient(value="price-center") public interface PriceCenterFeignClient { @GetMapping(value = "/prices/{id}") PriceInfo getPrice(@PathVariable("id") Integer id); }

Feign的组成

接口 作用 默认值 Feign.Builder Feign的入口 Feign.Builder Client Feign底层用什么去请求 和Ribbon配合时:LoadBalancerFeignClient <br>不和Ribbon配合时:Feign.Client.Default Contract 契约,注解支持 SpringMvcContract Encoder 编码器,用于将对象转换成HTTP请求消息体 SpringEncoder Decoder 解码器,将响应消息体转换成对象 ResponseEntityDecoder Logger 日志管理器 Slf4jLogger RequestInterceptor 用于为每个请求添加通用逻辑 需要手动实现

Feign的配置自定义

细粒度自定义Feign的日志级别配置

Feign的日志级别 打印内容 NONE(默认值) 不记录任何日志 BASIC 仅记录请求方法、URL、响应状态码以及执行时间(适用于线上) HEADERS 在BASIC的基础上,记录请求和响应的header FULL 记录请求和响应的header、body和元数据

1、Java代码方式

  • A、在@FeignClient注解上加入configuration属性
@FeignClient(value="price-center",configuration = PriceCenterFeignConfiguration.class) public interface PriceCenterFeignClient { @GetMapping(value = "/prices/{id}") PriceInfo getPrice(@PathVariable("id") Integer id); }
  • B、新建PriceCenterFeignConfiguration类,用于配置Feign日志级别
/** * 通过java代码,细粒度的方式(指定user-center)配置Feign的日志级别 * * 这个类别加@Configuration注解了,否则必须挪到@ComponentScan能扫描到的包以外 * */ public class PriceCenterFeignConfiguration{ @Bean public Logger.Level level(){ //让Feign打印所有请求的细节 return Logger.Level.FULL; } }
  • C、添加application.yml配置
logging: level: #配置Feign的日志级别 com.yibo.contentcenter.configuration.PriceCenterFeignConfiguration: debug

即Feign的日志级别是建立在上面配置的debug基础之上的,如果上面改为info,那么则不会输出任何日志

2、配置属性方式 首先去掉@FeignClient注解上的configuration属性

#细粒度的配置Feign的日志级别,这种是通过配置文件的方式 feign: client: config: #想要调用的微服务的名称 price-center: loggerLevel: full

全局自定义Feign的日志级别配置

Java代码方式

  • 方式一、让父子上下文ComponentScan重叠(强烈不建议使用)
  • 方式二、@EnableFeignClients(defaultConfiguration = xxx.class)(唯一正确方式)
@SpringBootApplication @EnableFeignClients(defaultConfiguration = GlobalFeignConfiguration.class) public class ContentCenterApplication { public static void main(String[] args) { SpringApplication.run(ContentCenterApplication.class, args); } } public class GlobalFeignConfiguration{ @Bean public Logger.Level level(){ //让Feign打印所有请求的细节 return Logger.Level.FULL; } }

配置属性方式 首先去掉@EnableFeignClients中的defaultConfiguration属性

feign: client: config: #全局配置 default: loggerLevel: full

Feign支持的配置项

代码方式支持的配置项

配置项 作用 Feign.Builder Feign的入口 Client Feign底层用什么去请求 Contract 契约,注解支持 Encocer 编码器,用于将对象转换成HTTP请求消息体 Decoder 解码器,将相应消息体转换成对象 Logger 日志管理 LoggerLevel 指定日志级别 Retryer 指定重试策略 ErrorDecoder 指定错误解码器 Request.Options 超时时间 Collection<RequestInterceptor> 拦截器 SetterFactory 用于设置Hystrix的配置属性,Feign整合Hystrix才会用

属性方式支持的配置项

feign.client.config: <feignName>: connectTimeout:5000 #连接超时时间 readTimeout:5000 #读取超时时间 loggerLevel:full #日志级别 errorDecoder:com.example.SimpleErrorDecoder #错误解码器 retryer:com.example.SimpleRetryer #重试策略 requestInterceptors: - com.example.FooRequestInterceptor #拦截器 # 是否对404错误码解码 # 处理逻辑详见feign.SynchronousMethodHandler#executeAndDecode decode404:false encoder:com.example.SimpleEncoder # 编码器 decoder:com.example.SimpleDecoder # 解码器 contract:com.example.SimpleContract # 契约

Feign配置代码方式 VS 属性方式 Feign配置优先级:全局代码<全局属性<细粒度代码<细粒度属性 image.png

  • 尽量使用属性配置,属性方式实现不了的情况下在考虑使用代码方式配置
  • 在同一微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性

Feign的继承特性

Spring Cloud中Feign的继承特性:https://blog.csdn.net/u012702547/article/details/78261306

创建一个基础的Maven工程,定义Controller接口写好SpringMvc注解,由服务提供方和服务消费方通过引入基础Maven工程,分别继承Controller接口,Feign继承特性方式用起来确实很方面,但是也带来一个问题,就是服务提供者和服务消费者的耦合度太高,此时如果服务提供者修改了一个接口的定义,服务消费者可能也得跟着变化,进而带来很多未知的工作量,因此小伙伴们在使用继承特性的时候,要慎重考虑。

Feign脱离Ribbon的使用

@FeignClient(name="baidu",url= "http://www.baidu.com") public interface BaiduFeignClient { @GetMapping("") public string index(); }

Feign调用https接口的话需要绕过SSL验证

Feign- 绕过SSL验证的方案

方案一——改写LoadBalancerFeignClient

import feign.Client; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; import org.springframework.context.annotation.Bean; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; /** * @Author: huangyibo * @Date: 2022/8/3 19:36 * @Description: feign client配置 */ public class FeignConfiguration { @Bean public CachingSpringLoadBalancerFactory cachingFactory(SpringClientFactory clientFactory) { return new CachingSpringLoadBalancerFactory(clientFactory); } @Bean @ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) throws NoSuchAlgorithmException, KeyManagementException { SSLContext ctx = SSLContext.getInstance("TLSv1.2"); X509TrustManager tm = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } }; ctx.init(null, new TrustManager[]{tm}, null); return new LoadBalancerFeignClient(new Client.Default(ctx.getSocketFactory(), (hostname, session) -> true), cachingFactory, clientFactory); } } @FeignClient(name = "OrderFeign", url= "https://yibo.com/order-center/openapi", configuration = FeignConfiguration.class) public interface OrderFeign { @PostMapping("/order") ResultBody<List<HouseDO>> queryHouseList(OrderQuery query); }

方案二——改写RestTemplate

@Bean public RestTemplate getRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { return true; } }).build(); SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(csf) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); return new RestTemplate(requestFactory); } @FeignClient(name = "OrderFeign", url= "https://yibo.com/order-center/openapi") public interface OrderFeign { @PostMapping("/order") ResultBody<List<HouseDO>> queryHouseList(OrderQuery query); }

RestTemplate VS Feign

尽量使用Feign,尽量杜绝使用RestTemplate

角度 RestTemplate Feign 可读性、可维护性 一般 极佳 开发体验 欠佳 极佳 性能 很好 中等(RestTemplate性能的50%左右) 灵活性 极佳 中等(内置功能可满足绝大多数需求)

Open Feign数据压缩功能

  • Spring Cloud Feign支持对请求和响应进行数据压缩(默认采用 gzip 压缩),以此来提高通信效率。

  • 如果在服务间单次传输的数据超过1K字节,强烈推荐开启数据压缩功能。

feign: compression: request: enabled: true # 开启请求的数据压缩 mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE min-request-size: 1024 # 配置压缩数据大小的下限,当传输的数据类型大于 1024 时,才会进行压缩 response: enabled: true # 配置响应GZIP压缩

Feign的性能优化

1、为Feign配置连接池,性能提升15%

Feign通过jdk中的HttpURLConnection向下游服务发起http请求(源码详见feign.Client)

public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = this.convertAndSend(request, options); return this.convertResponse(connection, request); } HttpURLConnection convertAndSend(Request request, Options options) throws IOException { HttpURLConnection connection = (HttpURLConnection)(new URL(request.url())).openConnection(); if (connection instanceof HttpsURLConnection) { HttpsURLConnection sslCon = (HttpsURLConnection)connection; if (this.sslContextFactory != null) { sslCon.setSSLSocketFactory(this.sslContextFactory); } if (this.hostnameVerifier != null) { sslCon.setHostnameVerifier(this.hostnameVerifier); } } ...... }

得出结论:缺乏连接池的支持,在达到一定流量的后服务肯定会出问题,可以用httpclient和okhttp替换掉jdk原生的HttpURLConnection,httpclient和okhttp都是支持连接池的

httpclient替换掉Feign原生的HttpURLConnection

  • 1、引入maven依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
  • 2、添加配置
feign: httpclient: #让feign使用apache httpclient做请求,而不是默认的HttpURLConnection enabled: true #feign的最大连接数 max-connections: 200 #feign单个路径的最大连接数127.0.0.1:9876 max-connections-per-route: 50

okhttp替换掉Feign原生的HttpURLConnection

  • 1、引入maven依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> <version>10.1.0</version> </dependency>
  • 2、添加配置
feign: client: config: default: # 服务名,填写 default 为所有服务,或者指定某服务 connectTimeout: 10000 # 连接超时,10秒 readTimeout: 20000 # 读取超时,20秒 httpclient: enabled: false # 关闭 ApacheHttpClient max-connections: 200 # 连接池连接最大连接数 max-connections-per-route: 50 # feign单个路径的最大连接数127.0.0.1:9876 time-to-live: 600 # 连接最大闲置时间,单位为秒,600秒==10分钟(缺省值为 900秒==15分钟) okhttp: enabled: true # 开启 okhttp
  • 3、添加java代码配置
@Configuration @ConditionalOnClass(Feign.class) @AutoConfigureBefore(FeignAutoConfiguration.class) public class FeignOkHttpConfig { @Autowired OkHttpLoggingInterceptor okHttpLoggingInterceptor; @Bean public okhttp3.OkHttpClient okHttpClient(){ return new okhttp3.OkHttpClient.Builder() //读取超时时间 .readTimeout(60, TimeUnit.SECONDS) //连接超时时间 .connectTimeout(60, TimeUnit.SECONDS) //写超时时间 .writeTimeout(120, TimeUnit.SECONDS) //设置连接池 .connectionPool(new ConnectionPool()) // .addInterceptor(); .build(); } }

2、为Feign设置合理的日志级别

  • Feign默认是不打印任何日志的,这个日志级别的性能是最好的,但是生产环境如果需要了解请求的具体细节,那么建议将Feign的日志级别设置为BASIC。

Feign传递Token

利用@RequestHeader,强烈不建议使用这种方式

利用RequestInterceptor实现Token传递,推荐使用

1、新建TokenFeignClientInterceptor实现RequestInterceptor重写apply()

/** * 发送FeignClient设置Header信息 * * 微服务之间通过Feign调用,通过拦截器在feign请求之前,把当前服务的token添加到目标服务的请求头里 */ @Component public class TokenFeignClientInterceptor implements RequestInterceptor { /** * token放在请求头 * @param requestTemplate */ @Override public void apply(RequestTemplate requestTemplate) { //1、从header里面获取token RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); if (requestAttributes != null) { HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); String token = request.getHeader("Authorization"); //2、将token传递 if(!StringUtils.isEmpty(token)){ requestTemplate.header("Authorization",token); } } } }

2、配置TokenFeignClientInterceptor

  • A、利用@FeignClient的属性configuration进行配置
@FeignClient(value="priceServer",configuration = TokenFeignClientInterceptor.class) public interface PriceFeignClient { @GetMapping(value = "/prices/{id}") PriceInfo getPrice(@PathVariable("id") Integer id); }
  • B、使用application.yml进行全局属性配置
feign: client: config: default: requestInterceptors: - com.yibo.orderapi.feignclient.TokenFeignClientInterceptor

RestTemplate传递Token

1、exchange()

/** * 此方法为演示restTemplate传递token * @param id * @param request * @return */ @GetMapping("/tokenRelay/{id}") public ResponseEntity<UserDTO> tokenRelay(@PathVariable("id") Integer id, HttpServletRequest request){ String token = request.getHeader("Authorization"); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add("Authorization",token); return restTemplate .exchange("http://user-center/users/{id}", HttpMethod.GET, new HttpEntity<>(httpHeaders), UserDTO.class, id); }

2、ClientHttpRequestInterceptor

/** * @Description: 通过此拦截器给RestTemplate请求添加Header传递Token */ public class TestRestTemplateTokenRelayInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { //1、从header里面获取token RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); String token = request.getHeader("Authorization"); HttpHeaders headers = httpRequest.getHeaders(); headers.add("Authorization",token); //保证RestTemplate请求继续执行 return clientHttpRequestExecution.execute(httpRequest,bytes); } } @Bean @LoadBalanced @SentinelRestTemplate //RestTemplate整合Sentinel public RestTemplate restTemplate(){ RestTemplate restTemplate = new RestTemplate(); //通过给RestTemplate添加拦截器,达到通过RestTemplate传递Header restTemplate.setInterceptors(Collections.singletonList(new TestRestTemplateTokenRelayInterceptor())); return restTemplate; }

3、使用OAuth2RestTemplate

//此参数SpringBoot已经声明好了只需要注入即可用 @Autowired private OAuth2ProtectedResourceDetails resource; //此参数SpringBoot已经声明好了只需要注入即可用 @Autowired private OAuth2ClientContext context; //OAuth2RestTemplate在发送请求的时候,在Http请求头里面会自动放入收到的token @Bean @LoadBalanced public OAuth2RestTemplate oAuth2RestTemplate(){ return new OAuth2RestTemplate(resource,context); }
RestTemplate整合HttpClient

引入maven依赖:

<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.12</version> </dependency>
SpringBoot框架RestTemplate+Httpclient的代码配置:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @Configuration public class HttpClientRestConfig { @Bean public ClientHttpRequestFactory clientHttpRequestFactory() { HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(); clientHttpRequestFactory.setHttpClient(HttpsClientPoolThread.builder().createSSLClientDefault()); //这里是使用了自定义的一个HttpsClientPoolThread线程池单例 以后有机会会单独写文章展示其配置内容, 大家可以先使用默认的HttpClients.createDefault()进行配置,或自定义线程池; clientHttpRequestFactory.setConnectTimeout(10000); clientHttpRequestFactory.setReadTimeout(10000); clientHttpRequestFactory.setConnectionRequestTimeout(200); return clientHttpRequestFactory; } @Bean public RestTemplate restTemplate() { return new RestTemplate(clientHttpRequestFactory()); } }

在这里 要说明下:

  • post put patch等请求 参数必须使用MultiValueMap进行接收和传递,否则 参数会为空!
  • get请求,如果需要使用Map传递参数,那么该Map一定不能是MultiValueMap! 否则, 传递的参数会附带上’[]’!
Spring框架的RestTemplate+Httpclient的代码配置:
@Configuration public class RestTemplateConfig { private Logger log = Logger.getLogger(this.getClass()); @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setRequestFactory(clientHttpRequestFactory()); restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); return restTemplate; } @Bean public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() { try { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { return true; } }).build(); httpClientBuilder.setSSLContext(sslContext); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslConnectionSocketFactory).build();// 注册http和https请求 PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);// 开始设置连接池 poolingHttpClientConnectionManager.setMaxTotal(200); // 最大连接数200 poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20); // 同路由并发数20 httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(2, true));// 重试次数 HttpClient httpClient = httpClientBuilder.build(); HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);// httpClient连接配置 clientHttpRequestFactory.setConnectTimeout(20000);// 连接超时 clientHttpRequestFactory.setReadTimeout(20000);// 数据读取超时时间 clientHttpRequestFactory.setConnectionRequestTimeout(200);// 连接不够用的等待时间 return clientHttpRequestFactory; } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { log.error(e.getMessage()); } return null; } }

拓展:

OkHttp设计原理

深入理解Feign之源码解析

Feign常见问题总结

如何使用Feign构造多参数的请求

参考:

spring cloud feign使用okhttp3

https://www.cnblogs.com/zhangbing0615/articles/9238311.html

上一篇:Spring Cloud Alibaba Sentinel组件
下一篇:没有了
网友评论