简介
Spring Cloud整合了OpenFeign项目,我们可以使用声明式的web服务客户端,简单来说就是创建一个接口和声明它即可,Feign除了提供自身的注解也支持使用JAX-RS注解。
Feign提供了自定义的编码器和解码器,在使用Feign时,我们可以使用Spring Cloud集成的Eureka、CircuitBreaker和LoadBalancer来实现http客户端的负载均衡。
使用
客户端
以下代码来自spring-cloud-openfeign 首先创建一个Spring Boot应用:
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
复制代码
@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);
@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
void delete(@PathVariable Long storeId);
}
复制代码
通过@EnableFeignClients注解来启用Spring Cloud Feign,StoreClient类是一个请求http服务的客户端,在业务代码使用中,我们只需把StoreClient当作普通类注入即可使用,如
@Autowired
private StoreClient storeClient;
复制代码
这样就可以发起一个http请求了,@FeignClient声明了我们请求的服务名为stores,这个注解还定义其他行为,如contentId、fallback等,接下来再讲。
当我们调用storeClient.getStores()时,Feign实质上会生成一个代理类,由代理类来真实发起http请求,@FeignClient和@RequestMapping声明信息,实质是请求http://stores/stores,由Spring Cloud Eureka来完成服务发现,所以这里我们只需使用服务名stores即可,也就是说这个例子得先引入Eureka。当然,如果我们不使用服务发现,还可以直接使用IP请求,只需在@FeignClient声明url属性即可。
这里的服务名和url还可以通过占位符来配置,如@FeignClient(name = "{feign.url}")
客户端的使用,就是这么简单,接下来我们看看服务端的代码
服务端
@FeignClient("stores")
public interface StoreServer {
@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);
@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
void delete(@PathVariable Long storeId);
}
复制代码
服务端的接口声明,跟客户端完全是一样的,但是对服务端而言,除了提供声明外,还需要提供自己的实现,实现由两种方式,一种是提供一个controller,一种是提供StoreServer接口的实现类。
controller:
@RequestMapping
@RestController
public class StoreServerController {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
public List<Store> getStores(){
....
}
@RequestMapping(method = RequestMethod.GET, value = "/stores")
public Page<Store> getStores(Pageable pageable){
....
}
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
public Store update(@PathVariable("storeId") Long storeId, Store store){
....
}
@RequestMapping(method = RequestMethod.DELETE, value = "/stores/{storeId:\\d+}")
public void delete(@PathVariable Long storeId){
....
}
}
复制代码
这跟我们平时提供的controller没什么两样,但有一点需要注意的是controller提供的接口契约必须跟StoreServer一致,不然找不到对应的映射关系。
接口实现类
@Service
public class StoreServerImpl implements StoreServer {
@Override
List<Store> getStores(){
....
}
@Override
Page<Store> getStores(Pageable pageable){
....
}
@Override
Store update(@PathVariable("storeId") Long storeId, Store store){
....
}
@Override
void delete(@PathVariable Long storeId){
....
}
}
复制代码
这种方式也很简单,跟平常的接口实现一模一样,没啥好说的。
核心配置
要想修改Feign配置,只需提供一个自定义的Configuration配置类,然后指定使用即可
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
复制代码
除了使用配置类,还可以使用配置文件,配置文件会优先于配置类,如果两者都定义了同一个属性,那么前者会覆盖后者。
1、timeout处理 connectTimeout:连接超时时间,防止客户端长时间阻塞在服务器连接上 readTimeout:连接建立后,当服务器太久响应时会触发
2、断路器 通过feign.circuitbreaker.enabled=true启动使用,熔断器遵循类名+#+ 方法名这样的匹配规则,即#(),如FooClient#bar()
当然,匹配规则可以修改,通过CircuitBreakerNameResolver
@Configuration
public class FooConfiguration {
@Bean
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
}
}
复制代码
断路器的行为可通过如下配置调整:
feign:
circuitbreaker:
enabled: true
alphanumeric-ids:
enabled: true
resilience4j:
circuitbreaker:
instances:
DemoClientgetDemo:
minimumNumberOfCalls: 69
timelimiter:
instances:
DemoClientgetDemo:
timeoutDuration: 10s
复制代码
resilience4j是一个断路器框架,具体参考resilience4j断路器
3、容错处理
Spring Cloud CircuitBreaker支持Feign使用fallback来实现容错处理,当断路器打开或者请求出错时,就会触发fallback调用。
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class Fallback implements TestClient {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
复制代码
fallback使用也挺简单的,在@FeignClient注解声明使用,然后再提供一个接口实现类,实现的方法逻辑就是容错处理的逻辑。
虽然可以实现容错处理,一旦发生错误,但是我们想知道引起错误的原因,这样我们就无从知道了。当然,我们可以通过在@FeignClient声明fallbackFactory
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();
}
@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {
@Override
public FallbackWithFactory create(Throwable cause) {
return new FallbackWithFactory();
}
}
static class FallbackWithFactory implements TestClientWithFactory {
@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}
@Override
public String getException() {
return "Fixed response";
}
}
复制代码
首先提供一个实现了FallbackFactory接口的自定义类TestFallbackFactory,然后再实现TestClientWithFactory接口如FallbackWithFactory。注意在TestFallbackFactory中,是使用FallbackWithFactory来范化的。
这样在TestFallbackFactory#create方法里,就可以拿到异常信息了。
总结
以上提供了Spring Cloud Feign的基本使用,还有3个重要属性,当然还有其他的属性配置,具体请参考spring-cloud-openfeign,还有以上的代码例子,也均来自此文档
在业务中,通过Spring Cloud Eureka和Feign来可以简化远程http调用,不再需要传统的发起get和post请求代码,还有把结果反序列化成对象,包括配置请求hostname,这一切都有服务发现和Feign来完成了。