feign get请求使用对象接收参数报错

feign get请求使用对象接收参数报错

我们知道SpringBoot get请求时,参数多的时候我们可以使用请求对象来接收,像下面这样:

@ApiOperation(value = "根据不同参数获取不同表数据", response = PicaResponse.class, notes = "demo")
    @GetMapping(value = "/")
    public PicaResponse<StudyInfoDto> getMegrezData(MegrezRequest request, HttpServletRequest httpServletRequest) {
        return PicaResponse.toResponse(megrezService.get(request));
    } 

但是feign调用服务的时候是不支持请求对象来接收参数的,当用请求对象接收参数时,调用会报如下错误:

{"timestamp":1482676142940,"status":405,"error":"Method Not Allowed", 
"exception":"org.springframework.web.HttpRequestMethodNotSupportedException","messa
ge":"Request method 'POST' not supported","path":"/data"}

调试时发现HttpURLConnection中的一段代码,jdk原生的http请求工具类,Feign默认使用的连接工具实现类

private synchronized OutputStream getOutputStream0() throws IOException {
  try {
      if(!this.doOutput) {
            throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)");
 } else {
      if(this.method.equals("GET")) {
           this.method = "POST";
 }

调用时必须要把参数分开来接收,像下面这样:

@FeignClient(value="pica-cloud-demo")
public interface MegrezServiceClient {

@GetMapping(value = "/data")
PicaResponse<StudyInfoDto> getMegrezData(@RequestParam("type") int type,
                                         @RequestParam("doctorId") int doctorId,
                                         @RequestParam("hospitalId") int hospitalId,
                                         @RequestParam("provinceId") long provinceId,
                                         @RequestParam("countryId") int countryId);
}

但是这种方式对于调用方来说显然是不友好的,那我们如何来使用请求对象来接收参数呢,按照下面这样操作即可,将feign默认调用的httpclient替换为apache的httpclient:
1.首先在client服务pom文件添加如下依赖:

	<dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.3</version>
    </dependency>

    <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-httpclient</artifactId>
        <version>9.3.1</version>
    </dependency>

2.在client对应接口接收对象前面加上@RequestBody注解

@GetMapping(value = "/data")
    PicaResponse<StudyInfoMapDto> getMegrezData(@RequestBody MegrezListRequest megrezListRequest);

同时在controller相应的接口也要加上@RequestBody注解;
 @ApiOperation(value = "根据不同参数获取不同表的排名数据", response = PicaResponse.class, notes = "demo")
@GetMapping(value = "/data")
public PicaResponse<StudyInfoMapDto> getMegrezData(@RequestBody(required = false) MegrezListRequest megrezListRequest, HttpServletRequest request) {
    return PicaResponse.toResponse(megrezService.get(megrezListRequest));
}

3.网上有说要在resources目录下面添加application.properties,里面内容是:

 feign:
  httpclient:
    enabled: true

但是我没加依然可以正常调用了。

服务调用服务是没问题了,可是这样有一个问题,我们用postman等类似工具调用的时候是调不通的,因为get请求是不能传请求体参数的,那么怎么样可以传form参数同样可以接收到呢,我加了一步处理,如果是form表单参数的话,将参数封装到请求体参数中,多加的代码如下:

@ApiOperation(value = "根据不同参数获取不同表的排名数据", response = PicaResponse.class, notes = "demo")
    @GetMapping(value = "/data")
    public PicaResponse<StudyInfoMapDto> getMegrezData(@RequestBody(required = false) MegrezListRequest megrezListRequest, HttpServletRequest request) {
        megrezListRequest = (null != megrezListRequest) ? megrezListRequest : MegrezRequestConverter.converterToRequest(request, MegrezListRequest.class);
        return PicaResponse.toResponse(megrezService.get(megrezListRequest));
    }

转化方法如下:

public static <T> T converterToRequest(HttpServletRequest request, Class<T> clazz) {
		Field[] declaredFields = clazz.getDeclaredFields();
		T t = null;
		try {
            t = clazz.newInstance();
            for (Field field : declaredFields) {
                field.setAccessible(true); 
                Class<?> type = field.getType();
                if (Collection.class.isAssignableFrom(type)) {
                    Type genericType = field.getGenericType();
                    if (genericType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
                        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                        if (actualTypeArguments.length > 0) {
                            type = (Class<?>) actualTypeArguments[0];
                            if (type.equals(Integer.class)) {
                                List<Integer> list = new ArrayList<>();
                                String[] s = value.split(",");
                                for (String v: s) {
                                    list.add(Integer.parseInt(v));
                                }
                                field.set(t, list);
                            }
                            if (type.equals(Long.class)) {
                                List<Long> list = new ArrayList<>();
                                String[] s = value.split(",");
                                for (String v: s) {
                                    list.add(Long.parseLong(v));
                                }
                                field.set(t, list);
                            }
                            if (type.equals(String.class)) {
                                field.set(t, Arrays.asList(value.split(",")));
                            }
                        }
                    }
                } else {
                    if (type.equals(Integer.class)) {
                        field.set(t, Integer.parseInt(value));
                    }
                    if (type.equals(Long.class)) {
                        field.set(t, Long.parseLong(value));
                    }
                    if (type.equals(String.class)) {
                        field.set(t, value);
                    }
                }
            }
        } catch (Exception e) {
		    logger.info("converterToRequest: " + e);
            throw new PicaException(PicaResultCode.SYSTEM_INNER_ERROR, e);
        }
		return t;
	}

这样处理完之后,我们既可以把参数分开接收,同时可以使用请求对象接收了。

可以通过Feign的`@RequestBody`注解将请求参数转换为JSON格式,同时通过定义`@Headers`注解设置请求的`Content-Type`为`application/json`。然后在定义Feign口时,可以使用`@Headers`注解定义接收响应的`Content-Type`为`application/protobuf`,并且使用`@ResponseParser`注解指定响应解析器为Protobuf格式。示例代码如下: ```java import feign.Headers; import feign.Param; import feign.RequestLine; import feign.Response; import feign.codec.Decoder; import feign.codec.Encoder; import feign.codec.ErrorDecoder; import feign.protobuf.ProtobufDecoder; import feign.protobuf.ProtobufEncoder; import feign.slf4j.Slf4jLogger; public interface MyService { @RequestLine("POST /api") @Headers({"Content-Type: application/json"}) @ResponseParser(ProtobufResponseParser.class) MyResponse postJson(@RequestBody MyRequest request); static class ProtobufResponseParser implements ResponseParser { private final ProtobufDecoder decoder = new ProtobufDecoder(MyResponse.class); @Override public Object parseResponse(Response response) throws IOException { return decoder.decode(response, MyResponse.class); } } static MyService connect(String url) { return Feign.builder() .logger(new Slf4jLogger()) .encoder(new ProtobufEncoder()) .decoder(new ProtobufDecoder()) .errorDecoder(new MyErrorDecoder()) .target(MyService.class, url); } } ``` 注意:这里假设`MyRequest`和`MyResponse`分别是请求和响应的Java对象,且`MyResponse`是Protobuf格式的。同时需要定义一个`ProtobufResponseParser`类,用于将响应转换为Java对象。在`Feign.builder()`中需要设置`encoder`和`decoder`为`ProtobufEncoder`和`ProtobufDecoder`,并且还需要定义一个错误解码器`MyErrorDecoder`,用于处理HTTP请求失败时的异常情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值