feign接口使用JSONObject作为参数遇到的问题

探讨Feign接口调用中对象引用的变化及序列化反序列化的影响,揭示为何通过Feign修改对象内部属性无法反映在返回结果中。

首先有几个小知识点需要强调一下:

  • 当方法的参数是对象时,参数其实是对象引用的拷贝
  • 对象的hashCode是两个对象equals的依据,但hashCode相等不代表他们是同一个对象。
  • 验证两个对象是否是同一个对象,可以使用“==“。

先上出现问题的代码
feign接口

import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Map;
import java.util.Set;

@FeignClient("${fxp.my-server}")
public interface IServer {
    @RequestMapping(value = "/testjson",method = RequestMethod.POST)
    Object testJson(@RequestBody JSONObject params);//注意实现时参数中的@RequestBody不会被实现类继承
}

实现类

@RestController
public class MyServer implements IServer{
    @Override
    public Object testJson(@RequestBody JSONObject params) {
        JSONObject context = parseContextFromParam(params);
        context.put("a","abc");
        return params;
    }
    private JSONObject parseContextFromParam(JSONObject params) {
    if (!params.containsKey("context")) {
        throw new MyException(
                CodeMessageEnum.ILLEGAL_PARAM.getCode(),
                CodeMessageEnum.ILLEGAL_PARAM.getMessage(),
             CodeMessageEnum.ILLEGAL_PARAM.getHttpStatus()
        );
    }
    return params.getJSONObject("context");
}
}

这是我遇到问题的代码(这里只是为了展示问题,采用最简化的代码),这段代码的目的时把传入的param进行简单加工(比如本代码中的给内层的”context”加一个键值对),然后返回给调用者。

在本地进行单元测试时是没有任何问题的,单元测试代码:

@AutoWired
private MyServer myServer;
    @Test
    public void testElements(){
        JSONObject param = new JSONObject();
        JSONObject context = new JSONObject();
        context.put("a","1");
        context.put("b","2");
        param.put("context",context);
        Object result = myServer.testJson(param);
        System.out.println(JSONObject.toJSONString(result, SerializerFeature.WriteMapNullValue));//显示空值键值对
    }

但是通过feign接口调用时就会出现问题,在testJson中不论怎么修改context都无法改变返回值。

差别在哪里呢?

feign接口的调用实际上是走的http协议,在参数传递的时候会经过序列化与反序列化的过程,最后我们在参数里边得到的JSONObject的结构可能会不一样。如图
这里写图片描述
在反序列化时spring会使用MappingJackson2HttpMessageConverter实现类(如果没配置你的参数中对应的converter的话),而Jackson在反序列化的时候会将内层的context解析成LinkedHashMap。

接下来看一下我们使用的fastjson的getJSONObject的实现,如下:

    public JSONObject getJSONObject(String key) {
        Object value = map.get(key);

        if (value instanceof JSONObject) {
            return (JSONObject) value;
        }

        if (value instanceof String) {
            return JSON.parseObject((String) value);
        }

        return (JSONObject) toJSON(value);
    }

在toJSON中
这里写图片描述
如果是LinkedHashMap的话getJSONObject方法会返回一个新的对象。
所以上面的代码不管怎么设置context都不会影响最终的结果。

