Feign
目录
简介
Feign是一种声明式的web service client。它让web service变得更容易。使用Feign你只需要创建一个接口并且写上注解。它提供插拔式的Feign注解和JAX-RS注解支持。Feign同样提供插拔式的编码解码器。Spring Cloud添加了Spring MVC的注解支持,在Spring web中默认使用相同的HttpMessageConverters。spring cloud集成了Ribbon 和 Eureka去提供负载均衡。
github,推荐参考 Spring Cloud Netflix中文文档 使用Spring Cloud Feign作为HTTP客户端调用远程HTTP服务 Feign正确的使用姿势和性能优化注意事项 @ConditionalOnMissingBean ConditionalOnClass ConditionalOnProperty 更多参考FeignClientsConfiguration
FeignClient注解
FeignClient注解被@Target(ElementType.TYPE)修饰,表示FeignClient注解的作用目标在接口上
@FeignClient(name = "github-client", url = "https://api.github.com", configuration = GitHubExampleConfig.class)
public interface GitHubClient {
@RequestMapping(value = "/search/repositories", method = RequestMethod.GET)
String searchRepo(@RequestParam("q") String queryStr);
}
声明接口之后,在代码中通过@Resource注入之后即可使用。@FeignClient标签的常用属性如下:
- name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀
hystrix dashboard
- 引用spring-cloud-starter-hystrix
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
启动类添加EnableCircuitBreaker http://IP:PORT/hystrix.stream,就会展示出一大堆的API监控数据了。
@EnableCircuitBreaker
Feign的Encoder、Decoder和ErrorDecoder
https://springcloud.cc/spring-cloud-netflix.html
Spring Cloud Netflix默认为feign(BeanType beanName:ClassName)提供以下bean:
Decoder feignDecoder:ResponseEntityDecoder(其中包含SpringDecoder)
Encoder feignEncoder:SpringEncoder
Logger feignLogger:Slf4jLogger
Contract feignContract:SpringMvcContract
Feign.Builder feignBuilder:HystrixFeign.Builder
Client feignClient:如果Ribbon被启用,它是一个LoadBalancerFeignClient,否则使用默认的feign客户端。
可以通过将feign.okhttp.enabled或feign.httpclient.enabled设置为true,并将它们放在类路径上来使用OkHttpClient和ApacheHttpClient feign客户端。
Spring Cloud Netflix 默认情况下不提供以下bean,但是仍然从应用程序上下文中查找这些类型的bean以创建假客户端:
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
创建其中一个类型的bean并将其放置在@FeignClient配置(例如上面的FooConfiguration)中)允许您覆盖所描述的每个bean。例:
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
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接口抛出业务异常以供调用者处理。
https://blog.csdn.net/synsdeng/article/details/78380552
FeignClientsConfiguration初始化配置中:默认Encoder类型为SpringEncoder,功能为调用Spring的HttpMessageConverters处理参数。默认Decoder类型为ResponseEntityDecoder,它包装了SpringDecoder,做的事情也就是调用Spring的HttpMessageConverters处理接口返回值。默认Contract类型为SpringMvcContract,功能为解析SpringMvc的如RequestMapping、RequestParam、PathVariable等注解。默认日志类为DefaultFeignLoggerFactory,使用的是Slf4j,会为每个FeignClient创建一个Logger,打印各自的请求日志。
FeignRibbonClientAutoConfiguration中最重的就是初始化Client实例,默认Client的类型为LoadBalancerFeignClient,它的缺省通信方式是feign.Client.Default,其实就是HttpURLConnection,也可以修改为使用HttpClient或者OkHttp方式。Targeter默认实现类为HystrixTargeter,接口只有一个方法为target,主要是调用Feign.Builder的target方法。Target中又调用build()方法生成ReflectiveFeign并调用ReflectiveFeign的newInstance生成最终实例
ErrorDecoder
https://www.jianshu.com/p/688674c66125
也就是feign client的处理跟nginx的是不一样的,feign client把非200的以及404(可以配置是否纳入异常)都算成error,都转给errorDecoder去处理了。
要特别注意,对于restful抛出的4xx的错误,也许大部分是业务异常,并不是服务提供方的异常,因此在进行feign client调用的时候,需要进行errorDecoder去处理,适配为HystrixBadRequestException,好避开circuit breaker的统计,否则就容易误判,传几个错误的参数,立马就熔断整个服务了,后果不堪设想。
@Configuration
public class BizExceptionFeignErrorDecoder implements feign.codec.ErrorDecoder{
private static final Logger logger = LoggerFactory.getLogger(BizExceptionFeignErrorDecoder.class);
@Override
public Exception decode(String methodKey, Response response) {
if(response.status() >= 400 && response.status() <= 499){
return new HystrixBadRequestException("xxxxxx");
}
return feign.FeignException.errorStatus(methodKey, response);
}
}
http://blog.didispace.com/rencong-1/
那么我们对于请求异常的解决方案就需要通过 HystrixBadRequestException 来解决了(不会触发熔断机制),根据返回响应创建对应异常并将异常封装进 HystrixBadRequestException,业务系统调用中取出 HystrixBadRequestException 中的自定义异常进行处理,封装异常说明:
public class UserErrorDecoder implements ErrorDecoder{
private Logger logger = LoggerFactory.getLogger(getClass());
public Exception decode(String methodKey, Response response) {
ObjectMapper om = new JiaJianJacksonObjectMapper();
JiaJianResponse resEntity;
Exception exception = null;
try {
resEntity = om.readValue(Util.toString(response.body().asReader()), JiaJianResponse.class);
//为了说明我使用的 WebApplicationException 基类,去掉了封装
exception = new WebApplicationException(javax.ws.rs.core.Response.status(response.status()).entity(resEntity).type(MediaType.APPLICATION_JSON).build());
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
}
// 这里只封装4开头的请求异常
if (400 <= response.status() || response.status() < 500){
exception = new HystrixBadRequestException("request exception wrapper", exception);
}else{
logger.error(exception.getMessage(), exception);
}
return exception;
}
}
为 Feign 配置 ErrorDecoder
@Configuration
public class FeignConfiguration {
@Bean
public ErrorDecoder errorDecoder(){
return new UserErrorDecoder();
}
}
业务系统处理异常说明:
@Override
public UserSigninResEntity signIn(UserSigninReqEntity param) throws Exception {
try {
//省略部分代码
UserSigninResEntity entity = userRemoteCall.signin(secretConfiguration.getKeys().get("user-service"), param);
//省略部分代码
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
//这里进行异常处理
if(ex.getCause() instanceof WebApplicationException){
throw (WebApplicationException) ex.getCause();
}
throw ex;
}
}
HystrixBadRequestException
这个异常主要是用来适配IllegalArgumentException这类异常。HystrixBadRequestException与其他HystrixCommand抛出的异常不同,该异常不会纳入circuit breaker的统计里头,即不会触发熔断。
Feign的HTTP Client
https://blog.csdn.net/neosmith/article/details/52449921
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
注意内容
- 未加工异常默认返回
{"timestamp":1491733807402,"status":500,"error":"Internal Server Error","exception":"com.ling.pl.core.commons.exception.BusinessException","message":"测试","path":"/demo/findByList"}
- 非期望类型默认错误返回
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
- 超时或服务不可用
java.util.concurrent.TimeoutException: null
- 在设置feignclient时,如果调用第三方的接口,则需要设置name属性和url属性,如果调用本地在注册中心注册的服务,需要将value属性设置为service-id,如果需要断路由则需要配置fallback属性。
- 在application配置时如果需要通过turbine进行归纳监控数据,则需要添加@EnableCircuitBreaker注解,当然,如果设置@SpringCloudApplication注解的话,它本身包含了@EnableCircuitBreaker。
- 还有一点就是,如果你添加了swgger,如果版本比较低会导致在使用feign的时候报类空异常,导致无法启动程序,这是由于swgger和feign在低版本的时候有冲突,采用较新版本不会有这个问题。
- 警告: FooConfiguration必须是@Configuration,但是注意不能在@CompinentScan中,否则将被用于每个@FeignClient。如果你使用@ComponentScan(或@ SpringBootApplication),你需要采取一些措施来避免它被列入(比如把它放在一个单独的,非重叠的包,或者指定包在@ComponentScan明确扫描)。
代码备份
HttpMessageConverter ResponseInfoConfig ResposeInfoHttpMessageConverter SynchronousMethodHandlerDecodeAspect SpringConfig FeignClientsConfiguration