openfeign,通过定义接口和使用注解的方式,实现服务调用,相比直接通过http调用,它更符合面向对象编程,并且具有更好的复用和维护效果。要想使用openfeign,需要引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
并在主启动类,添加@EnableFeignClients注解,开启服务调用的功能:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignConsumer {
public static void main(String[] args) {
SpringApplication.run(FeignConsumer.class,args);
}
}
然后,就可以编写接口进行服务调用了:
@FeignClient(name = "feign-provider")
public interface TestFeignService {
@GetMapping("/provider/{msg}")
String feignProvider(@PathVariable("msg") String msg);
@GetMapping("/provider/timeout")
String testTimeout();
}
feign-provider是服务提供者的服务名,两个接口地址,对应于服务提供者的接口地址:
@RestController
public class ProviderController {
@GetMapping("/provider/{msg}")
public String testFeign(@PathVariable("msg") String msg){
return "feignProvider "+msg;
}
// 测试调用超时
@GetMapping("/provider/timeout")
public String timeout(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "test feign timeout";
}
}
这里,主要是使用了@FeignClient注解,该注解的定义如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
@AliasFor("name")
String value() default "";
/** @deprecated */
@Deprecated
String serviceId() default "";
String contextId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default "";
String url() default "";
boolean decode404() default false;
Class<?>[] configuration() default {};
Class<?> fallback() default void.class;
Class<?> fallbackFactory() default void.class;
String path() default "";
boolean primary() default true;
}
关注以下几个属性:
- name:如果没有配置url属性,在name属性为服务提供方的微服务名;如果配置了url属性,则该值我了解到的是没有任何意义,可随便定义
- url:一般是配置服务提供方的ip:port的形式
- fallback:配置项需要实现接口,服务调用失败后(比如超时、服务不可用等),提供默认的处理
- fallbackFactory:配置项需要实现FallbackFactory<T>接口,和fallback效果类似,
配置一个就可以了,目前没有测试哪个的优先级更高
这几个属性,大家起了项目,动手测试一下,就能理解了,下面主要聊聊使用过程中遇到的一些问题:
(1)fallback不起作用
一开始配置了fallback属性,以为就会起作用:
@FeignClient(name = "feign-provider",fallback = TestFeignServiceFallback.class)
TestFeignServiceFallback 实现:
public class TestFeignServiceFallback implements TestFeignService{
@Override
public String feignProvider(String msg) {
return "feignProvider 默认返回";
}
@Override
public String testTimeout() {
return "testTimeout 默认返回";
}
}
但是,只是这样配置是不够的,还需要将TestFeignServiceFallback类交给Spring管理,并且打开一个开关:
feign:
hystrix:
enabled: true
打开这个开关,才会有熔断的效果,才会调用默认的处理
(2)请求消息的丢失
但是,熔断开关打开后,却会导致feign调用过程的消息丢失,比如,我在网关处的请求头添加了一个认证信息:
@Component
public class MyGlobalFilter implements GlobalFilter,Ordered{
/**
* 网关在请求头添加的信息,如果 请求头 有这个值,表示已经经过网关认证,可以访问微服务
*/
@Value("${auth.gateway.key}")
private String gatewayHeader;
@Value("${auth.gateway.value}")
private String gatewayValue;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// TODO 获取请求的url,如果是白名单则放行
// TODO 获取token,如果token正确则放行
// 认证通过之后,在请求头添加认证标志
request = exchange.getRequest().mutate().header(gatewayHeader,gatewayValue).build();
//把新的 exchange放回到过滤链
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return 0;
}
}
feign调用之间传递请求头信息:
@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor headerInterceptor() {
return template -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (null != attributes) {
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);
}
}
}
};
}
}
在服务调用方可用取到该请求头的认证信息,但是到了服务提供方,却拿到了该配置信息,如果没有打开熔断开关,都可以拿到该请求头的认证信息。
(3)超时控制
由于openfeign集成了ribbon:
因此可以通过配置ribbon,实现超时控制:
ribbon:
ConnectionTimeout: 10000 #连接超时 10 秒
ReadTimeout: 6000 #读取超时 6 秒