<think>根据用户的问题,用户希望了解如何在Feign客户端中定义同时包含Header和Body参数接口方法。结合引用的内容,特别是引用[4]中提到了在接口参数中添加注解,以及引用[5]中关于@FeignClient注解的注意事项,我们可以给出以下指导。 首先,在Feign中,Header参数通常使用`@RequestHeader`注解,而Body参数使用`@RequestBody`注解(对于POST/PUT等请求)。同时,引用[4]中的示例展示了如何定义包含`@RequestBody`的方法。 步骤: 1. 定义Feign客户端接口使用`@FeignClient`注解指定服务名称和路径(如果需要)。 2. 在方法上使用@RequestMapping(或@GetMapping, @PostMapping等)指定请求方法和路径。 3. 方法参数中,使用`@RequestHeader`注解来指定Header参数使用`@RequestBody`注解来指定Body参数。 示例代码: ```java import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @FeignClient(name = "example-service", path = "/api") public interface ExampleFeignClient { @PostMapping("/submit") String submitWithHeadersAndBody( @RequestHeader("Authorization") String authHeader, // Header参数 @RequestHeader("X-Custom-Header") String customHeader, // 另一个Header参数 @RequestBody MyRequest body // Body参数 ); } ``` 注意事项: - 每个Header参数都需要单独使用`@RequestHeader`注解,并指定Header的名称。 - Body参数只能有一个,通常使用`@RequestBody`注解,并且该参数应该是一个对象(可以是自定义的POJO类)。 - 如果同一个微服务中有多个Feign客户端接口,注意按照引用[5]的警告:`@FeignClient`的`name`(或`value`)和`path`要保持一致(如果指向同一个服务),并且`contextId`要唯一以避免冲突。 另外,引用[4]中还提到,在定义接口参数时,必须添加相应的注解(如`@RequestParam`, `@RequestBody`等),否则会出现异常。 因此,用户需要确保: 1. 每个参数都正确注解。 2. Header参数使用`@RequestHeader`,Body参数使用`@RequestBody`。 3. 注意Feign客户端的命名和路径配置。 如果用户需要传递多个Header,可以像示例中那样使用多个`@RequestHeader`参数,也可以使用Map来传递多个Header,但通常推荐显式声明。 相关问题: 1. 如何在Feign客户端中传递动态Header? 2. Feign客户端支持哪些类型的请求体编码? 3. 如何处理Feign客户端调用时的错误?</think>### 如何在Feign客户端中定义包含Header和Body参数接口方法 在Feign客户端中定义同时包含Header和Body参数接口方法,需结合注解正确声明参数位置。以下是具体实现步骤和示例: #### 1. **定义Feign客户端接口** 使用`@FeignClient`声明接口,并通过`@RequestMapping`系列注解指定请求方法: ```java import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; @FeignClient( name = "example-service", // 服务名 path = "/api", // 基础路径 contextId = "uniqueId" // 避免重复(引用[5]) ) public interface ExampleFeignClient { @PostMapping("/submit") String sendData( @RequestHeader("Authorization") String authToken, // Header参数 @RequestHeader("X-Custom-Header") String customHeader, @RequestBody MyRequest requestBody // Body参数 ); } ``` #### 2. **关键注解说明** - **`@RequestHeader`** 标注Header参数,需明确指定Header名称(如`"Authorization"`)。 - **`@RequestBody`** 标注Body参数,**只能有一个**,通常为POJO对象。 - **`@PathVariable`/`@RequestParam`** 若需URL路径参数或查询参数,可额外添加(非Header/Body)。 #### 3. **Body参数对象定义** Body需为可序列化的POJO: ```java public class MyRequest { private String data; private int priority; // 必须有无参构造器 + Getter/Setter } ``` #### 4. **调用示例** ```java // 通过Feign构建实例 ExampleFeignClient client = Feign.builder() .target(ExampleFeignClient.class, "http://service-url"); // 调用方法(传递Header和Body) String response = client.sendData( "Bearer token123", // @RequestHeader值 "custom-value", // @RequestHeader值 new MyRequest("test", 1) // @RequestBody值 ); ``` #### ⚠️ 注意事项 1. **每个参数必须显式注解**(如`@RequestHeader`),否则会抛出参数绑定异常(引用[4])。 2. **`contextId`唯一性**:同一服务内多个Feign客户端需不同`contextId`(引用[5])。 3. **Header多值传递**: 若需传递多个Header值,可声明多个`@RequestHeader`参数使用`Map<String, String>`: ```java String sendData(@RequestHeader Map<String, String> headers, @RequestBody MyRequest body); ``` #### 常见问题场景 - **错误**:漏掉`@RequestHeader`注解 ```java // 错误!未标注Header参数 String sendData(String authToken, @RequestBody MyRequest body); ``` 此时Feign会将`authToken`误认为Body的一部分,导致请求异常。 - **正确**:显式标注所有参数 ```java String sendData( @RequestHeader("Auth") String authToken, // 正确标注 @RequestBody MyRequest body ); ``` --- ### 相关问题 1. 如何在Feign客户端中动态传递Header值(如从上下文获取)? 2. Feign调用时如何统一添加全局Header(如认证令牌)? 3. 使用`@RequestBody`时如何自定义序列化格式(如XML或Protobuf)? 4. Feign客户端调用超时或重试策略如何配置? 5. 如何通过拦截器(Interceptor)在Feign请求前修改Header或Body?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值