【问题标题】:JSON parameter in spring MVC controllerSpring MVC控制器中的JSON参数
【发布时间】:2014-03-01 21:58:16
【问题描述】:

我有

@RequestMapping(method = RequestMethod.GET)
@ResponseBody
SessionInfo register(UserProfile profileJson){
  ...
}

我这样传递profileJson:

http://server/url?profileJson={"email": "mymail@gmail.com"}

但我的 profileJson 对象具有所有空字段。我应该怎么做才能让spring解析我的json?

【问题讨论】:

  • 将 JSON 传递给查询参数没有意义。您至少需要对其进行 URL 编码。
  • 您将 json 作为 URL 参数而不是正文(这是默认值)传递。通常将 JSON 作为参数传递是没有意义的。使用@RequestParam 注释您的方法参数。但是,如前所述,您应该将其作为请求的主体传递,也可能作为 POST 而不是 GET 请求传递。
  • 你几乎肯定想在这里使用 POST,拥有获取请求主体的情况非常罕见 (stackoverflow.com/questions/978061/http-get-with-request-body)
  • 我用的是jsonp,不支持POST。使用 @RequestParam 注释参数会出现异常“未找到匹配的编辑器或转换策略”
  • 只需将参数获取为String 并自行转换即可。

标签: java json spring spring-mvc


【解决方案1】:

解决这个问题的方法非常简单,它实际上会让你发笑,但在我开始之前,让我先强调一下,任何有自尊的 Java 开发人员都不会,我的意思是永远使用 JSON 而不使用Jackson 高性能 JSON 库。

Jackson 不仅是 Java 开发人员的工作马和事实上的 JSON 库,而且它还提供了一整套 API 调用,使 JSON 与 Java 集成变得轻而易举(您可以通过 http://jackson.codehaus.org/ 下载 Jackson)。

现在回答。假设您有一个看起来像这样的 UserProfile pojo:

public class UserProfile {

private String email;
// etc...

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

// more getters and setters...
}

...然后您的 Spring MVC 方法将 GET 参数名称“profileJson”转换为 JSON 值为 {"email": "mymail@gmail.com"} 在您的控制器中将如下所示:

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper; // this is your lifesaver right here

//.. your controller class, blah blah blah

@RequestMapping(value="/register", method = RequestMethod.GET) 
public SessionInfo register(@RequestParam("profileJson") String profileJson) 
throws JsonMappingException, JsonParseException, IOException {

    // now simply convert your JSON string into your UserProfile POJO 
    // using Jackson's ObjectMapper.readValue() method, whose first 
    // parameter your JSON parameter as String, and the second 
    // parameter is the POJO class.

    UserProfile profile = 
            new ObjectMapper().readValue(profileJson, UserProfile.class);

        System.out.println(profile.getEmail());

        // rest of your code goes here.
}

砰!你完成了。我鼓励你仔细阅读 Jackson API 的大部分内容,因为正如我所说,它是一个救命稻草。例如,您是否从控制器返回 JSON?如果是这样,您需要做的就是在您的库中包含 JSON,并返回您的 POJO,Jackson 将自动将其转换为 JSON。没有比这更容易的了。干杯! :-)

【讨论】:

  • 我希望这里有一些用于 @RequestParameter 注释的参数。
  • 为每个请求创建 ObjectMapper 并不是一个好习惯。控制器应该有字段 ObjectMapper。
  • 其实这是不正确的。首先,我假设“字段”是指控制器类的实例变量。其次,我的示例的重点是演示 ObjectMapper 的使用,而不是提供最佳实践并深入研究对象实例化的更精细的架构细节。第三,也是最重要的,将 ObjectMapper 作为控制器的实例变量是完全不好的做法,因为 Spring 控制器默认是单例的。正确的方法是遵循标准 MVC 模式并自动装配一个包含 ObjectMapper 的服务类。
  • @TheSaint 我只是好奇,为什么单例控制器拥有这样的字段是不好的做法?
  • 创建Converter 并让Spring MVC 自动使用它会更优雅(而且可能更高效)。以我的answer 为例。
