Spring Cloud OpenFeign
- 概述
Feign是一个声明式web服务客户端。可以像写接口一样定义http客户端。Feign还支持可插拔的编码器和解码器。Spring Cloud增加了对Spring MVC注释和使用Spring Web中默认使用的HttpMessageConverter的支持。Spring Cloud集成了Ribbon和Eureka,以及Spring Cloud LoadBalancer,在使用Feign时提供负载均衡的http客户端。
1.1 如何使用feign
Application
@SpringBootApplication
@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.GET, value = "/stores")Page<Store> getStores(Pageable pageable);@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")Store update(@PathVariable("storeId") Long storeId, Store store);
}
在@FeignClient注释中,字符串值(上面的“stores”)是任意的客户端名称,用于创建Ribbon或Spring Cloud LoadBalancer。还可以使用URL属性(绝对值或仅主机名)指定URL。应用程序上下文中bean的名称是接口的完全限定名。要指定自己的别名值,可以使用@FeignClient注释的唯一限定值。上面的负载均衡客户端将希望发现“存储”服务的物理地址。如果您的应用程序是Eureka客户端,那么它将解析Eureka服务注册表中的服务。如果不想使用Eureka,可以使用SimpleDiscoveryClient在外部配置中配置服务器列表。
为了保持向后兼容性,将用作默认的负载均衡器实现。但是,Spring Cloud Netflix Ribbon现在处于维护,所以我们建议改用Spring Cloud LoadBalancer。为此,请设置spring.cloud.loadbalancer.ribbon为false
1.2 覆盖 feign 默认配置
spring cloud对外支持的一个核心概念是可命名客户端。每个feign client都是一个组件集合的一部分,这些组件一起工作以按需联系远程服务器,并且该集成有一个名称,您可以使用@FeignClient注释将其命名为应用程序开发人员。spring cloud使用FeignClientsConfiguration为每个命名客户端创建一个新的集成,作为ApplicationContext。它包含(除其他外)一个feign.Decoder,一个feign.Encoder,和feign.Contract. 可以通过使用@FeignClient注释的contextId属性覆盖该集合的名称。
spring cloud允许您通过使用@FeignClient声明附加配置(在FeignClientsConfiguration之上)来完全控制feign client。
例子:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {//..
}
在这种情况下,客户端由FeignClientsConfiguration中已经存在的组件和FooConfiguration中的任何组件组成(后者将覆盖前者)。
FooConfiguration不需要用@Configuration注释。但是,如果是,那么要注意将它从任何可能包含此配置的@ComponentScan中排除,因为它将成为feign的默认源。feign.Decoder, feign.Encoder, feign.Contract等,如有明确规定。这可以通过将其放在来自任何@ComponentScan或@SpringBootApplication的独立的、不重叠的包中来避免,或者可以在@ComponentScan中显式地排除它。
注意:
1) serviceId属性现在已被弃用,取而代之的是name属性。
2) 除了更改ApplicationContext集成的名称之外,使用@FeignClient注释的contextId,它将覆盖客户端名称的别名,并且它将被用作为该客户端创建的配置bean的名称的一部分。
3)以前,使用url属性不需要name属性。现在需要使用name属性。
在name和url属性中支持占位符。
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {//..
}
Spring Cloud OpenFeign默认为feign提供以下bean (bean类型 bean名称:类名)
Decoder feignDecoder: ResponseEntityDecoder (包含一个 SpringDecoder)
Encoder feignEncoder: SpringEncoder
Logger feignLogger: Slf4jLogger
Contract feignContract: SpringMvcContract
Feign.Builder feignBuilder: HystrixFeign.Builder
Client feignClient: 如果Ribbon在类路径中并且被启用,那么它就是LoadBalancerFeignClient,
否则,如果Spring Cloud LoadBalancer在类路径中,则使用FeignBlockingLoadBalancerClient。
如果它们都不在类路径中,则使用默认的feign client。
spring-cloud-starter-openfeign同时支持spring-cloud-starter-netflix-ribbon和spring-cloud-starter- load-balancer。但是,由于它们是可选的依赖项,您需要确保您想要使用的依赖项已添加到您的项目中。
OkHttpClient和ApacheHttpClient feign clients可以通过设置feign.okhttpfeign.httpclient或feign.httpclient.enabled为true,并将它们放在类路径上。你可以通过提供一个自定义bean来自定义feign client,当使用Apache client时提供一个org.apache.http.impl.client.CloseableHttpClient的bean,使用OK client时提供一个okhttp3.OkHttpClient的bean.
Spring Cloud OpenFeign默认不提供以下bean给feign,但是仍然从应用上下文中查找这些类型的bean来创建feign客户端:
1)Logger.Level
2)Retryer
3)ErrorDecoder
4)Request.Options
5)Collection
6)SetterFactory
7)QueryMapEncoder
默认情况下创建一个类型为Retryer的Retryer.NEVER_RETRY bean,这将禁用重试。请注意,这种重试行为与Feign默认不同,在Feign默认中,它将自动重试IOExceptions,并将它们视为暂时的网络相关异常,以及从ErrorDecoder抛出的任何RetryableException。
创建其中一种类型的bean并将其放置在@FeignClient配置中(如上面的FooConfiguration)允许您覆盖所描述的每个bean。
例子: 这将用feign.Contract.Default替换SpringMvcContract。并将RequestInterceptor添加到RequestInterceptor的集合中。
@Configuration
public class FooConfiguration {@Beanpublic Contract feignContract() {return new feign.Contract.Default();}@Beanpublic BasicAuthRequestInterceptor basicAuthRequestInterceptor() {return new BasicAuthRequestInterceptor("user", "password");}
}
@FeignClient也可以使用配置属性进行配置
feign:client:config:feignName:connectTimeout: 5000readTimeout: 5000loggerLevel: fullerrorDecoder: com.example.SimpleErrorDecoderretryer: com.example.SimpleRetryerrequestInterceptors:- com.example.FooRequestInterceptor- com.example.BarRequestInterceptordecode404: falseencoder: com.example.SimpleEncoderdecoder: com.example.SimpleDecodercontract: com.example.SimpleContract
默认配置可以在@EnableFeignClients属性defaultConfiguration中以类似的方式指定,如上所述。不同之处在于此配置将应用于所有feign client。
如果您喜欢使用配置属性来配置所有的@FeignClient,您可以使用默认的feign名称创建配置属性。
feign:client:config:default:connectTimeout: 5000readTimeout: 5000loggerLevel: basic
如果我们同时创建@Configuration bean和配置属性,配置属性会被加载。它将覆盖@Configuration值。但是如果您想将优先级更改为@Configuration,您可以将feign.client.default-to-properties更改为false。
如果我们想创建多个具有相同名称或url的feign client,以便它们指向相同的服务器,但每个客户机都有不同的自定义配置,那么我们必须使用@FeignClient的上下文tid属性,以避免这些配置bean的名称冲突。
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {//..
}@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {//..
}
还可以配置FeignClient不从父上下文继承bean。你可以通过重写FeignClientConfigurer bean中的inheritParentConfiguration()来实现:
@Configuration
public class CustomConfiguration{@Bean
public FeignClientConfigurer feignClientConfigurer() {return new FeignClientConfigurer() {@Overridepublic boolean inheritParentConfiguration() {return false;}};}
}
1.3 手动创建 feign client
在某些情况下,可能有必要定制您的feign客户端,而使用上述方法是不可能的。在这种情况下,您可以使用Feign Builder API创建feign client。下面是一个示例,它创建了两个具有相同接口的feign客户端,但为每个客户端配置了一个单独的请求拦截器。
// FeignClientsConfiguration是Spring Cloud OpenFeign的默认配置
// PROD-SVC是feign调用的服务名称
// Feign Contract提供了SpringMVC注解的支持
@Import(FeignClientsConfiguration.class)
class FooController {private FooClient fooClient;private FooClient adminClient;@Autowiredpublic FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) {this.fooClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("user", "user")).target(FooClient.class, "https://PROD-SVC");this.adminClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")).target(FooClient.class, "https://PROD-SVC");}
}
1.4 feign继承支持
public interface UserService {@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")User getUser(@PathVariable("id") long id);
}@RestController
public class UserResource implements UserService {
}package project.user;@FeignClient("users")
public interface UserClient extends UserService {
}
1.5 feign请求/响应压缩
feign请求或响应开启 GZIP 压缩
feign.compression.request.enabled=true
feign.compression.response.enabled=true
压缩配置:(压缩类型和最小压缩阈值)
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
对于http客户端除了OkHttpClient,默认的gzip解码器可以解码UTF-8编码gzip响应:
feign.compression.response.enabled=true
feign.compression.response.useGzipDecoder=true
1.6 feign日志
为创建的每个虚拟客户机创建一个日志程序。默认情况下,日志记录器的名称是用于创建佯装客户机的接口的完整类名。佯装日志只对调试级别作出响应。
logging.level.project.user.UserClient: DEBUG
日志记录器。级别对象,你可以配置每个客户端,告诉假装多少日志。的选择是:
NONE 没有日志记录(默认)。
BASIC,只记录请求方法和URL以及响应状态代码和执行时间。
HEADERS,记录基本信息以及请求和响应标头。
FULL,记录请求和响应的头、正文和元数据。
例子:
@Configuration
public class FooConfiguration {@BeanLogger.Level feignLoggerLevel() {return Logger.Level.FULL;}
}
1.7 feign @QueryMap支持
OpenFeign @QueryMap注释支持将pojo用作GET参数映射。不幸的是,默认的OpenFeign QueryMap注释与Spring不兼容,因为它缺少value属性。
Spring Cloud OpenFeign提供了一个等效的@SpringQueryMap注释,它用于将POJO或Map参数注释为查询参数映射。
例如,Params类定义了参数param1和param2:
// Params.java
public class Params {private String param1;private String param2;// [Getters and setters omitted for brevity]
}
通过@SpringQueryMap注释使用Params类:
@FeignClient("demo")
public interface DemoTemplate {@GetMapping(path = "/demo")String demoEndpoint(@SpringQueryMap Params params);
}