Feign介绍
Feign是Netflix公司开源的轻量级rest客户端,使用Feign可以非常方便的实现Http 客户端。Spring Cloud引入Feign并且集成了Ribbon实现客户端负载均衡调用。
Feign测试
1.在yml文件里面增加了配置信息
1
2
3
|
feign: httpclient: enabled: true |
2.在客户端pom.xml文件中引入的依赖(消费者端)
1
2
3
4
5
6
7
8
9
10
11
12
|
<!-- 配置feign 发送请求使用 httpclient,而不是java原生 --> < dependency > < groupId >org.apache.httpcomponents</ groupId > < artifactId >httpclient</ artifactId > </ dependency > <!-- 此处不使用Apache HttpClient 的httpclient依赖,一定要使用下面这个依赖,因为我们要返回的是POJO类--> < dependency > < groupId >io.github.openfeign</ groupId > < artifactId >feign-httpclient</ artifactId > < version >10.1.0</ version > </ dependency > |
此处注意
此处依赖为什么使用io.github.openfeign的httpclient,而不使用Apache 的HttpClient替换Feign原生httpclient。
看了很多文章,都是说引用这个依赖:
1
2
3
4
5
6
|
<!-- 使用Apache HttpClient替换Feign原生httpclient--> <!-- <dependency>--> <!-- <groupId>com.netflix.feign</groupId>--> <!-- <artifactId>feign-httpclient</artifactId>--> <!-- <version>8.16.1</version>--> <!-- </dependency>--> |
但是不知道哪里的问题,在获取返回结果时一直报错:
Caused by: java.lang.NoSuchMethodError: feign.Response.create(ILjava/lang/String;Ljava/util/Map;Lfeign/Response$Body;)Lfeign/Response;
查看源码得知,openfeign在接受返回值时调用的不是httpclient的feign-core包的代码而是调用的本身的feign-core的代码,而本身的feign-core包中的Response类没有create方法。两个feign-core包中的Retryer接口不一致导致的,openfeign的feign-core版本为10.1.0 httpclient的版本为8.16.1。
找了半天问题,最后就把httpclient的依赖换成代码块中的依赖就OK了。
3.服务调用端接口为
此处使用POST请求,第6步有解释。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@Slf4j @RequestMapping ( "/list" ) @RestController public class WebQueryListController { @Autowired private TourismListService listService; @PostMapping ( "/ad/allByQuery" ) public ApiResult<Page<TourismAd>> allByQuery( @RequestBody TourismAdQuery adQuery){ ApiResult<Page<TourismAd>> pageApiResult = listService.selectAllAdByQuery(adQuery); return pageApiResult; } |
我的TourismAdQuery类继承了Page类(似乎没有影响)
1
2
3
4
5
6
7
8
9
|
@Data public class TourismAdQuery extends Page<TourismAd> { /** * 标题 */ private String title; 。。。。。。。 } |
4.服务调用端Service代码
此处@PostMapping地址为服务端提供的api接口地址
1
2
3
4
5
6
7
8
9
10
|
@FeignClient (name = "fisher-back-service" , fallback = TourismListFallback. class , configuration = FeignConfig. class ) public interface TourismListService { /** * 分页查询广告根据查询条件 * @param adQuery * @return */ @PostMapping (value = "/ad/get/allByQuery" ) ApiResult<Page<TourismAd>> selectAllAdByQuery(TourismAdQuery adQuery); |
5.服务调用端Fallback为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Slf4j @Service public class TourismListFallback implements TourismListService { /** * 分页查询广告根据查询条件 * * @param adQuery * @return */ @Override public ApiResult<Page<TourismAd>> selectAllAdByQuery(TourismAdQuery adQuery) { log.error( "调用 selectAllAdByQuery 方法异常,参数:{}" , adQuery); return null ; } |
6.服务提供端代码为
此处传进来的参数是一个POJO类,如果不使用@RequestBody注解 的话,feign远程调用时参数是无法被接收到的。
虽然获取数据时,大多数使用 Get请求方法,但是GET方法无法接收@RequestBody参数体。
所以只好改GET请求为POST请求。
1
2
3
4
5
6
7
8
9
10
11
12
|
@RestController @RequestMapping ( "/ad" ) public class TourismAdController extends BaseController<TourismAdService, TourismAd, Integer> { @Autowired private TourismAdService adService; @ApiOperation (value = "分页查询广告根据查询条件" , notes = "分页查询广告根据查询条件" , httpMethod = "POST" ) @PostMapping ( "/get/allByQuery" ) public ApiResult<Page<TourismAd>> allByQuery( @RequestBody TourismAdQuery adQuery){ return adService.selectAllByQuery(adQuery); } |
7.测试
调用接口http://localhost:9009/list/ad/allByQuery 传递json格式参数即可:
1
2
3
4
5
|
{ "address" : "" , "title" : "广告位1" , "size" : 6 } |
成功分页获取数据 自定义的返回类型数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
{ "data" : { "records" : [ { "id" : 1 , "title" : "广告位1" , "description" : "招商" , "sort" : 0 , "datetime" : "2019-09-26 17:46:50" , "updatetime" : "2019-09-26 17:46:50" , "peopleid" : 0 , "display" : 0 , "content" : "04004" , "file" : "444//44.jpg" , "leaseperson" : "找找" , "address" : "杭州市" , "idcard" : "1154465656656" , "phone" : "131654799" } ], "total" : 1 , "size" : 6 , "current" : 1 , "searchCount" : true , "pages" : 1 }, "code" : 200 , "message" : "分页获取成功" } |
Feign调用分页接口报错:Method has too many Body parameters
接口定义:
1
2
3
4
|
@ApiOperation (value = "分页查询会话" ) @PostMapping (Routes.SESSIONS_QUERY) JsonResult<Pagination<SessionInfo>> querySessions( @RequestBody @Valid SessionsQo qo, @PageableDefault (size = 20 , sort = "id" , direction = Sort.Direction.DESC) Pageable pageable); |
服务消费方调用报错:
Method has too many Body parameters: public abstract com.xingren.common.data.JsonResult com.xingren.xxx.yyy.contract.api.controller.ISessionController.querySessions(com.xingren.xxx.yyy.contract.qo.SessionsQo,org.springframework.data.domain.Pageable)
解决方法
通过搜索、调研,目前有三种解决方法:
1、将分页属性直接通过入参传递,接口定义如下:
1
2
3
4
|
@ApiOperation (value = "分页查询会话" ) @PostMapping (Routes.SESSIONS_QUERY) JsonResult<Pagination<SessionInfo>> querySessions( @RequestBody @Valid SessionsQo qo, @RequestParam ( "page" ) Integer page, @RequestParam ( "size" ) Integer size, @RequestParam ( "sort" ) Sort sort); |
2、将分页对象冗余在Qo中(通过继承实现):
1
2
3
4
5
6
7
8
|
@Data @NoArgsConstructor @ApiModel (value = "查询会话" ) public class SessionsQo extends PageableParam { @ApiParam (value = "会话id列表" ) private List<Long> sessionIdIn = Lists.newArrayList(); ... } |
3、通过注解传递(参考:Issue):
服务提供方定义注解:
1
2
3
4
|
@Target (ElementType.PARAMETER) @Retention (RetentionPolicy.RUNTIME) public @interface PageableParam { } |
服务提供方定义接口:
1
2
3
4
|
@ApiOperation (value = "分页查询会话" ) @PostMapping (Routes.SESSIONS_QUERY) JsonResult<Pagination<SessionInfo>> querySessions( @RequestBody @Valid SessionsQo qo, @PageableParam @SpringQueryMap Pageable pageable); |
服务消费方定义processor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@Bean public PageableParamProcessor pageableParamProcessor() { return new PageableParamProcessor(); } public static class PageableParamProcessor implements AnnotatedParameterProcessor { private static final Class<PageableParam> ANNOTATION = PageableParam. class ; @Override public Class<? extends Annotation> getAnnotationType() { return ANNOTATION; } @Override public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { int parameterIndex = context.getParameterIndex(); MethodMetadata data = context.getMethodMetadata(); data.queryMapIndex(parameterIndex); return true ; } } |
服务消费方自定义PageableUtil:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class PageableUtil extends PageRequest implements Map<String, Object> { public static final String PAGE = "page" ; public static final String SIZE = "size" ; public static final String SORT = "sort" ; @Delegate protected Map<String, Object> delegate = Maps.newHashMap(); public PageableUtil( int page, int size, Sort sort) { super (page, size, sort); delegate.put(PAGE, page); delegate.put(SIZE, size); if (Objects.nonNull(sort)) { delegate.put(SORT, sort.toString().replace( ": " , "," )); } } public PageableUtil( int page, int size) { super (page, size); delegate.put(PAGE, page); delegate.put(SIZE, size); } } |
定义PageableUtil原因:主要是因为Feign对QueryMap类型参数的序列化和反序列化的言七墨方式与Sort.Order的不兼容,导致排序失效。
服务消费方调用方式:
1
2
|
SessionsQo qo = SessionsQo.builder().sessionIdIn(Collections.singletonList(20L)).build(); JsonResult<Pagination<SessionInfo>> pageInfo = sessionContract.querySessions(qo, new PageableUtil( 0 , 5 , new Sort(Sort.Direction.DESC, |
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/qq_36068521/article/details/102565751