文章目录
Feign 简介
Spring Cloud 的 Feign 支持的一个中心概念就是命名客户端。Feign客户端使用@FeignClient
注册组合成组件,按需调用远程服务器。使用 FeignClientsConfiguration
创建一个新的集合作为每个命名客户端的ApplicationContext,包含 feign.Decoder
,feign.Encoder
和 feign.Contract
。
可以使用 Jersey
和 CXF
这些来写一个 Rest 或 SOAP 服务的java客服端,也可以直接使用 Apache HttpClient
来实现。但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。通过自定义的编码解码器以及错误处理,你可以编写任何基于文本的 HTTP API。
Feign 通过注解注入一个模板化请求进行工作。只需在发送之前关闭它,参数就可以被直接的运用到模板中。然而这也限制了 Feign,只支持文本形式的API,它在响应请求等方面极大的简化了系统。同时,它也是十分容易进行单元测试的。
Spring Cloud 应用在启动时,Feign 会扫描标有 @FeignClient
注解的接口生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个 RequetTemplate
对象,该对象封装了 HTTP 请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign 的模板化就体现在这里。
maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
Feign客户端接口(消费者)
使用 @FeignClient
请发起请求:
@FeignClient(value = "user", url = "${addr.url}")
public interface UserClient {
// Feign 独有的注解方式
@RequestLine("GET /user/index")
String index();
@RequestMapping(value = "/get0/{id}", method = RequestMethod.GET)
User findById(@PathVariable("id") Long id);
@RequestMapping(value = "/get1", method = RequestMethod.GET)
User get1(@RequestParam("id") Long id, @RequestParam("name") String name);
@RequestMapping(value = "/get2", method = RequestMethod.GET)
User get2(@RequestParam Map<String, Object> map);
@RequestMapping(value = "/hello2", method=RequestMethod.GET)
User hello2(@RequestHeader("name") String name, @RequestHeader("age") Integer age);
@RequestMapping(value = "/hello3", method=RequestMethod.POST)
String hello3(@RequestBody User user);
}
当前工程中有和 Feign Client 中一样的 Endpoint 时,Feign Client 的类上不能用 @RequestMapping 注解,否则当前工程该 endpoint http 请求且使用 accpet 时会报404。但是如果不包含 Accept header 时,请求是可以的。
不在 Feign Client 上使用 @RequestMapping 注解,无论是否包含Accept,请求都是可以的:
@FeignClient(name = "card", url = "http://localhost:7913",
fallback = CardFeignClientFallback.class,
configuration = FeignClientConfiguration.class)
public interface CardFeignClient {
@RequestMapping(value = "/v1/card/balance", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
Info info();
}
Feign将方法签名中方法参数对象序列化为请求参数放到 HTTP 请求中的过程,是由编码器 Encoder 完成的。同理,将 HTTP 响应数据反序列化为 Java 对象是由解码器 Decoder 完成的。
默认情况下,Feign会将标有 @RequestParam 注解的参数转换成字符串添加到 URL 中,将没有注解的参数通过 Jackson 转换成 json 放到请求体中。注意,如果在 @RequetMapping 中的 method 将请求方式指定为 POST ,那么所有未标注解的参数将会被忽略,例如:
@RequestMapping(value = "/group/{groupId}", method = RequestMethod.GET)
void update(@PathVariable("groupId") Integer groupId,
@RequestParam("groupName") String groupName,
DataObject obj);
此时因为声明的是 GET 请求没有请求体,所以 obj 参数就会被忽略。
在 Spring Cloud 环境下,Feig n的 Encoder 只会用来编码没有添加注解的参数。如果你自定义了Encoder, 那么只有在编码 obj 参数时才会调用你的 Encoder。对于 Decoder, 默认会委托给 SpringMVC 中的 MappingJackson2HttpMessageConverter
类进行解码。只有当状态码不在200 ~ 300之间时ErrorDecoder 才会被调用。ErrorDecoder 的作用是可以根据HTTP响应信息返回一个异常,该异常可以在调用 Feign 接口的地方被捕获到。我们目前就通过 ErrorDecoder 来使 Feign 接口抛出业务异常以供调用者处理。
Feign 在默认情况下使用的是 JDK 原生的 URLConnection 发送 HTTP 请求,没有连接池,但是对每个地址会保持一个长连接,即利用 HTTP 的 persistence connection 。我们可以用 Apache 的 HTTP Client替换 Feign 原始的 http client,从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从 Brixtion.SR5 版本开始支持这种替换,首先在项目中声明 Apache HTTP Client 和 feign-httpclient 依赖:
<!-- 使用Apache HttpClient替换Feign原生httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>${feign-httpclient}</version>
</dependency>
然后在 application.properties 中添加 feign.httpclient.enabled=true
配置文件application.yml
#feign
feign:
hystrix:
enabled: true
httpclient:
enabled: true
addr:
url: http://10.164.13.166:8080/msg-center/v1/sms/send
配置类Configuration
使用了配置 @Configuration 参数,自己定义 Configuration 类来自定义 FeignClientsConfiguration,并且 Configuration 类的类路径不能在启动类 Application 的扫描路径下,否则会覆盖该项目所有的 Feign 接口的默认配置
package com.spring.feigin.config;
import feign.auth.BasicAuthRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EurekaConfiguration {
// 配置只允许使用Feign自己的注解url方式:@RequestLine
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
}
// 配置eureka的登录名和密码
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("zhihao.miao", "123456");
}
}
定义主体启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class,args);
}
}
feign使用Hystrix
添加依赖
<!-- 整合hystrix,其实feign中自带了hystrix,引入该依赖主要是为了使用其中的hystrix-metrics-event-stream,用于dashboard -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
feign接口注解中,增加fallback指定类
@FeignClient (value = "${name}", url = "${addr.url}", fallback = UserFallBack.class)
指定类中处理熔断的后续逻辑
@Slf4j
@Component
public class PosMemberClientFallBack implements PosMemberClient {
@Override
public String addMember(MemberDTO memberDTO) {
log.warn("调用会员服务失败");
return ("调用服务失败,熔断”);
}
}
配置文件
#hystrix
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD
thread:
timeoutInMilliseconds: 30000
threadpool:
default:
coreSize: 500 #缺省为10
修改启动类
在启动类上添加@EnableHystrix 注解
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableHystrix
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
配置的fallback class也必须在FeignClient Configuration中实例化,否则会报
java.lang.IllegalStateException: No fallback instance of type class异常。
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
//实例化fallback
@Bean
public HystrixClientFallback fb(){
return new HystrixClientFallback();
}
}
在 Spring Cloud 中,Feign 和Ribbon 在整合了 Hystrix 后,可能会出现首次调用失败的问题,要如何解决该问题呢?
Hystrix 默认的超时时间是1秒,如果超过这个时间尚未响应,将会进入 fallback 代码。
而首次请求往往会比较慢(因为 Spring 的懒加载机制,要实例化一些类),这个响应时间可能就大于1秒了。
解决方案 :
方法一
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000
该配置是让Hystrix的超时时间改为5秒
方法二
hystrix.command.default.execution.timeout.enabled: false
该配置,用于禁用Hystrix的超时时间
方法三
feign.hystrix.enabled: fals
该配置,用于索性禁用feign的hystrix。该做法除非一些特殊场景,不推荐使用。
Feign的扩展配置
#Hystrix支持,如果为true,hystrix库必须在classpath中
feign:
hystrix:
enabled: false
#请求和响应GZIP压缩支持
compression:
request:
enabled: true
response:
enabled: true
#支持压缩的mime types
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
# 日志支持
logging:
level:
project:
user:
UserClient: DEBUG