스프링 Restful MessageConverter Ajax jQuery #1 : 스프링프레임워크 Spring Framework #1
스프링 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