Feign

来自ling
跳转至: 导航搜索

简介

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