简介
本文主要介绍如何使用spring-cloud-feign,在项目中使用Feign进行REST调用。
通常我们进行微服务间的REST调用时,一般会使用restTemplate,写起来也比较方便,例如:
ResponseEntity result = restTemplate.getForEntity(baseurl + "/users?serialNumber=18612341234", UserDTO.class);
ResponseEntity 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依赖即可。
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR2
pom
import
org.springframework.cloud
spring-cloud-starter-feign
application.yml配置
新版本的Feign默认是禁用Hystrix的,需要手工配置打开。
feign.hystrix.enabled: true
Feign本身可以与Eureka/Ribbon比较好的配合使用,不需要其它配置,直接使用"应用名"进行调用。
当微服务没有使用Eureka做服务发现时,就需要手工配置Ribbon。例如,当使用marathon-lb时,可以这样配置:
marathon-lb-internal.ribbon.listOfServers: marathon-lb-internal.marathon.mesos
这里指定了一个DNS,当然也可以写死一个或多个IP。
启用EnableFeignClients注解
最简单的,可以在Application.java上,加上这个注解:
@EnableFeignClients
或者,新建一个配置类,指定profile:
@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注解无效,具体原因没有深究,可以通过配置解决,示例如下:
@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中有时需要特别配置一下:
定义client接口
下面开始,正式进入正题,定义一个接口,并加上@FeignClient注解。
@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层中拷贝代码,稍做修改即可,非常方便。
但请注意:
@PathVariable("productId")
,需要显示的指定对应的参数名,不能像SpringMVC一样自动对应。- 目前只能使用
@RequestMapping
注解,而不能使用@GetMapping
等
编写fallback类
当调用失败时,会执行fallback类中的逻辑。
@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调用也非常简单,一行代码搞定:
ProductDTO productInfo = staticInfoClient.getProductInfo(entity.getProductId());
END
Feign的配置略微复杂,坑也比较多,有一定的学习成本的。但带来的好处理,在使用上即优雅又方便。
与RestTemplate相比,只能说是各有利弊吧,可以酌情选择。