概述
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属性
- B、新建PriceCenterFeignConfiguration类,用于配置Feign日志级别
- C、添加application.yml配置
即Feign的日志级别是建立在上面配置的debug基础之上的,如果上面改为info,那么则不会输出任何日志
2、配置属性方式 首先去掉@FeignClient注解上的configuration属性
#细粒度的配置Feign的日志级别,这种是通过配置文件的方式 feign: client: config: #想要调用的微服务的名称 price-center: loggerLevel: full全局自定义Feign的日志级别配置
Java代码方式
- 方式一、让父子上下文ComponentScan重叠(强烈不建议使用)
- 方式二、@EnableFeignClients(defaultConfiguration = xxx.class)(唯一正确方式)
配置属性方式 首先去掉@EnableFeignClients中的defaultConfiguration属性
feign: client: config: #全局配置 default: loggerLevel: fullFeign支持的配置项
代码方式支持的配置项
配置项 作用 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配置优先级:全局代码<全局属性<细粒度代码<细粒度属性
- 尽量使用属性配置,属性方式实现不了的情况下在考虑使用代码方式配置
- 在同一微服务内尽量保持单一性,比如统一使用属性配置,不要两种方式混用,增加定位代码的复杂性
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的性能优化
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依赖
- 2、添加配置
okhttp替换掉Feign原生的HttpURLConnection
- 1、引入maven依赖
- 2、添加配置
- 3、添加java代码配置
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进行配置
- B、使用application.yml进行全局属性配置
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