'Study...★'에 해당되는 글 30건

  1. 2011/06/16 java GC 관련
  2. 2011/01/18 [펌] DWR 설명과 사용법
  3. 2010/09/27 cpio 옵션
  4. 2010/09/10 [OHS] Virtual-Host ErrorLog 남기기..
  5. 2010/07/13 [OHS] 버전확인
Study...★/Java2011/06/16 17:21

[Enterprise Java는 거대한 동기화 머신이다] - 재미있는 GC 성능 사례

by 조동욱

Java에서의 Garbage Collection을 흔히 필요악으로 묘사한다. 확실히 GC는 편리한 객체 사용이라는 장점을 제공한다. 하지만 Java에서 발생하는 성능 저하 현상의 8,90%는 GC와 직간접적으로 관련이 있다고 해도 무방할 정도로 문제를 일으키기도 한다.

Garbage Collection은 [맹견]과 같다. 잘 길들이면 행복을 주지만, 자칫 치명적인 상처를 입힐 수도 있다.

GC에서 문제가 발생하는 상황은 매우 다양하다. 더구나 각 시스템마다 Unique한 성능 특징을 가지고 있어 일반적인 해결책이 무용한 경우가 많다.

하지만, 보편적으로 발생하는 몇 가지 사례를 통해 의미있는 통찰력을 얻을 수 있다는 사실은 확실하다. 이런 의미에서 앞으로 몇 차례에 걸쳐 전형적이면서 재밌는 GC 사례와 해결책을 논의해보고자 한다.


1. RMI 사용에 의한 주기적인 Full GC 발생

RMI를 사용할 경우 주기적으로(정확하게 말하면 60초마다) System.gc()를 호출한다.

아래의 GC Dump 예를 보자.

