'AJAX'에 해당되는 글 1건
Progress Bar using AJAX + DWR :: 2009/07/16 23:18
AJAX는 웹어플리케이션과 사용자간의 상호작용을 높히고, RIA 어플리케이션을 만드는 데 유용한 웹개발 기술 중 하나이다. AJAX를 이용하여 웹개발을 쉽게 할 수 있도록 여러 기능을 모아놓은 것이 AJAX 프레임워크이다. 자바스크립트 기반의 클라이언트 사이드 AJAX 프레임워크에는 Dojo, Ext JS, jQuery, Prototype, Script.aculo.us, Yahoo! UI Library 등으로 매우 다양하다. 또한 서버 사이드 AJAX 동작을 위해 JAVA 기반으로 구현된 AJAX 프레임워크에는 DWR, GWT 등이 있다.
AJAX는 본질적으로 비동기 요청을 수행하므로 서버쪽의 시간이 오래 걸리는 대량의 배치처리 등의 작업을 Invoke하는데 유용하게 사용되어 질 수 있다. 이러한 작업의 진행 상태를 모니터링하는데 필요한 진행상태 바 (Progress Bar)이다. AJAX 프레임워크로 DWR을 이용하여 다음과 같은 진행상태 바를 만들어 볼 수 있다.

1. DWR 서블릿 설정하기
- /WEB-INF/web.xml에 다음과 같은 DWR 서블릿 설정을 해준다.
- 참고로 org.directwebremoting.spring.DwrSpringServlet를 사용할 경우 스프링 어플리케이션 컨텍스트 설정에 dwr 네임스페이스를 등록하고, 그 곳에 DWR 처리용 클래스를 직접 등록하여 dwr.xml 없이 사용할 수도 있다.
2. 서버 쪽 Ajax 처리용 Java 클래스 작성
- 예로서 다음과 같은 서버쪽 배치업무를 수행할 DWR Ajax 처리용 클래스를 작성한다.
- HeavyLoadTaskExecutor는 Ajax 처리를 위해 내부 쓰레드의 시작, 중지를 수행하며 처리건수, 진행상태 등을 얻을 수 있게 작성되어 질 수 있다.
- HeavyLoadTask는 실제 업무처리를 위한 테스트용 클래스로 스프링에 의해 기본적으로 Singleton으로 인스턴스화되며 한 번에 한 개의 쓰레드의 실행만 허용한다.
3. 스프링 빈 설정
- 스프링을 이용하여 DI을 적용한다면 다음과 같은 스프링 빈 설정을 applicationContext.xml에 추가한다.
4. DWR 클래스 등록하기
- /WEB-INF/dwr.xml에 다음과 같이 AJAX 서비스로 노출할 클래스를 등록한다.
- 클라이언트에서 사용하도록 노출할 메소드를 포함하거나 제외 할 수도 있다.
5. DWR 클래스 테스트
- 웹어플리케이션을 서버에 배포 후 <웹어플리케이션 컨텍스트>/dwr/ 디렉토리에 접근하면 AJAX 처리용으로 등록된 클래스들을 확인 할 수 있다.
- 해당 클래스의 노출된 메소드들의 목록을 볼 수 있고, 이곳에서 클라이언트의 요청처럼 바로 실행해 볼 수 있다. 페이지 상단에 있는 JS 스크립트 주소는 클라이언트 페이지 작성시 포함되어 사용되어질 스크립트이다.
- 서버 배치업무 쓰레드를 시작하고, 진행 중에 상태값을 조회한 결과화면이다.
6. 클라이언트 페이지 작성하기
- 먼저 AJAX 요청할 서버 클래스에 대한 해당 자바스크립트를 포함한다.
- CSS를 이용하여 화면 모양을 구성하고, 아래와 같은 자바스크립트를 사용하여 서버의 배치업무에 대한 시작, 중지, 상태 조회 등을 비동기로 요청할 수 있다.
- 시작 요청 후 window 객체의 setTimeout()을 이용하여 주기적(1초간격)으로 상태 조회를 하고 화면에 진행상태 값을 업데이트한다.
7. 클라이언트 실행 결과
- 서버에 업무처리 시작 요청을 한 후에 주기적으로 진행상태 값을 조회하여 진행 상태를 표시한다
- 서버의 업무처리가 모두 완료된 경우
- 업무 처리 중간에 서버에 중지 요청을 한 경우
- 서버 업무 처리 중간에 오류가 발생하여 중지된 경우
AJAX는 본질적으로 비동기 요청을 수행하므로 서버쪽의 시간이 오래 걸리는 대량의 배치처리 등의 작업을 Invoke하는데 유용하게 사용되어 질 수 있다. 이러한 작업의 진행 상태를 모니터링하는데 필요한 진행상태 바 (Progress Bar)이다. AJAX 프레임워크로 DWR을 이용하여 다음과 같은 진행상태 바를 만들어 볼 수 있다.

