Feign远程调用传递对象参数并返回自定义分页数据的过程解析
作者:RyanJJ_
Feign介绍
Feign是Netflix公司开源的轻量级rest客户端,使用Feign可以非常方便的实现Http 客户端。Spring Cloud引入Feign并且集成了Ribbon实现客户端负载均衡调用。
Feign测试
1.在yml文件里面增加了配置信息
feign: httpclient: enabled: true
2.在客户端pom.xml文件中引入的依赖(消费者端)
<!-- 配置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。
看了很多文章,都是说引用这个依赖:
<!-- 使用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步有解释。
@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类(似乎没有影响)
@Data public class TourismAdQuery extends Page<TourismAd> { /** * 标题 */ private String title; 。。。。。。。 }
4.服务调用端Service代码
此处@PostMapping地址为服务端提供的api接口地址
@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为
@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请求。
@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格式参数即可:
{ "address": "", "title": "广告位1", "size": 6 }
成功分页获取数据 自定义的返回类型数据:
{ "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
接口定义:
@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、将分页属性直接通过入参传递,接口定义如下:
@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中(通过继承实现):
@Data @NoArgsConstructor @ApiModel(value = "查询会话") public class SessionsQo extends PageableParam { @ApiParam(value = "会话id列表") private List<Long> sessionIdIn = Lists.newArrayList(); ... }
3、通过注解传递(参考:Issue):
服务提供方定义注解:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface PageableParam { }
服务提供方定义接口:
@ApiOperation(value = "分页查询会话") @PostMapping(Routes.SESSIONS_QUERY) JsonResult<Pagination<SessionInfo>> querySessions(@RequestBody @Valid SessionsQo qo, @PageableParam @SpringQueryMap Pageable pageable);
服务消费方定义processor:
@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:
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的不兼容,导致排序失效。
服务消费方调用方式:
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,
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。