...
211.207: [Full GC 211.207: [ParNew: 123251K->24523K(742784K), 0.2391338 secs] ...
278.167: [Full GC 278.167: [ParNew: 140194K->3468K(742784K), 0.9600674 secs] ...
342.531: [Full GC 342.531: [ParNew: 81241K->646K(742784K), 0.5440857 secs] ...
...

위의 예를 보면 278 - 211 = 67(초), 342 - 278 = 64(초)로 항상 60여 초 단위로 Full GC가 발생하고 있다. 이런 현상이 목격되면 RMI에서 사용하는 Distributed GC에 의한 Explicit GC일 가능성이 대부분이다. RMI 통신을 위해 생성된 객체들을 주기적으로 정리하기 위해 Distributed GC가 작동하게 된다.

아래 2개의 옵션이 Distributed GC에 의한 Explicit GC의 간격을 결정한다.

sun.rmi.dgc.server.gcInterval
sun.rmi.dgc.client.gcInterval

JDK 1.6 이전에는 이 옵션들의 기본값이 60000(ms), 즉 60초였다. 60초마다 한번씩 System.gc()를 호출하게 된다.

System.gc() 호출에 의한 Explicit GC 작업은 Collector의 종류와 무관하게 항상 STW(Stop-The-World) 방식의 Full GC를 유발한다. 심지어 Low Pause Collector(-XX:+UseConcMarkSweepGC)를 사용하더라도 System.gc()는 여전히 STW 방식으로 작동한다. 큰 크기의 Heap을 사용하는 Application에서는 이런 빈번한 Full GC의 호출이 큰 성능 문제를 일으킨다.

기본적인 가이드는 다음과 같다.

1. -XX:+DisableExplicitGC 옵션을 사용해서 System.gc() 콜을 무의미하게 만들어 버린다. 가장 극단적이고 확실한 해결책이다.
2. -Dsun.rmi.dgc.server.gcInterval=3600000 -Dsun.rmi.dgc.client.gcInterval=3600000 (한시간) 과 같이 DGC Interval을 크게 준다.

JDK 1.6 부터는 Low Pause Collector(Concurrent Collector, -XX:+UseConcMarkSweepGC)를 사용할 경우 다음 옵션을 사용할 수 있다.
3. -XX:+ExplicitGCInvokesConcurrent 옵션을 사용해서 System.gc() 콜이 STW(Stop-The-World) 방식이 아닌 Concurrent 방식으로 이루어지도록 한다.


Distributed GC를 논외로 하더라도 Explicit GC는 절대로 남용되어서는 안된다. 메모리를 많이 사용하는 Application들에서 메모리를 좀 더 효율적으로 사용하기 위해 System.gc()를 틈틈이 호출하는 코드를 삽입하는 오류를 흔히 보게 되는데, 굉장히 잘못되고 위험한 방식이다.

Java의 자동화된 GC는 Explicit GC의 호출을 거의 필요로 하지 않는다. 따라서 무의미한 System.gc() 호출이 발생하지 않도록 해야 한다.

PS) 그렇다면, Explicit GC는 완전히 무용한 것일까? 흐음...
생각해 볼 문제다. 다음과 같은 경우에는 유용할 수도 있다.
-XX:+UseConcMarkSweepGC에 의해 활성화되는 Low Pause Collector는 Full GC를 Concurrent하게 진행한다. 이것을 가능하게 하기 위해 Compaction(압축) 작업을 하지 않는다. 따라서 Low Pause Collector에서는 Fragmentation이 발생할 확률이 높다.
Fragmentation이 지나쳐서 필요한 메모리를 확보하지 못하면 STW 방식의 Full GC가 발동되고, 비로소 Compaction 작업이 이루어진다. 웁스!!! 만일 이런 일이 시스템에 로드가 많이 걸린 시점에 발생했다면? Concurrent Collector의 장점이 사라져 버리는 것이다.

Low Pause Collector를 사용하는 환경에서 Fragmentation에 의한 STW Full GC가 자주 발생한다고 판명되면, 시스템의 로드가 적은 시점에 주기적으로 System.gc()를 호출하는 것도 한가지 솔루션이 될 수 있다. 아주 특수한 경우이지만 JVM 옵션으로도 해결이 안되는 경우에는 Workaround가 될 수 있다는 의미이다. 물론 정교한 JVM 옵션 설정으로 이런 문제가 생기지 않도록 하는 것이 최선이겠지만...

 

2. Paging In/Out에 의한 GC 지연

가끔 다음과 같은 현상이 발생하는 경우가 있다.

  • 평소에는 Full GC가 1초안에 끝났다. GC에 의해 Heap 사용량은 200M -> 100M 로 줄었다.
  • 그런데 어제 갑자기 Full GC가 20여초가 걸렸다. Heap 사용량의 변화는 210M -> 100M로 평소와 거의 비슷했다.

도대체 무슨 일이 벌어진 것인가?
정확한 원인을 GC log만을 보고 분석하기란 불가능하다. Full GC가 처리해야할 메모리의 양은 거의 비슷하다. 하지만 Full GC 소요시간은 20배가 늘었다. 논리적으로 가능한 이유들을 열거해 보면...

  • Full GC 당시 CPU Load가 심해서 GC 작업이 지연되었다 <-- CPU 문제
  • Full GC 당시 Memory Load가 심해서 Heap 영역이 Page Out 되었다 <-- Memory 문제
  • JVM 버그다 <-- Bug 데이터베이스 검색!!

다행히(?) 의외로 적지 않은 경우 2번째 이유, 즉 Paging In/Out에 의한 GC 지연이 발생한다.

아래에 실제로 발생한 사례가 있다.

다음과 같이 Low Pause Collector를 사용하는 상황에서...

-XX:+UseConMarkSweepGC -Xms2028m -Xmx2028m

New Generation Collection에 30초, Concurrent Mode가 구동되어서 총 78초가 흐르고 실패하고 말았다.

30263.967: [ParNew: 22279K->3192K(57344K), 30.0028214 secs]
30293.970: [CMS (concurrent mode failure): 1507043K->1045599K(2031616K), 78.8167675 secs]
....

위의 GC Log를 보면 한눈에 비정상적인 상황임을 알 수 있다.

  • 첫째, 57M의 New Generation을 정리하는데 무려 30초가 걸렸다.
  • 둘째, CMS(concurrent mode failure)가 무려 78초가 흐른 후 실패하고 말았다. Concurrent mode failure란 Tenured Space가 꽉 차기 전에 Concurrent Mode의 Collection 작업이 끝나지 않았다는 것을 의미한다.

다행히 이 현상이 발생한 당시의 OS 모니터링 정보에 의해 메모리 부족 현상에 의해 Paging In/Out이 왕성하게 발생한 것을 확인할 수 있었다.

시도한 Workaround는 Java Max Heap Size를 줄이는 것이었고, 더불어 몇몇 JVM Option이 수정되었다. Max Heap Size를 줄인 이유는 Java Process가 사용하는 메모리를 줄임으로써 OS에 메모리의 여유분을 주기 위해서이다.

비록 Java가 메모리 관리를 상당히 자동화하지만, 여전히 OS에 종속된 플랫폼이라는 것을 명심해야 한다. 따라서 OS 레벨의 CPU/Memory/IO 모니터링과 Java Heap Monitoring을 병행해야만 좀 더 합리적인 성능 문제 분석을 수행할 수 있다.

3. Heap 여유 공간이 충분한데도 OOME(OutOfMemoryException)이 발생한다?

간혹 Heap의 여유 공간이 충분한데도 OutOfMemory Error가 나는 경우가 있다. 이러한 상황을 이해하려면 Java Application이 사용하는 메모리가 여러 영역으로 나뉜다는 사실을 이해해야 한다.

Java Application이 사용하는 메모리 영역은 보통 다음과 같이 분류된다.

- Permanent Space: Class 정보를 저장
- Java Heap: Object 정보를 저장
- Native Heap: JNI, Thread Stack, 기타 Native 정보를 저장

우리가 흔히 접하는 Memory 문제는 대부분 Java Heap에서 발생한다. Java Application이 할당하는 오브젝트들이 Java Heap에 거주하기 때문에 가장 많은 메모리를 필요로 하기 때문이다.

하지만 위의 제목처럼 GC Log 등을 통해 모니터링을 해 본 결과 Java Heap의 여유 공간이 충분한데도 OOEM이 난다면? 다음과 같은 세가지 문제를 의심해보아야 한다.

- Permanent Space가 부족하지 않은가?
- Native Heap이 부족하지 않은가?
- 버그가 아닌가? :)

Permanet Space와 Native Heap에서 발생할 수 있는 메모리 부족 현상에 대해서 알아보자.

- Permanent Space의 부족
GC Log를 통해 Permanent Space가 부족한 지의 여부를 간접적으로 판단할 수 있다.
(GC Log에 대한 자세한 내용은 Google 검색....)

대부분의 Application이 기본 크기만으로 필요한 클래스들을 관리할 수 있다. 하지만 복잡한 Application 들, 특히 많은 수의 Servlet/JSP/EJB/Library 들을 로딩하는 Web Application의 경우 좀 더 큰 크기의 Permanent Space를 요구하기도한다.

-verbose:class 옵션을 사용하면 어떤 클래스들이 로딩되는지 확인할 수 있다.

Permanent Space의 부족 현상으로 판명나는 경우에는 -XX:PermSize-XX:MaxPermSize 옵션을 이용해 Permanent Space의 크기를 키워주어야 한다.

- Thread Stack Size가 큰 경우
우리가 Thread를 사용할 때마다 Native Heap에는 Thread가 사용하는 메모리가 할당된다. 시스템마다 기본적으로 할당되는 크기는 다르다.

Thread Stack의 크기를 256K라고 가정해보자. 4개의 Thread는 1M를 사용하고, 400개의 Thread는 무려 100M를 사용한다. 수많은 Thread를 사용하는 대형 시스템에서는 결코 간과할 수 없는 크기가 된다.

Application 모니터링을 통해 현재 생성한(사용 중인 아닌) 전체 Thread의 개수를 파악해야 한다. 만일 지나치게 많은(수백개 ~ 수천개) Thread가 생성되었다면 지나치게 큰 크기의 Native Heap을 사용하고 필연적으로 OOEM을 유발한다.

Thread 개수를 줄이는 가장 기본적인 방법은 Thread Pool을 사용하는 것이다. Weblogic/Jeus/Websphere와 같은 WAS들이 수백~수천의 동시 사용자를 감당할 수 있는 것은 Thread Pool을 잘 사용하기 때문이다. 대량의 Client와 통신을 수행하는 Server Application을 작성하는 경우에는 반드시 Thread Pool 기법을 사용해서 실제 생성되는 Thread의 개수를 줄여야 한다.

또 다른 방법은 Thread Stack Size를 줄이는 것이다. Thread 개수를 줄일 수 없다면 -Xss128K 정도의 작은 값을 부여해서 메모리 사용량을 줄일 수 있다. 단, Stack 크기가 줄어든 만큼 Stack Overflow 에러가 날 확률이 높아진다.

(PS) Application이 사용하는 Thread 개수를 모니터링하는 가장 좋은 방법은? Thread Dump(kill -3)가 가장 손쉽고 확실한 방법이다. JDK 1.5에서 추가된 Platform MBean(MXBean)과 JConsole 또한 좋은 방법이다. JConsole에 대해서는 아래 URL을 참조한다.

http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html

- File나 Socket을 지나치게 많이 오픈하는 경우
File/Socket을 Resource Limit를 초과하여 오픈하는 경우에도 OOEM이 발생한다. 우선 File이나 Socket을 열고 닫지 않은, 이른바 Resource Leak을 의심해봐야 하며 필요한 경우 다음과 같이 File/Socket의 Resource Limit을 키워야 한다.

ulimit -n 10000

- JNI를 사용하는 Library들...
JDK가 제공하는 Library나 3rd Party가 제공하는 Library들 중에서 JNI를 사용하는 경우 Native Heap을 사용한다. Native Heap은 Java Heap과는 전혀 독립적으로 사용된다. 따라서 Java Heap은 여유가 있음에도 OOEM이 발생하게 된다.

Max Heap(-Xmx) 크기를 500M 정도로 지정했는데 OS 모니터링을 통해 본 Java Process의 메모리 크기가 2G라면? 십중 팔구 Native Heap의 크기가 지나치게 커진 것이다. Thread Stack 크기와 더불어 JNI Heap을 의심해보아야 한다.

Oracle OCI JDBC Driver가 JNI를 사용하는 대표적인 3rd Party Library이다. OCI JDBC Driver는 Thin Driver에 비해 성능 면에서 다소 유리하지만 Native Heap 공간을 많이 사용하는 경향이 있다. 따라서 OCI Driver를 사용하는 경우에는 Process의 메모리 크기를 잘 모니터링해야 한다.

한가지 역설적인 것은 Native Heap을 좀더 크게 사용하기 위해서 Java Heap의 크기를 줄여야 하는 경우가 있다는 것이다. 즉, OutOfMemory 부족 현상이 Native Heap에서 발생하는 경우에는 Java Heap의 크기를 줄이는 것이 대안이 될 수 있다. 32bit 환경에서는 하나의 Java 프로세스가 사용할 수 있는 최대 메모리 공간은 2G로 제한된다. 이 공간을 Java Heap이 다 써버리면 Native Heap이 사용될 수 있는 공간이 그만큼 줄어든다. 그만큼 OOEM이 날 확률이 높아진다.

- 노파심에...
시스템의 물리적 메모리가 2G이다. Java Application의 성능을 극대화하기 위해 Max Heap Size를 1.8G 정도 부여할려고 한다. 바람직한가? 그럴 수도 있지만 생각치 못한 역효과가 있을 수 있다. 앞에서 소개한 몇가지 사례를 기억하자.

Java Heap에 지니치게 많은 메모리를 할당함으로써 Permanet Space나 Native Heap의
부족을 초래하게 되고 이로 인해 OOEM이나 Paging In/Out에 의한 성능 저하 현상이 유발될 수 있기 때문이다.

메모리는 항상 필요한 만큼만...
4. 큰 New Generation에 의한 Full GC 발생

다음과 같은 상황을 가정해보자
  • 전체 Heap의 크기는 1.5G이다.
  • New Generation의 크기를 전체 Heap의 1/4 정도로 크게 잡았다(일반적인 가이드)
  • 1G 정도에 육박하는 객체 정보를 메모리에 관리하고자 한다.

위와 같은 Java Application을 구동하면 그 결과는 어떻게 될까?

Full GC가 자주 발생할 확률이 매우 높다. Tenured Space가 큰 메모리 객체(1G)를 계속 보유하고 있기 때문에 New Generation에서 Promotion 되는 객체를 감당하지 못하게 되는데 이 경우 반드시 Full GC가 발생한다. 이런 현상을 Promotion Failure라고 부른다.

Promotion Failure는 Minor GC에서는 항상 발생할 가능성이 있다. Minor GC가 발생할 때 Live Object들은 Survior Space나 Tenured Space로 옮겨지게 되는데, 이 때 Tenured Space에 공간이 부족하면 Full GC가 발생하게 된다.

위의 상황을 다시 정리해보면 Tenured Space에 1G에 달하는 객체들이 상주하고 있다. 이런 상황에서 New Generation의 크기를 크게 하면 Promotion Failure가 발생할 확률이 매우 높아진다. 따라서 이런 경우에는 New Generation의 크기를 작게 주어야 한다.

New Generation 크기의 속성을 잘 이해할 필요가 있다. New Generation의 크기가 크면 Minor GC는 유리해지지만 Full GC에서는 불리해질 확률이 높다는 기본 원리를 염두에 두고 Application 속성에 따라 최적의 크기를 찾을 필요가 있다.

PS) 다행히 New Generation과 Survivor Space의 크기를 JVM에 의해 자동으로 관리된다. -XX:+UseAdaptiveSizePolicy 옵션에 의해 New Generation의 자동화된 크기 관리가 이루어지면 디폴트로 활성화되어 있다. 하지만 간혹 강제적으로 -XX:NewRatio 옵션을 이용해 크기를 설정해야만 문제가 해결되는 경우가 있다.

