关于spring:将JSON发布到REST API

Posting JSON to REST API

我正在创建一个RESTAPI,它将接受JSON请求。

我用卷发测试它:

1
curl -i -POST -H 'Accept: application/json' -d '{"id":1,"pan":11111}' http://localhost:8080/PurchaseAPIServer/api/purchase

< BR>但出现以下错误:

1
2
3
4
5
6
7
HTTP/1.1 415 Unsupported Media Type
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 1051
Date: Wed, 25 Apr 2012 21:36:14 GMT

The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().

调试时,它甚至不会进入控制器中的创建操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.app.model.Purchase;
import com.app.service.IPurchaseService;

@Controller
public class PurchaseController {

    @Autowired
    private IPurchaseService purchaseService;

    @RequestMapping(value ="purchase", method = RequestMethod.GET)
    @ResponseBody
    public final List<Purchase> getAll() {
        return purchaseService.getAll();
    }

    @RequestMapping(value ="purchase", method = RequestMethod.POST)
    @ResponseStatus( HttpStatus.CREATED )
    public void create(@RequestBody final Purchase entity) {
        purchaseService.addPurchase(entity);
    }
}

更新

我在appconfig.java中添加了jackson config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@ComponentScan(basePackages ="com.app")
public class AppConfig {

    @Bean
    public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
    {
        final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();

        HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };

        String[] supportedHttpMethods = {"POST","GET","HEAD" };

        annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
        annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

        return annotationMethodHandlerAdapter;
    }
}

我的装备现在工作正常:

1
2
3
4
5
6
7
8
9
curl -i -H"Content-Type:application/json" -H"Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT

[{"id":1,"pan":111}]

但我在尝试发帖时得到了以下信息:

1
2
3
4
5
6
7
8
9
10
curl -i -X POST -H"Content-Type:application/json" -H"Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d"{"id":2,"pan":122}"

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close

The request sent by the client was syntactically incorrect ().

我的模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Entity
@XmlRootElement
public class Purchase implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 6603477834338392140L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private Long pan;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getPan() {
        return pan;
    }

    public void setPan(Long pan) {
        this.pan = pan;
    }

}

知道我哪里出错了吗?

谢谢


正如SDouglass建议的那样,SpringMVC自动检测Jackson并设置一个MappingJacksonHTTPMessageConverter来处理与JSON之间的转换。但我确实需要明确地配置转换器,以使其正常工作,正如他所指出的。

我添加了以下内容,而我的curl get请求正在工作..万岁。

应用配置.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@ComponentScan(basePackages ="com.app")
public class AppConfig {

    @Bean
    public AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter()
    {
        final AnnotationMethodHandlerAdapter annotationMethodHandlerAdapter = new AnnotationMethodHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();

        HttpMessageConverter<?>[] httpMessageConverter = { mappingJacksonHttpMessageConverter };

        String[] supportedHttpMethods = {"POST","GET","HEAD" };

        annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
        annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

        return annotationMethodHandlerAdapter;
    }
}

< BR>

1
2
3
4
5
6
7
8
9
curl -i -H"Content-Type:application/json" -H"Accept:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 26 Apr 2012 21:19:55 GMT

[{"id":1,"pan":111}]

但是下面的curl-post仍然不起作用(从不点击控制器操作,也不提供控制台调试信息)。

1
2
3
4
5
6
7
8
9
10
curl -i -X POST -H"Content-Type:application/json"  http://localhost:8080/PurchaseAPIServer/api/purchaseMe -d"{"id":2,"pan":122}"

HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 971
Date: Thu, 26 Apr 2012 21:29:56 GMT
Connection: close

The request sent by the client was syntactically incorrect ().

