2010년 8월 30일 월요일

Spring에서 DWR 사용하기

DWR(Direct Web Remoting)은 일반 서비스를 Ajax 형태로 제공할 수 있도록 자동 변환해주는 기능을 제공한다.
서버사이드 기능은 DwrServlet에서 처리되고, 클라이언트 기능은 자동 생성되는 자바 스크립트에서 대부분의 기능이 구현된다.

TrafficInfo(도메인객체)를 리턴하는 검색 기능을 제공하는 TrafficService 가 있다고 가정하자
TrafficService를 DWR을 이용하여 개발하는 예제를 살펴보도록 하자.

TrafficInfo.java - 도메인 객체
package com.roadrantz.traffic;

public class TrafficInfo {

  private String summary;
  private String details;
  public TrafficInfo() {
    super();
  }
  public String getSummary() {
    return summary;
  }
  public void setSummary(String summary) {
    this.summary = summary;
  }
  public String getDetails() {
    return details;
  }
  public void setDetails(String details) {
    this.details = details;
  }
}


TrafficService.java - 서비스 인터페이스
package com.roadrantz.traffic;

public interface TrafficService {

  TrafficInfo[] getTrafficInfo(String zipCode, int zoom, int severity);
}


TrafficServiceImpl.java - 서비스 구현클래스
package com.roadrantz.traffic;

public class TrafficServiceImpl implements TrafficService {
  private String appId = "springinaction";
    
  public TrafficServiceImpl() {
    super();
  }

  public String getAppId() {
    return appId;
  }

  public void setAppId(String appId) {
    this.appId = appId;
  }

  @Override
  public TrafficInfo[] getTrafficInfo(String zipCode, int zoom, int severity) {
    try {
      TrafficInfo[] trafficInfo = new TrafficInfo[2];
      
      trafficInfo[0] = new TrafficInfo();
      trafficInfo[0].setSummary("summary 1");
      trafficInfo[0].setDetails("detail 1");
      
      trafficInfo[1] = new TrafficInfo();
      trafficInfo[1].setSummary("summary 2");
      trafficInfo[1].setDetails("detail 2");
      
      return trafficInfo;

    } catch(Exception e) {
      e.printStackTrace(System.out);
      return new TrafficInfo[] {};
    }
  }
}


위의 세 클래스는 보통 스프링에서 서비스를 개발하는 것과 다를게 없다.
이렇게 개발된 서비스를 별도의 컨트롤러나 Restful 개발 프레임웍을 사용하지 않고
AJAX 형태로 서비스를 제공하는 방법을 살펴보자.

먼저 WEB-INF에 web.xml 파일과 dwr.xml파일을 작성해 주어야 한다.

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>simple-dwr-project</display-name>
  
  <servlet>
    <servlet-name>dwr</servlet-name>
    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
    <init-param>
      <param-name>debug</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>dwr</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
  </servlet-mapping>

</web-app>

