摘要
微服务架构中,若各个微服务通过RestTemplate相互调用,较为不便,SpringCloud整合了Feign,服务相互调用的Web客户端。本文介绍如何使用Feign。
参考文档:http://cloud.spring.io/spring-cloud-static/Finchley.RELEASE/single/spring-cloud.html#_spring_cloud_openfeign
略:22.4、22.7-
正文
1.1 如何加入Feign
- 加入Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
为被调用服务创建FeignClient
此处以user-service服务的findById接口为例
@FeignClient(name="user-service")
public interface UserFeignClient{
@RequestMapping(value="/{id}", method = RequestMethod.GET)
public User finaById(@PathVariable("id") Long id);
}
- 启动加入
@EnableFeignClients
注解声明开启Feign
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
可以通过url属性指定请求,如:@FeignClient(name="xxx",url="http://ip:port/")
1.2 自定义Feign配置
1.2.1 配置类实现自定义配置
SpringCloud默认为每个FeignClient创建一个FeignClientsConfiguration
,可以通过@FeignClient(name="user-service",configuration = UserServiceFeignClientConfig.class)
的方式自定义FeignClient配置,这种情况下配置由FeignClientsConfiguration
和UserServiceFeignClientConfig
共同组成(后者优先级较高)。
注:UserServiceFeignClientConfig
可以不标注@Configuration
,若标注该注解,应该避免被Spring容器扫描到,否则会成为全局Feign配置类。
1.2.2 配置文件实现自定义配置
feign:
client:
config:
# 指定服务的Feign配置
user-service:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
# 全局Feign配置
default:
connectTimeout:5000
readTimeout:5000
loggerLevel:basic
默认使用ribbon客户端,若配置feign.okhttp.enabled
和feign.httpclient.enabled
为true
1.2.3 配置类和配置文件的优先级
当配置类和配置文件同时存在,配置文件将优先生效,若希望配置类优先生效,在配置类中加入feign.client.default-to-properties=false
即可。
1.2.4 使用Hystrix需要注意
若需要在RequestInterceptor使用ThreadLocal绑定变量,需要设置Hystrix隔离级别为SEMAPHORE或者关闭Hystrix。
# 关闭
feign:
hystrix:
enabled: false
# 设置隔离级别
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
1.3 手动创建FeignClient
通过下面的例子讲解如何手动创建FeignClient,首先要去掉之前项目中的@FeignClient和@EnableFeignClients。
@Import(FeignClientsConfiguration.class)
class UserController {
private UserFeignClient userFeignClient;
@Autowired
public UserController(Decoder decoder, Encoder encoder, Client client, Contract contract) {
this.userFeignClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "http://USER-SERVICE");
}
}
1.4 加入Hystrix支持
通过下面的例子介绍如何为Feign加入Hystrix
@FeignClient(name = "user-service", fallback = UserServiceClientFallback.class)
public interface UserServiceClient {
@RequestMapping(method = RequestMethod.GET, value = "/{id}")
User findById(@PathVariable("id") Long id);
}
public class UserServiceClientFallback implements UserServiceClient {
@Override
public User findById(Long id) {
return new User();
}
}
若希望得到异常信息,通过下面的例子介绍Hystrix如何获取Feign调用异常
@FeignClient(name = "user-service", fallbackFactory = UserServiceClientFallback.class)
public interface UserServiceClient {
@RequestMapping(method = RequestMethod.GET, value = "/{id}")
User findById(@PathVariable("id") Long id);
}
@Component
public class UserServiceClientFallback implements FallbackFactory<UserServiceClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClient() {
@Override
public User findById(Long id) {
return new User();
}
};
}
}
1.5 @FeignClient属性详解
public @interface FeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
boolean decode404() default false;
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* 指定该FeignClient的配置
*/
Class<?>[] configuration() default {};
/**
* 指定回调函数类
*/
Class<?> fallback() default void.class;
/**
* 指定回调函数工厂类
*/
Class<?> fallbackFactory() default void.class;
/**
* 为所有当请求地址加入统一前缀,若目标微服务设置了context-path属性,配置该属性可以简化代码。
*/
String path() default "";
/**
* 是否设置FeignClient为primary bean,效果类似@Primary注解。
* 整合Hystrix时,容器中可能会有多个该类型bean,该属性主要用于这种情况。
*/
boolean primary() default true;
}
常见问题
1 使用FeignClient出现404问题
- 检查目标服务是否配置
context-path
属性,若配置过,则设置@FeignClient的path属性与目标服务context-path
一致。
2 RequestParam.value() was empty on parameter 0
- 检查异常的FeignClient类中,所有的@RequestParam是否指定了value属性,如:@RequestParam(value = “param”)
3 Required String parameter ‘xxx’ is not present
- 同问题2
4 not annotated with HTTP method type (ex. GET, POST)
- FeignClient不识别@GetMapping、@PostMapping等注解,应该通过这种方式指定请求类型:@RequestMapping(method = RequestMethod.GET)
- 使用FeignClient继承方式时,有时即使接口方法使用@RequestMapping(method = RequestMethod.GET)方式声明请求类型,也会出现该异常,可以在重写的方法上再标记一次@RequestMapping(method = RequestMethod.GET,value=”xxx”)
未完待续