자매 기사인 "Build RESTful web services using Spring 3"(참고자료 참조)에서는 RESTful 웹 서비스를 빌드하는
"Spring 방법"을 소개하였다. 이는 RESTful 웹 서비스의 중요한 기능인 여러 표현을 제작하기 위해 ContentNegotiatingViewResolver를 사용하는
방법도 설명했다. 이 기사에서는 HttpMessageConverter를 사용하여 여러 표현을 제작하는 또다른 방법과
HttpMessageConverter로 서비스와 통신하기 위해 RestTemplate를 사용하는 방법을 보여주는 기사의 예제를
설명한다.
이 섹션에서는 주요 Spring 기능의 개요 또는 RESTful 웹 서비스를 지원하는 어노테이션을 제공한다.
@Controller@Controller어노테이션을 사용하여 MVC에서 컨트롤러가 되고 HTTP 요청을 처리할 클래스의 어노테이션을 작성한다.@RequestMapping@RequestMapping어노테이션을 사용하여 특정 HTTP 메소드, URI 또는 HTTP 헤더를 처리해야 하는 함수의 어노테이션을 작성한다. 이 어노테이션은 Spring REST 지원의 키이다. 사용자는method매개변수를 변경하여 다른 HTTP 메소드를 처리한다.예를 들면, 다음과 같다.
@RequestMapping(method=RequestMethod.GET, value="/emps", headers="Accept=application/xml, application/json")@PathVariable- URI에서 경로 변수는
@PathVariable어노테이션을 사용하는 매개변수로 삽입될 수 있다.예를 들면, 다음과 같다.
@RequestMapping(method=RequestMethod.GET, value="/emp/{id}") public ModelAndView getEmployee(@PathVariable String id) { … }
- 다른 유용한 어노테이션
-
@RequestParam을 사용하여 URL 매개변수를 메소드로 삽입한다.@RequestHeader를 사용하여 특정 HTTP 헤더를 메소드로 삽입한다.@RequestBody를 사용하여 HTTP 요청 본문을 메소드로 삽입한다.@ResponseBody를 사용하여 HTTP 응답 본문으로 내용이나 오브젝트를 리턴한다.HttpEntity<T>가 매개변수로 제공되는 경우 이를 사용하여 자동으로 메소드로 삽입한다.ResponseEntity<T>를 사용하여 사용자 정의 상태나 헤더로 HTTP 응답을 리턴한다.예를 들면, 다음과 같다.
public @ResponseBody Employee getEmployeeBy(@RequestParam("name") String name, @RequestHeader("Accept") String accept, @RequestBody String body) {…} public ResponseEntity<String> method(HttpEntity<String> entity) {…}
메소드에 삽입 가능한 지원되는 어노테이션 또는 오브젝트의 전체 목록은 Spring 문서를 참조한다(참고자료 참조).
서로 다른 MIME 유형으로 동일한 자원을 표현하는 것은 RESTful 웹 서비스의 중요한 부분이다. 일반적으로 서로 다른 "accept" HTTP 헤더로 동일한 URI를 사용하여 다른 표현을 사용하는 자원을 페치할 것이다. 또한 다른 URI나 다른 요청 매개변수를 사용하는 URI를 사용할 수 있다.
"Build RESTful web services using Spring 3"(참고자료 참조)에서는
동일한 URI를 처리하기 위해 다른 뷰 리졸버를 취할 수 있는 ContentNegotiatingViewResolver를
소개하였다(accept 헤더의 차이 이용). 따라서 여러 표현을 제작하기 위해
ContentNegotiatingViewResolver를 사용할 수 있다.
또한 여러 표현을 제작하는 또다른 방법이 있다 — HttpMessageConverter와
c@ResponseBody 어노테이션의 결합 이용. 이 방법으로는 뷰 기술을 사용하지 않아도 된다.
HTTP 요청과 응답은 텍스트 기반인데, 이는 브라우저와 서버가 원시 텍스트를 교환하여 통신한다는 의미이다. 하지만 Spring을 사용하면
컨트롤러 클래스의 메소드는 순수 'String' 유형과 도메인 모델(또는 기타 Java 내장 오브젝트)을 리턴한다. 어떻게 Spring이 오브젝트를
원시 텍스트로 직렬하거나 직렬 취소할 수 있는가? 이는 HttpMessageConverter이 처리한다. Spring은
사용자의 일반적인 필요에 맞출 수 있는 구현을 번들했다. 표 1에 몇 가지 예제가 있다.
표 1. HttpMessageConverter 예제
| 사용 항목 | 가능한 작업 |
|---|---|
| StringHttpMessageConverter | 요청 및 응답에서부터 문자열 읽기/쓰기. 기본값으로 이는 text/* 매체 유형을 지원하고 text/plain의 내용 유형(Content-Type)으로 쓴다. |
| FormHttpMessageConverter | 요청 및 응답에서부터 양식 데이터 읽기/쓰기. 기본값으로 이는 application/x-www-form-urlencoded 매체 유형을 읽고 데이터를 MultiValueMap<String,String>으로 쓴다. |
| MarshallingHttpMessageConverter | Spring의 마샬러/언마샬러를 사용하여 XML 데이터 읽기/쓰기. 이는 application/xml 매체 유형의 데이터를 변환한다. |
| MappingJacksonHttpMessageConverter | Jackson의 ObjectMapper를 사용하여 JSON 데이터 읽기/쓰기. 이는 application/json 매체 유형의 데이터를 변환한다. |
| AtomFeedHttpMessageConverter | ROME의 피드 API를 사용하여 ATOM 피드 읽기/쓰기. 이는 application/atom+xml 매체 유형의 데이터를 변환한다. |
| RssChannelHttpMessageConverter | ROME의 피드 API를 사용하여 RSS 피드 읽기/쓰기. 이는 application/rss+xml 매체 유형의 데이터를 변환한다. |
이 섹션에서는 여러 표현을 제작할 수 있는 간단한 RESTful 웹 서비스를 빌드하는 것을 학습한다. 샘플에 사용된 일부 자원은 "Build RESTful web services using Spring 3"(참고자료 참조)에서 빌드되었다. 또한 샘플 코드도 다운로드할 수 있다.
먼저 HttpMessageConverter를 구성해야 한다. 여러 표현을 제작하려면
몇 가지 HttpMessageConverter 인스턴스를 사용자 정의하여 오브젝트를 서로 다른 매체 유형으로 변환한다. 이 섹션에서는 JSON, ATOM
및 XML 매체 유형을 다룬다.
가장 간단한 예제로 시작하자. JSON은 경량 데이터 상호 교환 형식으로 사람이 읽고 쓰기에 쉽다. 목록 1에서는 JSON 변환기를 구성하는 코드를 보여준다.
목록 1. rest-servlet.xml에서 HttpMessageConverter 구성
<bean class="org.springframework.web.servlet.mvc.annotation
.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonConverter" />
<ref bean="marshallingConverter" />
<ref bean="atomConverter" />
</list>
</property>
</bean>
<bean id="jsonConverter"
class="org.springframework.http.converter.json
.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean> |
구성에서 세 가지 변환기가 등록된다.
MappingJacksonHttpMessageConverter는 오브젝트를 JSON으로 변환하고 그 반대의 경우에도 사용된다. 이 내장
변환기는 Jackson의 ObjectMapper를 사용하여 JSON을 JavaBean으로 맵핑한다. 따라서 다음
Jackson JAR 파일을 클래스경로에 추가해야 한다.
- org.codehaus.jackson.jar
- org.codehaus.jackson.mapper.jar
다음 단계는 JSON 표현을 요구하는 요청을 처리하는 메소드를 쓰는 것이다. 목록 2에서 세부사항을 보여준다.
목록 2. EmployeeController에서 정의된 JSON 요청 처리
@RequestMapping(method=RequestMethod.GET, value="/emp/{id}",
headers="Accept=application/json")
public @ResponseBody Employee getEmp(@PathVariable String id) {
Employee e = employeeDS.get(Long.parseLong(id));
return e;
}
@RequestMapping(method=RequestMethod.GET, value="/emps",
headers="Accept=application/json")
public @ResponseBody EmployeeListinggetAllEmp() {
List<Employee> employees = employeeDS.getAll();
EmployeeListinglist = new EmployeeList(employees);
return list;
}
|
@ResponseBody 어노테이션은 리턴 오브젝트(Employee 또는
EmployeeList)를 응답 본문 내용으로 만드는 데 사용되고, 이로 인해
MappingJacksonHttpMessageConverter 가 JSON으로 맵핑될 것이다.
HttpMessageConverter와
@ResponseBody를 사용하여 Spring의 뷰 기술을 포함시키지 않고, 여러 표현을 구현할 수 있다
-- ContentNegotiatingViewResolver의 사용을 능가하는 장점.
이제 CURL 또는 REST 클라이언트 Firefox 플러그인을 사용하여 요청을 호출할 수 있다. HTTP 헤더인
Accept=application/json를 추가시키는 것을 유의하자. 목록 3에는
JSON 형식에서 원하는 응답을 보여준다.
목록 3. getEmp() 및 getAllEmp()에 대한 JSON 결과
Response for /rest/service/emp/1
{"id":1,"name":"Huang Yi Ming","email":"huangyim@cn.ibm.com"}
Response for /rest/service/emps
{"count":2,
"employees":[
{"id":1,"name":"Huang Yi Ming","email":"huangyim@cn.ibm.com"},
{"id":2,"name":"Wu Dong Fei","email":"wudongf@cn.ibm.com"}
]}
|
Spring의 내장 변환기인
MarshallingHttpMessageConverter는 오브젝트와 XML(OXM) 사이에 맵핑하는 데 사용된다. 예제는 변환기에 대해 마샬러/언마샬러로 JAXB 2를 사용한다. 목록 4는
구성을 보여준다.
목록 4. MarshallingHttpMessageConverter 구성
<bean id="marshallingConverter"
class="org.springframework.http.converter.xml
.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml"/>
</bean>
<bean id="jaxbMarshaller"
class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>dw.spring3.rest.bean.Employee</value>
<value>dw.spring3.rest.bean.EmployeeList</value>
</list>
</property>
</bean>
|
JAXB 2에는 java.util.List<T>를 XML로 맵핑하는 훌륭한 지원이 없다는 것을 이해하는 것이 중요하다. 일반적인 실제 사례는 오브젝트의 콜렉션에 대해 랩퍼 클래스를 추가하는 것이다. "Build RESTful web services using Spring 3"(참고자료 참조)을 참조하거나 이 JAXB 어노테이트된 클래스의 세부사항에 대한 소스 코드를 다운로드하자.
요청을 처리하는 컨트롤러에서 메소드는 어떠한가? 목록 2의 코드를 살펴보자.
당연히 여기에 코드를 추가하지 않아도 된다는 사실을 알게 된다. 다음과 같이
Accept 헤더에 지원되는 또다른 매체 유형을 추가하기만 하면 된다.
headers=”Accept=application/json, application/xml”
|
변환기는 요청한 유형(JSON 또는 XML)에 적합하게 오브젝트를 맵핑할 것이다. 목록 5에는 application/xml 표현을 요청할 때에 원하는 결과를 보여준다.
목록 5. getEmp() 및 getAllEmp()에 대한 XML 결과
Response for /rest/service/emp/1
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employee>
<email>huangyim@cn.ibm.com</email>
<id>1</id>
<name>Huang Yi Ming</name>
</employee>
Response for /rest/service/emps
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<employees>
<count>2</count>
<employee>
<email>huangyim@cn.ibm.com</email>
<id>1</id>
<name>Huang Yi Ming</name>
</employee>
<employee>
<email>wudongf@cn.ibm.com</email>
<id>2</id><name>Wu Dong Fei</name>
</employee>
</employees>
|
ATOM 피드는 RESTful 웹 서비스에서 데이터를 교환하기 위한 또다른 대중적인 형식이다. Atom 피드 문서는 피드에 대한 메타데이터와 이와 연관된 일부 또는 모든 항목이 포함된 Atom
피드의 표현이다. 그 루트는 atom:feed 요소이다. 또한 교환 형식 및 작동을 정의하는 ATOM Publish Protocol(APP)도 있다. (ATOM 및 APP
형식을 정의하는 것은 이 기사의 범위를 벗어난다. 자세한 정보는 참고자료를 참조한다.)
예제는 AtomFeedHttpMessageConverter를 사용하여 ROME ATOM API를 활용하는 ATOM 피드를 변환한다. 따라서
JAR 파일인 sun.syndication.jar을 클래스경로에 포함시켜야 한다.
목록 6에는 이 변환기의 구성을 보여준다.
목록 6. AtomFeedHttpMessageConverter 구성
<bean id="atomConverter"
class="org.springframework.http.converter.feed
.AtomFeedHttpMessageConverter">
<property name="supportedMediaTypes" value="application/atom+xml" />
</bean>
|
목록 7에는 ATOM 요청과 피드 생성을 처리하는 코드를 보여준다.
목록 7. EmployeeController & AtomUtil 클래스에서 getEmpFeed()
@RequestMapping(method=RequestMethod.GET, value="/emps",
headers="Accept=application/atom+xml")
public @ResponseBody Feed getEmpFeed() {
List<Employee> employees = employeeDS.getAll();
return AtomUtil.employeeFeed(employees, jaxb2Mashaller);
}
public static Feed employeeFeed(
List<Employee> employees, Jaxb2Marshaller marshaller) {
Feed feed = new Feed();
feed.setFeedType("atom_1.0");
feed.setTitle("Employee Atom Feed");
List<Entry> entries = new ArrayList<Entry>();
for(Employee e : employees) {
StreamResult result = new StreamResult(
new ByteArrayOutputStream());
marshaller.marshal(e, result);
String xml = result.getOutputStream().toString();
Entry entry = new Entry();
entry.setId(Long.valueOf(e.getId()).toString());
entry.setTitle(e.getName());
Content content = new Content();
content.setType(Content.XML);
content.setValue(xml);
List<Content> contents = new ArrayList<Content>();
contents.add(content);
entry.setContents(contents);
entries.add(entry);
}
feed.setEntries(entries);
return feed;
}
|
상기 코드에서는 다음에 주목한다.
-
getEmpFeed()메소드는getAllEmp()와 동일한 URI를 처리하지만,Accept헤더가 다르다. -
employeeFeed()메소드를 통해Employee오브젝트를 XML로 마샬한 다음에 이를 피드 항목의<content>요소로 추가한다.
목록 8에는 URI /rest/service/emps에 대한 application/atom+xml 표현을 요청할 때에 결과물을 보여준다.
목록 8. application/atom+xml을 요청할 때에 /rest/service/emps에 대한 결과물
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Employee Atom Feed</title>
<entry>
<title>Huang Yi Ming</title>
<id>1</id>
<content type="xml">
<employee>
<email>huangyim@cn.ibm.com</email>
<id>1</id>
<name>Huang Yi Ming</name>
</employee>
</content>
</entry>
<entry>
<title>Wu Dong Fei</title>
<id>2</id>
<content type="xml">
<employee>
<email>wudongf@cn.ibm.com</email>
<id>2</id>
<name>Wu Dong Fei</name>
</employee>
</content>
</entry>
</feed>
|
지금까지 예제에서 HTTP
GET 메소드를 처리하기 위한 몇 가지 메소드를 구현했다. 목록 9에는 POST, PUT 및 DELETE
메소드의 구현을 보여준다.
목록 9. EmployeeController에서 POST, PUT 및 DELETE 메소드
@RequestMapping(method=RequestMethod.POST, value="/emp")
public @ResponseBody Employee addEmp(@RequestBody Employee e) {
employeeDS.add(e);
return e;
}
@RequestMapping(method=RequestMethod.PUT, value="/emp/{id}")
public @ResponseBody Employee updateEmp(
@RequestBody Employee e, @PathVariable String id) {
employeeDS.update(e);
return e;
}
@RequestMapping(method=RequestMethod.DELETE, value="/emp/{id}")
public @ResponseBody void removeEmp(@PathVariable String id) {
employeeDS.remove(Long.parseLong(id));
}
|
@RequestBody 어노테이션은
addEmp() 및
updateEmp() 메소드에 사용된다. 이는 HTTP 요청 본문을 취하고
등록된 HttpMessageConverter를 사용하여 오브젝트 클래스로 변환하려고 시도한다. 다음 섹션에서
이러한 서비스와 통신하는 RestTemplate를 사용할 것이다.
RestTemplate를 사용하여 REST 서비스와 통신
"Build RESTful web services using Spring 3"(참고자료 참조)은 REST 서비스를 테스트하기 위해
CURL 및 REST 클라이언트를 사용하는 방법에 대해 소개했다. 프로그래밍 레벨에서
Jakarta Commons의 HttpClient는 이를 수행하기 위해 일반적으로 사용된다(하지만 이는 이 기사의 범위를 벗어난다). RestTemplate라는 Spring REST 클라이언트도 사용할 수 있다. 이는
JdbcTemplate 및
JmsTemplate와 같이 Spring에서 다른 템플리트 클래스와 개념적으로 유사하다.
RestTemplate는 HttpMessageConverter도 사용한다. 요청에서
오브젝트 클래스를 전달할 수 있고 변환기가 맵핑을 처리하도록 할 수 있다.
목록 10에는 RestTemplate의 구성을 보여준다. 이는 이전에 소개된 세 가지 변환기도 사용한다.
목록 10. RestTemplate 구성
<bean id="restTemplate"
class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<ref bean="marshallingConverter" />
<ref bean="atomConverter" />
<ref bean="jsonConverter" />
</list>
</property>
</bean>
|
이 기사의 예제는 서버 사이에 통신을 간소화할 수 있는 일부 메소드만 사용했다. RestTemplate는
다음을 비롯한 다른 메소드를 지원한다.
-
exchange: 요청 본문으로 특정 HTTP 메소드를 실행하고 응답을 받는다. -
getForObject: HTTPGET메소드를 실행하고 오브젝트로서 응답을 받는다. -
postForObject:특정 요청 본문으로 HTTPPOST메소드를 실행한다. -
put: 특정 요청 본문으로 HTTPPUT메소드를 실행한다. -
delete: 특정 URI를 위한 HTTPDELETE메소드를 실행한다.
다음 코드 샘플은 RestTemplate를 사용하는 방법을 설명하는 데 유용하다. 사용된 API의 자세한 설명은 RestTemplate API(참고자료 참조)를 참조한다.
목록 11에는 요청에 헤더를 추가한 다음에 그 요청을 호출하는 방법을 보여준다. MarshallingHttpMessageConverter를 사용하면
응답을 받고 이를 유형화된 클래스로 변환할 수 있다. 다른 매체 유형을 사용하여 다른 표현을 테스트할 수 있다.
목록 11. XML 표현에 대한 요청
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<EmployeeList> response = restTemplate.exchange(
"http://localhost:8080/rest/service/emps",
HttpMethod.GET, entity, EmployeeList.class);
EmployeeListingemployees = response.getBody();
// handle the employees
|
목록 12에는 새 직원을 서버로 POST하는 방법을 보여준다. 서버 측 서비스인
addEmp()는 application/xml과 application/json의 매체 유형으로 데이터를 수락할 수 있다.
목록 12. 새 직원 POST
Employee newEmp = new Employee(99, "guest", "guest@ibm.com");
HttpEntity<Employee> entity = new HttpEntity<Employee>(newEmp);
ResponseEntity<Employee> response = restTemplate.postForEntity(
"http://localhost:8080/rest/service/emp", entity, Employee.class);
Employee e = response.getBody();
// handle the employee
|
목록 13에는 원래 직원을 업데이트하기 위해 수정된 직원을 PUT하는 방법을 보여준다.
이는 또한 요청 URI에서 플레이스홀더({id})로 사용할 수 있는 기능을 보여준다.
목록 13. 직원을 업데이트하기 위한 PUT
Employee newEmp = new Employee(99, "guest99", "guest99@ibm.com");
HttpEntity<Employee> entity = new HttpEntity<Employee>(newEmp);
restTemplate.put(
"http://localhost:8080/rest/service/emp/{id}", entity, "99");
|
목록 14에는 기존 직원을 DELETE하는 방법을 보여준다.
목록 14. 기존 직원 DELETE
restTemplate.delete(
"http://localhost:8080/rest/service/emp/{id}", "99");
|
이 기사에서는 Spring 3에서 소개된 HttpMessageConverter에 대해 학습했다.
이는 여러 표현에 대해 클라이언트와 서버측 지원 모두를 제공한다. 제공된 소스 코드를 사용하면,
이 기사에서 HttpMessageConverter의 구현과 "Build
RESTful web services using Spring 3"에서 ContentNegotiatingViewResolver를 사용하는 구현 사이에 차이점을 살펴볼 수 있다.
| 설명 | 이름 | 크기 | 다운로드 방식 |
|---|---|---|---|
| Article source code | src_code.zip | 11KB | HTTP |
교육
- 자매 기사인 "Build RESTful web services using Spring 3"(developerWorks, 2010년 7월)에서는
RESTful 웹 서비스를 빌드하는 "Spring 방법"을 소개하였다.
- 위키피디아에서 REST와 관련 링크에 대한 소개를 확인하자.
- Spring MVC showcase를 살펴보고 이 기술로 가능한 작업에 대한 아이디어를 얻자. 여기에는 샘플 프로젝트와 함께 지원하는 슬라이드 프리젠테이션과 스크린캐스트가 들어있다.
- Spring 3의 모든 것에 대해 알아보자.
- RestTemplate APIs에 대해 자세히 알아보자.
- JAXB Reference Implementation
Project에 대해 모두 읽어보자.
- 대부분 신디케이션 형식으로 Java에서 쉽게 작업할 수 있는 Atom/RSS Java 유틸리티 세트인 ROME에 대해 살펴보자.
- ATOM에 대해 자세히 읽어보자.
- 프로토콜과 그 기본 조작 및 기능에 대한 높은 수준의 개요는 developerWorks 시리즈인 "Getting to know the Atom Publishing Protocol"(2006년 10월)을 읽어보자.
- 빠른 오픈 소스 JSON 프로세서인 Jackson에 대해 알아보자.
제품 및 기술 얻기
- 최신 Spring 릴리스를 받자.
- Jackson을 다운로드하자.
- IBM 제품 평가판을 다운로드하거나 IBM
SOA Sandbox의 온라인 시험판을 살펴보고 DB2®, Lotus®,
Rational®, Tivoli® 및 WebSphere® 애플리케이션 개발 도구와 미들웨어 제품을 사용해 볼 수 있다.
토론
- 지금 My developerWorks 프로파일을 작성하고 REST, 웹 서비스 또는 Spring에 대한 관심 목록을 설정해 보자. My developerWorks에서 최신 정보를 자주 확인하자.
- 지금 My developerWorks 프로파일을 작성하고 REST, 웹 서비스 또는 Spring에 대한 관심 목록을 설정해 보자. My developerWorks에서 최신 정보를 자주 확인하자.
- 웹 개발에 관심이 있는 다른 developerWorks 멤버를 찾아보자.
- My developerWorks:
그룹에서 웹 개발자들과 서로의 웹 개발 경험과 지식을 공유할 수 있다.
- 자신의 지식을 공유하자. 웹 주제를 다루는 developerWorks 그룹에 참여하자.
- Roland Barcia는 자신의 블로그에서
Web 2.0 및 미들웨어에 대해 설명했다.
- developerWorks 멤버의 shared bookmarks on Web topics를 따라가 보자.
- 빠른 해답: Web 2.0 Apps forum을 방문하십시오.