Full Stack Web Developer.
Syaku (샤쿠)

Java, JS (ES6+), Spring, Spring security, jQuery, Reactjs, Bootstrap.

        

08-04 05:26


Spring Boot 2 보기 Front-end 보기 DevOps 보기 Spring 3 보기 Spring Security 3 보기

스프링 Restful MessageConverter Ajax jQuery #1 : 스프링프레임워크 Spring Framework #1

written by Seok Kyun. Choi. 최석균

스프링 Restful MessageConverter

개발환경

Mac OS X 10.9.4
JAVA 1.6
Apache Tomcat 7.x
Spring 3.1.1
Spring Tool Suite 3.5.1
Maven 2.5.1
jQuery 1.11.0


** pom.xml dependency 목록이 빠졌네요;;; xml과 json 필요한 라이브러리를 추가해주셔야합니다. 나중에 다시 정리해서 추가하겠습니다~

스프링에서 Restful 을 어떻게 활용할 수 있는 지 알아본다. 참고로 Restful이 도입된 시기가 오래되지 않아 스프링의 버전마다 지원방식이 조금씩 다르다.
그리고 왠만하면 콘솔이나 터미널 창에서 curl 명령어로 테스트 해보고, RestfulClient 는 정확한 사용법을 알고 사용한다. RestfulClient 의해 잘못된 요청으로 오류가 발생할 수 있기 때문이다.

RequestMethod 가 PUT, DELETE 인 경우 일반적인 방법으로 요청 데이터(parameter)를 얻을 수 없다. 그래서 @RequestBody 를 사용해야 한다.

스프링에서 기본적으로 Restful 을 지원하기 때문에 추가적인 설정없이 컨트롤러에 어노테이션을 추가하여 구현할 수 있다.

@소스 Context-Servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
     xmlns="http://www.springframework.org/schema/mvc"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:beans="http://www.springframework.org/schema/beans"
     xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="
     http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     ">

     <annotation-driven />

     <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <beans:property name="prefix" value="/WEB-INF/views/sample/" />
          <beans:property name="suffix" value=".jsp" />
     </beans:bean>

     <context:component-scan base-package="com.syaku.sample.restful" />
</beans:beans>

@소스 RestfulController.java

package com.syaku.sample.restful;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;

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;

@Controller
@RequestMapping(value = "/restful")
public class RestfulController {
     private static final Logger logger = LoggerFactory.getLogger(RestfulController.class);

     // jQuery Restful 요청페이지
     @RequestMapping(value = "/", method = RequestMethod.GET)
     public String dispRestfulView(Model model) {
          return "restful";
     }

     Restful 메서드 삽입...

}

다음부터 컨트롤러 클래스는 생략하고 메서드 소스만 작성하겠다.
예제 소스에 method = RequestMethod 는 의미에 맞게 작업한 것이 아니라 테스트로 이것저것 넣어보았습니다.

우선 사용되는 어노테이션과 속성에 대해 알아보겠다. 단 Restful 에서 사용되는 정보에 대한 것만 설명하겠다.

@RequestMapping
컨트롤러에 기본이 되는 요청정보를 설정하는 어노테이션이다.
Accept 와 content-Type 에 대한 접근 허용을 사용하기 위해 header 라는 속성을 사용했다면, 3.1부터는 consumes 와 produces 로 나눠 사용할 수 있다.