/dwr/* 으로 요청된 모든 URL을 DwrServlet이 처리되도록 서블릿을 설정한 예제다.
URL 패턴은 개발환경에 맞게 설정하면된다.

WEB-INF/dwr.xml
<!DOCTYPE dwr PUBLIC
  "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN"
  "http://www.getahead.ltd.uk/dwr/dwr10.dtd">
<dwr>
  <allow>
    <convert match="com.roadrantz.traffic.TrafficInfo" converter="bean"></convert>
    <create javascript="Traffic" creator="new">
      <param name="class" value="com.roadrantz.traffic.TrafficServiceImpl"></param>
      <exclude method="setAppId"/>
      <exclude method="getAppId"/>
    </create>
  </allow>
</dwr>  

위의 설정파일은 TrafficInfo 서비스를 DWR을 이용하여 Ajax로 서비스를 제공할 수 있도록 한다.
서비스와 무관한 setAppId와 getAppId는 ajax서비스에서 배제시킨다.

아래의 소스는 JSP파일에서 JavaScript를 이용해서 DWR 서비스를 호출하는 예제다.
실제로 DWR서비스를 호출하여 패킷을 살펴보면 Json 포맷의 응답이 전달되는 것을 확인할 수 있다.

아래의 예제는 zipCode가 5자리가 될 때 DWR서비스를 호출하는 예제다.
아래의 소스에서 주의깊게 살펴볼 부분은
스크립트 파일을 include하는 부분과 cellFuncs 부분이다.

dwr/engine.js 파일과 dwr/util.js 파일은 DWR에서 공통적으로 제공하는 파일들이고,
dwr/interface/Traffic.js는 TrafficService를 호출하기 위해서 DWR이 생성한 스크립트 파일이다.

cellFuncs는 AJAX로 호출한 결과를 Table의 TD로 설정해 주는 역할을 한다.


샘플 Jsp 파일
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>DWR Sample</title>

<script type="text/javascript" src="dwr/engine.js"></script>
<script type="text/javascript" src="dwr/interface/Traffic.js"></script>
<script type="text/javascript" src="dwr/util.js"></script>

<script type="text/javascript">

function criteriaChanged() {
  var zipCode = document.trafficForm.zip.value;
  var zoom = document.trafficForm.zoom.value;
  var severity = document.trafficForm.severity.value;

  if(zipCode.length == 5) {
    Traffic.getTrafficInfo(zipCode, zoom, severity, updateTable);
  } else {
    DWRUtil.removeAllRows("trafficTable");
  }
}

var cellFuncs = [
                 function(data) {
                     var cb = document.createElement("input");
                     cb.type = "checkbox";
                     return cb; 
                 },
                 function(data) { return data.summary; },
                 function(data) { return data.details; }
                 ];

function updateTable(results) {
  //alert(results);
  DWRUtil.removeAllRows("trafficTable");
  DWRUtil.addRows("trafficTable", results, cellFuncs);
}


</script>

</head>
<body>

<form name="trafficForm">
<table>
  <tr>
    <td>zip</td>
    <td><input type="text" name="zip" onkeyup="criteriaChanged();"/> </td>
  </tr>
  <tr>
    <td>zoom</td>
    <td><input type="text" name="zoom" onkeyup="criteriaChanged();"/> </td>
  </tr>
  <tr>
    <td>severity</td>
    <td><input type="text" name="severity" onkeyup="criteriaChanged();"/> </td>
  </tr>
  
</table>
</form>

<table width="100%" border="1">
  <thead>
    <tr>
      <td>S</td>
      <td width="100">Summary</td>
      <td>Details</td>
    </tr>
  </thead>
  <tbody id="trafficTable">
  </tbody>
</table>
</body>
</html> 
 
 
 

 

DWR요청













DWR 응답

Spring에서 Jaxb (Un)Marshaller 사용하기

스프링에서 XOM(Xml-Object Mapping)을 사용하는 방법이다.
이미 WebService를 사용하기 위해서 JAXB 애너테이션을 사용하고 있는 경우라면
더욱 사용하기가 용이하다.

다음은 User 객체를 XML로 마샬링하는 방법과 XML을 User객체로 언마샬링하는 방법을 보여주는 간단한 예제다.

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

 <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
  <property name="classesToBeBound">
   <list>
    <value>sample.maven.jaxws.User</value>
   </list>
  </property>
 </bean>
 
</beans>    

아래의 소스는 사용자 정보를 담고 있는 Jaxb 클래스다.
CXF에서 XmlType 애너테이션만 설정하면 WSDL이 생성되는 것과는 달리
Jaxb2Marshaller에서는 XmlRootElement 애너테이션을 반드시 설정해 주어야 한다.

User 클래스
package sample.maven.jaxws;

import java.sql.Timestamp;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlType
@XmlRootElement(name="user", namespace="org.sds.sample")
public class User {
 private long userId;
 private String loginId;
 private String userName;
 private Timestamp createDate;
 public long getUserId() {
  return userId;
 }
 public void setUserId(long userId) {
  this.userId = userId;
 }
 public String getLoginId() {
  return loginId;
 } 
 public void setLoginId(String loginId) {
  this.loginId = loginId;
 }
 public String getUserName() {
  return userName;
 }
 public void setUserName(String userName) {
  this.userName = userName;
 }
 @XmlJavaTypeAdapter(value=DateTimestampAdapter.class)
 public Timestamp getCreateDate() {
  return createDate;
 }
 public void setCreateDate(Timestamp createDate) {
  this.createDate = createDate;
 }
 @Override
 public String toString() {
  return "User [userId=" + userId + ", loginId=" + loginId
    + ", userName=" + userName + ", createDate=" + createDate + "]";
 }
 
 
}


아래는 JUnit테이스 케이스다.
User객체를 XML로 마샬링하는 예제와 XML을 User객체로 언마샬링하는 예제가 포함되어 있다.

Jaxb 테스트 케이스
package sample.maven.jaxws;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

import javax.annotation.Resource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration(locations={"classpath*:/spring/context-*.xml"})
@RunWith(SpringJUnit4ClassRunner.class)

public class UserXomTest {

 @Resource(name="jaxb2Marshaller")
 Marshaller marshaller;
 
 @Resource(name="jaxb2Marshaller")
 Unmarshaller unmarshaller;
 
 @Test
 public void testMarshaller() throws Exception {
  User user = new User();
  user.setLoginId("loginId");
  
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  
  StreamResult result = new StreamResult(out);
  marshaller.marshal(user, result);
  
  System.out.println(out.toString());
  
  ByteArrayInputStream in = 
   new ByteArrayInputStream(out.toByteArray());
  
  StreamSource source = new StreamSource(in);
  User newUser = (User) unmarshaller.unmarshal(source);
  
  System.out.println("new user: " + newUser);
 }
}

2010년 8월 10일 화요일

Eclipse Memory PermSize 오류 (Java1.6.20_b20)

이번 포스트는 배껴 쓴 것입니다.
원문은 http://hanavy.egloos.com/10545584 입니다.





새로운 패치버전이 아니라 공지라서 좀 이상해서 클릭해 보니 결국 Eclipse 라기보다는 어찌 보면
Sun(이젠 Oracle이지)의 문제로 인한 버그였다.

일단 영문 전문은 다음과 같다.


Oracle/Sun VM 1.6.0_21 on Windows

The Eclipse 3.3 - 3.6 launchers for Windows have a problem with the Oracle/Sun Java VM version '1.6.0_21'.

UPDATE: The latest JDK/JRE downloads have fixed this problem, so the simplest way to resolve is to download and re-install from http://www.java.com (alternative link is http://java.sun.com/javase/downloads/index.jsp)


Before the fix was released, there were three choices to work around this:

switch back to '1.6.0_20' (as of July 19, 2010 it can still be downloaded here)
Change the commandline for launching or add the following line after "-vmargs" to your Eclipse.ini file:
-XX:MaxPermSize=256m
(Detailed instructions/examples)
For Helios, download the fixed eclipse_1308.dll and place it into
(eclipse_home)/plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.1.0.v20100503
The bug itself is open for voting and comments as 6969236 on the Java BugParade, and as bug 319514 on the Eclipse side.



요는 Sun 이 Oracle로 넘어가면서 새로 나오게 된 최신 VM인 1.6.0의 21번 패치에서 java/javaw 실행파일의
내부 문자열에서 VM 종류를 확인하기 위한 회사명(vendor flag)을 Sun에서 Oracle로 변경하면서 Eclipse가
새로운 vm을 제대로 인식하지 못한 것이었다.
그래서 확장 옵션인 메모리 Permsize를 추가하는 옵션을 제대로 주지 못하여 고질적인 메모리 부족 현상이
일어난 것이었다.

해결책은 위에 나온 것 처럼 3가지(4가지?)가 있다.
가장 좋은 방법은 일단 번호 나온 위에 업데이트 된 것처럼 자바의 새 패치 버전이 나온 듯 하다.
(하긴 Eclipse가 개발자들 사이에서 가장 많이 쓰이니 빨리 대응해야 했겠지...)

java -version 명령으로 확인할 경우 이전 자바는 다음과 같은 내용이 나타난다.


Java(TM) SE Runtime Environment (build 1.6.0_21-b06)
Java HotSpot(TM) Client VM (build 17.0-b16, mixed mode, sharing)


위의 굵은 글씨로 표시한 부분을 보면 알겠지만 b06 버전이다.
이 버전이 문제가 있으며 똑같이 패치번호는 21번으로 동일하지만 지금 새로 받으면 b07 버전이 된다.
위 공지의 첫번째 내용은 아직 그 전에 내용으로 20번 패치버전으로 받으라는 얘기였다.

두번째는 eclipse가 내부적으로 메모리 세팅을 하지 못하는 상황이므로 강제로 MaxPermSize를 vmargs로 추가해 주는 방법이다.

세번째는 이제부터 이클립스 버전 따라 다를텐데 일단 현재 최신인 Helios 기준으로 변경된 vm의 벤더 인식 알고리즘을 바꾼 dll을 바꿔끼우는 방법이다.
그 아래 줄줄히 옛날 버전의 대응방법도 나와있다.

이 상황을 겪으면서, 다시 한번 프로그램의 유지보수에 대한 내용을 또 고민해 볼 수 있는 기회가 되었다.
정확히 어떠한 문자열인지, 그게 일종의 내부 정보인지 아니면 공식으로 인정되는 부분인지는 확인할 수 없었지만
(앞이면 Eclipse Foundation에서 잘못한거고 뒤면 Oracle에서 잘못 한거겠지..)
의외로 별 거 아닌거 바꾼 걸로 파급효과가 크게 되었다.

실제 나는 별거 아니라고 바꿔서 배포했다가 그걸 참조했던 프로그램들 때문에 고생했던 기억이 몇 번 있는 나로서는
남 얘기(?)같지 않고 연민감도 드는 일이었다..(덕택에 며칠 삽질은 했다만...)