'Study...★ > Java' 카테고리의 다른 글

IBM JVM 튜닝 - 1  (2) 2011/07/03
java GC 관련  (0) 2011/06/16
[펌] DWR 설명과 사용법  (0) 2011/01/18
각 환경별 Servlet & JNI 정리  (0) 2010/06/24
Posted by 달콤한녀석
Study...★/Java2011/01/18 09:33

DWR (Direct Web Remoting)


  • DWR은 Direct Web Remoting 의 약자로서 ajax를 기반으로 하여 client side 의 javascript 로 server side 의 Java Beans을 interactive하게 조작할 수 있는 framework 입니다.
  • 현재 stable version 은 1.1 이고 dwr.dev.java.net 에서 development version인 2.0 소스를 checkout 할 수 있습니다. 특히 2.0 에서는 rajax (reverse ajax)라는 개념으로 역으로 Java Beans에서 client side 의 javascript를 조작가능하도록 구현되어 있습니다.
  • 또한 Struts, Spring, Webwork, JSF 등 web framwork 와의 연동을 위한 libraries 를 제공하고 있습니다.
  • 테스트를 위한 sample 소스를 82서버의 개발cvs repository 에 'dwr_ex' module 로 등록되어 있으므로 checkout 받아 테스트할 수 있습니다.
  • 개발자인 Joe Walker가 운영중인 사이트 http://getahead.ltd.uk/dwr/ 에서 online manual과 많은 정보를 구할 수 있습니다.



DWR Architecture


  • DWR은 ajax 와 결합된 Client side, DWRServlet, Server side JavaBeans 를 가지는 MVC패턴과 유사한 구조입니다. 이는 Javascript 로 DWREngine(ajax engine)을 통해 view page 갱신없이 실시간 서비스를 호출하여 dhtml로 화면을 조작할 수 있는 기능을 제공합니다.
  • Client side 인 view page는 javascript + html 만으로도 충분히 개발가능하나 페이지 로딩 시 서버에서 가져온 데이터를 리스트 또는 상세내역으로 보여주어야 할 때는 JSP 페이지를 사용하여야 합니다.
  • DWRServlet의 경우 controller 의 역할로써 /dwr/engine.js, /dwr/util.js, /dwr/interface/TestObject.js 의 요청이 들어오면(uri-mapping) dwr.xml 에 등록된 TestObject을 참조하여 Javascript를 생성해주는 역할을 수행합니다.
  • JavaBeanTestObject의 경우 Business Layer의 component를 생성하여 서비스를 호출할 수 있는 메소드를 Javascript 에서 사용할 수 있도록 제공해주어야 합니다.


  • 위 그림에 대한 설명은 다음과 같습니다.
  • 1] Init 수행

페이지 로딩 시 /dwr/engine.js, /dwr/util.js, /dwr/interface/TestObject.js 의 요청이 들어오면 DWRServlet은 dwr.xml를 로딩하여 TestObject 에 대한 메소드를 Javascript로 호출할 수 있도록 생성해 줍니다. 이로써 TestObject를 Javascript에서 조작가능하게 됩니다.

  • 2] call method 수행

Init 수행 후 TestObject.getList() 호출 시 DWREngine 을 호출하여 서버쪽 TestObject JavaBean의 getList() 메소드를 호출하게 됩니다. 만약 getList() 메소드에 return value가 존재한다면 callback function 을 다음과 같이 정의하여야 합니다.

 

 

1] callback function 분리 :
// TestObject 의 getList() 메소드 호출.
TestObject.getList(getListCallback);
function getListCallback(data) {
// returned value 처리
};
2] anonymous function 으로 구현 :  
// TestObject 의 getList() 메소드 호출.
TestObject.getList( {
callback : function(data) {
// returned value 처리
},
errorHandler : function(message, exception) {
// server message, error 처리
}
});

  • 위와 같이 call method 이후 returned value 손쉽게 combo box 및 table에 자동 셋팅할수 있도록 util.js 내에 DWRUtil 을 제공합니다.


 


