Spring Cloud的Feign支持的一个中心概念就是命名客户端.Feign客户端使用@FeignClient注册组合成组件,按需调用远程服务器.
Spring Cloud使用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的模板化就体现在这里。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
dependencies>
@FeignClient(value = “user”, url = “${addr.url}”)
public interface UserClient {
//feign独有的注解方式
@RequestLine("GET /user/index")
String index();
@RequestMapping(value = "/get0/{id}", method = RequestMethod.GET)
public User findById(@PathVariable("id") Long id);
@RequestMapping(value = "/get1", method = RequestMethod.GET)
public User get1(@RequestParam("id") Long id, @RequestParam("name") String name);
@RequestMapping(value = "/get2", method = RequestMethod.GET)
public User get2(@RequestParam Map 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环境下,Feign的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依赖:
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-httpclientartifactId>
<version>${feign-httpclient}version>
dependency>
然后在application.properties中添加
feign.httpclient.enabled=true
#feign
feign:
hystrix:
enabled: true
httpclient:
enabled: true
addr:
url: http://10.164.13.166:8080/msg-center/v1/sms/send
使用了配置@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);
}
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
dependency>
@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。该做法除非一些特殊场景,不推荐使用。
#Hystrix支持,如果为true,hystrix库必须在classpath中
feign.hystrix.enabled=false
#请求和响应GZIP压缩支持
feign.compression.request.enabled=true
feign.compression.response.enabled=true
#支持压缩的mime types
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
# 日志支持
logging.level.project.user.UserClient: DEBUG
参考资料
springcloud 官网
feign的github地址