@RequestMapping(consumes = “application/xml”, produces = {"application/xml” , "application/json"} )

consumes 는 content-Type 의 접근 허용을 설정한다.
produces 는 accept 의 접근 허용을 설정한다.

여러 타입을 설정할 경우 { } 배열로 설정한다.

@RequestBody
미디어 타입(content-type) 요청에 맞게 메세지컨버터가 선택되고 컨버팅(DATA,XML,JSON to/from Object) 된다.

@ResponseBody
미디어 타입(Accept) 요청에 맞게 메세지컨버터가 선택되어 응답하게 된다.

메세지컨버터들은 AnnotationMethodHandlerAdapter 에 의해 등록되게 되는 데 3.1 부터 RequestMappingHandlerAdapter 대처되었다.
스프링에서 기본적으로 지원하는 메세지컨버터는 아래와 같고 몇가지만 예제로 설명하겠다.

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • ResourceHttpMessageConverter
  • SourceHttpMessageConverter
  • FormHttpMessageConverter
  • Jaxb2RootElementHttpMessageConverter
  • MappingJackson2HttpMessageConverter
  • MappingJacksonHttpMessageConverter
  • AtomFeedHttpMessageConverter
  • RssChannelHttpMessageConverter

참고 : http://springsource.tistory.com/89

StringHttpMessageConverter

모든 요청의 미디어 타입을 허용하며, 지원하는 객체는 strings 이다. 응답 미디어 타입은 text/plain 이다.

@RequestMapping(value = "/StringHttpMessageConverter", method = RequestMethod.POST)
public @ResponseBody String procRestfulStringHttpMessageConverter(@RequestBody String body) {
     logger.info("Syaku Restful RequestBody : " + body);

     return body;
}

Request

curl -i http://localhost:8080/restful/StringHttpMessageConverter -X POST -d "모든 미디어 타입을 지원하며 응답은 text/plain 형식으로 한다."

Response

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 39
Date: Wed, 13 Aug 2014 04:56:14 GMT
?? ??? ??? ???? ??? text/plain ???? ??.

응답에 글자가 깨져서 출력된다. 이럴때는 curl 에 옵션을 하나 추가하면 된다. -H "Accept:text/plain;charset=utf-8"

FormHttpMessageConverter

application/x-www-form-urlencoded 미디어 타입을 허용하며, 지원하는 객체는 MultiValueMap<String, String> 이다. 응답 미디어 타입은 application/json 이다. 파라메터 형식으로 데이터를 요청해야 한다. 예) no=1&user_id=syaku&name=최석균

@RequestMapping(value = "/MultiValueMap", method = RequestMethod.PUT)
public @ResponseBody Map procRestfulMap(@RequestBody MultiValueMap<String, String> body) {
     logger.info("Syaku Restful RequestBody : " + body.toString());

     return body;
}

Request

curl -i http://localhost:8080/restful/MultiValueMap -X PUT -d "no=1&user_id=syaku&name=최석균" -H "content-Type:application/x-www-form-urlencoded" -H "accept:application/json"

Response

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 13 Aug 2014 05:28:52 GMT
{“no”:[“1”],”user_id”:[“syaku”],”name”:[“최석균”]}

MappingJacksonHttpMessageConverter

application/json 미디어 타입을 허용한다. 지원하는 객체는 자바빈이나 HashMap 이다.
json 형식 미디어 타입의 데이터를 객체를 자바빈이나 Map 에 담아 주고, 자바빈이나 HashMap 의 객체를 json 형식으로 응답 해주는 역활을 한다.

요청 데이터를 심플하게 다루고 싶을 때 사용하면 유용할 것이다.

@RequestMapping(value = "/json", method = RequestMethod.POST)
public @ResponseBody Map procRestfulJson(@RequestBody Map body) {
     logger.info("Syaku Restful RequestBody : " + body.toString());

     return body;
}

Request

curl -i http://localhost:8080/restful/json -X POST -d "{\"no\":1 , \"user_id\":\"syaku\" , \"name\":\"최석균\"}" -H "content-Type:application/json" -H "accept:application/json"

Response

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 13 Aug 2014 06:09:49 GMT
{“no”:1,”user_id”:”syaku”,”name”:”최석균”}

Map 을 이용한 경우 이고 자바빈을 이용할 경우 아래와 같다.

@RequestMapping(value = "/json2", method = RequestMethod.PUT)
public @ResponseBody Foo procRestfulJson2(@RequestBody Foo foo) {
     logger.info("Syaku Restful RequestBody : " + foo.toString());

     return foo;
}

@소스 Foo.java

package com.syaku.sample.restful;

public class Foo {

     int no;
     String user_id;
     String name;

     public void setNo(int no) { this.no = no; }
     public int getNo() { return this.no; }

     public void setUser_id(String user_id) { this.user_id = user_id; }
     public String getUser_id() { return this.user_id; }

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

Request

curl -i http://localhost:8080/restful/json2 -X PUT -d "{\"no\":1 , \"user_id\":\"syaku\" , \"name\":\"최석균\"}" -H "content-Type:application/json" -H "accept:application/json"

Response

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 13 Aug 2014 06:12:19 GMT
{“name”:”최석균”,”user_id”:”syaku”,”no”:1}

Jaxb2RootElementHttpMessageConverter

application/xml 미디어 타입을 허용한다. 지원하는 객체는 JAXB2의 어노테이션이 있는 클래스에 변환한다.
MappingJacksonHttpMessageConverter 함께 사용하여 xml -> json 이나 json -> xml 과 같은 결과를 만들수 있다.

Jaxb2RootElementHttpMessageConverter 사용하기 위해서는 필수적으로 자바빈 클래스가 있어야 한다. 기존에 Foo 클래스에 어노테이션을 추가하여 작업하도록 한다.

@RequestMapping(value = "/xml", method = RequestMethod.DELETE)
public @ResponseBody Foo procRestfulXml(@RequestBody Foo foo) {
     logger.info("Syaku Restful RequestBody : " + foo.toString());

     return foo;
}

이전 소스와 다를게 없다…

@소스 Foo.java

package com.syaku.sample.restful;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "result")
public class Foo {

     @XmlElement
     int no;
     @XmlElement
     String user_id;
     @XmlElement
     String name;

     생략 ...
}

Request

curl -i http://localhost:8080/restful/xml -X DELETE -d "{\"no\":1 , \"user_id\":\"syaku\" , \"name\":\"최석균\"}" -H "content-Type:application/json" -H "accept:application/xml"

Response

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Wed, 13 Aug 2014 06:55:05 GMT
<?xml version=”1.0” encoding=”UTF-8” standalone=”yes”?><result><no>1</no><user_id>syaku</user_id><name>최석균</name></result>

@XmlAccessorType(XmlAccessType.FIELD) 없을 경우 아래와 같은 오류가 발생한다.

Servlet.service() for servlet [appServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Could not instantiate JAXBContext for class [class com.syaku.domain.Foo]: 1 counts of IllegalAnnotationExceptions; nested exception is com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
Class has two properties of the same name "name"

참고 : http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html

jQuery Restful 요청하기

@소스 restful.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="utf-8">
        <title>스프링프레임워크 Restful</title>
        <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
        <script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
    </head>
    <body>

        <script type="text/javascript">

        $(function() {
            var json = "{\"no\":1 , \"user_id\":\"syaku\" , \"name\":\"최석균\"}";
            var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><result><no>1</no><user_id>syaku</user_id><name>최석균</name></result>";
            var param = "name=최석균&no=1&user_id=syaku";

            $("#string").click(function() {
                $.ajax({
                    url : '/restful/StringHttpMessageConverter',
                    data: param,
                    type: 'POST',
                    dataType : 'text',
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader("Accept", "text/plain; charset=UTF-8");
                        xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
                    }
                }).done(function(body) {
                    $('#response').val(body);
                });

            });

            $("#map").click(function() {
                $.ajax({
                    url : '/restful/MultiValueMap',
                    data: param,
                    type: 'PUT',
                    dataType : 'text',
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader("Accept", "application/json");
                    }
                }).done(function(body) {
                    $('#response').val(body);
                });

            });


            $("#json").click(function() {
                $.ajax({
                    url : '/restful/json',
                    data: json,
                    type: 'POST',
                    dataType : 'text',
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader("Accept", "application/json");
                        xhr.setRequestHeader("Content-Type", "application/json");
                    }
                }).done(function(body) {
                    $('#response').val(body);
                });

            });

            $("#json2").click(function() {
                $.ajax({
                    url : '/restful/json2',
                    data: json,
                    type: 'PUT',
                    dataType : 'text',
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader("Accept", "application/json");
                        xhr.setRequestHeader("Content-Type", "application/json");
                    }
                }).done(function(body) {
                    $('#response').val(body);
                });

            });

            $("#xml").click(function() {
                $.ajax({
                    url : '/restful/xml',
                    data: xml,
                    type: 'DELETE',
                    dataType : 'text',
                    beforeSend: function(xhr) {
                        xhr.setRequestHeader("Accept", "application/xml");
                        xhr.setRequestHeader("Content-Type", "application/xml");
                    }
                }).done(function(body) {
                    $('#response').val(body);
                });

            });


        });

        </script>

        <div>
        <button id="string">StringHttpMessageConverter</button>

        <button id="map">FormHttpMessageConverter</button>

        <button id="json">MappingJacksonHttpMessageConverter</button>
        <button id="json2">MappingJacksonHttpMessageConverter2</button>

        <button id="xml">Jaxb2RootElementHttpMessageConverter</button>
        </div>
        <div>
        <textarea rows="10" cols="150" id="response"></textarea>
        </div>

    </body>
