자바/Web2009. 7. 3. 10:38
웹 어플리케이션을 하나 하나 만들어보려는 게으름 탈피 프로젝트에 의거 하나 하나 만들기 위해 자료 검색을 시작

최근 핫 이슈인  RESTful을 좀 건들여 보려고 했다. 마침 Struts 를 사용해서 Rest 한 구조를 구현할 수 있도록

플러그인들이 소개되어 있는데 이넘이 바로 struts2-convention-plugin 과 struts2-rest-plugin 이다.

그런데 이넘들이 만만한 자료가 없더라.. 넘쳐나는 수많은 자료중에 쓸만한건 거의 없었으니.. ㅡ_ㅡ;

게다가 struts 사이트에는 maven을 이용한 설정.. ㅡ_ㅡ

쌩 무시하고 나름 검색을 통해 환경을 구축하고 테스트를 한번 슬쩍 돌려봤다.

자 이제 시작!!



참고 사이트

http://struts.apache.org/2.x/docs/convention-plugin.html
http://struts.apache.org/2.x/docs/rest-plugin.html
http://www.zulutown.com/blog/2009/01/28/rest-web-application-with-struts21-rest-and-convention-plugins/

환경
JDK 6
Tomcat 6.0
Eclipse 3.4.1 EE version
Struts 2.1.6
JQuery
Prototype

Eclipse 디렉토리 구성

위에 이미지를 보면 알겠지만 필요한 binary들은 다음과 같다.
  • commons-beanutils-1.7.0.jar
  • commons-collections-3.2.jar
  • commons-fileupload-1.2.1.jar
  • commons-io-1.3.2.jar
  • commons-lang-2.3.jar
  • commons-logging-1.0.4.jar
  • ezmorph-1.0.3.jar
  • freemarker-2.3.13.jar
  • json-lib-2.1.jar
  • ognl-2.6.11.jar
  • struts2-convention-plugin-2.1.6.jar
  • struts2-core-2.1.6.jar
  • struts2-rest-plugin-2.1.6.jar
  • xpp3_min-1.1.3.4.O.jar
  • xstream-1.2.2.jar
  • xwork-2.1.2.jar
모두 struts2 lib 바이너리에 있으므로 해당 파일을 프로젝트에 추가해 주면 된다.



web.xml

 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>Framework</display-name>
 
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
            org.apache.struts2.dispatcher.FilterDispatcher
        </filter-class>
      <init-param>
          <param-name>struts.i18n.encoding</param-name>
          <param-value>utf-8</param-value>
      </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>


struts.xml

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN"
    "http://struts.apache.org/dtds/struts-2.1.dtd">

<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.convention.action.suffix" value="Controller"/>
    <constant name="struts.convention.action.mapAllMatches" value="true"/>
    <constant name="struts.convention.default.parent.package" value="rest-default"/>
    <constant name="struts.convention.package.locators" value="test"/>
</struts>

struts의 convention plugin 과 rest plugin은 무설정을 지향하고 있다. 따라서 struts.xml 파일에서는 설정할 것이 거의 없다.

이정도의 설정으로 거의 겜오버.. ㅡ_ㅡ)=b

여기서 주의할 것은 struts.convention.action.suffix 와 struts.convention.package.locators 설정이다.

struts.convention.action.suffix 는 앞으로 Rest 한 Action을 처리하고자 할때 실제로 Action을 취해줄 Class의 Suffix 가 된다. 앞으로 설명할 예제와 같이 http://localhost:8080/[Context]/messages 라는 호출이라면 이것을 처리할 Action의 이름은 MessagesController 가 되어야 해당 요청을 올바르게 처리할 수 있다.

또한 이 액션을 찾기 위해 패키지를 Search 하게 되는데 어떤 패키지를 찾을지 선택해 주는것이 바로 struts.convention.package.locators 이다. 이 설정은 ,로 여러개를 설정할 수 있으며 설정한 패키지를 검색하면서 해당 Action을 찾게 된다.

Action을 찾는 규칙과 요청처리 규칙은 다음번에 정리하도록 해야겠다..

