在Spring Cloud Netflix栈中,各个微服务都是以HTTP接口的形式暴露自身服务的,因此在调用远程服务时就必须使用HTTP客户端。我们可以使用JDK原生的URLConnection、Apache的Http Client、Netty的异步HTTP Client, Spring的RestTemplate。但是,用起来最方便、最优雅的还是要属Feign了。
Feign是一种声明式、模板化的HTTP客户端
Contronller层通过feignClient调用微服务,获取所有任务
"tsa/task") (publicclassTaskController{ TaskFeignClienttaskFeignClient; "/getAll") (publicList<TaskVO>getAll() { List<TaskVO>all=taskFeignClient.getAll(); returnall; } }
@FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入。
Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里。
qualifier="taskFeignClient", name="service-tsa",fallback=TaskFeignClientDegraded.class) (publicinterfaceTaskFeignClient { value="taskApiController/getAll") (List<TaskVO>getAll(); }
微服务端
"taskApiController") (publicclassTaskApiController{ privateTaskServicetaskService; "/getAll") (publicList<TaskVO>getAll() { log.info("--------getAll-----"); List<TaskVO>all=taskService.getAll(); returnall; } }
坑1
首先再次强调Feign是通过http协议调用服务的,重点是要理解这句话
如果FeignClient中的方法有@PostMapping注解,则微服务TaskApiController中对应方法的注解也应当保持一致为@PostMapping
如果不一致,则会报404的错误
调用失败后会触发它的熔断机制,如果@FeignClient中不写@FeignClient(fallback = TaskFeignClientDegraded.class),会直接报错
11:00:35.686 [http-apr-8086-exec-8] DEBUGc.b.p.m.b.c.AbstractBaseController-Gotanexceptioncom.netflix.hystrix.exception.HystrixRuntimeException: TaskFeignClient#getAll() failedandnofallbackavailable. atcom.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819) atcom.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
坑2
自己写好的微服务没有运行起来,然后自己的客户端调用这个服务怎么也调用不成功还不知道问题在哪
当时自己微服务运行后,控制台如下
Process finished with exit code 0
我以前以为Process finished with exit code 1才是运行失败的意思
所以只要出现 Process finished with exit code 就说明运行失败
服务成功启动的标志为
11:29:16.483 [restartedMain] INFO c.b.p.ms.tsa.TsaServiceApplication - Started TsaServiceApplication in 37.132 seconds (JVM running for 39.983)
坑3
RequestParam.value() was empty on parameter 0
如果在FeignClient中的方法有参数传递一般要加@RequestParam(“xxx”)注解
错误写法
qualifier="taskFeignClient", name="service-tsa",fallback=TaskFeignClientDegraded.class) (publicinterfaceTaskFeignClient { value="taskApiController/getAll") (List<TaskVO>getAll(Stringname); }
或
value="taskApiController/getAll") (List<TaskVO>getAll(Stringname);
正确写法
value="taskApiController/getAll") (List<TaskVO>getAll( ("name") Stringname);
微服务那边可以不写这个注解
疑问
在 SpringMVC 和 Springboot 中都可以使用 @RequestParam 注解,不指定 value 的用法,为什么到了 Spring cloud 中的 Feign 这里就不行了呢?
这是因为和 Feign 的实现有关。Feign 的底层使用的是 httpclient,在低版本中会产生这个问题,听说高版本中已经对这个问题修复了。
- Fiegn Client with Spring Boot: RequestParam.value() was empty on parameter 0
- Feign bug when use @RequestParam but not have value
- https://www.xttblog.com
坑四
FeignClient中post传递对象和consumes = "application/json"
按照坑三的意思,应该这样写
qualifier="taskFeignClient", name="service-tsa",fallback=TaskFeignClientDegraded.class) (publicinterfaceTaskFeignClient { value="taskApiController/getAll") (List<TaskVO>getAll( ("vo") TaskVOvo); }
很意外报错
16:00:33.770 [http-apr-8086-exec-1] DEBUGc.b.p.a.s.PrimusCasAuthenticationFilter-proxyReceptorRequest=false16:00:33.770 [http-apr-8086-exec-1] DEBUGc.b.p.a.s.PrimusCasAuthenticationFilter-proxyTicketRequest=false16:00:33.770 [http-apr-8086-exec-1] DEBUGc.b.p.a.s.PrimusCasAuthenticationFilter-requiresAuthentication=false16:00:34.415 [hystrix-service-tsa-2] DEBUGc.b.p.m.b.f.PrimusSoaFeignErrorDecoder-errorjson:{ "timestamp":1543564834395, "status":500, "error":"Internal Server Error", "exception":"org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException", "message":"Failed to convert value of type 'java.lang.String' to required type 'com.model.tsa.vo.TaskVO'; nestedexceptionisjava.lang.IllegalStateException: Cannotconvertvalueoftype'java.lang.String'torequiredtype'com.model.tsa.vo.TaskVO': nomatchingeditorsorconversionstrategyfound","path":"/taskApiController/getAll" }
开着错误信息想了半天突然想明白了
Feign本质是通过http 请求的,http怎么能直接传递对象呢,一般都是把对象转换为json通过post请求传递的
正确写法应当如下
qualifier="taskFeignClient", name="service-tsa",fallback=TaskFeignClientDegraded.class) (publicinterfaceTaskFeignClient { value="taskApiController/getAll",,consumes="application/json") (List<TaskVO>getAll(TaskVOvo); }
也可以这样写
value="taskApiController/getAll") (List<TaskVO>getAll(TaskVOvo);
此时不用,consumes = "application/json"
但是第一种写法最正确的 因为FeignClient是在我们本地直接调用的,根本不需要这个注解,Controller调用方法的时候就是直接将对象传给FeignClient,而FeignClient通过http调用服务时则需要将对象转换成json传递。
微服务这边如下
"taskApiController") (publicclassTaskApiController{ privateTaskServicetaskService; "/getAll") (publicList<TaskVO>getAll(TaskVOvo) { log.info("--------getAll-----"); List<TaskVO>all=taskService.getAll(); returnall; } }
我第一次写这个的时候方法参数里面什么注解都没加,可以正常跑通,但是传过去的对象却为初始值,实际上那是因为对象根本就没传
如果用get方法传递对象呢?
- https://blog.csdn.net/cuiyaoqiang/article/details/81215483
- https://blog.csdn.net/u014281502/article/details/72896182
- https://blog.csdn.net/neosmith/article/details/52449921
当然还是推荐使用post请求传递对象的