본문 바로가기

spring

Spring을 이용한 RESTful 서비스 1

Spring을 이용하면 RESTful 서비스를 쉽게 구현할수가 있습니다. 예전에 Apache CXF를 Spring과 연동하여 서비스를 구현한적이 있는데 RESTful 형식이 아무래도 SOAP보다는 쉽게 적용할 수 있습니다. 그렇다고 CXF를 이용한 XML 웹 서비스가 많이 복잡하지는 않습니다만...

확장자로 매핑되는게 아니기때문에.. web.xml에 다음과 같이 설정을 합니다.

web.xml

<servlet>

        <servlet-name>spring</servlet-name>

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

               <init-param>

                       <param-name>contextConfigLocation</param-name>

                       <param-value>/WEB-INF/spring-servlet.xml</param-value>

               </init-param>

               <load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

        <servlet-name>spring</servlet-name>

        <url-pattern>/</url-pattern>

</servlet-mapping>

Spring 설정파일은 다음과 같이 정의합니다.

spring-servlet.xml

<mvc:default-servlet-handler/>

<mvc:annotation-driven/>

JSON으로 데이터를 주고 받을 생각임으로 원래는 AnnotationMethodHandlerAdapter 빈을 등록하고 messageConverters 속성에 MappingJacksonHttpMessageConverter를 추가하여야 하나 <mvc:annotation-driven/>이 아래에 있는 bean설정을 자동으로 설정해줍니다.  messageConverter를 설정해야만 @RequestBody, @ResponseBody 같은 annotation을 통해 HTTP 요청, 응답 메세지 본문을 통채로 다룰 수 있습니다. @RequestBody는 "Content-Type" header값에 따라  messageConverter가 선택되고, @ResponseBody "Accept" header값에 따라 선택됩니다. 

당연히 MappingJacksonHttpMessageConverter를 사용하기 위해서는 jackson 라이브러리를 추가해줘야 합니다. 전 jackson-all-1.9.4.jar을 사용했습니다.

토비의 스프링3, p.1274를 보면 <mvc:annotation-driven/>은 빈의 설정을 변경할 수 없으니, 설정 변경이 필요할 때는 AnnotationMethodHandlerAdapter, DefaultAnnotationHandlerMapping 직접 빈으로 등록하는 내용이 있습니다. <mvc:annotation-driven/>에 의해 자동으로 등록되는 빈을 다시 <bean>태그로 등록하지 않도록 하라는 내용도 있습니다. 그래서 위의 설정은 아래로 대체될수 있습니다. 

<mvc:default-servlet-handler/>는 RESTful 을 구현했을때(특정 확장자로 매핑되는게 아닌 경우) 스태틱 리소스(html, js, css 등)에 대한 요청 처리를 서블릿 컨테이너의 디폴트 서블릿으로 처리하게 만들어 줍니다. 이설정은 외국 WAS에만 자등으로 등록됨으로 국내 JEUS같은 WAS를 사용하신다면  <mvc:default-servlet-handler default-servlet-name="WorkerServlet"/>와 같이 JEUS의 디폴트 서블릿 이름을 설정해주어야 합니다. JSON외의 다양한 포맷을 사용하실려면 콘테트 교섭(?)이 가능한 ContentNegotiatingViewResolver를 사용하시면 됩니다.

spring-servlet.xml

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"

               p:order="0" />

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"

               p:messageConverters-ref="messageConverters" />

<util:list id="messageConverters">

        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"

                       p:supportedMediaTypes="application/json" />

</util:list>

이제 설정부분에 대한 모든 정의는 끝났고, 실제 Controller, Service, Dao클래스를 직접구현하시면 됩니다. 아래는 Controller 소스입니다.

MemoController.java

package com.tistory.aircook.api.controller;

 

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import com.tistory.aircook.api.service.MemoService;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ExceptionHandler;

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;

 /**

 * MemoController

 * @author francis Lee

 * @since 2012. 07. 10.

 */

@Controller

public class MemoController {

        private final Log logger = LogFactory.getLog(getClass());

        @Autowired

        private MemoService memoService;


        @RequestMapping(value = "/memos", method = RequestMethod.GET)

        @ResponseBody

        public Map list() {

               if (logger.isDebugEnabled()) {

                       logger.debug("action LIST, method " + RequestMethod.GET);

               }

               List list = memoService.getListMemo();

               if (logger.isDebugEnabled()) {

                       logger.debug("list : " + list);

               }

               Map result = new HashMap();

               result.put("result", Boolean.TRUE);

               result.put("data", list);

               return result;

        }

 

        @RequestMapping(value = "/memos/{seq}", method = RequestMethod.GET)

        @ResponseBody

        public Map view(@PathVariable String seq) {

               if (logger.isDebugEnabled()) {

                       logger.debug("action VIEW, method " + RequestMethod.GET);

               }

               Map map = memoService.getMemo(seq);

               if (logger.isDebugEnabled()) {

                       logger.debug("map : " + map);

               }

               Map result = new HashMap();

               result.put("result", Boolean.TRUE);

               result.put("data", map);

               return result;

        }

 

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

        @ResponseBody

        public Map add(@RequestBody Map body) {

               if (logger.isDebugEnabled()) {

                       logger.debug("action ADD, method " + RequestMethod.POST);

                       logger.debug("body : " + body);

               }

               int key = (Integer) memoService.setInsertMemo(body);

               if (logger.isDebugEnabled()) {

                       logger.debug("key : " + key);

               }

               Map result = new HashMap();

               result.put("result", Boolean.TRUE);

               return result;

        }

 

        @RequestMapping(value = "/memos/{seq}", method = RequestMethod.PUT)

        @ResponseBody

        public Map modify(@PathVariable String seq, @RequestBody Map body) {

               if (logger.isDebugEnabled()) {

                       logger.debug("action MODIFY, method " + RequestMethod.PUT);

                       logger.debug("seq : " + seq);

                       logger.debug("body : " + body);

               }

               body.put("seq", seq);

               int affected = memoService.setUpdateMemo(body);

               if (logger.isDebugEnabled()) {

                       logger.debug("affected : " + affected);

               }

               Map result = new HashMap();

               result.put("result", Boolean.TRUE);

               return result;

        }

 

        @RequestMapping(value = "/memos/{seq}", method = RequestMethod.DELETE)

        @ResponseBody

        public Map remove(@PathVariable String seq) {

               if (logger.isDebugEnabled()) {

                       logger.debug("action REMOVE, method " + RequestMethod.DELETE);

                       logger.debug("seq : " + seq);

               }

               int affected = memoService.setDeleteMemo(seq);

               if (logger.isDebugEnabled()) {

                       logger.debug("affected : " + affected);

               }

               Map result = new HashMap();

               result.put("result", Boolean.TRUE);

               return result;

        }

 

        @ExceptionHandler(Exception.class)

        // @ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)

        @ResponseBody

        public Map handleException(Exception e) {

               Map result = new HashMap();

               result.put("result", Boolean.FALSE);

               return result;

        }

}

파이어폭스의 플러그인중 RESTClient를 설치해 서비스를 테스트 할 수 있습니다. 아래는 실제 화면 캡처입니다.