所以我添加了logback以开始一些详细的调试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<configuration>

   
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>

   
        <file>/home/thomas/springApps/purchaseapi.log</file>
        <encoder>
            <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n
            </pattern>
        </encoder>
    </appender>

    <logger name="org.hibernate" level="DEBUG" />

    <logger name="org.springframework" level="TRACE" />
    <logger name="org.springframework.transaction" level="INFO" />
    <logger name="org.springframework.security" level="INFO" /> <!-- to debug security related issues (DEBUG) -->
    <logger name="org.springframework.web.servlet.mvc" level="TRACE" /> <!-- some serialization issues are at trace level here: org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod -->

    <!-- our service -->
    <logger name="com.app" level="DEBUG" />
    <!-- <logger name="com.app" level="INFO" /> --><!-- to follow if setup is being executed -->

    <root level="INFO">
       
    </root>

</configuration>

将跟踪级调试添加到org.springframework.web.servlet.mvc中给出了问题的答案。

1
2
3
4
5
6
7
2012-04-28 14:17:44,579 DEBUG [http-bio-8080-exec-3] o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor [AbstractMessageConverterMethodArgumentResolver.java:117] Reading [com.app.model.Purchase] as"application/json" using [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter@74a14fed]
2012-04-28 14:17:44,604 TRACE [http-bio-8080-exec-3] o.s.w.s.m.m.a.ServletInvocableHandlerMethod [InvocableHandlerMethod.java:159] Error resolving argument [0] [type=com.app.model.Purchase]
HandlerMethod details:
Controller [com.app.controller.PurchaseController]
Method [public void com.app.controller.PurchaseController.create(com.app.model.Purchase)]

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unexpected character ('p' (code 112)): was expecting double-quote to start field name

我把我的卷发改成了下面的一个,一切都很好:

1
2
3
4
5
curl -i -X POST -H"Content-Type:application/json" http://localhost:8080/PurchaseAPIServer/api/purchase -d '{"pan":11111}'
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Sat, 28 Apr 2012 13:19:40 GMT

希望有人觉得这很有用。


如果我没记错的话,Spring文档会说Spring MVC将自动检测类路径上的Jackson,并设置一个mappingJacksonHTTPMessageConverter来处理与JSON之间的转换,但是我认为我遇到过这样的情况,我必须手动/明确地配置该转换器以获取工作中的事情。您可以尝试将此添加到MVC配置XML中:

1
2
3
4
5
6
7
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
        </list>
    </property>
</bean>

更新:正是这个加上正确格式化发布的JSON,请参见https://stackoverflow.com/a/10363876/433789


