使用Java作为服务器和jQuery作为客户端的简单RESTful JSON POST

3

在我提问之前,我必须说我已经阅读了20多个关于这个问题的问题和文章,但是没有一个能够解决它。

我的问题是,我有一个像这样的Java RESTful服务器:

@RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
@ResponseBody
public void downloadByCode(@RequestBody final String stringRequest, final HttpServletResponse response)
{
    try
    {
        final ObjectMapper objectMapper = new ObjectMapper();
        final JsonNode jsonRequest = objectMapper.readValue(stringRequest, JsonNode.class);

        // ...
        // some processings here to create the result
        // ....

        final ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(result);
        // Flush the result
        outputStream.flush();
    }
    catch (final Exception exception)
    {
        LOG.debug("Exception Thrown [downloadByCode]", exception);
    }
}

我尝试了不同的方法在jquery中向该服务器发送json(但它们都导致错误):

$.ajax({
   url:"/downloadByCode",
   type:"POST",
   data: JSON.stringify({"name":"value"}) });

415 "错误信息":"不支持内容类型'application/x-www-form-urlencoded;charset=UTF-8'","类型":"HttpMediaTypeNotSupportedError"

因此,我尝试通过添加contentType来修复它:

$.ajax({
   url:"/downloadByCode",
   contentType:"application/json",
   type:"POST",
   data: JSON.stringify({"name":"value"}) });

400错误消息:"无法为类[class java.lang.String]实例化JAXBContext:null;嵌套异常是javax.xml.bind.JAXBException - 有关链接异常:[java.lang.NullPointerException","type":"HttpMessageConversionError"

我尝试直接发送json对象而不是JSON.stringify,但仍然出现了相同的400错误。

我尝试在@requestMapping中添加不同的consumes,但仍然没有成功。

我尝试定义自己的类而不是JsonNode,但这并没有改变任何东西。

有什么想法吗?


你是否按照错误提示创建了InputData作为内部类? - NTyler
如错误所述,您需要声明“public static class”才能让它正常工作。至于地图,那就是您访问它的方式。我不会永久保留这种方式,只是为了测试是否正常工作。使用@RequestBody的正确方法是创建一个类来保存反序列化后的JSON数据。 - NTyler
2
你可能需要像Vinh Vo建议的那样使用POJO类。将该类声明为静态或将其放在单独的文件中,这样就可以了。 - NTyler
@NTyler,你想把它发布为答案以获取赏金吗? - Arashsoft
1
赏金应该给Vinh Vo,他的答案是正确且第一个的。这个类只需要是静态的,因为你把它变成了内部类,如果你在它自己的文件中创建这个类,那么静态修饰符就是不必要的。 - NTyler
显示剩余7条评论
11个回答

3

请尝试创建新的类:

public class InputData{
   private String name;
   public String getName(){
      return name;
   }
   public void setName(String name){
      this.name = name;
   }
}

那么,
public void downloadByCode(@RequestBody InputData stringRequest, final HttpServletResponse response)

And

$.ajax({
   url:"/downloadByCode",
   contentType:"application/json",
   type:"POST",
   data: JSON.stringify({"name":"value"}) });

谢谢您的建议,但我遇到了这个错误:无法为类[$InputData]实例化JAXBContext:\r\n异常描述:$InputData需要一个零参数构造函数或指定的工厂方法。请注意,非静态内部类没有零参数构造函数,因此不受支持。 - Arashsoft

1
看起来你已经在Spring配置中配置了'Jaxb2RootElementHttpMessageConverter'或类似的XML转换器,这也导致你注册了一个 XML 转换器,并且 @RequestBody 和 @ResponseBody 工作基于注册的消息转换器。因此,为了解决问题,请使用 JSON 消息转换器,例如'MappingJacksonHttpMessageConverter'。一旦你注册了 JSON 消息转换器,创建一个 bean 类来保存你的 json 数据,并像下面这样与 RequestBody 一起使用:
// It has to meet the json structure you are mapping it with
public class YourInputData {
    //properties with getters and setters
}

更新 1:

由于您定义了多个消息转换器,Spring 默认尝试使用第一个可用的转换器。为了使用特定的消息转换器(在本例中是 Jackson 转换器),您应该像下面这样从客户端指定 'Accept' 标头:

$.ajax({     
          headers: {          
                 "Accept" : "application/json; charset=utf-8",         
                "Content-Type": "application/json; charset=utf-8"   
  }     
  data: "data",    
  success : function(response) {  
       ...  
   } })