</html>

http://localhost:8080/restful/ 접속하여 테스트할 수 있다.

Spring 3.1 Restful 참고 사이트

http://www.techiekernel.com/2012/12/restful-web-service-with-spring-31.html


posted syaku blog

Syaku Blog by Seok Kyun. Choi. 최석균.

http://syaku.tistory.com


  • Imovator at 2014.10.29 13:36 신고

    안녕하세요. 궁금한게 있어서요~
    DELETE메서드로 호출하면 컨트롤러에서 xml로만 받을 수 있나요~?
    GET, POST는 잘 되는데 delete는 unsupported media type 메시지가 뜨네요 ㅠ ㅠ

    • 샤쿠 syaku at 2014.10.29 13:47 신고

      넵 잘됩니다. 다만 본 포스팅에는 빠진 내용이 있습니다. pom.xml 에 라이브러를 추가하는 내용인데요. 아마 그것이 없어 오류가 나는 것 같네요;; pom.xml 에 아래의 라이브러를 추가해보겠습니까??

      <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.4.2</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>${org.springframework-version}</version>
      </dependency>
      <dependency>
      <groupId>com.thoughtworks.xstream</groupId>
      <artifactId>xstream</artifactId>
      <version>1.4.7</version>
      </dependency>



      jackjson : json

      위 문제가 아니라면 미디어 타입 설정이 잘못되었던가 파라메터를 컨버터하는 빈(vo,dto)이 잘못된것 같네요~

  • 삽질중 at 2015.06.26 02:28

    안녕하세요... 4시간째 삽질중에.. 질문하나 드리겠습니다.

    MappingJacksonHttpMessageConverter 사용하고,,

    var json = "{\"no\":1 , \"user_id\":\"syaku\" , \"name\":\"최석균\"}";

    $("#json").click(function() {
    $.ajax({
    url : '/restful/json',
    data: json,
    type: 'POST',
    dataType : 'text',
    beforeSend: function(xhr) {
    xhr.setRequestHeader("Accept", "application/json");
    xhr.setRequestHeader("Content-Type", "application/json");
    }
    }).done(function(body) {
    $('#response').val(body);
    });

    });

    이 AJAX 코드 사용해서 돌려봤는데 정상으로 돌아갔습니다.

    그런데, var json 값을 제가 작성한 form으로 변경시켜서
    var json=$("#join_form").serialize();
    로 변경시키니깐 400 Error가 나오면서 Bad request가 뜨네요..

    다른 설정을 추가로해줘야할게있나요..?

    삽질중 지쳐서 질문드립니다..

    • 샤쿠 syaku at 2015.06.26 16:36 신고

      400 에러는 서버에 데이터는 전송이 되었으나 바인딩과정에서 오류가 발생할때 생기는 에러일 경우가 대부분입니다.

      serialize 를 하시면 파라메터형식으로 데이터가 완성되기때문에 서버에서는 json type으로 받을수가 없습니다~

      그래서 json type 으로 전송해야만 합니다. 그래서 추천드리는 라이브러리는 jquery.serializeJSON 사용하시면 폼에있는 데이터를 읽어 json 객체로 만듭니다~

      var json = $('#join_form').serializeJSON();

      전송할때는 json 문법의 string 형식이여야합니다.

      data: JSON.stringify( json ) // json to string

      하시면 될것입니다

      jquery.serializeJSON: https://github.com/marioizquierdo/jquery.serializeJSON

  • 삽질중 at 2015.06.26 19:39

    답변 감사드립니다!

  • livewire at 2015.12.08 18:26

    안녕하세요~ ! 한가지 물어봐도 될까요? !

    google buff로 보내고 받고 싶은데요 잘 모르겠습니다.

    @RequestMapping(value="/protobuf", method = RequestMethod.POST, produces = "application/x-protobuf") //
    public void Protobuf(@RequestBody byte[] body)throws IOException
    {
    GoogleData data = GoogleData.parseFrom(body)
    }

    이렇게 하면 body에 값이 안들어오고 415 에러 발생합니다.

    그래서 produces = "application/x-protobuf를 produces = "application/octet-stream로 변경하면
    body에 byte 값이 들어오긴 하는데 goolg buff parse 가 안되는군요 ㅠㅠ

    protobuf 를 @RequesstMapping 에서 어떻게 사용하는지 모르겠습니다. 혹시 알고 계시면 조언부탁드립니다.

  • JSM at 2017.03.23 13:39

    좋은글 감사합니다.ㅎㅎㅎ

댓글 남기기
◀ PREV 1···9293949596979899100···313 NEXT ▶