BlueXIII's Blog

热爱技术,持续学习

0%

Feign-声明式REST调用

简介

本文主要介绍如何使用spring-cloud-feign,在项目中使用Feign进行REST调用。

通常我们进行微服务间的REST调用时,一般会使用restTemplate,写起来也比较方便,例如:

1
2
3
ResponseEntity<UserDTO> result = restTemplate.getForEntity(baseurl + "/users?serialNumber=18612341234", UserDTO.class);

ResponseEntity<String> result = restTemplate.exchange(baseurl + "/users/1715043034165359", HttpMethod.PUT, new HttpEntity(user), String.class);

但RestTemplate这种方式的缺点是代码量略大,且不太直观。

微服务强调跨语言解耦,不提倡以前那种将API部分打包并分发的方式。不同微服务间的业务代码的冗余不可避免。使用Feign,就可以简化Rest客户端这一部分的代码。

引入pom.xml依赖

Spring Boot工程中,直接引入spring-cloud-starter-feign依赖即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>

application.yml配置

新版本的Feign默认是禁用Hystrix的,需要手工配置打开。

1
feign.hystrix.enabled: true

Feign本身可以与Eureka/Ribbon比较好的配合使用,不需要其它配置,直接使用”应用名”进行调用。

当微服务没有使用Eureka做服务发现时,就需要手工配置Ribbon。例如,当使用marathon-lb时,可以这样配置:

1
marathon-lb-internal.ribbon.listOfServers: marathon-lb-internal.marathon.mesos

这里指定了一个DNS,当然也可以写死一个或多个IP。

启用EnableFeignClients注解

最简单的,可以在Application.java上,加上这个注解:

1
@EnableFeignClients

或者,新建一个配置类,指定profile:

1
2
3
4
@Profile("enable-feign")
@Configuration
@EnableFeignClients(basePackages = {"com.sitech.sdtools"})
public class FeignConfiguration {}

使用配置类+profile的好处是,可以根据不同的环境,非常方便的启用/禁用Feign。
特别是在单元测试时,由于mockito无法对Feign生成的Bean进行Mock,这时就可以在profile中禁用Feign,直接执行Fallback。

另外,需要注意一下,如果配置类的路径不是在根路径,而是在com.foo.bar.config这样的包下,需要加上”basePackages”参数。

解决Bean冲突

我目前的Spring Clound的版本是Dalston.SR2,如果集成了Hystrix,编写Fallback类后会有Bean冲突的问题,貌似是自动生成的@Primary注解无效,具体原因没有深究,可以通过配置解决,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfiguration {
@Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new FeignFilterRequestMappingHandlerMapping();
}
};
}

private static class FeignFilterRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) && (AnnotationUtils.findAnnotation(beanType, FeignClient.class) == null);
}
}
}

开启日志

Feign的日志是DEBUG级别,在LogBack中有时需要特别配置一下:

1
2
3
<logger name="com.foo.bar.client" additivity="false" level="debug">
<appender-ref ref="stdout"/>
</logger>

定义client接口

下面开始,正式进入正题,定义一个接口,并加上@FeignClient注解。

1
2
3
4
5
6
@FeignClient(name = "marathon-lb-internal", fallback = StaticInfoClientFallback.class)
@RequestMapping(value = "/sd/staticinfo")
public interface StaticInfoClient {
@RequestMapping(value = "/products/{productId}", method = RequestMethod.GET)
public ProductDTO getProductInfo(@PathVariable("productId") Long productId);
}

可以直接在服务端的Controller层中拷贝代码,稍做修改即可,非常方便。

但请注意:

  1. @PathVariable("productId"),需要显示的指定对应的参数名,不能像SpringMVC一样自动对应。
  2. 目前只能使用@RequestMapping注解,而不能使用@GetMapping

编写fallback类

当调用失败时,会执行fallback类中的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
class StaticInfoClientFallback implements StaticInfoClient {
private static final Logger logger = LoggerFactory.getLogger(TradeInfoClientFallback.class);

@Override
public ProductDTO getProductInfo(Long productId) {
logger.error("StaticInfoClient.getProductInfo 执行失败");

ProductDTO dto = new ProductDTO();
dto.setProductId(productId);
dto.setProductName("产品名称暂时无法获取");
return dto;
}
}

注意不要忘记加上@Component

发起REST调用

REST调用也非常简单,一行代码搞定:

1
ProductDTO productInfo = staticInfoClient.getProductInfo(entity.getProductId());

END

Feign的配置略微复杂,坑也比较多,有一定的学习成本的。但带来的好处理,在使用上即优雅又方便。
与RestTemplate相比,只能说是各有利弊吧,可以酌情选择。