谢谢您的回复,我已经有JSON转换器了: <bean class="org.springframework.http.converter.json.MappingJacksonHttp essageConverter" /> - Arashsoft
我创建了一个与我的JSON {"name","value"} 结构相同的bean类,但是我遇到了这个错误:无法为类[$InputData]实例化JAXBContext:\r\n异常描述:$InputData需要一个零参数构造函数或指定的工厂方法。请注意,非静态内部类没有零参数构造函数,因此不受支持。 - Arashsoft
实际问题是它尝试使用JAXB转换器,而应该使用Jackson转换器。 - James
我认为你是完全正确的。我定义了三个消息转换器<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/><bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"/><bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" /> 。现在的问题是,为什么它在contentType为application/json时使用JAXB而不是Jackson。 - Arashsoft
我注意到我的JSON结构类是一个内联类,应该是static。在我的类中添加了static后,Spring现在选择了正确的转换器,accept标签也变得不必要了。感谢您解决这个问题所付出的努力。 - Arashsoft

1
首先,如果您正在使用Maven,您应该添加jackson的依赖项。
 <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.4.1</version>
    </dependency>

或者您可以下载jar包并将其放在项目类路径中(也可以使用其他映射器)。
然后您应该创建一个模型或DTO类,您可以在其中映射您的json。
 public class Data{
     private String name;

     pubilc Data(){}

    //getter and setter

 }

然后你控制器

@RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
@ResponseBody
public Data downloadByCode(@RequestBody final Data data, final HttpServletResponse response)
{
//your code
    return data;
}

AJAX调用
$.ajax({
   url:"/downloadByCode",
   contentType:"application/json",
   type:"POST",
   data: JSON.stringify({"name":"value"}) });

(可选)您可以通过以下方式定义bean,告诉对象映射器不要在缺少属性时失败以覆盖行为:
 @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false));
        return converter;
    }

http://websystique.com/springmvc/spring-mvc-requestbody-responsebody-example/


谢谢你的帮助。这个答案是“理想情况下”一切应该如何工作!但是,在我的环境中它会生成错误。 - Arashsoft

1
最终的答案是这个问题中多个回答/评论的结合,我在此总结如下:
1- 您必须确保在spring配置中有适当的json转换器,例如MappingJacksonHttpMessageConverter(感谢@java Anto)
2- 您必须创建一个与您的json对象具有相同结构的POJO类(请参见@Vinh Vo的答案)
3- 除非它是静态类,否则您的POJO类不能是内联类。这意味着它应该有自己的java文件或者它应该是静态的。(感谢@NTyler)
4- 如果您在对象映射器中设置得当,您的POJO类可以缺少您的json对象的某些部分(请参见@Aman Tuladhar的答案)
5- 您的ajax调用需要contentType:“application/json”,并且您应该使用JSON.stringify发送数据
以下是完美运行的最终代码:
public static class InputData
{
    private String name

    public String getName()
    {
        return name;
    }