2014年,我想对这个问题进行一些更新,帮助我解决了同样的问题。

  • 更新代码以替换Spring3.2中已弃用的AnnotationMethodHandlerAdapter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        @Configuration
        public class AppConfig {

    [cc]    @Bean
        public RequestMappingHandlerAdapter  annotationMethodHandlerAdapter()
        {
            final RequestMappingHandlerAdapter annotationMethodHandlerAdapter = new RequestMappingHandlerAdapter();
            final MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter();

            List<HttpMessageConverter<?>> httpMessageConverter = new ArrayList<HttpMessageConverter<?>>();
            httpMessageConverter.add(mappingJacksonHttpMessageConverter);

            String[] supportedHttpMethods = {"POST","GET","HEAD" };

            annotationMethodHandlerAdapter.setMessageConverters(httpMessageConverter);
            annotationMethodHandlerAdapter.setSupportedMethods(supportedHttpMethods);

            return annotationMethodHandlerAdapter;
        }
    }

    [/cc]

  • HTTP/1.1 415不支持的媒体类型错误

  • 在花了很多小时试图弄明白为什么即使添加了正确的JSON配置,我仍然会得到415个错误之后,我最终意识到问题不在服务器端,而是在客户机端。为了让Spring接受JSON,您必须确保将"content-type:application/json"和"accept:application/json"作为HTTP头的一部分发送。具体来说,它是一个Android应用程序httpurlConnection,我必须将其设置为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
        public static String doPost(final String urlString,final String requestBodyString) throws IOException {
            final URL url = new URL(urlString);

            final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            try {
              urlConnection.setReadTimeout(10000 /* milliseconds */);
              urlConnection.setConnectTimeout(15000 /* milliseconds */);
              urlConnection.setRequestProperty("Content-Type","application/json");
              urlConnection.setRequestProperty("Accept","application/json");
              urlConnection.setDoOutput(true);
              urlConnection.setRequestMethod("POST");
              urlConnection.setChunkedStreamingMode(0);

              urlConnection.connect();

              final PrintWriter out = new PrintWriter(urlConnection.getOutputStream());
              out.print(requestBodyString);
              out.close();

              final InputStream in = new BufferedInputStream(urlConnection.getInputStream());
              final String response =  readIt(in);

              in.close(); //important to close the stream

              return response;

            } finally {
              urlConnection.disconnect();
            }
        }


    这里有一个单元测试解决方案,类似于YoramGivon的答案——https://stackoverflow.com/a/22516235/1019307。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class JSONFormatTest
    {
        MockMvc mockMvc;

        // The controller used doesn't seem to be important though YMMV
        @InjectMocks
        ActivityController controller;  

        @Before
        public void setup()
        {
            MockitoAnnotations.initMocks(this);

            this.mockMvc = standaloneSetup(controller).setMessageConverters(new MappingJackson2HttpMessageConverter())
                    .build();
        }

        @Test
        public void thatSaveNewDataCollectionUsesHttpCreated() throws Exception
        {
            String jsonContent = getHereJSON02();
            this.mockMvc
                    .perform(
                         post("/data_collections").content(jsonContent).contentType(MediaType.APPLICATION_JSON)
                                    .accept(MediaType.APPLICATION_JSON)).andDo(print()).andExpect(status().isCreated());
        }

        private String getHereJSON01()
        {
            return"{"dataCollectionId":0,"name":"Sat_016","type":"httpUploadedFiles"," ...
        }
    }

    运行单元测试,print()应该打印出mockhttpservletrequest,包括异常。

    在Eclipse中(不确定如何在其他IDE中这样做),单击异常链接,将打开该异常的属性对话框。勾选"已启用"框以打破该异常。

    调试单元测试,Eclipse将在异常时中断。检查它会发现问题。在我的例子中,这是因为我的JSON中有两个相同的实体。


    我也遇到了同样的问题,通过我的代码中的两个更改解决了这个问题:

  • 方法参数中缺少@pathvariable,我的方法没有
  • 我的SpringConfig类中的以下方法,因为我在处理程序拦截器中使用的方法被弃用,并给出了一些问题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public RequestMappingHandlerAdapter RequestMappingHandlerAdapter()
    {
        final RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        final MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
        final String[] supportedHttpMethods = {"POST","GET","HEAD" };

        requestMappingHandlerAdapter.getMessageConverters().add(0, mappingJacksonHttpMessageConverter);
        requestMappingHandlerAdapter.setSupportedMethods(supportedHttpMethods);

        return requestMappingHandlerAdapter;
    }


  • 尝试添加post请求中的描述符。也就是说,在EDOCX1[0]中添加标题:

    1
    Content-Type: application/json

    如果不添加,不管实际发送什么,curl都将使用默认的text/html

    另外,在PurchaseController.create()中,您必须添加接受的类型是application/json


    我也有同样的问题,我解决了它。

    1如该线程中所述,添加mappingjackson2httpmessageconverter(另请参见第4节http://www.baeldung.com/spring-httpmessageconverter-rest)

    2使用正确的命令(带转义符号):

    1
    curl -i -X POST -H"Content-Type:application/json" -d"{"id":"id1","password":"password1"}" http://localhost:8080/user

    尝试在应用程序配置中添加以下代码

    1
    2
    3
    4
    5
    6
    <mvc:annotation-driven>
      <mvc:message-converters>
          <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
              <property name="objectMapper" ref="jacksonObjectMapper" />
          </bean>
      </mvc:message-converters>


    我经历过一次,最后通过添加jar文件jackson-mapper-asl.jar解决了这个问题。检查是否包含了所有这些依赖项,尽管异常本身并没有告诉您这一点。

    而且您确实不需要显式地配置bean,也不需要在@requestmapping语句中放置"consumes"。我用的是弹簧3.1 btw。

    contenttype:"application/json"是唯一需要配置的。是的,客户方。