1. DWR 서블릿 설정하기
- /WEB-INF/web.xml에 다음과 같은 DWR 서블릿 설정을 해준다.
- 참고로 org.directwebremoting.spring.DwrSpringServlet를 사용할 경우 스프링 어플리케이션 컨텍스트 설정에 dwr 네임스페이스를 등록하고, 그 곳에 DWR 처리용 클래스를 직접 등록하여 dwr.xml 없이 사용할 수도 있다.
<servlet>
<servlet-name>dwr-invoker</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-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping> |
2. 서버 쪽 Ajax 처리용 Java 클래스 작성
- 예로서 다음과 같은 서버쪽 배치업무를 수행할 DWR Ajax 처리용 클래스를 작성한다.
- HeavyLoadTaskExecutor는 Ajax 처리를 위해 내부 쓰레드의 시작, 중지를 수행하며 처리건수, 진행상태 등을 얻을 수 있게 작성되어 질 수 있다.
package net.java2go.demo.ajax.dwr;
import java.util.HashMap;
import java.util.Map;
public class HeavyLoadTaskExecutor {
private HeavyLoadTask heavyLoadTask;
public HeavyLoadTaskExecutor(HeavyLoadTask heavyLoadTask) {
this.heavyLoadTask = heavyLoadTask;
}
public void startTask(String taskId) {
heavyLoadTask.start(taskId);
}
public void stopTask() {
heavyLoadTask.stop();
}
public boolean isTaskRunning() {
return heavyLoadTask.isRunning();
}
public int getTaskProgressRate() {
return heavyLoadTask.getProgressRate();
}
public Map getTaskStatus() {
Map map = new HashMap();
map.put("running", new Boolean(heavyLoadTask.isRunning()));
map.put("total", new Integer(heavyLoadTask.getTotalCount()));
map.put("done", new Integer(heavyLoadTask.getDoneCount()));
map.put("progress", new Integer(heavyLoadTask.getProgressRate()));
map.put("message", heavyLoadTask.getMessage());
return map;
}
} |
- HeavyLoadTask는 실제 업무처리를 위한 테스트용 클래스로 스프링에 의해 기본적으로 Singleton으로 인스턴스화되며 한 번에 한 개의 쓰레드의 실행만 허용한다.
package net.java2go.demo.ajax.dwr;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HeavyLoadTask {
private final Log log = LogFactory.getLog(getClass());
private Thread taskThread;
private boolean running;
private int totalCount;
private int doneCount;
private String message;
private String taskId;
public HeavyLoadTask() {
log.info("HeavyLoadTask created.");
}
public synchronized void start(String taskId) {
this.taskId = taskId;
if (!running) {
this.running = true;
this.totalCount = 0;
this.doneCount = 0;
this.message = null;
taskThread = new Thread(new TaskThread());
taskThread.start();
log.info("Thread started.");
} else {
log.info("Thread has already been running.");
}
}
public synchronized void stop() {
if (running) {
this.running = false;
taskThread.interrupt();
log.info("Thread interrupted.");
}
}
public boolean isRunning() {
return running;
}
public int getTotalCount() {
return totalCount;
}
public int getDoneCount() {
return doneCount;
}
public String getMessage() {
return message;
}
public int getProgressRate() {
return totalCount == 0 ? 0 : doneCount * 100 / totalCount;
}
/**
* TaskThread
*/
private class TaskThread implements Runnable {
public TaskThread() {
}
public void run() {
try {
log.debug("--- Thread Start ---");
log.debug("taskId=" + taskId);
totalCount = 30;
log.debug("totalCount=" + totalCount);
for (int i = 0; i < totalCount; i++) {
if (Thread.currentThread().isInterrupted()) {
break;
}
// An intentional exception
if ((taskId == null || taskId.length() == 0) && i == 10) {
throw new RuntimeException("Test error occurred!");
}
Thread.sleep(1000);
log.debug("Task " + i + " done.");
doneCount++;
}
// Thread.sleep(200);
} catch (InterruptedException ex) {
// Ignore.
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
message = ex.getMessage();
} finally {
running = false;
log.info("Thread stopped.");
log.debug("--- Thread Stop ---");
}
}
}
} |
3. 스프링 빈 설정
- 스프링을 이용하여 DI을 적용한다면 다음과 같은 스프링 빈 설정을 applicationContext.xml에 추가한다.
<bean id="heavyLoadTask" class="net.java2go.demo.ajax.dwr.HeavyLoadTask" />
<bean id="heavyLoadTaskExecutor"
class="net.java2go.demo.ajax.dwr.HeavyLoadTaskExecutor">
<constructor-arg>
<ref bean="heavyLoadTask" />
</constructor-arg>
</bean> |
4. DWR 클래스 등록하기
- /WEB-INF/dwr.xml에 다음과 같이 AJAX 서비스로 노출할 클래스를 등록한다.
- 클라이언트에서 사용하도록 노출할 메소드를 포함하거나 제외 할 수도 있다.
<!DOCTYPE dwr PUBLIC
"-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN"
"http://getahead.ltd.uk/dwr/dwr20.dtd">
<dwr>
<allow>
<!--
<convert converter="bean" match="net.java2go.demo.ajax.dwr.Country" />
<create creator="new" javascript="CountryManager">
<param name="class" value="net.java2go.demo.ajax.dwr.CountryManager" />
<include method="getCountryCodes" />
<include method="getCountry" />
</create>
-->
<create creator="spring" javascript="HeavyLoadTaskExecutor">
<param name="beanName" value="heavyLoadTaskExecutor" />
<include method="startTask" />
<include method="stopTask" />
<include method="isTaskRunning" />
<include method="getTaskProgressRate" />
<include method="getTaskStatus" />
</create>
</allow>
</dwr> |
5. DWR 클래스 테스트
- 웹어플리케이션을 서버에 배포 후 <웹어플리케이션 컨텍스트>/dwr/ 디렉토리에 접근하면 AJAX 처리용으로 등록된 클래스들을 확인 할 수 있다.