dwr libraries 설치


  • dwr 라이브러리는 dwr.jar 하나이며 참조하는 라이브러리도 함께 설치되어야 합니다.
  • 최소 설치 libraries :
  1. dwr.jar : dwr core library
  2. bsf-2.3.jar : Bean Scripting Framework library ( http://jakarta.apache.org/bsf/index.html )
  3. bsh-2.0b4.jar : Bean Shell Framework library ( http://www.beanshell.org/ )
  4. commons-logging-1.0.4.jar : Jakarta Common Logging library ( http://jakarta.apache.org/commons/logging/ )
  5. commons-validator-1.1.4.jar : Jakarta Common Validator library ( http://jakarta.apache.org/commons/validator/ )
  6. jakarta-oro-2.0.8.jar : Jakarta ORO library ( http://jakarta.apache.org/oro/ )
  7. log4j-1.2.12.jar : log4j library ( http://logging.apache.org/log4j/docs/index.html )

  • web framework 와 연동하기 위한 선택적인 libraries :
  1. spring.jar : Spring Framework http://www.springframework.org/
  2. struts.jar : Struts Framework http://struts.apache.org/
  3. hibernate3.jar : hibernate Framework http://www.hibernate.org/
  4. webwork.jar : webwork Framework http://www.opensymphony.com/webwork/
  5. jsf-api.jar, jsf-impl.jar : JSF Framework http://java.sun.com/javaee/javaserverfaces/reference/docs/index.html

 

등이 필요합니다.

 


DWRServlet 설정 - web.xml



  • dwr_ex 모듈 내 WebContent/WEB-INF/web.xml 를 참조하십시요.

 


  • 기본적인 DWRServlet 설정

 

 

<servlet>
<servlet-name>dwr-invoker</servlet-name>
<display-name>DWR Servlet</display-name>
<description>Direct Web Remoter Servlet</description>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param> 
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>


  • dwr.xml 을 업무별로 나누어 사용이 가능합니다. http://getahead.ltd.uk/dwr/server/servlet/multiconfig 을 참조하십시요.
  • dwr-user.xml, dwr-admin.xml 두개의 설정파일을 사용하고자 하는 경우 DWRServlet 을 dwr-user-invoker, dwr-admin-invoker 두 개 설정, 각각의 dwr.xml을 설정합니다.

 

 

<servlet>
<servlet-name>dwr-user-invoker</servlet-name>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>config-user</param-name>
<param-value>WEB-INF/dwr-user.xml</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>dwr-admin-invoker</servlet-name>
<servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
<init-param>
<param-name>config-admin</param-name>
<param-value>WEB-INF/dwr-admin.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-admin-invoker</servlet-name>
<url-pattern>/dwradmin/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dwr-user-invoker</servlet-name>
<url-pattern>/dwruser/*</url-pattern>
</servlet-mapping>

 


dwr.xml 작성



  1. dwr.xml에 javascript 객체로 조작할 JavaBean 을 설정함
  2. 해당 JavaBean에 대한 Life scope 지정이 가능 (page, script, request, session, application)
  3. JavaBean에서 사용해야할 메소드 filtering : include, exclude method 지정
  4. Value object(JavaBean) 사용하기 위한 각종 converter 제공
  5. ajax 요청을 가로채는 Filter 설정

  • 다음은 DwrTest을 Javascript 에서 사용하기 위한 설정입니다.
  • Foo 객체의 경우 updateFoo(Foo foo), deleteFoo(Foo foo) 메소드 호출 시 파라미터의 Value Object 로써 사용하기 위해 bean converter 로 설정되어 있음

 


 

<dwr>
<init>
...
</init> 
<allow>
…
<create creator="new" javascript="DwrTest" scope="request">
<param name="class" value="fullbox.dwr.test.DwrTest"/>
</create>
<convert match="fullbox.dwr.test.Foo" converter="bean"/>
…
</allow>
</dwr>

 


client page 작성


  • client page 내에는 다음을 반드시 작성하여야 합니다.
  1. DwrTest.js 는 동적으로 생성되는 스크립트로써 DwrTest 객체를 조작하기 위해서는 반드시 아래와 같이 작성해 주어야 함.
  2. engine.js 는 DWREngine 이 정의된 스크립트임
  3. util.js 는 DWRUtil 이 정의된 스크립트임
  4. example (dwr_ex.html 참조)

 

 

<html>
<head>
...
<script language="javascript" src="/dwr/interface/DwrTest.js"></script>
<script language="javascript" src="/dwr/engine.js"></script>
<script language="javascript" src="/dwr/util.js"></script>
</head>
<body>
</body>
</html>

 


dwr_ex 모듈 테스트


  • 82서버 개발 cvs 에서 dwr_ex 모듈을 checkout 받습니다.
  • tomcat의 web context 로 'dwr_ex'를 설정하여 tomcat을 기동합니다.
  • eclipse wtp를 사용하여 web context로 등록하면 tomcat 에 다음과 같이 설정됩니다.

 

 

<Context docBase="dwr_ex" path="/dwr_ex" reloadable="true" source="org.eclipse.jst.j2ee.server:dwr_ex"/>

 


dwr example 개발



에러 메시지 핸들러 구현


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex01.html 을 참조하십시요.

  • Javascript 로 조작하기 위한 DwrExample 클래스를 다음과 같이 정의합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
public String getData1() {
throw new NullPointerException("NullPointerException occurred!");
}
}

  • DwrExample 클래스를 dwr.xml 에 다음과 같이 설정합니다. scope 을 'session' 으로 설정하였으므로 DwrExample 생성 후 HttpSession 에 저장됩니다.

 

 

<create creator="new" javascript="DwrExample" scope="session">
<param name="class" value="fullbox.dwr.test.DwrExample"/>
</create>

  • dwr_ex01.html 은 다음과 같이 작성합니다.
  • 메시지 처리 방법은 DWREngine 의 setErrorHandler(), setWarningHandler() 메소드에 의해 정의됩니다.
  • 자세한 내용은 http://getahead.ltd.uk/dwr/other/errors 을 참조하십시요.

  • 먼저 여러 개의 ErrorHandler function 을 지정합니다. 이 function 들은 error message 를 다양하게 표시하기 위한 로직을 포함하고 있습니다. 아래는 간단히 메시지만 다르게 하여 서로 다른 ErrorHandler function 을 구현한 예제입니다.

 

 

// Global ErrorHandler 를 지정하는 function.
function eh0(msg) {
alert("Global ErrorHandler : " + msg);
}
// 새로운 ErrorHandler 를 지정하는 function 1).
function eh1(msg) {
alert("Applied New ErrorHandler : " + msg);
}
// 새로운 ErrorHandler 를 지정하는 function 2).
function eh2(msg, ex) {
// FunkyException 에 존재하는 getWhen() 메소드를 ex.when 으로 호출.
alert(msg + ", date=" + ex.when);
}


  • 한 페이지의 모든 메시지의 처리를 동일하게 표시하려면 body태그의 onLoad() 시 init()을 실행하도록 합니다. init() function 은 먼저 Global ErrorHandler function 인 eh0 을 설정합니다.

 


 

// body onLoad() 에서 초기화하는 function : Global ErrorHandler를 설정.
function init() {
// ErrorHandler, WarningHandler 설정.
DWREngine.setErrorHandler(eh0);
DWREngine.setWarningHandler(eh0);
// Loading Message 설정.
DWRUtil.useLoadingMessage();
}

  • 따라서 객체의 모든 메소드 호출 시 오류가 발생하면 위에서 정의한 대로 메시지가 처리됩니다.

  • 객체의 특정 메소드 호출 시(ex : DwrExample.getData1()) 처리 메시지를 다르게 보여주어야 한다면 해당 메소드를 호출하기 전에 DWREngine.setErrorHandler() 를 다음과 같이 재정의하여야 합니다.

 


 

function getDataByAppliedErrorHandler() {
// 새로운 ErrorHandler를 적용.
DWREngine.setErrorHandler(eh1);
DwrExample.getData1(getDataCallback);
}

  • 단, 위 방법은 DWREngine.setErrorHandler() 에 직접 설정하는 것으로 Global하게 적용되므로 서로 다른 메소드 호출 시 각각 다른 방법으로 메시지를 처리하는 것은 부적절합니다.

  • Global ErrorHandler 의 설정을 그대로 유지하면서 DwrExample.getData1() 호출 시 다음과 같이 정의하여 특정 메시지를 처리할 수 있습니다.

 


 

function getDataByLocalErrorHandler() {
// 새로운 Local ErrorHandler를 적용.
DwrExample.getData1({
callback : getDataCallback,
errorHandler : function(msg, ex) {
alert("Local ErrorHandler : " + msg);
}
});
}

  • 위와 같이 작성하면 Global ErrorHandler 의 설정과 Local ErrorHandler 의 설정을 분리할 수 있습니다.

 


사용자 정의 Exception 처리


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex01.html 을 참조하십시요.

  • 사용자 정의의 FunkyException 클래스를 다음과 같이 작성합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class FunkyException
extends RuntimeException {
private final static long serialVersionUID = 1L;
private Date date; 
public FunkyException(String msg) {
super(msg);
}
public FunkyException(String msg, Date date) {
super(msg);
this.date = date;
}
public Date getWhen() {
return date;
}
}

  • 먼저 구현한 Exception (ex : FunkyException) 을 dwr.xml 의 bean converter로 설정하여야 합니다.

 

 

<convert match="fullbox.dwr.test.FunkyException" converter="bean" />

  • 다음은 DwrExample 클래스를 다음과 같이 작성합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
public String getData1() { ... }
public String getData2() {
Date when = new Date();
// FunkyException 예외 발생.
throw new FunkyException("FunkyException occurred!", when);
}
}

  • dwr_ex01.html 에서 객체의 특정 메소드 호출 시(ex : DwrExample.getData2()) 처리 메시지를 다르게 보여주어야 한다면 해당 메소드를 호출하기 전에 DWREngine.setErrorHandler() 를 다음과 같이 재정의하여야 합니다.

 

 

function getDataThrownFunkyExceptionByAppliedErrorHandler() {
// 새로운 ErrorHandler를 적용.
DWREngine.setErrorHandler(eh2);
DwrExample.getData2(getDataCallback);    	    
}

  • 단, 위 방법은 DWREngine.setErrorHandler() 에 직접 설정하는 것으로 Global하게 적용되므로 서로 다른 메소드 호출 시 각각 다른 방법으로 메시지를 처리하는 것은 부적절합니다.

  • Global ErrorHandler 의 설정을 그대로 유지하면서 DwrExample.getData2() 호출 시 다음과 같이 정의하여 특정 메시지를 처리할 수 있습니다.

 

 

function getDataThrownFunkyExceptionByLocalErrorHandler() {
// 새로운 Local ErrorHandler를 적용.
DwrExample.getData2({
callback : getDataCallback,
errorHandler : function(msg, ex) {
alert("Local ErrorHandler : " + msg + " , date=" + ex.when);
}
});
}

  • 위와 같이 작성하면 Global ErrorHandler 의 설정과 Local ErrorHandler 의 설정을 분리할 수 있습니다.

 


JavaBean 값 셋팅


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex02.html 을 참조하십시요.

  • 이번 예제는 Javascript 객체를 통해 DwrExample JavaBean에 값을 셋팅하고 결과를 출력하는 예제입니다.

  • DwrExample 클래스에 name 을 셋팅하는 setName() 메소드와, Foo 객체를 셋팅하는 setFoo() 메소드를 추가합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
... 이전 예제의 메소드 ...
private Foo foo;
public void setName(String name) {
System.out.println("### name : " + name);
}
public void setFoo(Foo foo) {
System.out.println("### foo.name : " + foo.getName());
System.out.println("### foo.title : " + foo.getTitle());
System.out.println("### foo.content : " + foo.getContent());
this.foo = foo;
}
public Foo getFoo() {
return foo;
}
}

  • Foo 클래스는 name, title, content 필드를 갖고 각 set, get 메소드를 가진 클래스입니다. dwr_ex 모듈 내 src/fullbox/dwr/test/Foo.java 를 참조하십시요.


  • dwr.xml 에 보면 Foo 클래스는 다음과 같이 bean converter 로 설정되어 있습니다.

 

 

<convert match="fullbox.dwr.test.Foo" converter="bean"/>


  • dwr_ex02.html 에서 다음과 같이 function 을 작성합니다.

 

 

// name 값을 DwrExample JavaBean에 셋팅.
function setName() {
var name = frm.name.value;
DwrExample.setName(name);
}
// name, title, content 값을 DwrExample JavaBean에 셋팅.
function setFoo() {
var foo = {
name : frm.name.value, 
title : frm.title.value,
content : frm.content.value
};
DwrExample.setFoo(foo);     
}
// 저장된 Foo 값을 가져오는 function.
function getFoo() {
DwrExample.getFoo(getFooCallback);
}
// getFoo()의 callback function.
function getFooCallback(foo) {
DWRUtil.setValue("data", 
"name : " + foo.name + 
" , title : " + foo.title + 
" , content : " + foo.content);
}


  • 입력폼을 생성하고 function setName(), setFoo() 을 호출하여 tomcat 콘솔 로그창에 셋팅된 값이 정상적으로 출력되는지 확인합니다.

  • getFoo() 을 호출하면 div 태그 내에 값이 변경되는지 확인합니다.


 


combobox 동적 구성


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex03.html 을 참조하십시요.

  • 이번 예제는 Javascript 객체를 통해 DwrExample JavaBean에 값을 가져와 동적으로 Combobox를 생성하는 예제입니다.

  • DwrExample 클래스에 주 리스트를 가져오는 메소드와 도시 리스트를 가져오는 메소드를 다음과 같이 정의합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
... 이전 예제의 메소드 ...
private String[][] address = {
{"Florida", "Bryceville"}
,{"Florida", "Elkton"}
,{"Florida", "Glen Saint Mary"}
,{"Michigan", "Birmingham"}
,{"Michigan", "Marine City"}
,{"Michigan", "Chesterfield"}
,{"Minnesota", "Almelund"}
,{"Minnesota", "Castle Rock"}
,{"Minnesota", "Dalbo"}
,{"California", "Los Angeles"}
,{"California", "Orange"}
};
public Map getComboListState() {
Map map = new HashMap();
String curState;
String prevState = "";
for( int i = 0 ; i < address.length ; i++ ) {
curState = address[i][0];
if(!curState.equals(prevState))
map.put(address[i][0], address[i][0]);
prevState = curState;
}
return map;
}
public Map getComboListCity() {
Map map = new HashMap();
for( int i = 0 ; i < address.length ; i++ ) {
map.put(address[i][1], address[i][1]);
}
return map;
}
}

  • dwr_ex03.html 에서 다음과 같이 function 을 작성합니다.

 

 

// dynamic Combobox 설정 function.
function getComboListState() {
DwrExample.getComboListState(getListComboCallback);
}
// dynamic Combobox 설정 function.
function getComboListCity() {
DwrExample.getComboListCity(getListComboCallback);
}
// callback function.
function getListComboCallback(data) {
DWRUtil.removeAllOptions("listCombo");
var defaultSelect = {"":" -- select -- "};
DWRUtil.addOptions("listCombo", defaultSelect);
DWRUtil.addOptions("listCombo", data);
}

  • 빈 select 태그의 name 값을 'listCombo' 로 정의합니다.

  • function getComboListState(), getComboListCity() 를 호출하였을 때 listCombo 박스의 내용이 동적으로 변경되는지 확인합니다.

 


상위 combobox 선택에 따른 하위 combobox 동적 구성


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex04.html 을 참조하십시요.

  • 이번 예제는 상위 Combobox 에서 특정 주를 선택 시 해당하는 하위 도시 리스트를 sub Combobox에 동적으로 셋팅하는 예제입니다.

  • DwrExample 클래스에서 주 리스트를 가져오는 메소드는 combobox 동적 구성를 참조하며 도시 리스트를 가져오는 메소드는 다음과 같이 정의합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
... 이전 예제의 메소드 ...   
private String[][] address = { ... };
public Map getComboListState() { ... }
public Map getComboListCityByState(String state) {
Map map = new HashMap();
for( int i = 0 ; i < address.length ; i++ ) {
if(address[i][0].equals(state))
map.put(address[i][1], address[i][1]);
}
return map;
}
}

  • dwr_ex04.html 에서 다음과 같이 function 을 작성합니다.

 

 

// init() function 에 페이지 로딩 시 stateCombo박스를 셋팅하는 것을 호출합니다.
function init() {
... ErrorHandler 설정...
// dynamic state Combobox 설정 function.
DwrExample.getComboListState(getComboListStateCallback);
}
// dynamic Combobox 설정 function.
function getComboListCityByState() {
// $() 으로 현재 html doucument 내 지정된 id, ids 에 해당하는 element object를 가져옴.
var state = $("stateCombo").value;
DwrExample.getComboListCityByState(state, getComboListCityCallback);
}
// callback function.
function getComboListStateCallback(data) {
DWRUtil.removeAllOptions("stateCombo");
var defaultSelect = {"":" -- select -- "};
DWRUtil.addOptions("stateCombo", defaultSelect);
DWRUtil.addOptions("stateCombo", data);
}
// callback function.
function getComboListCityCallback(data) {
DWRUtil.removeAllOptions("cityCombo");
var defaultSelect = {"":" -- select -- "};
DWRUtil.addOptions("cityCombo", defaultSelect);
DWRUtil.addOptions("cityCombo", data);
}

  • 두개의 빈 select 태그를 생성하고 각각 name 값을 'stateCombo', 'cityCombo' 로 정의합니다.

  • stateCombo 의 onChange="getComboListCityByState();" 를 설정합니다.

  • 작성완료 후 페이지 로딩 시 stateCombo 박스에 정상적으로 값이 셋팅되는지 확인합니다.

  • stateCombo박스의 값을 변경하면 정상적으로 cityCombo박스의 값이 변경되는지 확인합니다.

 


검색어추천 박스 만들기


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex05.html 을 참조하십시요.

  • 이번 예제는 입력박스에서 하나씩 입력할 때마다 실시간으로 입력된 단어로 시작되는 추천단어들을 검색해주는 예제입니다.

단, 현재 100 ~ 199 까지의 숫자문자열만 배열에 저장되어 있으므로 검색어 범위를 100 ~ 199 사이만 입력하여야 합니다. 그리고 검색어 추천 리스트에서 특정 검색어를 더블클릭하면 div 태그에 선택한 검색어를 표시합니다.

  • DwrExample 클래스에서 다음과 내용을 작성합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
... 이전 예제의 메소드 ...
private static String[] titles = new String[199];
static {
for( int i = 100 ; i < titles.length ; i++ ) {
titles[i] = "" + i;
}
}
public List getRecommendSearch(String title) {
List result = new ArrayList();
for( int i = 100 ; i < titles.length ; i++ ) {
if(titles[i].startsWith(title)) {
result.add(titles[i]); 
}
}
return result;
}
}


  • dwr_ex05.html 에서 다음과 같이 function 을 작성합니다.

 

 

function recommendSearch() {
var arg = $("title").value;
DwrExample.getRecommendSearch(arg, recommendSearchCallback);    
}
function recommendSearchCallback(data) {
// table 내 각 열에 대한 정의를 다음과 같이 각 열대로의 function 을 정의함
var cellFuncs = [
function(data) {
return "<input type='text' name='recommend' style='border:0px;cursor:hand' " + 
" value='" + data + "' onClick='selectRecommend(this.value)' readOnly />";
}
];
DWRUtil.removeAllRows("RecommendTable");
DWRUtil.addRows("RecommendTable", data, cellFuncs);
}
function selectRecommend(data) {
DWRUtil.setValue("recommendDiv", data);
}

  • 다음은 검색어를 입력할 입력박스와 div태그, 그리고 검색어 추천 리스트를 보여줄 table태그를 다음과 같이 정의합니다.

 

 

<form name="frm">
<table width="60%">
<tr>
<td width="50%">
<input type="text" name="title" value="" onKeyUp="recommendSearch();" />
</td>
<td width="50%">
<div id="recommendDiv" style="border:1px solid #000000;">
Recommended argments
</div>
</td>
</tr>
</table>
</form>
<table style="border:1px solid #000000;" cellspacing="0" cellpadding="0">
<tbody id="RecommendTable" border="0" >
</tbody>
</table>


  • 위 소스에서 입력박스에 있는 onKeyUp() 이벤트 핸들러를 통해서 키를 누른 후 땔 때마다 검색어 추천리스트를 서버에서 조회하게 됩니다. 정상적으로 작동하는 지 입력박스에 한단어씩 입력하여 테스트합니다.

  • 조회된 검색어 추천 리스트에서 더블클릭했을 때 입력박스 옆의 recommendDiv 에 선택한 검색어 추천 단어가 정상적으로 셋팅되는지 확인합니다.


 


간단한 게시판(CRUD 기능) 만들기


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex06.html 을 참조하십시요.

  • 이번 예제는 검색폼을 이용하여 검색한 리스트에서 입력,수정,삭제를 하는 간단한 게시판을 구현합니다.
  • 이 게시판은 페이지의 갱신없이 CRUD 가 수행되므로 동적인 페이지의 변경 및 리스트의 갱신 등을 필요로 하는 복잡한 화면에서 사용하기에 적절합니다.

  • DwrExample 클래스에서 다음과 같이 데이터베이스의 연결없이 static variable 로 CRUD를 처리하는 메소드를 간단히 구현합니다.

 

 

package fullbox.dwr.test;
import java.util.Date;
public class DwrExample {
... 이전 예제의 메소드 ...
// 데이터의 선언
private static Map datas = new HashMap();
static {
for (int i = 100 ; i < 199 ; i++ ) {
Foo foo = new Foo();
foo.setName("" + i);
foo.setTitle("title" + i);
foo.setContent("content" + i);
datas.put(foo.getName(), foo);
}
}
// 조회 메소드
public List listFoo(String name) {
List result = new ArrayList();
Iterator it = new TreeMap(datas).values().iterator();
while(it.hasNext()) {
Foo foo = (Foo)it.next();
if(foo.getName().startsWith(name)) {
result.add(foo);
}
}
return result;
}
// 상세내역 조회 메소드
public Foo viewFoo(String name) {
return (Foo)datas.get(name);
}
// 입력, 수정 메소드
public void updateFoo(Foo foo) {
datas.remove(foo.getName());
datas.put(foo.getName(), foo);
}
// 삭제 메소드
public void deleteFoo(Foo foo) {
datas.remove(foo.getName());
}
}


  • dwr_ex06.html 에서 다음과 같이 function 을 작성합니다.

 

 

// 조회 function
function listFoo() {
var arg = $("search").value;
// 첫번째 매개변수는 실매개변수로, 두번째는 callback function을 셋팅.
DwrExample.listFoo(arg, listFooCallback); 
}
// 조회 callback function
function listFooCallback(foos) {
// fooTable tbody 내에서 다시 조회 되었을 때 id 가 'pattern'인 tr태그만 남겨두고 나머지 삭제.
DWRUtil.removeAllRows("fooTable", { 
filter : function(tr) {
return (tr.id != "pattern");
}
});
// 루프 돌면서 foo 리스트를 각 span 태그의 id에 해당하는 위치에 값 셋팅.
for (var i = 0; i < foos.length; i++) {
var foo = foos[i];
var name = foo.name;
DWRUtil.cloneNode("pattern", { idSuffix:name });
DWRUtil.setValue("tname" + name, name);
DWRUtil.setValue("ttitle" + name, foo.title);
DWRUtil.setValue("tcontent" + name, foo.content);
// 복사한 tr 태그를 화면에 나타나게 함.
$("pattern" + name).style.display = "";
}
}
// 상세내역 조회 function
function viewFoo(pttnid) {
// pttnid 는 'pattern110' 이런 형태이므로 'pattern' prefix를 잘라낸 나머지가 foo.name 임.
var name = pttnid.substring(7);
DwrExample.viewFoo(name, function(foo) {
DWRUtil.setValue("name", foo.name);
DWRUtil.setValue("title", foo.title);
DWRUtil.setValue("content", foo.content);
// 수정, 삭제 시 name 필드는 readonly로 셋팅.
$("name").readonly = true;
});
}
// 입력,수정 function
function insertFoo() {
var foo = {
name : $("name").value, 
title : $("title").value,
content : $("content").value
};
// DWREngine의 batch 단위로 묶어서 일괄처리 함. performance 향상.
DWREngine.beginBatch();
DwrExample.updateFoo(foo);
listFoo();
DWREngine.endBatch();
}
// 삭제 function
function deleteFoo() {
var foo = {
name : $("name").value, 
title : null,
content : null
};
// DWREngine의 batch 단위로 묶어서 일괄처리 함. performance 향상.
DWREngine.beginBatch();
DwrExample.deleteFoo(foo);
listFoo();
frmReset();
DWREngine.endBatch();
}
// 폼 리셋 function
function frmReset() {
var val = frm.search.value;
frm.reset();
frm.search.value = val;
frm.name.readonly = false;
}

  • 위 스크립트는 기능별로 구현한 것이므로 function 내 주석을 참조하면 쉽게 이해할 수 있습니다.

  • 다음은 검색어를 입력할 입력박스와 조회버튼에 대한 태그를 다음과 같이 작성합니다. 이벤트 핸들러는 onclick 입니다.

 

 

<table width="60%">
<tr>
<td>
<input type="text" name="search" value="" />
<input type="button" name="listfoo" value="search" onclick="listFoo();" />
</td>
</tr>
</table>


  • 다음은 리스트를 표시할 테이블과 상세내역 테이블을 다음과 같이 작성합니다. 리스트 테이블의 tr태그 내의 viewFoo() function 이 onDblClick 이벤트핸들러로 설정되어 있습니다.

 

 

<table width="60%" style="border:1px solid #000000;" cellspacing="0" cellpadding="0">
<thead>
<tr>
<th width="20%">Name</th>
<th width="40%">Title</th>
<th width="40%">Content</th>
</tr> 
</thead>
<tbody id="fooTable">
<tr id="pattern" style="display:none;cursor:hand;" onDblClick="viewFoo(this.id);" >
<td><span id="tname">Name</span></td>
<td><span id="ttitle">Title</span></td>
<td><span id="tcontent">Content</span></td>
</tr>
</tbody>
</table>
<br />
<table width="60%">
<tr>
<td>Name</td>
<td width="80%"><input type="text" id="name" name="name" value="" /></td>
</tr>
<tr>
<td>Title</td>
<td><input type="text" id="title" name="title" value="" /></td>
</tr>
<tr>
<td>Content</td>
<td><input type="text" id="content" name="content" value="" /></td>
</tr>
</table>

  • 위 소스에서 viewFoo() 의 매개변수의 값으로 tr 태그의 id 값을 넘겨주는 데 이는 'pattern' + 'name 값' 이므로 각 tr태그마다 유일하게 정의되어 있습니다. 그러므로 prefix 인 'pattern'을 제외한 'name 값'을 해당 tr태그의 키값으로 하여 각 foo 를 수정하거나 삭제할 수 있습니다.

  • 다음은 CUD 버튼을 작성합니다. 각각 function은 button 의 onclick 이벤트 핸들러로 설정되어 있습니다.

 

 

<table width="60%">
<tr>
<td>
<input type="button" name="insert" value="insert Foo" onclick="insertFoo();" />
<input type="button" name="update" value="update Foo" onclick="insertFoo();" />
<input type="button" name="delete" value="delete Foo" onclick="deleteFoo();" />
</td>
</tr>
</table>

  • dwr_ex06.html 의 코드 작성 완료 후 테스트를 위하여 '100' ~ '199' (데이터값) 의 특정 값을 입력하여 'search' 버튼을 선택합니다.

  • 조회된 리스트에서 특정 데이터를 선택합니다. 그리고 아래 상세내역에서 title, content 항목을 수정 후 'update Foo' 버튼을 선택하면 정상적으로 수정되고 조회되는지 확인합니다.

  • 조회된 리스트에서 특정 데이터를 선택합니다. 그리고 아래 상세내역에서 'delete Foo' 버튼을 선택하면 정상적으로 삭제되고 조회 후 데이터가 삭제되었는지 확인합니다.

  • 아래 상세내역 초기화 상태에서 name, title, content 항목을 입력후 'insert Foo' 버튼을 선택하면 정상적으로 입력되고 조회되는지 확인합니다.

 


Web Framework Integration


  • struts, spring, webwork, jsf 등 web framework 의 bean 설정 파일에 등록된 JavaBeans를 DWR 에서 Javascript 로 조작가능하게 하는 것으로 간단한 설정만으로 연동이 가능합니다.


 


Struts Integration


  • DWR 보다 Struts 의 설정이 먼저 초기화되어야 하므로 web.xml 에 Servlet(Struts 의 ActionServlet, DWR의 DWRServlet) 설정 시 load-on-startup 태그로 초기화 순서를 정해야 합니다.

 

 

<web-app>
...
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
...
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
...
<load-on-startup>2</load-on-startup>
</servlet>
...
</web-app>


  • Struts 의 설정파일인 struts-config.xml 에 다음과 같이 DwrExample 을 설정합니다.

 

 

<struts-config>
..
<form-beans>
<form-bean name="DwrExample" type="fullbox.dwr.test.DwrExample" />
</form-beans>
..
</struts-config>


  • DWR 의 설정파일인 dwr.xml 에 struts-config.xml 에 설정된 DwrExample 을 다음과 같이 설정합니다.

 

 

<allow>
...
<create creator="struts" javascript="DwrExample">
<param name="formBean" value="DwrExample"/>
</create>
...
</allow>

  • 이로써 Struts 에 설정된 DwrExample 을 DWR 에서 조작가능합니다.

 


Spring Integration


  • 'dwr_ex' 모듈 내 WebContent/dwr_ex06_spring.html 을 참조하십시요.

  • Spring의 설정파일을 로딩하기 위해 web.xml 에 다음과 같이 설정합니다.

 

 

<web-app>
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
...
</web-app>

  • Spring 의 설정파일인 beans.xml 에 다음과 같이 DwrExample 을 설정합니다.

 

 

<beans>
...
<bean id="DwrExample" class="fullbox.dwr.test.DwrExample" />
...
</beans>

  • DWR 의 설정파일인 dwr.xml 에 beans.xml 에 설정된 DwrExample 을 다음과 같이 설정합니다.

 

 

<allow>
...
<create creator="spring" javascript="DwrExample">
<param name="beanName" value="DwrExample"/>
</create>
...
</allow>

  • 이로써 Spring 에 설정된 DwrExample 을 DWR 에서 조작가능합니다.


출처 : http://blog.daum.net/p1good/11741330

'Study...★ > Java' 카테고리의 다른 글

java GC 관련  (0) 2011/06/16
[펌] DWR 설명과 사용법  (0) 2011/01/18
각 환경별 Servlet & JNI 정리  (0) 2010/06/24
Sun JVM Heap Dump 분석  (2) 2010/02/24
Posted by 달콤한녀석

CPIO 옵션
 
i     압축해제하라는 옵션
o    압축 생성 옵션
p    표준입력에서 파일명을 읽는다.
B    5120 bytes블럭단위로 입출력한다.
   아스키 형식으로 헤더정보를 읽고 쓴다.
d    없는디렉토리는 생성한다.
   기존파일과 겹치면 덮어쓰지 않는다.
   파일의 변경시간을 그대로 유지한다.
   파일명 목록을 출력한다.
h    헤더의 형식을 tar와 같이 읽고 쓴다.
   장치파일도 읽고 쓴다.
 
CPIO 파일 만들기
 
cpio 를 이용한 특정 확장자 파일만 압축하기.
# find . -name '*.c' -print | cpio -o > data.cpio
 
# ls | cpio -o > ../test.cpio

 
CPIO 파일 풀기
 
-i cpio로 백업한 data를 restore 한다
-o 출력 - 묶을때
-p 통과모드
 

-d restore중 필요한 디렉토리를 자동으로 생성시킨다
-m 백업 되있는 data들의 날짜, 시간을 그대로 유지한다
-v verbose
 
# cpio -idmv < test.cpio

# cpio -i < data.cpio
# cpio -dmvi < example.cpio
# cpio -dmvi < /root/test.cpio
 
 
gz으로 압축되어 있는것을 풀기
 
예1:
# gunzip targetfile.cpio.gz
# cpio -idmv < targetfile.cpio
 
예 2:
# zcat targetfile.cpio.gz | cpio -idmv
 
 
압축 하여 CPIO 만들기
 
tar 를 이용한 특정 확장자 파일 압축하기 (리눅스)
find . -name "*.c" | xargs tar -cvf soure.tar
xargs 를 파라미터 수가 제한적이어서, 파일수가 많으면 불가능함.
 
cpio로 파일 옮기기

한 디렉토리 또는 파일시스템에서 다른 디렉토리 또는 파일시스템으로 옮겨야 할 대량의 파일이 있을 경우 다음의 명령을 이용

# find /old_directory -depth | cpio -pdmv /new_directory

이 명령은 소유자와 권한, 디렉토리 구조를 유지하면서 지정한 old_directory 밑의 모든 파일을 new_directory로 옮긴다.
 
# cd src_dir_name
# find . -depth -print | cpio -pudm target_dir_name

'Study...★ > Operationg System' 카테고리의 다른 글

cpio 옵션  (0) 2010/09/27
Linux SSH 접속 초기문구 변경  (0) 2010/04/27
SSH root 접근 제한  (0) 2010/04/21
VM ware Network 설명  (0) 2010/04/20
Posted by 달콤한녀석
Study...★/Web Server2010/09/10 13:21

webtier를 설치하고 virtual-host 셋팅후 error 로그가 제대로 남지 않는걸 발견했다 -0-;
대략 난감 ㅠㅠ.....

이것저것 발버둥을 해봤지만.. 도통 찾을 수 없는 ㅠㅠ

이때.... 옆자리의 xxx양이 알려줘서 해결 할 수 있었다.. ㅎㅎ 쌩유~

우선 첫번째로 httpd.conf의 주석들을 살펴보도록 한다.

# Logging mode is set to odl-text mode by default.
뭔 얘긴진 잘 모르지만 로그가 odl-text mode가 default라고 한다.. 그냥 한번 좀 더 살펴보자..
# If you wish to use the apache type log instead then uncomment the
# ErrorLog and LogLevel lines below and set OraLogMode to apache
#
# ErrorLog "${ORACLE_INSTANCE}/diagnostics/logs/${COMPONENT_TYPE}/${COMPONENT_NAME}/error_log"

#
# LogLevel: Control the number of messages logged to the error_log.
# Possible values include: debug, info, notice, warn, error, crit,
# alert, emerg.
#
# The LogLevel directive will take effect only when OraLogMode is set to apache
#
# LogLevel warn

#Directives to setup logging via ODL
OraLogDir "${ORACLE_INSTANCE}/diagnostics/logs/${COMPONENT_TYPE}/${COMPONENT_NAME}"
#OraLogMode odl-text
OraLogMode apache
위의 주석내용에 있었던것처럼 OraLogMode를 아파치로 변경하도록 한다.
OraLogSeverity WARNING:32
OraLogRotationParams S 10:70

이렇게 로그와 관련된 부분만을 수정하고 OHS를 재시작 하였다..
결론은 두둥... error로그가 제대로 생긴다 >_<


'Study...★ > Web Server' 카테고리의 다른 글

[OHS] Virtual-Host ErrorLog 남기기..  (0) 2010/09/10
[OHS] 버전확인  (0) 2010/07/13
Posted by 달콤한녀석
Study...★/Web Server2010/07/13 14:15
예) Linux 

export LD_LIBRARY_PATH=/OFM/webtier/lib
/OFM/webtier/ohs/bin/httpd -version

Server version: Oracle-HTTP-Server/2.2.14 (Unix)
Server built:   Apr  5 2010 10:27:05
Server label:   APACHE_11.1.1.3.0_LINUX_100405.0928

'Study...★ > Web Server' 카테고리의 다른 글

[OHS] Virtual-Host ErrorLog 남기기..  (0) 2010/09/10
[OHS] 버전확인  (0) 2010/07/13
Posted by 달콤한녀석

티스토리 툴바