    public void setName(final String name
    {
        this.name = name;
    }
}

@RequestMapping(value = "/downloadByCode", method = RequestMethod.POST)

@ResponseBody
public void downloadByCode(@RequestBody final InputData request, final HttpServletResponse response)
{
    try
    {
        String codes = request.getName();

        // ...
        // some processings here to create the result
        // ....

        final ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(result);
        // Flush the result
        outputStream.flush();
    }
    catch (final Exception exception)
    {
        LOG.debug("Exception Thrown [downloadByCode]", exception);
    }
}

这是jQuery的Ajax请求:
$.ajax({
   url:"/downloadByCode",
   contentType:"application/json",
   type:"POST",
   data: JSON.stringify({"name":"value"}) });

1
您可以使用org.springframework.http.HttpEntity<String>作为请求类型,将请求体作为字符串传递。以下是使用您的代码作为基础的示例:
@RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
@ResponseBody
public void downloadByCode(final HttpEntity<String> request, final HttpServletResponse response)
{
    try
    {
        final ObjectMapper objectMapper = new ObjectMapper();
        final JsonNode jsonRequest = objectMapper.readValue(request.getBody(), JsonNode.class);

        // ...
        // some processings here to create the result
        // ....

        final ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(result);
        // Flush the result
        outputStream.flush();
    }
    catch (final Exception exception)
    {
        LOG.debug("Exception Thrown [downloadByCode]", exception);
    }
}

如果你计划返回字符串值作为结果,那么使用String作为返回类型可能会更好,像这样:

@RequestMapping (value = "/downloadByCode", method = RequestMethod.POST)
@ResponseBody
public String downloadByCode(HttpEntity<String> request) {
    String requestBody = request.getBody();
    String result;

    // ...
    // some processings here to create the result text
    // ....

    return result;
}

我使用Spring Boot制作了一个简单的应用程序,并使用HttpEntity提出的解决方案以及使用POJO的其他示例。要运行此应用程序,您需要安装Maven和JDK >= 1.7。
#clonning repository with sample
git clone git@github.com:mind-blowing/samples.git

#change current folder to sample application root
cd samples/spring-boot/rest-handler-for-plain-text

#running application using maven spring-boot plugin
mvn spring-boot:run

应用程序启动后,您可以打开http://localhost:8080,您将看到一个带有JQuery的简单使用方式的HTML页面,用于发送POST请求,请求和响应的文本将在HTML页面上可见,在控制器中,我添加了两个处理程序,第一个使用HttpEntity,第二个使用POJO。

控制器:SampleRestController.java

HTML页面:index.html

项目:https://github.com/mind-blowing/samples/tree/master/spring-boot/rest-handler-for-plain-text


我要试一试!谢谢 :) - Arashsoft
我收到了这个错误:不支持内容类型“application/x-www-form-urlencoded;charset=UTF-8” - Arashsoft
尝试像之前一样在js请求中使用contentType: "application/json"选项。$.ajax({ url: "/downloadByCode", contentType: "application/json", type: "POST", data: '{"name":"value"}' }).always(function (data) { alert(data); }); - Ivan M.
我使用 "application/json" contentType 进行测试,但是出现了以下错误:无法为类 [class java.lang.String] 实例化 JAXBContext: null; 嵌套异常为 javax.xml.bind.JAXBException\n - 附带链接的异常:\n[java.lang.NullPointerException] - Arashsoft
我同意你的看法,很可能是你的Spring配置有问题,不幸的是,如果没有查看它的能力,很难准确地说出问题所在。但是为了向您展示如何轻松实现我提出的使用Spring Boot处理REST请求的解决方案,我创建了一个示例应用程序,您可以在更新的答案中找到它,我已经在我的本地环境中验证过它可以正常工作。此外,我也曾遇到过相同的错误“Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported”,直到我添加了“contentType: "application/json"”后才正常工作。 - Ivan M.
显示剩余2条评论

1

尝试使用@RequestBody final Map<String, String> stringRequest

另外,您需要在@RequestMapping上添加consumes = "application/json",因为您在AJAX调用中使用了该内容类型

如果Spring不喜欢您发送Ajax的格式,则会收到400 - 我过去遇到了很多麻烦,似乎最好只在必要时才忽略标头类型和内容类型


谢谢您的回答。我已将我的请求正文更改为以下内容: public void downloadOrdersByCode(@RequestBody final Map<String, String> requestMap, final HttpServletResponse response) 但是,我仍然收到与上述相同的400错误。 - Arashsoft
你还需要我的回答中的 consumes 部分(或者从你的 ajax 调用中删除它)- 这样可以吗? - user4255955

1

您可以尝试将响应作为ResponseEntity发送,而不是直接使用HttpServletResponse。我的猜测是第二个参数,即HttpServletRequest参数,是导致问题的原因。我从未使用过它。我总是使用Spring MVC API发送响应。


谢谢您的回复,问题是它从未到达响应阶段。我的意思是,当它尝试以JSON作为输入接收时,它会生成错误。我的响应是一个CSV文件,我认为当前代码应该很好,因为它在使用GET方法时完美地工作。 - Arashsoft

1
使用Jersey api,您可以尝试: @POST public void downloadByCode(String stringRequest)
我认为您会发现您的帖子正文在stringRequest中。

谢谢,如果我不能在没有额外API的情况下解决它,也许我会尝试一下。 - Arashsoft

0

我对Java并不是很精通,但据我所知,你的Java代码应该是这样的。

public class downloadByCode{
  @GET
  @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
  public Response downloadByCode(@QueryParam("paramater1") final String parameter 1, @Context HttpServletRequest httpRequest) {

如果这不起作用,您可以将代码保存在某个地方并分享它。

我认为这段代码是基于GET方法的,我想使用POST方法获取JSON对象(或JSON字符串)。 - Arashsoft

0

删除您的downloadByCode方法上的@ResponseBody


没有改变,我认为问题与获取JSON输入有关。输出部分看起来很好。 - Arashsoft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接