简介 本文主要介绍如何使用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上,加上这个注解:
或者,新建一个配置类,指定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层中拷贝代码,稍做修改即可,非常方便。
但请注意:
@PathVariable("productId")
,需要显示的指定对应的参数名,不能像SpringMVC一样自动对应。
目前只能使用@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相比,只能说是各有利弊吧,可以酌情选择。