《Spring Cloud Netflix官方文档》7.声明式 REST 客户端: Feign

原文链接

Feign 是一个声明式的web服务客户端。它使得编写web服务客户端更简单,创建一个接口并加上注解就能使用Feign了,它还支持JAX-RS类型的注解,可插入式的编码和解码,Spring cloud 为他加入了spring mvc的注解支持,以及在spring web开发过程中默认使用同样的 HttpMessageConverters 。Spring Cloud整合了Ribbon和Eureka为使用feign的过程中提供了一个负载均衡的http客户端。

7.1 开始使用 Feign

在你的项目中使用Feign可以使用 group为 org.springframework.cloud,artifact id 为spring-cloud-starter-feign的 starter 来引入依赖。通过查看 Spring Cloud Project page 来获取更多关于当前版本Spring cloud的信息。

Spring boot项目示例:

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaClient
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

StoreClient.java

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

@FeignClient注解中的stores字符串是一个任意的客户端(微服务)名字,它是用来创建一个Ribbon的负载均衡客户端( 关于Ribbon的支持),你同样可以指定一个url属性(绝对值或者主机名)。这个bean在应用上下文中的名字为接口的全限定名,你也可以使用 @FeignClient 注解中的 qualifier 属性给bean指定一个别名

上面的 Ribbon 客户端(Feign底层使用Ribbon)将会去找寻 “stores” 微服务的物理地址,如果你的应用是一个Eureka的客户端Feign会去Eureka的注册中心去找,如果你不想使用Eureka,你可以在项目外部的配置中简单的配置一个服务列表(看上一节的使用样例)

7.2 重写Feign配置

Spring Cloud对Feign的支持最核心的概念就是客户端的命名,每个feign客户端其实就是Feign全体组件中的一部分,在需要时,一起完成调用远程微服务的工作。应用的开发者使用@FiengClient注解给这个总体部分命名,Spring cloud 通过 FeignClientsConfiguration 为每一个已命名客户端创建一个应用上下文。这里面包含 一个 feign.Decoder, 一个 feign.Encoder 和一个 feign.Contract.

在 Spring Cloud 中,你可以通过 @FeignClient 注解声明额外的配置(比 FeignClientsConfiguration 级别高)去控制feign客户端,例如:


@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}

在上面这个示例中,feign客户端在FooConfiguration中的配置将会覆盖FeignClientsConfiguration中的配置。

注意:

  • FooConfiguration不需要使用@Configuration注解。如果加上了,需要将它从@ComponentScan注解中排除否则它将会作为默认的 feign.Decoder, feign.Encoder, feign.Contract 等等组件 配置来源,如果加上了@Configuration注解,你可以将它放在一个分离的,非重叠性的 @ComponentScan 注解或者@SpringBootApplication 注解扫描包中,或者在@ComponentScan中显示的排除掉
  • serviceId属性被删除了,使用name属性>
  • 以前使用url属性的时候,name属性是不需要的,现在需要了

也可以在nameurl属性中使用占位符


@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}

Spring Cloud Netfix 默认给 feign 提供下列的beans(格式“Bean类型 Bean名字: 类名”)
Decoder feignDecoder: ResponseEntityDecoder (包装了SpringDecoder)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contrac feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: 如果开启了Ribbon使用LoadBalancerFeignClient, 否则使用默认的 feign Client.

你也可以通过设置 feign.okhttp.enabled 或者 feign.httpclient.enabledtrue 来启用 OkHttpClient 或者 ApacheHttpClient 用以替代默认的 HttpURLConnection. 当然,你同时也得将对应的jar放在classpath里面.

Spring Cloud Netfix 默认没有给feign装配下列的beans,但是仍然可以从应用上下文中找到这些类型的bean去创建feign 客户端:

Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection
* SetterFactory

你可以在配置类里面创建上述类型的bean , 用@FeignClient注解里面的configuration属性指向配置类(像上面的 FooConfiguration 类):

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

这个配置类用 feign.Contract.Default 替换了Spring cloud netflix 默认的 SpringMvcContract,并且增加了一个请求拦截器。
可以用同样的方法在 @EnableFeignClients 注解的 defaultConfiguration 属性上面指定一个默认的feign配置,不同的是,这个配置将应用在所有的feign客户端上面

注意: 如果你需要在你的 RequestInterceptor 中使用 ThreadLocal 去绑定变量,你同时还需要设置对应的hystrix线程隔离策略为“SEMAPHORE”或取消HystrixFeign中的使用.

application.yml:

# To disable Hystrix in Feign
feign:
  hystrix:
    enabled: false

# To set thread isolation to SEMAPHORE
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE

7.3 手动创建 Feign 客户端

有时候你可能需要自定义你的Feign客户端,那么你就可以使用Feign Builder API,下面是个使用API的例子,分别创建了两个 Feign 客户端并配置了单独的请求拦截器。