아뭏든 지금 위의 그림과 같이 rest.test라는 패키지에 MessagesController를 만들어 놨으므로 struts.convention.package.locators는 test라고 설정해 놓았다.

struts의 rest plugin은 요청 결과를 따로 코딩해주지 않아도 xml 과 json 형식으로 결과 반환이 가능한데 이는 원하는 결과 요청 url 에 .xml 이나 .json을 붙여서 요청하면 된다. 완전 행복한 기능이 아닐수 없다.

우선 최종 결과 페이지를 보기 전에 샘플 소스를 보자.

Sample Source

 package rest.test;

public class Message {
    private String id;
    private String text;
    private String author;

    public Message() {
        super();
    }

    public Message(String id, String text, String author) {
        super();
        this.id = id;
        this.text = text;
        this.author = author;
    }

    public String getId() {
        return id;
    }

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

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }

    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Message other = (Message) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
}

 package rest.test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MessageService {
    public static HashMap<String, Message> messages = new HashMap<String, Message>();
    private static int nextMessageId = 4;

    static {
        Message message1 = new Message("1", "hello", "john");
        Message message2 = new Message("2", "world", "ted");
        Message message3 = new Message("3", "rest", "sam");
        messages.put("1", message1);
        messages.put("2", message2);
        messages.put("3", message3);
    }

    public static List<Message> findAll() {
        return new ArrayList<Message>(messages.values());
    }

    public static Message find(String id) {
        return messages.get(id);
    }

    public static void save(Message message) {
        if (message.getId() == null) {
            String id = String.valueOf(nextMessageId);
            message.setId(id);
            nextMessageId++;
        }
        messages.put(message.getId(), message);
    }

    public static void remove(String id) {
        messages.remove(id);
    }
}


 package rest.test;

import java.util.Collection;

import org.apache.struts2.rest.DefaultHttpHeaders;
import org.apache.struts2.rest.HttpHeaders;

import com.opensymphony.xwork2.ModelDriven;

public class MessagesController implements ModelDriven<Object> {

    private static final long serialVersionUID = 89268916175477696L;
    private Message model = new Message();
    private String id;
    private Collection<Message> list;

    public HttpHeaders create() {
        MessageService.save(model);
        return new DefaultHttpHeaders("create");
    }

    public HttpHeaders destroy() {
        return new DefaultHttpHeaders("destroy");
    }

    public HttpHeaders show() {
        return new DefaultHttpHeaders("show").disableCaching();
    }

    public HttpHeaders update() {
        MessageService.save(model);
        return new DefaultHttpHeaders("update");
    }

    public HttpHeaders index() {
        list = MessageService.findAll();
        return new DefaultHttpHeaders("index").disableCaching();
    }

    public Object getModel() {
        return (list != null ? list : model);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        if (id != null) {
            this.model = MessageService.find(id);
        }
        this.id = id;
    }
}

위의 소스는 참고사이트로 링크된 Rest Sample 관련 블로그에서 업어왔다.

소스를 슬쩍 살펴봐도 아주 간단한 소스이기 때문에 특별히 어려운건 없겠으나 Controller는 사전지식이 좀 있어야 한다. 하지만 이번 포스팅에서는 넘어가도록 하자. 저거 설명하는것도 일이다. ㅡ_ㅡ;

다음번 포스팅때를 기대하시라...


결과

http://localhost:8080/[Context]/messages.json

http://localhost:8080/[Context]/messages.xml

지금 요청은 MessagesController를 이용해 get 요청을 처리한 결과이다.

순서가 좀 이상하게 되었지만 기본적으로 Rest한 서비스에 대한 기본 지식이 있어야 이해가 되는 부분이다.

다음 포스팅에 Rest에 대한 정리와 Struts에서 처리 방식을 정리해 봐야겠다.




Posted by 양군이당

댓글을 달아 주세요

  1. 웹개발하는 신입

    양군님 질문 있습니다 ㅠㅠ
    restfut에서 경로를 그냥 src안 즉, 패키지를 생성안하고 만들고 싶은대... 방법이 없는것인지요..

    2010.07.28 12:02 [ ADDR : EDIT/ DEL : REPLY ]