- 해당 클래스의 노출된 메소드들의 목록을 볼 수 있고, 이곳에서 클라이언트의 요청처럼 바로 실행해 볼 수 있다. 페이지 상단에 있는 JS 스크립트 주소는 클라이언트 페이지 작성시 포함되어 사용되어질 스크립트이다.

- 서버 배치업무 쓰레드를 시작하고, 진행 중에 상태값을 조회한 결과화면이다.

6. 클라이언트 페이지 작성하기
- 먼저 AJAX 요청할 서버 클래스에 대한 해당 자바스크립트를 포함한다.
- CSS를 이용하여 화면 모양을 구성하고, 아래와 같은 자바스크립트를 사용하여 서버의 배치업무에 대한 시작, 중지, 상태 조회 등을 비동기로 요청할 수 있다.
- 시작 요청 후 window 객체의 setTimeout()을 이용하여 주기적(1초간격)으로 상태 조회를 하고 화면에 진행상태 값을 업데이트한다.
<script type='text/javascript' src='<c:url
value='/dwr/interface/HeavyLoadTaskExecutor.js'/>'></script>
<script type='text/javascript' src='<c:url value='/dwr/engine.js'/>'></script>
<script type='text/javascript' src='<c:url value='/dwr/util.js'/>'></script>
<h1>Progress Bar</h1>
<p>Progress Bar using Ajax + DWR</p>
<div id="executor">
Task ID: <input type="text" id="taskId" style="width:100px;"/>
<input type="button" id="btnStart" value="Start" onclick="startTask()"/>
<br/>
<div id="progress" style="display:none;">
<div id="progCnt">Processing...</div>
<div id="progBar">
<img id="progImg" src="<c:url value="/images/sizebar.gif" />"/>
<span id="progRate"></span>
</div>
<div id="message" style="display:none;"></div>
<input type="button" id="btnStop" value="Stop" onclick="stopTask()"/>
</div>
</div>
<script>
function startTask() {
var taskId = $('taskId').value;
HeavyLoadTaskExecutor.startTask(taskId, reply1);
}
function stopTask() {
HeavyLoadTaskExecutor.stopTask(reply2);
}
function getTaskStatus() {
HeavyLoadTaskExecutor.getTaskStatus(reply3);
}
function checkTaskRunning() {
HeavyLoadTaskExecutor.isTaskRunning(reply4);
}
function reply1(data) {
$('btnStart').disabled = true;
$('btnStop').style.display = 'block';
$('progCnt').innerHTML = "Processing...";
$('progImg').style.width = 0;
$('progRate').innerHTML = '';
$('progress').style.display = 'block';
$('message').innerHTML = '';
$('message').style.display = 'none';
statusTimeout = window.setTimeout('getTaskStatus()', 1000);
}
function reply2(data) {
$('btnStart').disabled = false;
$('btnStop').style.display = 'none';
$('message').innerHTML = "Stopped";
$('message').style.display = 'block';
window.clearTimeout(statusTimeout);
}
function reply3(data) {
var running = data['running'];
var total = data['total'];
var done = data['done'];
var progress = data['progress'];
var message = data['message'];
$('progImg').style.width = progress * 2;
$('progRate').innerHTML = progress + '%';
if (running == false) {
$('progCnt').innerHTML = "Processed: <strong>"+done+"</strong>";
if (message != null && message != '') {
$('message').innerHTML = message;
$('message').style.display = 'block';
} else {
$('btnStart').disabled = false;
$('btnStop').style.display = 'none';
}
window.clearTimeout(statusTimeout);
} else {
$('progCnt').innerHTML = "Processing... <strong>"+done+"/"+total+"</strong>";
$('btnStart').disabled = true;
$('btnStop').style.display = 'block';
statusTimeout = window.setTimeout('getTaskStatus()', 1000);
}
}
function reply4(data) {
if (data == true) {
$('progCnt').innerHTML = "Processing...";
$('btnStart').disabled = true;
$('progress').style.display = 'block';
statusTimeout = window.setTimeout('getTaskStatus()', 1000);
}
}
checkTaskRunning();
</script> |
7. 클라이언트 실행 결과
- 서버에 업무처리 시작 요청을 한 후에 주기적으로 진행상태 값을 조회하여 진행 상태를 표시한다




"The quality of any [software] is ultimately determined by the quality of the interaction between one human and one system." - Jef Raskin -