【解决方案2】:

这可以通过自定义编辑器来完成,它将 JSON 转换为 UserProfile 对象:

public class UserProfileEditor extends PropertyEditorSupport  {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        ObjectMapper mapper = new ObjectMapper();

        UserProfile value = null;

        try {
            value = new UserProfile();
            JsonNode root = mapper.readTree(text);
            value.setEmail(root.path("email").asText());
        } catch (IOException e) {
            // handle error
        }

        setValue(value);
    }
}

这是为了在控制器类中注册编辑器:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(UserProfile.class, new UserProfileEditor());
}

这是如何使用编辑器来解组 JSONP 参数:

@RequestMapping(value = "/jsonp", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseBody
SessionInfo register(@RequestParam("profileJson") UserProfile profileJson){
  ...
}

【讨论】:

  • 这看起来很笼统,我想知道是否真的有必要为我想以这种方式解析的每个类编写和注册自定义编辑器。
  • 将此添加到调度程序 servlet xml 对我有用 ''
  • 使用Converter 可以更简单。见我的answer for an example
【解决方案3】:

您可以创建自己的Converter 并让 Spring 在适当的地方自动使用它:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
class JsonToUserProfileConverter implements Converter<String, UserProfile> {

    private final ObjectMapper jsonMapper = new ObjectMapper();

    public UserProfile convert(String source) {
        return jsonMapper.readValue(source, UserProfile.class);
    }
}

正如您在以下控制器方法中看到的那样,不需要什么特别的:

@GetMapping
@ResponseBody
public SessionInfo register(@RequestParam UserProfile userProfile)  {
  ...
}

如果您使用组件扫描,Spring 会自动选择转换器并使用 @Component 注释转换器类。

详细了解Spring Convertertype conversions in Spring MVC

【讨论】:

  • 鉴于这看起来很通用,我们可以对类进行参数化吗? class JsonToUserProfileConverter&lt;T&gt; implements Converter&lt;String, T&gt;Spring 是否还会自动选择并使用这个类(通过从结果类型推断 T)?
  • Spring 解析类型参数,因此应该可以按预期工作(唯一的例外是 Kotlin 类型的嵌套类型参数,这并不总是有效)。
【解决方案4】:

这确实解决了我的直接问题,但我仍然很好奇您如何通过 AJAX 调用传入多个 JSON 对象。

最好的方法是拥有一个包含您要传递的两个(或多个)对象的包装器对象。然后,您将 JSON 对象构造为两个对象的数组,即

[
  {
    "name" : "object1",
    "prop1" : "foo",
    "prop2" : "bar"
  },
  {
    "name" : "object2",
    "prop1" : "hello",
    "prop2" : "world"
  }
]

然后在您的控制器方法中,您将请求正文作为单个对象接收并提取两个包含的对象。即:

@RequestMapping(value="/handlePost", method = RequestMethod.POST, consumes = {      "application/json" })
public void doPost(@RequestBody WrapperObject wrapperObj) { 
     Object obj1 = wrapperObj.getObj1;
     Object obj2 = wrapperObj.getObj2;

     //Do what you want with the objects...


}

包装对象看起来像...

public class WrapperObject {    
private Object obj1;
private Object obj2;

public Object getObj1() {
    return obj1;
}
public void setObj1(Object obj1) {
    this.obj1 = obj1;
}
public Object getObj2() {
    return obj2;
}
public void setObj2(Object obj2) {
    this.obj2 = obj2;
}   

}

【讨论】:

    【解决方案5】:

    只需在此参数前添加@RequestBody 注释

    【讨论】:

    • 这不起作用。我收到 HTTP 415:服务器拒绝此请求,因为请求实体的格式不受所请求方法的请求资源支持。
    • @RequestBody 默认查看请求的正文。 OP 的内容是作为查询字符串的一部分发送的请求参数。
    猜你喜欢
    • 1970-01-01
    • 2013-01-11
    • 2012-07-06
    • 2010-10-21
    • 1970-01-01
    • 1970-01-01
    • 2014-03-17
    相关资源
    最近更新 更多