@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(
            Decoder decoder, Encoder encoder, Client client) {
        this.fooClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
                .target(FooClient.class, "http://PROD-SVC");
        this.adminClient = Feign.builder().client(client)
                .encoder(encoder)
                .decoder(decoder)
                .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
                .target(FooClient.class, "http://PROD-SVC");
    }
}

注意:

  • FeignClientsConfiguration 是 spring cloud netflix 默认提供的配置
  • PROD-SVC 是待请求的服务的名字

7.4 Feign Hystix 支持

如果 Hystrix 类在项目的 classpath 里面 并且 feign.hystrix.enabled=true, Feign将用一个熔断器(circuit breaker)包装所有的方法,返回一个可用的 com.netflix.hystrix.HystrixCommand 类,它可以使用反应性的模式( 通过queue方法 可以调用 toObservable 方法或 observe方法或 asynchronous 使用)

在每个feign客户端里创建scope 为”prototype”类型的 Feign.Builder bean可以取消Hystrix的支持,例如:

@Configuration
public class FooConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}

注意:

  • 在Spring Cloud Dalston版本之前,只要Feign类存在你项目的classpath里面,Feign默认会给所有的方法都加上熔断器(circuit breaker),这个默认的行为在Spring Cloud Dalston版本中变为可选择的方法

7.5 Feign Hystrix 的 Fallbacks

Hystrix supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given @FeignClient set the fallback attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.

Hystrix 支持回调函数的概念: 当执行方法出错或者断点为开启状态时一段默认路径的代码将会被执行,可以通过在@FeignClient注解上设置fallback属性到对应的实现类上来开启回调功能,需要声明这个实现类为spring的bean.

@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

static class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

如果你想获取造成回调方法的原因,你可以使用@FeignClient注解的fallbackFactory属性:

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
    @Override
    public HystrixClient create(Throwable cause) {
        return new HystrixClientWithFallBackFactory() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}

注意:

  • 有一个限制存在于Feign fallbacks的实现类和 Hystrix fallbacks的工作原理。Fallbacks目前不支持返回 com.netflix.hystrix.HystrixCommandrx.Observable 类的方法

7.6 Feign 和 @Primary 注解

当使用Feign 的 Hystrix回调方法时,有许多相同类型的beans存在于应用上下文中,这会导致@Autowired注解由于找不到具体的bean或主要的bean导致不能注入,为了解决这个问题,Spring Cloud Netflix 标记所有Feign实例bean为@Priamry类型,所以spring框架可以正确注入bean,有些情况下这并不需要,可以通过在@FeignClient注解里设置primary属性为off来关闭它

@FeignClient(name = "hello", primary = false)
public interface HelloClient {
    // methods here
}

7.7 Feign 的继承支持

Feign supports boilerplate apis via single-inheritance interfaces. This allows grouping common operations into convenient base interfaces.
Feign 通过单独继承接口可以支持模板apis,这可以使得将共同的操作方法都放在基础接口里面:

UserService.java.

public interface UserService {

    @RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
    User getUser(@PathVariable("id") long id);
}

UserResource.java.

@RestController
public class UserResource implements UserService {

}

UserClient.java.

package project.user;

@FeignClient("users")
public interface UserClient extends UserService {

}

注意:

  • 通常来说不建议在服务端和客户端共享一个接口,它虽然是轻耦合的,但在上述例子中 Spring MVC 并不会正常工作(方法参数映射没有被继承)

7.8 Feign 请求/响应体 压缩

你可以通过开启以下的属性开启对Feign请求的请求体或响应体进行GZIP压缩功能:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

Feign 请求的压缩设置可以像设置你的web server一样进行设置:

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

这些属性可以允许你可选的设置压缩文件类型和最小的请求长度

7.9 Feign 日志

A logger is created for each Feign client created. By default the name of the logger is the full class name of the interface used to create the Feign client. Feign logging only responds to the DEBUG level.

每创建一个Feign客户端的时候也会创建一个logger,默认logger的名字为接口的全类名,Feign 仅记录DEBUG 级别的日志:

application.yml.

logging.level.project.user.UserClient: DEBUG

这个 Logger.Level 对象可以在每个客户端中进行配置,不同的等级对应不同的日志量:

  • NONE, 不记录(默认)
  • BASIC, 仅记录请求的方法和地址以及响应的状态码和执行时间
  • HEADERS, 单独的记录请求和响应头的信息
  • FULL, 记录请求和响应的请求头,请求体和元数据

下面演示设置Logger.LevelFULL 类型:

@Configuration
public class FooConfiguration {
    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}
  • Trackback 关闭
  • 评论 (1)
    • mushishi
    • 2018/04/03 7:52下午

    @FeignClient(name = “${feign.name}”, url = “${feign.url}”)

    这个url通过 spring cloud config 获取, 但是不能动态刷新; 又遇到过么 加上@RefreshScope 也没有用

return top