[출처] http://theeye.pe.kr/entry/Proxt-AJP-mod_rewrite%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-Apache-22-Tomcat-%EC%99%84%EB%B2%BD-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0

여러분의 JAVA/JSP는 어떤 WAS에서 돌리고 계신가요? 톰캣을 쓰신다고요?

그럼 웹서버는 아파치를 사용하시겠군요. 연동을 위해 톰캣 사이트에서 받을 수 있는 mod_jk를 많이들 쓰실것입니다.

하지만 아파치 2.2.x에서 제공하는 Proxy AJP를 사용하면 매우 깔끔하게 연동을 할 수 있습니다.

톰캣은 설치가 되어 정상적으로 동작하고 있는 상황이라 가정하고 아파치 설정만을 적어보겠습니다.

[이곳]에서 가장 최신의 아파치 2.2 버젼을 다운받습니다. 압축을 풀고 컴파일을 합니다.

설정(쓰레드 방식)

[root@Theeye src]$ CC="gcc" CFLAGS="-O2" ./configure --prefix=/usr/local/httpd --enable-so --enable-proxy-ajp --enable-cgi --enable-rewrite --enable-speling --enable-usertrack --enable-deflate --enable-ssl --enable-cache --enable-disk-cache --enable-expires --enable-file-cache --enable-headers --enable-mem-cache --enable-mime-magic --enable-proxy --enable-mods-shared=all --with-mpm=worker

설정(프로세스 방식)
[root@Theeye src]$ CC="gcc" CFLAGS="-O2" ./configure --prefix=/usr/local/httpd --enable-so --enable-proxy-ajp --enable-cgi --enable-rewrite --enable-speling --enable-usertrack --enable-deflate --enable-ssl --enable-cache --enable-disk-cache --enable-expires --enable-file-cache --enable-headers --enable-mem-cache --enable-mime-magic --enable-proxy --enable-mods-shared=all --with-mpm=prefork

컴파일
[root@Theeye src]$ make
[root@Theeye src]$ make install


아파치가 설치된 디렉토리의 conf/httpd.conf 파일을 열어 다음의 두가지가 정상적으로 존재하는지 확인합니다.
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule rewrite_module modules/mod_rewrite.so


그리고 마지막쯤에 있는 가상호스팅 관련 설정을 인클루드 하도록 설정합니다.
Include conf/extra/httpd-vhosts.conf


아파치 설치 디렉토리의 conf/extra/httpd-vhost.conf 파일을 열어 가상호스트를 추가합니다.

<VirtualHost *:80>
ServerAdmin your@emailhere
DocumentRoot /var/www/html
ServerName theeye.pe.kr
ErrorLog logs/theeye.pe.kr-error_log
CustomLog logs/theeye.pe.kr-access_log common
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} \.(htm|html|xhtml|js|css|jpg|gif|png|swf)$
RewriteRule (.*) - [L]
RewriteRule (.*) ajp://localhost:8009$1 [P]
</VirtualHost>


위와같은 rewrite를 통한 설정은 htm, html, xhtml, js, css, jpg, gif, png, swf 확장자를 가진 요청은 아파치가 처리하도록 하고 나머지는 톰캣이 처리하도록 합니다. 원하시는 확장자를 마음껏 추가하셔도 됩니다.

톰캣이 설치된 디렉토리의 conf/server.xml의 설정을 변경합니다. Connector설정을 확인합니다.
<Connector port="8009" protocol="AJP/1.3" maxThreads="150" minSpareThreads="25"  maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100"  debug="0" connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8" />


아파치 가상호스트에 지정한것과 동일한 호스트를 설정합니다.
<Host name="theeye.pe.kr" debug="0" appBase="/var/www/html" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
<Context path="" docBase="/var/www/html" debug="0" reloadable="false" crossContext="true" />
</Host>


자, 이제 아파치와 톰캣의 설정이 모두 끝났습니다. 위와 같이 사용하시면 아파치에서 처리할 파일과 톰캣이 처리할 파일들을 따로 분리할 필요도 없으며 확장자에 따라 정확히 아파치와 톰캣이 분산 처리를 하게 됩니다.

정적인 파일들을 톰캣이 처리할 필요는 없으니깐요^^

추가로 특정 호스트에게만 오픈하는 관리자 페이지거나 개발서버일 경우 다음과 같이 접근 가능 아이피를 지정할 수 있습니다.

<VirtualHost *:80>
ServerAdmin your@emailhere
DocumentRoot /var/www/html
ServerName theeye.pe.kr
ErrorLog logs/theeye.pe.kr-error_log
CustomLog logs/theeye.pe.kr-access_log common
RewriteEngine On
RewriteCond !%{REMOTE_ADDR} ^127\.0\.0\.1$
RewriteRule ^.*$ - [F]
RewriteCond %{REQUEST_FILENAME} \.(htm|html|xhtml|js|css|jpg|gif|png|swf)$
RewriteRule (.*) - [L]
RewriteRule (.*) ajp://localhost:8009$1 [P]
</VirtualHost>


127.0.0.1 아이피를 가진 호스트가 아니라면 아파치에서 바로 에러페이지를 출력하게 됩니다.



ProxyAJP에 대해 잘 모르신다면 링크의 글을 보시거나 좀더 검색해 보시고 보시면 좋을 것같습니다.

제가 운영하는 투명아이 호스팅에서 JSP 호스팅을 좀더 편하게 구현할 방법을 찾아보니 여기까지 왔습니다.

정말 간단하다 못해 너무 단순하게 강력한 효과를 얻을 수 있는 방법이라고 생각합니다.

우선 보통의 경우 mod_jk를 이용하여 연동을 하게 되는데요, 이때에 볼 수 있는 설정 파일은 대충 다음과 같습니다.

LoadModule          jk_module       modules/mod_jk.so
JkWorkersFile /usr/local/tomcat/conf/workers.properties
JkLogFile /var/log/httpd/mod_jk.log
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories -ForwardLocalAddress
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
JkRequestLogFormat "%w %V %T"
JkLogLevel info
JkMount             /servlet/*      ajp13
JkMount             /manager/*      ajp13
JkMount             /flex/*         ajp13
JkMount             /*.jsp          ajp13
JkMount             /*.do           ajp13

위의 방법은 Apache 2.2의 ProxyAJP 모듈을 이용한 방법으로 고쳐 보겠습니다. [참고]

다음과 같은 설정 파일을 proxy_ajp.conf 파일로 만들어 Apache의 설정 파일에 Include되도록 합시다.
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
ProxyRequests On
ProxyVia On
ProxyPass /manager/ ajp://localhost:8009/manager/
ProxyPass /servlet/ ajp://localhost:8009/servlet/
ProxyPass /flex/ ajp://localhost:8009/flex/
ProxyPassMatch ^/.*\.(jsp|do)$ ajp://localhost:8009/
# 보안을 위해 WEB-INF 접근 불가설정
<LocationMatch "/WEB-INF">
deny from all
</LocationMatch>
# 보안을 위해 META-INF 접근 불가설정
<LocationMatch "/
META-INF">
deny from all
</LocationMatch>

ProxyPass를 사용하여 특정 디렉토리를 톰캣에서 처리하게 할 수 있습니다.

ProxyPassMatch를 이용하여 특정 확장자를 가진 파일을 톰캣에서 처리하도록 할 수 있습니다. 정규식을 잘 쓰면 되겠네요.

이제는 더이상 worker.properties니 하는 파일이 필요없습니다. 위와 같은 방법으로 간단하게 처리 가능합니다.

WEB-INF나 META-INF 디렉토리로의 접근 보안 문제는 위와같이 해결할 수 있습니다.

위와 같은 설정으로 웹호스팅 환경의 멀티 가상호스트상황에서도 일일이 연동 설정을 할 필요없어졌습니다.



또 다른방법

블로그 이미지

유효하지않음

,
 

Apache 설치방법 보기

Apache 웹서버에 SSL를 적용하기 위해 아래 두 항목이 웹서버에 설치되어 있어야 합니다.

- Openssl 암호화 라이브러리
- Mod_ssl 모듈

위 두 항목이 웹서버에 설치되어 있다면 개인키를 생성하고 생성된 개인키를 바탕으로 CSR 파일을 생성합니다.
생성된 CSR 파일을 한비로에 접수하여 정식 인증서를 발급받습니다.
발급된 인증서를 웹서버에 설치하게 되면 SSL 설정을 완료하게 됩니다.

위 일련의 진행사항은 아래와 같은 절차를 따르게 됩니다.

1. openssl 라이브러리 설치상태 확인
2. mod_ssl 모듈 설치상태 확인
3. 개인키 생성
4. CSR 생성
5. 한비로에 접수
6. 정식 인증서 발급
7. SSL 설정


1. Openssl 라이브러리 설치상태 확인

먼저 SSL를 설치하고자 하는 웹서버에 openssl 라이브러리 설치상태를 find 명령어를 활용하여 아래와 같이 확인합니다.
 
사용자 삽입 이미지








위와 같은 값을 보인다면 openssl 라이브러리 모듈은 rpm으로 설치된것입니다.
만약에 경로가 /usr/local 아래 있다면 모듈은 소스로 설치된 것입니다.
rpm으로 설치된 것이라면 rpm -qa 명령어를 사용하여 openssl-devel 설치여부도 함께 점검합니다. 버전에 따라 라이브러리 버전이 아래보이는 값과 차이가 있을수 있습니다.

사용자 삽입 이미지

Openssl 은 암호화 처리를 위한 독립 모듈로 최신버젼으로 설치하는 것을 권장합니다.


2. Mod-ssl Openssl 라이브러리 설치상태 확인

Openssl 과 마찬가지로 웹서버의 mod_ssl 설치여부를 점검합니다.
Apache 웹서버는 두가지 방식으로 모듈설치를 지원하고 있습니다. 정적과 동적인 방식으로 정적으로 설치된 경우는 아파치의 재설치까지 요구되며 동적인 경우는 손쉽게 모듈 설치가 가능합니다.
Apache 가 /usr/local 아래 설치된 것을 기준으로 아래와 같은 방식으로 확인합니다.

정적으로 설치된 mod_ssl 모듈확인
사용자 삽입 이미지
 







동적으로 설치된 mod_ssl 모듈확인

사용자 삽입 이미지








웹서버에 설치된 모듈중 mod_so.c 를 먼저 확인후 동적으로 설치된 모듈중 mod_ssl.so 를 확인합니다.
동적으로 설치된 경우 apache 설치 디렉토리의 module 이나 libexec 디렉토리내에 mod_ssl.so 의 존재여부를 확인합니다.


3. 개인키 생성

Openssl 명령어를 이용하여 웹서버의 RSA키( 1024비트 암호화 )를 생성합니다. ( sslhanbiro.key 는 임의로 지정된 키값입니다. 원하는 이름으로 키를 생성합니다. )
패스워드를 지정하게 됩니다. 이때 입력된 패스워드는 차후 여러차례 사용되므로 본인만이 알 수 있는 패스워드로 지정해 주시는 것이 좋습니다.

사용자 삽입 이미지











이때 생성되는 개인키는 반드시 백업을 받아놓고 사용하는 것이 좋습니다.
생성된 키는 아래와 같이 확인이 가능합니다. 패스워드를 확인 하는데 이때는 키생성시 입력한 패스워드를 입력합니다.
개인키 생성까지 완료되면 이제 CSR 생성을 하게됩니다.

사용자 삽입 이미지
















4. 인증요청서( CSR ) 생성

*발급이 완료된 인증서는 재발급 또는 변경이 불가하므로 CSR 생성시 절대 주의 바랍니다.

☞ CSR ( Certificate Signing Request ) 이란?


SSL 서버를 운영하는 회사의 정보를 암호화하여 인증기관으로 보내 인증서를 발급받게 하는 일종의 신청서입니다.
CSR은 ASCII 텍스트 화일로 생성됩니다.
CSR을 생성할 때 서버의 식별명을 입력하게 됩니다. 식별명은 각 서버를 공유하게 나타내는 이름으로 다음과 같은 정보를 포함합니다.

Country Name ( 국가코드) [] : KR
State or Province Name ( 지역 ) [] : Seoul
Locality Name ( 시/군/구 ) [] : Seocho
Organization Name ( 회사명 ) [] : Hanbiro Inc
Organizational Unit Name ( 부서명 ) [] : Linux Team
Common Name ( 서비스도메인명 ) [] : www.hanbiro.com
Email Address [] : hanbiro@hanbiro.com

☞ CSR 항목에 대한 설명

Country Name : 이것은 두 자로 된 ISO 형식의 국가 코드입니다.
State or Province Name : 시 이름을 입력해야 하며 약어를 사용할 수 없습니다.
Locality Name : 이 필드는 대부분의 경우 생략이 가능하며 업체가 위치한 곳를 나타냅니다.
Organization : 사업자 등록증에 있는 회사명과 일치되는 영문회사명을 입력하시면 됩니다.
Organization Unit : "리눅스 관리팀", "윈도우 관리팀" 등과 같이 업체의 부서를 입력할 수 있습니다.
Common Name : 인증받을 도메인주소를 입력하시면 됩니다.

이 정보로 웹 사이트를 식별하므로 호스트 이름을 변경할 경우 다른 디지털 ID를 요청해야 합니다.
호스트에 연결하는 클라이언트 브라우저가 디지털 ID의 이름과 URL이 일치하는지를 확인합니다.

☞ CSR 항목 입력시 주의사항
* Common Name 에는 인증서를 설치할 사이트의 도메인의 이름을 정확하게 입력하셔야 합니다.
* Common Name 에는 IP 주소, 포트번호, 경로명, http:// 나 https:// 등은 포함할 수 없습니다.
* CSR 항목에는 < > ~ ! @ # $ % ^ * / \ ( ) ? 등의 특수 68 문자를 넣을 수 없습니다.
* CSR 생성후 서버에 개인키 (Private Key) 가 생성됩니다. 개인키를 삭제하거나 분실할 경우 인증서를 발급받아도 설치가 불가합니다. 따라서 꼭 개인키를 백업받아 두셔야 합니다.
* 정보입력과정 마지막에 나오는 A challenge password 와 An optional company name 두 항목은 입력하지 마시고 Enter 만 누르고 넘어가야 합니다. 두 정보가 입력될 경우 잘못된 CSR 생성될 수 있습니다.

↘ 위 주의사항을 유의하여 아래와 같은 절차로 CSR 생성을 진행합니다.

사용자 삽입 이미지

















↘ 생성된 CSR 정보는 아래처럼 확인이 가능합니다.

사용자 삽입 이미지





















5. 한비로에 접수--->6. 정식 인증서 발급

생성된 CSR 을 출력하면 아래와 같은 base64 형식의 문서를 볼 수 있습니다.

사용자 삽입 이미지

















이문서의 첫 줄 -----BEGIN … 부터 마지막 줄 -----END … 까지 복사하여 지정된 SSL 접수페이지에 복사하여 붙여 넣은 뒤 입력정보와 함께 전송하면 접수가 완료됩니다.

7. 인증서 설치

접수한 CSR 파일이 정상적으로 생성되었다면 별다른 문제없이 인증서를 발급 받을 수 있습니다.
인증서 파일은 신청시 기록한 Email 주소를 통해 인증서를 첨부파일로 수신하게 됩니다.

① 인증서 서버에 복사

메일로 받은 인증서 파일을 압축을 해제하시면 AddTrustExternalCARoot.crt,UTNAddTrustServerCA.crt, 도메인.crt 3개의 파일을 보실 수 있습니다.
여기에서 필요한 파일은 UTNAddTrustServerCA.crt,도메인.crt 2개의 파일입니다.
이 파일을 서버에서 적절한 위치에 복사합니다.

② 웹서버 환경설정

아파치가 설치된 디렉토리로 이동하여 conf 디렉토리내의 httpd.conf 파일의 복사본을 만들어 둡니다.
웹서버 설정의 기본이 되는 파일로 만일의 경우를 대비하여 백업본을 유지하는 것이 좋습니다.
백업이 완료되면 vi 편집기를 이용하여 httpd.conf 내용중 아래사항을 설정하신 정보에 맞게끔 수정합니다.

httpd.conf 예문

사용자 삽입 이미지




























1. <VirtualHost 127.0.0.1:443> 127.0.0.1 를 사용하는 장비의 아이피로 변경
 - 443 : SSL 통신포트번호 입니다. 일반적으로 웹서버는 80 포트를 사용합니다.
2. SSLEngine 스위치 off 를 on 으로 변경해 줍니다.
3. SSLCertificateFile / SSLCertificateKeyFile 에는 인증서의 설치경로와 개인키 파일의 경로를 적어 줍니다.

③ root 인증서 경로설정

SSLCACertificateFile 에는 root 인증서 위치를 알려주는 것으로 유저의 브라우저에 신뢰받는 CA리스트가 없을 경우를 위해 경로를 반드시 지정해 주어야 합니다.


④ 웹서버 재실행

사용자 삽입 이미지

















설정파일의 정상적인 수정여부를 점검하기 위한 체크 ./httpd -t
수정된 사항의 적용을 위해 아파치 데몬정지 ./apachectl stop
아파치 데몬 활성화 ./apachectl startssl ( 아파치 데몬 활성화는 ./apachectl start 로 가능합니다. 여기에 ssl 를 붙여줌으로서 ssl 를 사용하게 됩니다. )

초기 개인키 생성시 입력했던 패스워드를 기억하시고 계실겁니다. SSL 실행을 위해 패스워드를 물어보는데 이때 개인키 생성시 입력했던 패스워드를 입력하시면 SSL 웹데몬이 활성화 됩니다.

⑤ 웹서버 포트점검

아래와 같이 활성화된 데몬의 포트를 점검해 봅니다.

사용자 삽입 이미지









⑥ 웹서비스 동작상태 점검

인터넷 주소창에 https://사용도메인 와 입력후 해당 페이지의 정상적인 동작 여부를 점검합니다.
SSL 설정한 사이트에 대한 정상적인 서비스 상태 점검

사용자 삽입 이미지




































페이지 하단을 보시면 열쇠 아이콘이 보이게 됩니다. 아이콘을 클릭하게 되면 위와 같이 인증서 정보를 확인하실수 있습니다.

블로그 이미지

유효하지않음

,
* Apache modssl 작업 (http://www.comodossl.co.kr/SSL/InstallGuide/Apache.htm)

openssl genrsa -des3 -out www.test.net.key 1024

openssl req -new -key www.test.net.key -out www.moaz.net.csr

* 인증서 발급이 완료되며 아래와 같이 키파일과 발송된 crt파일을 이용하여 PKCS12형식으로 변환해 주신후 톰캣설정을 해줍니다.

PKCS12 로 변환
openssl pkcs12 -export -in www_test_net.crt -out tomcat.pkcs12 -name "www.test.net" -inkey www.test.net.key

내용 확인
keytool -list -v -keystore tomcat.pkcs12 -storetype pkcs12

톰캣설정
ceptCount="100" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="/usr/local/tomcat/conf/tomcat.pkcs12" keypass="비밀번호" keystoreType="PKCS12" />
블로그 이미지

유효하지않음

,

이 경우는 대부분이 데이터 입력, 삭제 또는 복구 작업시에 일어나며, Archive Mode 운
영중일때 발생한다.

 [운영 로그 확인]

 c:\> type C:\oracle\product\10.2.0\SID\alert_SID

   ; 오류가 발생하면 위와 같은 로그 파일을 생성한다.

 Thu Apr 06 21:45:55 2006
Errors in file c:\oracle\product\10.2.0\admin\oradb\bdump\oradb_arc0_3556.trc:

ORA-19815: 경고: db_recovery_file_dest_size/2147483648바이트는 100.00%가 사용 중이므로, 나머지 0바이트를 사용할 수 있습니다.

   ; 위의 로그를 확인 한 결과 dest_size가 full이 되어서 발생한 행걸림 현상을 확인.

 [로그 확인]

 sql> archive log list;

 데이터베이스 로그 모드                        아카이브 모드
 자동 아카이브                                      사용
 아카이브 대상                                      USE_DB_RECOVERY_FILE_DEST
 가장 오래된 온라인 로그 순서                1
 아카이브할 다음 로그                            1
 현재 로그 순서                                      3

 sql> show parameter archive;

 NAME                                 TYPE                              VALUE
 -------------------------- ------------------------- -------------------------------
 archive_lag_target               integer                             0
 log_archive_config              string
 log_archive_dest                 string
 log_archive_dest_1              string

    ; 위의 내용을 보면 log_archive_dest에 보면 Value가 없다. 진행이 안된 상태이다.

  sql>show parameter dest;

   ; dest에 관련된 파라메터를 확인한다.


 [해결방안1]
  DB를 Shutdown immediate를 한다.
  initSID.ora 파일 내용중 dest_size line을 주석처리한다.
  DB를 Startup 한다

 [해결방안2]
  sql> alter system set db_recivery_file_dest_size=3000M;
  운영중인 DB에서 dest_size를 늘려준다.


alert.log파일에 이런 에러가 떴거든요...

ORA-19815: 경고: db_recovery_file_dest_size/2147483648바이트는 85.28%가 사용 중이므로,
나머지 316203008바이트를 사용할 수 있습니다.


Thu Jul 13 10:54:55 2006
************************************************************************
You have following choices to free up space from flash recovery area:
1. Consider changing RMAN RETENTION POLICY. If you are using Data Guard,
   then consider changing RMAN ARCHIVELOG DELETION POLICY.
2. Back up files to tertiary device such as tape using RMAN
   BACKUP RECOVERY AREA command.
3. Add disk space and increase db_recovery_file_dest_size parameter to
   reflect the new space.
4. Delete unnecessary files using RMAN DELETE command. If an operating
   system command was used to delete files, then use RMAN CROSSCHECK and
   DELETE EXPIRED commands.
************************************************************************
 
 
 
가장 많이 발생하는 원인은 아카이브 로그 파일이 계속 쌓일 경우입니다.

OS 상에서 아카이브 로그 파일을 삭제하였을 경우에도 RMAN 상에서는 삭제한 걸 인식하지 못합니다.

해결 방법으로는

1) db_recovery_file_dest_size의 크기를 늘려 준다.

2) 필요 없는 아카이브 파일 OS에서 삭제후 RMAN에서 삭제

 - OS 상에서 아카이브 삭제

 - RMAN> crosscheck archivelog all;

    RMAN> delete expired archivelog all;

3) 백업 정책 확인
 

블로그 이미지

유효하지않음

,

NSLOOKUP 사용법

OS/Linux 2008. 7. 25. 17:57

8.1. NSLOOKUP

네임서버를 운영하고 관리하는데 있어 문제를 발견하고 해결하기 위해 Resolver의 입장으로 네임서버를 시험해볼 필요가 있다. 대부분의 시스템에 기본 설치되어 있는 nslookup은 dig와 함께 가장 널리 사용되는 네임서버 질의 도구로써, 도메인 메니저의 기본 무기중 하나이다.

$ nslookup
Default Server:  ns.nobreak.com
Address:  210.105.79.2
> exit

nslookup은 실행후 대화형 프롬프트 '>'를 표시하고 /etc/resolv.conf에 정의된 첫 번째 네임서버를 기본 질의 서버로 설정한다. nslookup은 BIND와 달리 하나의 서버만을 질의에 사용하기 때문에 'Default NS -> Timeout -> Error'와 같이 동작한다.


8.1.1. 도메인 네임 검색

nslookup은 기본적으로 입력된 도메인에 대해 A 레코드를 검색하고, IP 주소(in-addr.arpa)에 대해서는 PTR 레코드를 검색한다. set type=RR 설정으로 A 레코드 이외의 레코드 또한 검색할 수 있으며, RR(Resource Record)에는 A, ANY, CNAME, HINFO, MX, NS, PTR, SOA, TXT 등이 올 수 있다. 이중 ANY는 관련된 레코드들을 모두 출력하라는 약속 기호이다.

> www.kr.freebsd.org.                 # IP 검색
Name:    www.kr.freebsd.org
Address:  150.183.110.39

> ftp.kr.freebsd.org.
Name:    www.kr.freebsd.org           # ftp는 www의 CNAME
Address:  150.183.110.39
Aliases:  ftp.kr.freebsd.org

> 150.183.110.39                      # 도메인 검색
Name:    www.kr.freebsd.org
Address:  150.183.110.39

> set type=MX                         # MX 레코드 검색
> kr.freebsd.org.
kr.freebsd.org  preference = 10, mail exchanger = mail.kr.freebsd.org

> set type=NS                         # NS 레코드 검색
> kr.freebsd.org.                     # 도메인 위임 확인
kr.freebsd.org     nameserver = ns.kr.freebsd.org
kr.freebsd.org     nameserver = ns2.kr.freebsd.org
ns.kr.freebsd.org  internet address = 150.183.110.2
ns2.kr.freebsd.org internet address = 150.183.110.3

> 46.102.39.in-addr.arpa.             # 인버스 도메인 위임 확인
kr.freebsd.org     nameserver = ns.kr.freebsd.org
kr.freebsd.org     nameserver = ns2.kr.freebsd.org
ns.kr.freebsd.org  internet address = 150.183.110.2
ns2.kr.freebsd.org internet address = 150.183.110.3


8.1.2. 기본 쿼리 서버 변경

nslookup은 기본적으로 recurse 모드로 동작하기 때문에, 때론 해당 도메인의 Authority를 갖는 특정 네임서버에 직접 질의를 하여 Authoritative 응답(네임서버의 캐쉬에서가 아닌)을 확인 할 필요가 있다. server, lserver 명령으로 기본 질의 서버를 변경 할 수 있다. 두 명령은 주어진 네임서버의 주소(쿼리가 아닌)를 찾을 때 사용할 질의 서버의 차이인데, server 는 현재의 기본 서버를 통하고, lserver 는 시스템 기본 서버(nslookup 구동시 초기 설정되는)를 사용함이 다르다. lserver 명령은 타 네임서버로 스위칭 한 후, 다시 다른 네임서버로 스위칭하려 하는데, 현재의 네임서버가 동작하지 않아 해당 네임서버의 주소를 검색하지 못할 때 사용한다. 다음을 보자.

$ nslookup
Default Server:  ns.nobreak.com
Address:  210.105.79.2

nslookup 구동시의 기본 서버 ns.nobreak.com 이 lserver 명령에서 주어진 NS의 주소를 찾기위한 질의 서버가 된다.

> server ns.jp.freebsd.org.        # 기본 서버 변경
Default Server:  ns.jp.freebsd.org
Address:  199.100.7.25

> server ns.nobreak.com.
*** Can't find address for server ns.nobreak.com: Non-existent host/domain

ns.jp.freebsd.org를 통해 ns.nobreak.com을 찾을 수가 없다. 이때에는 lserver 명령으로 시스템 기본 서버를 통해 ns.nobreak.com 의 주소를 검색한다.

> lserver ns.nobreak.com.
Default Server:  ns.nobreak.com
Address:  210.105.79.2

루트 네임서버를 질의 서버로 하고자 할 때는, 간단히 root 명령을 사용할 수 있다.

> root
Default Server:  a.root-servers.net
Address:  198.41.0.4


8.1.3. 네임 서버처럼 질의하기

네임서버는 Resolver의 요청을 처리하기 위해, 네임스페이스를 검색하며, 여러 네임서버와 통신을 하는데, nslookup으로 동일한 과정을 밟아보도록 하자. 네임서버가 인터넷상에서 어떻게 동작하며, 네임서버들 간에는 어떤 사건들이 발생하고, 여러분을 위해 무엇을 하는지, 구체적인 느낌을 받을 수 있을 것이다.

그림 8-1. 네임서버처럼 질의하기

사용자 삽입 이미지

(1)
> set norecurse     # Iterative 모드로 전환
> www.kr.freebsd.org.
Server:  ns.nobreak.com
Address:  210.105.79.2

Name:    www.kr.freebsd.org
Served by:
- H.ROOT-SERVERS.NET
          128.63.2.53
          ORG
- B.ROOT-SERVERS.NET
          128.9.0.107
          ORG
...

ORG. 가 관리되는 루트 서버들의 목록을 레퍼런싱 해준다.

(2)
> server h.root-servers.net.
> www.kr.freebsd.org.
Server:  h.root-servers.net
Address:  128.63.2.53

Name:    www.kr.freebsd.org
Served by:
- WHO.CDROM.COM
          204.216.27.3
          FREEBSD.ORG
- NS1.CRL.COM
          165.113.1.36
          FREEBSD.ORG
- NS2.CRL.COM
          165.113.61.37
          FREEBSD.ORG

(3)
> server who.cdrom.com.
> www.kr.freebsd.org.
Server:  who.cdrom.com
Address:  204.216.27.3

Name:    www.kr.freebsd.org
Served by:
- ns.kr.freebsd.org
          150.183.110.2
          kr.freebsd.org
- ns2.kr.freebsd.org
          150.183.110.3
          kr.freebsd.org

(4)
> server ns.kr.freebsd.org.
> www.kr.freebsd.org.
Server:  ns.kr.freebsd.org
Address:  150.183.110.2

Name:    www.kr.freebsd.org
Address:  150.183.110.39


8.1.4. Zone Transfer

해당 도메인의 Zone에 대한 복사본을 얻기위해, Primary로부터 Zone 데이터베이스를 끌어오는 작업을 Zone Transfer라 한다. 이 작업은 주로 Secondary NS 측에서 이루어지며, 때때로 얼마나 많은 수의 호스트가 등록되어 있는지 혹은 Zone의 문법적 오류를 검사하기 위해 관리자가 수동으로 조작하기도 한다. Zone Transfer는 Authority를 갖는 네임서버에 직접 질의하여야 하므로, nslookup 상에서 해당 NS로 질의 서버를 변경한후, ls 명령을 사용한다.

> server ns.kr.freebsd.org.
> ls -t A kr.freebsd.org.      # A 레코드 출력
 kr.freebsd.org.                server = ns.kr.freebsd.org
 kr.freebsd.org.                server = ns2.kr.freebsd.org
 mail                           150.183.110.32
 mqueue                         150.183.110.33
 www                            150.183.110.39
 www2                           150.183.110.40

> ls -d kr.freebsd.org.        # 모든 레코드 출력
 kr.freebsd.org.                SOA   ns.nobreak.com hostmaster.kr.freebsd.org.
                                (1999031501 21600 1800 1209600 86400)
 kr.freebsd.org.                NS    ns.nobreak.com
 kr.freebsd.org.                NS    ns2.nobreak.com
 kr.freebsd.org.                MX    10   mail.kr.freebsd.org
 kr.freebsd.org.                MX    20   mqueue.kr.freebsd.org
 cvsup                          CNAME www.kr.freebsd.org
 mail                           A     150.183.110.32
 mqueue                         A     150.183.110.33
 ftp                            CNAME www.kr.freebsd.org
 ftp2                           CNAME www2.kr.freebsd.org
 ftp3                           CNAME ftp.free.nobreak.com
 www                            A     150.183.110.39
 www                            HINFO Pentium-200  FreeBSD 2.2.8
 www                            TXT  "Korea FreeBSD Users Group"
 www2                           A     150.183.110.40
 www2                           HINFO Pentium-133MHz  FreeBSD 2.2.8
 www2                           TXT  "Korea FreeBSD Users Group"

> ls -t MX kr.freebsd.org > MX-kr.freebsd.org   # 파일로 저장
> view MX-kr.freebsd.org                        # 파일 내용 확인

BIND의 경우 named-xfer라는 외부 프로그램을 사용해 Zone Transfer를 수행한다. 네임서버의 입장에서 부트 파일에 Secondary 설정이 있을 경우의 처리과정을 살펴보자.

secondary       kr.freebsd.org  210.105.79.2    sec-kr.freebsd.org

BIND는 secondary 명령을 만나면 내부적으로 다음과 같이 동작한다.

loop(Interval == TTL) {
        named-xfer -z kr.freebsd.org -f /var/named/sec-kr.freebsd.org -s Current_Serial 210.105.79.2
        switch ( $? ) {      // named-xfer 는 환경 변수 '$?'에 결과를 복귀함
                case 0 : OK; // 시리얼이 같음, Zone Transfer가 필요치 않음
                case 1 : OK; // 시리얼이 증가했음, Zone Transfer가 성공적으로 수행됨
                case 2 : ERROR; // 네임서버를 찾을 수 없음
                                // 혹은 네임서버가 도메인의 Authority를 갖지 않음
                case 3 : ERROR: // 시리얼이 감소했음, 기존의 백업카피 유지
        }
        primary  kr.freebsd.org  sec-kr.freebsd.org
}

보안의 이유로 허락된 곳(예: Secondary NS's IP)에서만 Zone Transfer를 허용하고자 한다면, Primary NS의 부트파일에 다음과 같은 옵션을 준다. (Zone Transfer만을 제한하는 것이기 때문에, 호스트에 대한 개별 쿼리는 허용된다)

xfrnets  210.105.79.3&255.255.255.255  210.105.80.128&255.255.255.128

이것은 BIND-4의 설정예인데, IP 210.105.79.3과 210.105.80.129-254 에서만 Zone Transfer를 허용하라는 의미이다. BIND-4에서는 개별 IP와 서브넷으로 나누어진 블럭에 대해 "IP&Mask"의 형식으로 목록을 작성하며, 클래스 전체를 허용하고자 할 경우엔 210.105.79.0 과 같이 마스크를 생략하여도 된다.

options {
        allow-transfer { localnets; 210.105.79.3; };
};

BIND-8의 경우에 해당 서버가 속한 네트워크와, 210.105.79.3만을 허용한 예이다. localnets는 예약어이며 다른 예약어로는 any, none, localhost 가 있다.


8.1.5. 초기화 파일 .nslookuprc

nslookup은 실행시 ~/.nslookuprc 파일이 존재하면, 내용을 읽어 옵션을 조정한다. 매번 설정하는 옵션이 있다면, 본 파일을 통해 간편화할 수 있겠다.

* .nslookuprc 파일 예
set type=NS
set nosearch
set debug

dig -t AXFR nextguide.net
블로그 이미지

유효하지않음

,

DNS 캐시 포이즈닝 다중 취약점 보안업데이트 권고

□ 개요
  o DNS 캐시 포이즈닝이 가능한 신규 취약점들이 발견되어 DNS 관리자의 주의를 요함[1]
  o 본 취약점은 DNS에서 DNS transaction ID와 source port number를 부여할 때,
     예상하기 쉬운 임의의 값을 생성하기 때문에 발생함
     ※ 해당 신규 취약점들은 기존 알려진 내용을 기반으로 효율적인 공격이 가능하도록 함

□ 해당시스템
  o 캐시/리졸빙 서버로 이용되는 각종 DNS 서버 시스템
    Cisco Systems, Inc.  
    Debian GNU/Linux  
    Infoblox  
    Internet Software Consortium
    Juniper Networks, Inc.
    Microsoft Corporation
    Nominum
    Red Hat, Inc.
    Sun Microsystems, Inc.
    Wind River Systems, Inc. 등

□ 영향
  o 공격자는 해당 취약점을 이용하여 DNS 쿼리 정보를 변경할 수 있음
    - 공격 성공 시, DNS 쿼리 데이터 변경, 삭제 등의 작업 가능
       (피싱, 악성코드 유포등에 악용될 수 있음)

□ 해결 방안
  o 캐시/리졸빙 DNS 서버로 사용되는 시스템을 운영 중이라면, 해당 보안 취약점에 대비하고,
     시스템 성능 향상을 위하여 각 벤더사의 DNS 최신 버젼으로 업그레이드를 권고[2][3]

  o 패치가 어려울 경우, 신뢰할 수 있는 호스트에 대해서만 recursive query에 대한 응답이
     가능하도록 설정할 것을 권고

  o Recursion 기능이 필요하지 않을 경우, Disable(비활성화)시킬 것을 권고

  o 보안 장비(방화벽, 침입탐지시스템, 침입방지시스템 등), 네트워크 장비 등에서
     DNS 서비스를 사용 중이라면, 비활성화(disable) 시킬 것을 권고
    - 특히 BIND는 방화벽과 라우터에서 DNS 서비스로 자주 사용되기 때문에,
       만일 필요로 하지 않는 서비스라면, 비활성화 시킬 것을 권고

□ 참조 사이트
     [1] http://www.kb.cert.org/vuls/id/800113
     [2] http://www.isc.org/index.pl?/sw/bind
     [3] http://www.microsoft.com/technet/security/Bulletin/MS08-037.mspx
     [4] http://www.securityfocus.com/brief/779

[참고]
1. F.A.Q

  o 캐시/리졸빙 DNS에서만 영향을 받나요?
      - 네, 해당 취약점은 캐시/리졸빙을 하지 않는 DNS에는 영향을 주지 않습니다.

  o 캐시 포이즈닝이란 무엇입니까?
      - DNS 프로토콜 자체의 취약성으로 캐시 DNS에 저장된 쿼리 정보가 위,변조되는 것을 말합니다.

  o 어떻게 취약한지 확인할 수 있나요?
     - 다음 명령 실행 결과 아래와 같은 응답을 받는다면, 취약하다고 볼 수 있습니다.

       1) 취약점 확인 방법: 
          $ dig @aaa.bbb.ccc.ddd +short porttest.dns-oarc.net TXT

          가. 취약점 존재 DNS 확인 결과 :
          z.y.x.w.v.u.t.s.r.q.p.o.n.m.l.k.j.i.h.g.f.e.d.c.b.a.pt.dns-oarc.net. "aaa.bbb.
          ccc.ddd is POOR: 26 queries in 4.0 seconds from 1 ports with std dev 0.00"

          나. 정상 DNS 확인 결과 :
          z.y.x.w.v.u.t.s.r.q.p.o.n.m.l.k.j.i.h.g.f.e.d.c.b.a.pt.dns-oarc.net. "IP-of-GOOD
          is GOOD: 26 queries in 2.0 seconds from 26 ports with std dev 17685.51"

2. 기타 문의사항

  o 한국정보보호진흥원 인터넷침해사고대응지원센터 : 국번없이 118

블로그 이미지

유효하지않음

,

IMF 시즌2

정치이슈 2008. 7. 9. 15:56

사용자 삽입 이미지


블로그 이미지

유효하지않음

,
[기술문서]
o 홈페이지 개발 보안 가이드 (KISA)
- http://www.krcert.or.kr/docDown.jsp?dn=1
- http://www.krcert.or.kr/docDown.jsp?dn=2

o 민간사이버안전매뉴얼 기업 정보보호담당자용 (KISA)
- 제 5장 응용 서버 관리
- http://www.boho.or.kr/infor_data/securitypro.zip
 




o 웹 서버 보안 지침(KAIST ISC 이현우 님)



o Apache 웹서버 보안 관리(KISA, 정현철 님)



o Apache 웹서버 보안 관리(KISA, 정현철 님)



o 침해사고 분석 절차 가이드



o 웹 서버 구축 보안점검 가이드



o 홈페이지 개발 보안 가이드



o 웹 어플리케이션 보안템플릿(PHP 버전)



o GUIDE_CD



o 리눅스보안문서





ARP Spoofing 관련 대규모 악성코드 은닉 사고 급증에 따른 피해 주의
※ KrCERT 홈페이지 → 기술문서 →ARP Spoofing 악성코드 감염사고 분석 
기술문서 다운로드 바로가기 



※ KrCERT 홈페이지 → 기술문서 → ARP Spoofing 공격 및 대책

기술문서 다운로드 바로가기





[웹 보안 도구]

o WebKnight
- AQTRONIX사에서 개발한 IIS 웹서버용 공개 웹방화벽으로 SQL Injection 공격 등 IIS 웹서버의 주요 공격 차단 가능
- http://www.aqtronix.com/?PageID=99
- KISA 설치 운영 가이드 : http://www.krcert.or.kr/firewall2/fwDown2.jsp?fkind=11

o ModSecurity
- Ivan Ristic이 개발한 Apache 웹서버용 공개 웹방화격으로 PHP Injection 공격 등 Apache 웹서버의 주요 공격 차단 가능
- http://www.modsecurity.org/
- KISA 설치 운영 가이드 : http://www.krcert.or.kr/firewall2/fwDown2.jsp?fkind=12

o Nikto : 웹 취약점 스캐너, Nessus에 플러그인 형태로 포함됨
o Paros, Brup, Achilles : 웹 연결 분석
o 상용제품
 - 웹 취약점 스캐너 : AppScan, WebInspect, ScanDo, Acunetix, nProtect Online Scanner, Web Insight Scanner, PSScanW3B
 - 웹 어플리케이션 감사 도구 : SCA, DevInspect
블로그 이미지

유효하지않음

,

[출처] http://dada.pe.kr/392

생성 날짜로 보니 어제쯤에 4.1 SNAPSHOT이 release 된 거 같다. 라이브러리 저장소가기
요즘 메시지 큐를 프로젝트에 적용하고 있는데 며칠동안 삽질한 결과, 만족할만한 결과가 나오길래 포스팅해도 될꺼 같다는 판단하에 남겨놓는다.

● 개발환경
  - H/W : Dell insprion 6400
  - OS : Windows XP
  - Platform : JDK 1.5
  - IDE : Ecilpse 3.1 + WDT

● 사용라이브러리
- 꽤나 많다. 대부분 activeMQ 라이브러리 안에 포함되어있다. 포함되지 않은 것들만 소개한다.
- activeMQ 4.1 SNAPSHOT
- jakarta commons
  - net 1.4.1
  - collection 3.1
  - dbcp 1.2.1, pool .12 (DB 사용시에 필요^^)
- xercesImpl (XML 파서)
- geronimo : Geronimo 시작하기(activeMQ 내부적으로 사용되는 라이브러리기 때문에 굳이 읽을 필요는 없다)

● 참고문서
- 라이브러리 안에 간단한 예제를 보면 이해하기 쉽다.
  -  examples이라는 폴더 안에 있다.

● 선수지식
- AciveMQ 홈페이지 가서 최소한 JMS의 내용이라도 이해하고 있어야 쉽다.

● 구조
- example과 다른 것은 없지만, 약간 변경했다.

사용자 삽입 이미지


● 설명
- 어설프지만 개념은 요렇다! 자세한 건 AciveMQ 홈페이지에서...
사용자 삽입 이미지

- kr.pe.dada.main.ActiveMQSample.java 
main 클래스. 먼저 Broker(Queue)를 생성한 다음, Consumer를 생성하고, Producer를 이용해서 5개의 메시지를 Broker에게 넘긴다. Thread.sleep이 사용된 이유는 각각 라이브러리를 로딩하는 데 시간이 걸리기 때문이다.

- kr.pe.dada.broker.ActiveMQBroker.java 
지정된 xml 설정파일을 xbean 이용해서 읽는다. 굳이 xml 설정파일을 읽지않고 쓰려면 example의 예제처럼 BrokerService를 생성하고 하나하나 setter 메소드로 설정해주면 된다 :)

- kr.pe.dada.broker.ActiveMQProducer.java
Message를 보낼 목적지(Destination)을 설정하고, 현재 시간을 Message에 실어 Broker로 보낸다. (send 메소드) session 생성시에 유의할 점이 있는데, 나중에 간단히 설명하도록 하겠다.

- kr.pe.dada.broker.ActiveMQConsumer.java
Producer가 Broker에게 던지면, Broker가 다시 Consumer에게 Event(MessageListner를 통해서..)를 알린다. 역시 Producer와 동일하게 Destination을 정하고(같은 Destination을 사용해야한다.), Listner 2개를 등록한다.

- activemq.xml
Broker에 대한 전반적인 설정 xml. 내용이 많으니 Xml Configuration을 참고하자. 내가 만든 예제는 JDBC를 이용하여 Queue를 DB로 이용한 예제다. DB말고 메모리, 파일로도 사용가능하니 꼭 읽어봐야한다^^


- nabble

● 삽질기
  1. Ack 문제
- 버그인 듯한데...  Producer와 Consumer에서 session 생성시 다음과 같이 되어있다.
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
여러가지 장애처리를 염두해두느라, Producer와 Broker를 돌리고 있는 도중 Consumer를 죽여봤다. 즉, Message가 계속 Queue에 쌓이는 상태에서 Consumer를 따로 실행하면, 일단 Message를 잘 받는다. 하지만, 다시 죽이고 실행하면 이전의 Message를 또 받는 것이다. (dequeue가 제대로 되질 않는다. nabble 포럼에서는 AUTO_ACKNOWLEDGE의 버그일 꺼라 추측하고 있다. 자동으로 ACK을 날려주는 옵션인 듯한데, 자동으로 ACK을 날려주지 않는다!)

포럼에서는 여러가지 해결책을 제시하고 있는데, 나의 경우는 다음과 같이 처리했다. Consumer와 Producer 동일하게 처리한다.
session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);

그리고 Message를 주고 받을 때, 다음을 호출한다. Consumer에서는 OnMessage 메소드에서 Producer에서는 Send 메소드 호출후, acknowledge를 하면, 양쪽으로 ACK을 날림으로써 Message주고 받은 것을 알려준다.
message.acknowledge();

  2. mysql InnoDB engine 문제
- DB에 Table 생성시 에러가 발생
Table 'messagequeue.ACTIVEMQ_ACKS' doesn't exist

innoDB를 사용함으로써, PK의 데이터 사이즈(bytes)가 초과되어 발생하는 문제다. 아래 페이지를 참고해서 다음과 같이 처리한다. Unable to create ACTIVEMQ_ACK table
<persistenceadapter>
<journaledjdbc usejournal="false" journallogfiles="5" datadirectory="../activemq-data" datasource="#ds">
<statements>
<statements stringiddatatype="VARCHAR(128)" msgiddatatype="VARCHAR(128)" containernamedatatype="VARCHAR(128)">
</statements>
</statements></journaledjdbc>

샘플소스
블로그 이미지

유효하지않음

,
작성자 : 김재탁(gagboy@gmail.com)

이 튜토리얼의 일부 내용은 Quartz 1.4.2 javadoc 의 CronTrigger 에서 가져왔다.

Introduction

cron 은 오랜시간동안 사용되어진 UNIX 툴이다. 즉 cron 의 스케쥴링 능력은 강력하고 입증된 것이다.

만약에 SimpleTrigger 의 정확한 간격에 의해 실행되는 schedule 이 아닌, 달력과 같은 개념을 바탕으로 작업을 실행시키는 schedule 을 필요로 한다면, SimpleTrigger 보다는 CronTrigger 가 더 어울릴 것이다.

CronTrigger 로는 "매일 금요일 정오", 또는 "매평일(토,일 제외) 9:30분 마다", 또는 "매 월, 수, 금 의 오전 9시~오전 10시 사이에 매 5분마다" 와 같은 스케쥴을 정의 할 수 있다.

또한, SimpleTrigger 와 같이, CronTrigger 는 schedule 의 효력이 시작되는 startTime 과 schedule 이 중지 되는 (optional) endTime 를 설정할 수도 있다.

Format

Cron-Expressions 은 공백으로 분리되는 6~7 개의 문자열로 구성된다. 각 필드들은 어떤 allowed values(허락된 값 - 아래 표 참조) 값도 가지고 있을수 있으며, allowed special characters(허락된 특수문자 - 아래 표 참조) 와 함께 다양한 조합을 만들수 있다:


Field Name Mandatory? Allowed Values Allowed Special Characters
Seconds YES 0-59 , - * /
Minutes YES 0-59 , - * /
Hours YES 0-23 , - * /
Day of month YES 1-31 , - * ? / L W C
Month YES 1-12 or JAN-DEC , - * /
Day of week YES 1-7 or SUN-SAT , - * ? / L C #
Year NO empty, 1970-2099 , - * /


cron 표현식은 다음과 같은 모양이 될수 있다: * * * * ? *
혹은 다음과 같이 더욱 복잡하게되 표현된다: 0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010

Special characters

  • * ("all values") 모든값 - 해당 필드의 모든 값. 예를들어 minutes 필드의 "*" 는 "매분" 을 의미한다.
  • ? ("no specific value") 값 정의 안함 - day-of-month 와 day-of-week 에 사용할 수 있다. 이것은 두 필드중 한 필드는 사용하지 않고 다른 한 필드만 사용하려고 할때 유용하다. 예를들어 무슨 요일에 상관 없이 매달 10일에 실행시키고 싶다면 day-of-month 필드에 "10" 을 적고 day-of-week 필드에 "?" 을 적으면 된다. 좀더 명확하게 알고 싶으면 아래 예제를 보도록 한다.
  • - - 범위를 기술한다. 예를들어 Hour 필드의 "10-12" 는 "10, 11, 12 시" 를 의미한다.
  • , - 추가적인 값들을 기술한다. 예를들어 day-of-week 필드의 "MON,WED,FRI" 는 "월요일, 수요일, 금요일" 을 의미한다.
  • / - 증가값을 정의할 때 사용된다. 예를들어, '0/15' 를 Minutes 필드에 기술하면, 이것은 '0 분에서 시작해서 매 15분 마다' 를 의미한다. 만약 '3/20' 을 Minutes 필드에 기술 했다면 이것은 '3분에서 시작해서 그 시간내의 매 20분 마다' 를 의미한다. - 다르게 말하자면 Minutes 필드에 '3,23,43' 이라고 적은것과 같다. '*' 뒤에 '/' 를 사용 할 수도 있다 - 이것은 '/' 전에 0을 가지는 것과 같다. day-of-month 필드의 '1/3' 은 "달의 첫번째 날에서부터 시작하여 매 3일 마다" 를 의미한다.
  • L ("last") - day-of-month 와 day-of-week 에서만 사용 될 수 있다. 이것은 마지막을 의미하는 "last" 의 짧은 표현이지만, 각 두 필드에서는 서로 다른 의미를 가지고 있다. 예를들어 day-of-month 의 "L" 필드는 "월의 마지막 일자" - 즉, 1월에는 31일, 평년(윤년이 아닌) 의 2월에는 28일을 의미한다. 만약 day-of-week 필드에 독립적으로 사용한다면 이것은 "7" 혹은 "SAT" 를 의미한다. 그러나 day-of-week에 다른 값 뒤에 사용된다면 이것은 "해당월의 마지막 XX요일" 을 의미하게 된다 - 예를들어 "6L" 또는 "FRIL" 은 "월의 마지막 금요일" 을 말하게 되는것이다. "L" 옵션을 사용할때 혼란스러운 결과를 얻지 않으려면 list 나 range(범위) 값을 지정하지 말아야 한다.
  • W ("weekday") - 주어진 날짜의 가장 가까운 평일(월~금) 을 정의할때 사용된다. 예를들어, day-of-month 필드에 "15W" 라고 정의했다면 이것은 "해당월의 15 일에 가장 가까운 평일" 을 의미한다. 그러므로 만약 15일이 토요일이라면 trigger 는 금요일인 14일에 실행되며, 15일이 일요일이라면 trigger 는 월요일인 16일에 실행되게 되며, 15일이 화요일이라면 15일에 실행되게 된다. 반면에 day-of-month 필드에 "1W" 라고 정의했고 1일이 토요일이라면, 트리거는 월요일인 3일에 실행된다. 즉 'W' 는 월의 경계를 넘지 않는다. 'W' 문자는 day-of-month 필드가 범위나 리스트가 아닌 하나의 날짜 일때만 사용 될 수 있다.

'L' 과 'W' 문자는 day-of-month 필드에 'LW' 로 함께 쓰일수 있다. 이것은 "월의 마지막 평일" 을 의미한다.

  • # - day-of-week 필드에만 사용될 수 있으며 월의 "몇번째" X요일 을 정의할때 사용된다. 예를들어 day-of-week 필드의 "6#3" 또는 "FRI#3" 은 "월의 세번째 금요일" 을 의미한다. 다른 예제로 "4#5" = 는 5번째 수요일을 의미하는데 만약 해당 월에 5번째 수요일이 없다면 이것은 실행되지 않는다.
  • C ("calendar") 아직 지원 안되는 기능 - 이것은 연관된 Calendar(달력) 으로부터 값이 계산되어 진다는 것을 의미한다. if any. If no calendar is associated, then it is equivalent to having an all-inclusive calendar. A value of "5C" in the day-of-month field means "the first day included by the calendar on or after the 5th". A value of "1C" in the day-of-week field means "the first day included by the calendar on or after Sunday".

months 필드와 days of the week 필드의 케릭터들은 대소문자를 구분하지 않는다. 즉 MON 은 mon 과 같다.

Examples

여기 모든 예제가 있다:


표현식 의미
0 0 12 ? * WED 매 수요일 12:00 pm
0 0/5 * * * ? 매 5분 간격마다 fire
10 0/5 * * * ? 매 5분마다 fire, at 10 seconds after the minutes (10:00:10 am, 10:05:10 am ...)
0 30 10-13 ? * WED,FRI 10:30, 11:30, 12:30, 13:30 매 수요일, 금요일마다
0 0/30 8-9 5,20 * ? 매 30분간격 8시부터 9시 사이 매월 5일과 20일 (8:00, 8:30, 9:00, 9:30)
0 0 12 * * ? 매일 정오에 실행
0 15 10 ? * * 매일 10:15am 에 실행
0 15 10 * * ? 매일 10:15am 에 실행
0 15 10 * * ? * 매일 10:15am 에 실행
0 15 10 * * ? 2005 2005년일 동안 매일 10:15am 에 실행
0 * 14 * * ? 매일 2pm 에서 2:59pm 까지 매분마다 실행
0 0/5 14 * * ? 매일 2pm 에서 2:55pm 까지 5분마다 실행
0 0/5 14,18 * * ? 매일 2pm 에서 2:55pm 까지 5분마다 실행 그리고 매일 6pm 에서 6:55pm 까지 5분마다 실행
0 0-5 14 * * ? 매일 2pm 에서 2:05pm 까지 매분마다 실행
0 10,44 14 ? 3 WED 3월의 모든 수요일에 2:10pm 과 2:44pm 에 실행
0 15 10 ? * MON-FRI 매 월요일, 화요일, 수요일, 목요일, 금요일의 10:15am 에 실행
0 15 10 15 * ? 매월 15일의 10:15am 에 실행
0 15 10 L * ? 매월 마지막날의 10:15am 에 실행
0 15 10 ? * 6L 매월 마지막 금요일의 10:15am 에 실행
0 15 10 ? * 6L 매월 마지막 금요일의 10:15am 에 실행
0 15 10 ? * 6L 2002-2005 2002, 2003, 2004, 그리고 2005 년의 매월 마지막 금요일의 10:15am 에 실행
0 15 10 ? * 6#3 매월 세번째 금요일의 10:15am 에 실행
0 0 12 1/5 * ? 달의 첫번째 날에서 부터 5일 마다 12pm(정오)에 실행
0 11 11 11 11 ? 매 11월 11일의 11:11am 에 실행

day-of-week and day-of-month 필드의 "?" 과 "*" 의 효과를 주의 깊게 보라.

Notes

  • 'C' 문자에 대한 기능이 아직 완성되지 않았다.
  • day-of-week 와 a day-of-month 모두에게 값을 정의하는 기능이 아직 완성되지 않았다 (지금은 두 필드중 한곳에 반드시 "?" 를 적어줘야 한다).
  • 자정에서 1:00 AM 사이에 실행되도록 설정할 때는 주의하라 - "daylight savings(서머타임)" 에 의해 시간이 앞으로 가거나 뒤로 가는것 때문에 실행을 반복하거나 건너 뛸 수 있다.
블로그 이미지

유효하지않음

,

[출처]  http://blog.naver.com/jinriver/10161382

Job Scheduling in Java
by Dejan Bosanac
번역 허태명
03/10/2004


어떤 프로젝트에서 정확히 정해진 시간이나 일정한 시간 간격으로 실행되는 작업이 필요할 수 있다.
이 글에서 우리는 자바 개발자가 표준 Java Timer API를 사용하여 어떻게 이러한 요구사항을 구현할 수 있는지 살펴볼 것이다.
그리고, Java Timer API가 제공하는 기본적인 스케쥴링 시스템 외에 추가적인 기능을 필요로 하는 사람을 위해 오픈 소스 라이브러리인 Quartz에 대해 살펴볼 것이다.

먼저 스케쥴링 작업을 필요로 할 때, 이러한 상황을 인식하는데 도움을 줄 수 있는 일반적인 유스케이스에 대해서 몇 가지 살펴보자. 그
리고 나서 우리는 여러분의 기능적 요구에 가장 알맞는 최선의 해결책을 찾아 볼 것이다.

대부분의 비지니스 애플리케이션은 유저들이 필요로 하는 보고서와 통계를 가지고 있다.
시스템에 투자하는 사람들의 일반적인 목적은 대량의 데이터를 수집하고 그것을 미래의 비지니스 계획을 세우는데 도움이 되는 방식으로 보는 것이기 때문에,
이러한 일을 가능하게 해주는 보고서가 없는 시스템은 상상하기 힘들다. 이러한 보고서를 생성하는데 있어서의 문제점은 처리해야할 데이터의 양이 대량이기 때문에,
일반적으로 데이터베이스 시스템에 큰 부하가 걸린다는 것이다. 이러한 부하는 시스템이 보고서를 생성하는 동안,
전체적인 애플리케이션의 성능을 떨어뜨리고 단지 데이터 수집을 위하여 시스템을 사용하는 유저에게도 영향을 끼친다.
또한 유저입장에서 생각한다면, 생성하는데 10분이 걸리는 보고서는 좋은 응답시간의 예가 아니다.

우리는 먼저 실시간으로 실행될 필요가 없는, 캐쉬될 수 있는 종류의 보고서에 대해 살펴볼 것이다. 기쁘게도 대부분의 보고서가 이러한 부류에 들어간다.
-- 작년 6월의 어떤 제품 판매에 관한 통계, 또는 1월의 회사 소득 등. 이러한 캐쉬가 가능한 보고서는 간단한 방법으로 해결할 수 있다
: 시스템이 한가할 때 또는 데이터베이스 시스템의 부하가 최소일 때, 보고서를 생성하도록 스케쥴링하면 된다.
여러분이 많은 사무실(모두 같은 시간대에 있는)을 가지고 있는 지역 책 판매자를 위한 애플리케이션을 만든다고 해보자.
여러분은 주당 소득에 대한 보고서(아마도 대량의)를 생성할 필요가 있다.
매주 일요일 밤과 같이 시스템이 사용되지 않는 시간에 데이터베이스에 캐쉬해서 이러한 작업을 스케쥴링 할 수 있다.
이러한 방식으로 구현하면, 판매 담당자는 여러분의 애플리케이션에서 성능상의 문제점을 찾지 못할 것이다.
그리고 회사 관리부는 필요한 모든 데이터를 빠른 시간에 구할 수 있을 것이다.

다음으로 다룰 두번째 예제는 계정 사용기한 만료와 같은 일련의 공지(notification)를 애플리케이션 유저에게 보내는 작업에 관한 것이다.
이것은 유저 데이터에서 날짜 필드를 사용하여 유저의 조건을 검사하는 쓰레드를 생성함으로써 행해질 수 있다.
그러나 이러한 경우 스케쥴러를 사용하는 것이 명백하게 더욱 우아한 해결책이고, 전체적인 시스템 아키텍쳐(아키텍쳐는 중요하다. 그렇지 않은가?)의
측면에서도 더 좋다. 복잡한 시스템에서 여러분은 이러한 종류의 공지를 많이 가지고 있을 것이고, 이외의 다른 많은 경우에도 또한 스케쥴러 시스템이 필요할 것이다.
따라서, 스케쥴링 작업을 필요로 하는 각각의 경우에 대해 해결책을 구현하는 것은 시스템을 변경하고 유지보수하는 것을 더욱 어렵게 할 것이다.
일일이 스케쥴링 작업을 구현하는 대신 여러분은 애플리케이션의 모든 스케쥴링을 담당하는 API를 사용해야만 한다. 이것이 이 글의 나머지 부분에서 다루는 주제이다.

간단한 해결책
자바 애플리케이션에 기본적인 스케쥴러를 구현하기 위해 여러분은 어떤 외부 라이브러리도 필요 없다.
J2SE 1.3 이후로 자바는 이러한 목적으로 사용될 수 있는 java.util.Timer, java.util.TimerTask 두 개의 클래스를 포함하고 있다.
 먼저 이 API로 가능한 모든 것을 설명해주는 간단한 예제를 만들어보자.

package net.nighttale.scheduling;

import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ReportGenerator extends TimerTask {

  public void run() {
    System.out.println("Generating report");
    //TODO generate report
  }

}

class MainApplication {

  public static void main(String[] args) {
    Timer timer  new Timer();
    Calendar date = Calendar.getInstance();
    date.set(
      Calendar.DAY_OF_WEEK,
      Calendar.SUNDAY
    );
    date.set(Calendar.HOUR, 0);
    date.set(Calendar.MINUTE, 0);
    date.set(Calendar.SECOND, 0);
    date.set(Calendar.MILLISECOND, 0);
    // Schedule to run every Sunday in midnight
    timer.schedule(
      new ReportGenerator(),
      date.getTime(),
      1000 * 60 * 60 * 24 * 7
    );
  }
}

이 글의 모든 예제 코드는 글 말미의 링크에서 다운로드 받을 수 있다.

위의 코드는 서두에서 언급한, 시스템이 한가한 시간(예제의 경우 일요일 밤)에 보고서를 생성하도록 스케쥴링하는 예제를 구현하고 있다.

먼저 우리는 실제로 스케쥴링된 작업을 수행하는 "worker" 클래스를 구현해야 한다. 우리의 예제에서 이것은 ReportGenerator 이다.
이 클래스는 java.lang.Runnable을 구현하는 java.util.TimerTask 클래스를 상속받아야 한다.
그리고 나머지 할 일은 보고서를 생성하는 코드를 run() 메소드 안에 오버라이드하는 것 밖에 없다.

우리는 이 객체의 실행을 Timer 클래스의 스케쥴링 메소드 중의 하나를 이용해서 스케쥴링한다.
예제의 경우 최초 실행 날짜와 밀리세컨드 단위의 실행 주기를 인자로 받아들이는 schedule() 메소드를 사용한다.
(왜냐하면 우리는 이 보고서를 매주 생성할 것이기 때문이다.)

스케쥴링 기능을 사용할 때, 우리는 스케쥴링 API가 제공하는 리얼타임에 대한 보증을 알아야만 한다.
불행하게도 자바의 특성과 다양한 플랫폼에서의 구현때문에, 각각의 JVM에서의 쓰레드 스케쥴링의 구현은 일치하지 않는다.
그러므로, Timer는 우리의 스케쥴링 작업이 정확한 규정된 시간에 실행될 것이라고 보증할 수 없다.
우리의 스케쥴링 작업은 Runnable 객체로 구현되어 있고 때때로 일정 시간동안 sleep 상태가 된다.
그러면 Timer는 규정된 순간에 이들을 깨운다.
그러나 정확한 실행 시간은 JVM의 스케쥴링 정책과 현재 얼마나 많은 쓰레드가 프로세서를 기다리고 있느냐에 따라 달라진다.
우리의 스케쥴링 작업 실행을 지연시킬 수 있는 두 가지 일반적인 경우가 있다. 첫째, 많은 수의 쓰레드가 실행되기를 기다리고 있는 경우이다;
둘째, 가비지 콜렉션의 활동에 의해 지연되는 경우가 있다.
이러한 영향들은 다른 JVM에서 스케쥴러를 실행 또는 가비지 콜렉터의 옵션 튜닝과 같은 여러가지 기법을 사용함으로써 최소화될 수 있다.
그러나 그것은 이 글의 주제를 벗어나는 것이다.

다시 본론으로 돌아오자. Timer 클래스에는 두 개의 다른 스케쥴링 메소드 그룹이 있다
: 고정된 딜레이로 스케쥴링하는 schedule() 메소드와 고정된 비율로 스케쥴링하는 scheduleFixedRate() 메소드가 그것이다.
첫번째 그룹의 메소드들을 사용할 때, 각 작업 실행의 딜레이는 다음 작업 실행으로 전달될 것이다.
후자 그룹의 경우 딜레이를 최소화하면서 모든 연속된 작업 실행은 최초 작업 실행의 시간에 맞춰 스케쥴링 될 것이다.
어떤 메소드를 사용하느냐는 여러분의 시스템에 어떤 파라미터가 더 중요하느냐에 달려 있다.

매우 중요한 것이 한 가지 더 있다: 각 Timer 객체는 쓰레드를 백그라운드로 시작한다.
이러한 방식은 J2EE 애플리케이션 서버와 같은 환경에서는 바람직하지 않을 것이다. 왜냐하면 이러한 쓰레드들이 컨테이너 영역 내에 있지 않기 때문이다.


평범한 것을 넘어서
지금까지 애플리케이션에서 어떻게 스케쥴링을 하는지 살펴 보았고, 이것은 간단한 요구사항에서는 충분하다.
그러나 고급 유저와 복잡한 요구사항을 위해서는 유용한 스케쥴링을 지원하기 위해 더 많은 기능들을 필요로 한다.
이러한 경우 두 가지 일반적인 해결책이 있다. 첫번째는 자신이 필요로 하는 기능을 가지고 있는 스케쥴러를 만드는 것이다;
두번째는 필요로 하는 요구사항을 충족하는 프로젝트를 찾아내는 것이다.
시간과 자원을 절약할 수 있고 다른 누군가의 노력을 중복해야 할 필요가 없기 때문에, 두번째 해결책이 대부분의 경우에 있어서 더욱 적합할 것이다.

이러한 요구사항은 우리를 단순한 Timer API 이상의 훨씬 뛰어난 장점들을 가진 오픈 소스 작업 스케쥴링 시스템인 Quartz 로 유도한다.

Quartz의 첫번째 장점은 영속성이다. 만약 여러분의 작업이 앞서의 예제와 같이 "정적"이라면 영속성 지원은 필요 없을 것이다.
그러나 종종 어떤 조건이 충족됐을 때 "동적"으로 수행되는 작업을 필요로 할 때도 있다.
그리고 이러한 작업들이 시스템 재시작(또는 시스템 다운) 사이에도 실행되야 할 때가 있다.
Quartz는 비 영속성과 영속성 작업 모두를 제공한다. 영속성 작업의 상태는 데이터베이스에 저장될 것이다.
따라서 이러한 작업들이 실행되지 않는 경우가 없을 거라고 확신할 수 있다.
영속성 작업은 시스템에 추가적인 성능 감소를 유발하기 때문에 주의깊게 사용해야만 한다.

Timer API는 원하는 실행시간을 단순하게 설정할 수 있는 메소드가 부족하다.
위 예제에서 본 대로, 실행시간을 설정하기 위해서 할 수 있는 것은 시작일자와 반복주기를 설정하는 것 밖에 없다.
분명히, 유닉스의 cron을 사용해 본 사람들은 스케쥴러를 그와 유사하게 설정할 수 있기를 바랄 것이다.
Quartz는 원하는 시간, 날짜를 유연한 방법으로 설정할 수 있게 해주는 org.quartz.CronTrigger를 정의하고 있다.

개발자는 자주 한 가지 이상의 기능을 필요로 한다
: 작업의 이름에 의한 작업의 관리와 조직화.
Quartz에서 당신은 이름이나 그룹에 의해 원하는 작업을 찾아낼 수 있다. 이것은 스케쥴된 작업이 많은 환경에서 큰 도움이 될 것이다.

이제 보고서 생성 예제를 Quartz를 사용하여 구현하고 라이브러리의 기본적인 기능들에 대해 설명할 것이다.

package net.nighttale.scheduling;

import org.quartz.*;

public class QuartzReport implements Job {

  public void execute(JobExecutionContext cntxt)
    throws JobExecutionException {
      System.out.println(
        "Generating report - " +
cntxt.getJobDetail().getJobDataMap().get("type")
      );
      //TODO Generate report
  }

  public static void main(String[] args) {
    try {
      SchedulerFactory schedFact
       new org.quartz.impl.StdSchedulerFactory();
      Scheduler sched  schedFact.getScheduler();
      sched.start();
      JobDetail jobDetail
        new JobDetail(
          "Income Report",
          "Report Generation",
          QuartzReport.class
        );
      jobDetail.getJobDataMap().put(
                                "type",
                                "FULL"
                               );
      CronTrigger trigger  new CronTrigger(
        "Income Report",
        "Report Generation"
      );
      trigger.setCronExpression(
        "0 0 12 ? * SUN"
      );
      sched.scheduleJob(jobDetail, trigger);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
Quartz는 Job, Trigger라는 두 개의 기본 추상 계층을 정의한다. Job은 실제 실행되는 작업의 추상 계층이고,
Trigger는 언제 작업이 실행되어야 하는지를 나타내는 추상 계층이다.

Job은 인터페이스이다. 그래서 우리가 해야할 일은 클래스가 org.quartz.Job(또는 나중에 살펴보게 될 org.quartz.StatefulJob)
인터페이스를 구현하도록 하고 execute() 메소드를 오버라이드하는 것 뿐이다.

예제에서 java.util.Map의 변형된 구현인 jobDataMap 어트리뷰트를 통해 어떻게 Job에게 파라미터를 전달할 수 있는지 살펴 봤다.
상태가 있는 작업, 또는 비 상태 작업 중 어떤 것을 구현하느냐 결정하는 것은 스케쥴링 작업의 실행동안 이러한 파라미터들을 변경하기를 원하느냐
아니냐를 결정하는 문제이다. Job 인터페이스를 구현한다면 모든 파라미터들은 작업이 최초로 스케쥴링 되는 순간에 저장된다.
그리고 이후의 변경은 모두 버려진다. execute() 메소드 내에서 StatefulJob의 파라미터를 변경한다면,
작업이 다음에 새로 스케쥴링 될 때 이 새로운 값이 전달될 것이다.
고려해야할 한 가지 중요한 사항은 StatefulJob 인터페이스를 구현한 작업들은 실행되는 동안 인자들이 변할 수 있기 때문에 동시에 실행될 수 없다는 것이다.

Trigger에는 두 가지의 기본적인 Trigger가 있다: SimpleTrigger 와 CronTrigger. SimpleTrigger는 기본적으로 Timer API가 제공하는 것과 같은 기능을 제공한다.
작업이 시작된 이후에 정해진 간격으로 반복해서 실행되는 경우, SimpleTrigger를 써야 한다.
SimpleTrigger는 시작일, 종료일, 반복횟수, 실행 주기를 정의할 수 있다.

위의 예제에서는 더욱 사실적인 바탕에서 작업을 스케쥴링할 수 있는 유연성때문에 CronTrigger를 사용했다.
CrinTrigger 사용함으로써 "매주 평일 오후 7시" 또는 "토요일과 일요일 매 5분마다"와 같은 스케쥴링 작업을 할 수 있다.
더 이상 cron에 관해 설명하지는 않을 것이다. cron에 관한 세부사항은 CronTrigger의 Javadoc을 참조하기 바란다.

위의 예제를 실행하기 위해 클래스패쓰에 기본적인 Quartz의 설정을 하는 quartz.properties 파일을 필요로 한다.
만약 파일이름을 다르게 사용하기를 원한다면, 파일이름을 StdScheduleFactory 생성자에 인자로 전달해야만 한다.
 아래에 최소한의 프로퍼티들만 설정한 파일의 예제가 있다:

#
# Configure Main Scheduler Properties
#

org.quartz.scheduler.instanceName = TestScheduler
org.quartz.scheduler.instanceId = one

#
# Configure ThreadPool
#

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount =  5
org.quartz.threadPool.threadPriority = 4

#
# Configure JobStore
#

org.quartz.jobStore.misfireThreshold = 5000

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
표준 Timer API에 비해 Quartz가 가진 또 다른 장점은 쓰레드 풀의 사용이다. Quartz는 작업 실행을 위한 쓰레드를 얻기 위해 쓰레드 풀을 사용한다. 쓰레드 풀의 크기는 동시에 실행될 수 있는 작업의 수에 영향을 미친다. 실행해야할 작업이 있지만 쓰레드 풀에 남아 있는 쓰레드가 없다면, 작업은 여분의 쓰레드가 생길 때까지 sleep 상태가 될 것이다. 시스템에서 얼마나 많은 쓰레드를 사용할 지는 매우 결정하기 어려운 문제이고, 실험적으로 결정하는 것이 가장 좋다. 쓰레드 풀 크기의 기본 값은 5이고 수천개의 작업을 다루지 않는다면 이것은 충분할 것이다. Quartz 자체에서 구현한 쓰레드 풀이 있지만, 다른 쓰레드 풀의 구현이 있다면 그것을 사용하는데 제약을 받지는 않는다.

이제 JobStore에 관해 살펴 보자.JobStore는 Job과 Trigger에 관한 모든 데이터를 보존한다. 따라서 작업에 영속성을 부여할 것인지 아닌지 결정하는 것은 어떤 JobStore를 사용하느냐에 달려 있다. 예제에서 우리는 org.quartz.simpl.RAMJobStore를 사용했다. 이것은 모든 데이터는 메모리에 저장될 것이고 그러므로 비 영속성이라는 것을 의미한다. 따라서 애플리케이션이 다운되면 스케쥴링 작업에 관한 모든 데이터는 사라질 것이다. 어떤 상황에서 이것은 바람직한 방식이다. 그러나 데이터를 보존하고 싶다면 애플리케이션이 org.quartz.simpl.JDBCJobStoreTX(또는 org.quartz.simpl.JDBCJobStoreCMP)를 사용하도록 설정해야 한다.JDBCJobStoreTX는 좀 더 많은 설정 파라미터를 필요로 하고 그것은 아래 예제에서 설명할 것이다.

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_

#
# Configure Datasources
#

org.quartz.dataSource.myDS.driver = org.postgresql.Driver
org.quartz.dataSource.myDS.URL = jdbc:postgresql:dev
org.quartz.dataSource.myDS.user = dejanb
org.quartz.dataSource.myDS.password =
org.quartz.dataSource.myDS.maxConnections  5

Quartz와 관계형 데이터베이스를 성공적으로 사용하기 위하여 먼저 Quartz가 필요로 하는 테이블을 생성할 필요가 있다.
적절한 JDBC 드라이버를 가지고 있다면 어떤 데이터베이스 서버도 사용 가능하다.
docs/dbTables 폴더에서 필요한 테이블을 생성하는 초기화 스크립트를 찾을 수 있다.

테이블을 생성한 후에 표준 SQL 쿼리를 특정 RDBMS의 SQL 문법에 맞게 변경해주는 위임(delegate) 클래스를 선언해야 한다.
예제에서 우리는 PostgreSQL을 데이터베이스 서버로 선택했고 따라서 org.quartz.impl.jdbcjobstore.PostgreSQLDelegate 클래스를 위임 클래스로 설정했다.
당신이 사용하는 특정 데이터베이스 서버를 위해 어떤 위임 클래스를 사용해야 하는지에 관한 정보는 Quartz 문서를 참조하기 바란다.

tablePrefix 파라미터는 데이터베이스에서 Quartz 테이블이 사용할 접두어를 정의한다. 디폴트는 QRTZ_ 이다.
이런 방식으로 데이터베이스의 나머지테이블과 구분할 수 있다.

사용하는 매 JDBC store마다 어떤 datasource를 사용할 것인지 정의할 필요가 있다.
이것은 일반적인 JDBC의 설정이기 때문에 여기서 더 이상 설명하지는 않을 것이다.

Quartz의 뛰어난 점은 이러한 설정을 변경한 후에 보고서 생성 예제의 코드를 단 한줄도 변경하지 않고, 데이터를 데이터베이스에 저장할 것이라는 것이다.


Advanced Quartz
지금까지 프로젝트에 Quartz를 사용하기 위한 좋은 기초가 될 수 있는 기본적인 것에 대해 살펴 보았다.
이외에도 Quartz 라이브러리는 당신의 수고를 크게 덜어줄 수 있는 뛰어난 아키텍쳐를 가지고 있다.

Quartz는 기본적으로 제공하는 것 외에 애플리케이션의 문제를 해결하는데 도움이 되는 뛰어난 아키텍쳐를 가지고 있다.
그 중 중요한 기능 중의 하나는 listener 이다: 이것은 시스템에 어떤 이벤트가 발생할 때 호출된다. 세 가지 종류의 리스너가 있다:
JobListener, TriggerListener, SchedulerListener 이다.

리스너는 시스템에 무언가 이상이 생겨서 이에 대한 공지나 알림 기능을 원할 때 특히 유용하다.
예를 들어 보고서 생성 중에 에러가 발생하면 개발 팀에게 이를 알리는 우아한 방법은 E-mail이나 SMS를 보내는 JobListener 를 만드는 것이다.

JobListener 는 더 흥미로운 기능을 제공한다. 시스템 자원의 가용성에 크게 의존하는 일(그다지 안정적이지 못한 네트워크와 같은)을 다루는 작업을 상상해보라.
이러한 경우 작업이 실행될 때 자원이 사용 불가능하다면 이를 재실행 시키는 리스너를 만들 수 있다.

Quartz는 또한 Trigger 가 잘못 실행되거나 스케쥴러가 다운되서 실행되지 않았을 때의 상황을 다룰 수 있다.
Trigger 클래스의 setMisfireInstruction() 메소드를 사용함으로써 오실행에 관한 처리를 설정할 수 있다.
이 메소드는 오실행 명령 타입을 인자로 받아들인고, 그 값은 다음중의 하나가 될 수 있다:

Trigger.INSTRUCTION_NOOP: 아무 일도 하지 않는다.
Trigger.INSTRUCTION_RE_EXECUTE_JOB: 즉시 작업을 실행한다.
Trigger.INSTRUCTION_DELETE_TRIGGER: 오실행한 작업을 삭제한다.
Trigger.INSTRUCTION_SET_TRIGGER_COMPLETE: 작업이 완료를 선언한다.
Trigger.INSTRUCTION_SET_ALL_JOB_TRIGGERS_COMPLETE: 작업을 위한 모든 trigger의 완료를 선언한다.
Trigger.MISFIRE_INSTRUCTION_SMART_POLICY: 특정 Trigger의 구현에 가장 알맞은 오실행 처리 명령을 선택한다.
CronTrigger와 같은 Trigger 구현은 유용한 오실행 처리 명령을 새로 정의할 수 있다. 이것에 관한 더 많은 정보는 이 클래스들의 Javadoc을 참고하기 바란다.
TriggerListener를 사용함으로써 오실행이 발생했을 경우 취할 액션에 관해 더 많은 제어권을 가질 수 있다.
또한 트리거의 실행이나 종료와 같은 트리거 이벤트에 반응하기 위해 이것을 사용할 수 있다.

SchedulerListener 는 스케쥴러 종료나 작업과 트리거의 추가나 제거와 같은 전체적인 시스템 이벤트를 다룬다.

여기서 우리는 보고서 생성 예제를 위한 간단한 JobListener 를 보여줄 것이다. 먼저 JobListener 인터페이스를 구현하는 클래스를 작성해야 한다.

package net.nighttale.scheduling;

import org.quartz.*;


public class MyJobFailedListener implements JobListener {

  public String getName() {
    return "FAILED JOB";
  }

  public void jobToBeExecuted
    (JobExecutionContext arg0) {
  }


  public void jobWasExecuted(
    JobExecutionContext context,
    JobExecutionException exception) {

    if (exception != null) {
      System.out.println(
        "Report generation error"
      );
      // TODO notify development team
    }
  }
}
그리고 예제의 main 메소드에 다음을 추가한다:

sched.addGlobalJobListener(new MyJobFailedListener());
이 리스너를 스케쥴러 작업 리스너의 전체 목록에 추가함으로써 리스너는 모든 작업의 이벤트에 대해 호출 될 것이다.
물론 리스너를 특정 작업에 대해서만 설정할 수도 있다. 그렇게 하기 위해 Scheduler의 addJobListeners() 메소드를 사용해야 한다.
그리고 JobDetail의 addJobListener() 메소드를 사용해서 등록된 리스너를 리스너의 작업 목록에 추가해라.
이 때 리스너 이름을 파라미터로 사용한다. 리스너 이름은 리스너의 getName() 메소드의 리턴값을 말한다.

sched.addJobListener(new MyJobFailedListener());
jobDetail.addJobListener("FAILED JOB");
리스너가 정말로 동작하는지 테스트하기 위해, 다음을 보고서 생성 작업의 execute() 메소드 안에 추가한다.

throw new JobExecutionException();
작업이 실행된 후 리스너의 jobWasExecuted() 메소드가 실행되고, 예외가 발생한다. 예외는 null 이 아니기 때문에 "Report generation error" 메세지를 화면에서 볼 수 있을 것이다.

리스너에 관한 마지막 사항은 애플리케이션의 성능을 떨어뜨릴 수 있기 때문에 시스템에서 사용되는 리스너의 갯수에 유의해야한다는 것이다.

Quartz의 기능을 확장할 수 있는 방법이 한 가지 더 있다. 그것은 플러그 인을 이용하는 것이다. 플러그 인은 실질적으로 여러분이 필요한 어떤 일도 할 수 있다; 단지 해야할 일은 org.quartz.spi.SchedulerPlugin 인터페이스를 구현하는 것 뿐이다. 이 인터페이스는 구현해야할 두 개의 메소드를 정의한다 -- 하나는 초기화(스케쥴러 객체를 파라미터로 받는)를 위한 것이고, 또 하나는 종료를 위한 것이다. 나머지는 모두 여러분에게 달려 있다. SchedulerFactory 가 플러그 인을 사용하도록 하기 위해 quartz.properties 파일에 플러그 인 클래스와 몇 가지 옵션 설정 파라미터(플러그인를에서 필요로 하는)를 추가한다. In order to make SchedulerFactory use a certain plug-in, all you have to do is to add a line in the properties file (quartz.properties) with the plug-in class and a few optional configuration parameters (which depend on the particular plug-in). There are a few plug-ins already in Quartz itself. One is the shutdownHook, which can be used to cleanly shut down the scheduler in case the JVM terminates. To use this plug-in, just add the following lines in the configuration file:

org.quartz.plugin.shutdownHook.class =
   org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownHook.cleanShutdown = true

어떤 환경에서도 융통성 있는
지금까지 Standalone 애플리케이션에서 Quartz를 사용하는 것을 살펴 봤다. 이제 Quartz 인터페이스를 자바 개발자의 가장 보편적인 환경 하에서 어떻게 사용하는지 살펴 보자.

RMI
RMI를 사용하는 분산 애플리케이션에서 Quartz는 지금까지 본 것과 마찬가지로 간단하다. 차이점은 설정 파일 밖에 없다.

두 가지 필수 단계가 있다: 먼저 Quratz를 우리의 request를 다루는 RMI 서버로 설정하는 것이다. 그러고 나면 단지 일반적인 방식으로 사용하기만 하면 된다.

이 예제의 소스 코드는 실질적으로 첫번째 예제와 동일하지만, 우리는 이것을 두 부분으로 나눌 것이다: 스케쥴러 초기화와 스케쥴러 사용.

package net.nighttale.scheduling.rmi;

import org.quartz.*;

public class QuartzServer {

  public static void main(String[] args) {
 
    if(System.getSecurityManager() != null) {
      System.setSecurityManager(
        new java.rmi.RMISecurityManager()
      );
    }
   
    try {
      SchedulerFactory schedFact =
       new org.quartz.impl.StdSchedulerFactory();
      Scheduler sched = schedFact.getScheduler();
      sched.start();
    } catch (SchedulerException se) {
      se.printStackTrace();
    }
  }
}
위에서 볼 수 있듯이, 스케쥴러 초기화 코드는 security manager를 설정하는 부분을 포함하고 있다는 것 외엔 다른 것과 다를 바 없다. 중요한 것은 설정 파일(quartzServer.properties)안에 있다. 그것은 다음과 같다:

#
# Configure Main Scheduler Properties
#
org.quartz.scheduler.instanceName = Sched1
org.quartz.scheduler.rmi.export = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099
org.quartz.scheduler.rmi.createRegistry = true

#
# Configure ThreadPool
#

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 4

#
# Configure JobStore
#

org.quartz.jobStore.misfireThreshold = 5000

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
강조된 부분을 주목하라. 이것은 이 스케쥴러가 RMI를 통하여 인터페이스를 반출하고 RMI registry를 실행하기 위해 파라미터들을 제공해야 한다는 것을 보여준다.

서버를 성공적으로 배치하기 위해 몇 가지 할 일이 더 있다. 그것들은 모두 RMI를 통하여 객체를 반출하기 위한 전형적인 작업이다. 먼저 rmiregistry 를 시작할 필요가 있다. 이것은 다음과 같이 호출함으로써 이루어진다: 유닉스 시스템에서는

   rmiregistry &
또는 윈도우 플랫폼에서는

   start rmiregistry
 

다음에 QuartzServer 클래스를 다음과 같은 옵션으로 시작한다:

java  -Djava.rmi.server.codebase
   file:/home/dejanb/quartz/lib/quartz.jar
   -Djava.security.policyrmi.policy
   -Dorg.quartz.propertiesquartzServer.properties
   net.nighttale.scheduling.rmi.QuartzServer
이제 이러한 파라미터들을 좀 더 자세하게 살펴보자. Quartz의 Ant 빌드 태스크는 필요한 RMI 클래스를 클라이언트가 codebase로 가리키도록 이 클래스들을 생성하는 rmic 호출을 포함하고 있다. 그러기 위해 빌드 파일에 -Djava.rmi.server.codebase 파라미터로 시작하도록 설정해야 한다:추가적으로 quartz.jar 의 완전한 경로도 포함되어야 한다.(물론 이것은 라이브러리의 URL이 될 수도 있다.)

분산 시스템에서 중요한 이슈는 보안이다; 그러므로 RMI는 보안 정책을 사용하도록 강제할 것이다. 예제에서는 모든 권한을 허용하는 기본 정책(rmi.policy)을 사용했다.

grant {
  permission java.security.AllPermission;
};
실제 적용할 때는 시스템의 보안 요구사항에 따라 정책을 변경해야 한다.

이제 스케쥴러는 RMI를 통해 Job을 받아들일 준비가 되었다. 이제 클라이언트 코드를 살펴보자.

package net.nighttale.scheduling.rmi;

import org.quartz.*;
import net.nighttale.scheduling.*;

public class QuartzClient {

  public static void main(String[] args) {
    try {
      SchedulerFactory schedFact =
       new org.quartz.impl.StdSchedulerFactory();
      Scheduler sched = schedFact.getScheduler();
      JobDetail jobDetail = new JobDetail(
        "Income Report",
        "Report Generation",
        QuartzReport.class
      );
 
      CronTrigger trigger = new CronTrigger(
        "Income Report",
        "Report Generation"
      );
      trigger.setCronExpression(
        "0 0 12 ? * SUN"
      );
      sched.scheduleJob(jobDetail, trigger);
    } catch (Exception e) {
      e.printStackTrace();
    }
   }
}
위에 나온 바와 같이 클라이언트 소스는 이전 예제와 동일하다. 물론 여기에도 quartzClient.properties 의 설정은 다르다. 추가해야 할 것은 이 스케쥴러가 RMI 클라이언트(proxy)이고, 서버를 찾을 registry의 위치를 설정하는 것 뿐이다.

# Configure Main Scheduler Properties 

org.quartz.scheduler.instanceName = Sched1
org.quartz.scheduler.rmi.proxy = true
org.quartz.scheduler.rmi.registryHost = localhost
org.quartz.scheduler.rmi.registryPort = 1099
나머지는 모두 서버 측에서 이루어지기 때문에 클라이언트 측에 이외의 다른 어떤 설정도 필요하지 않다. 사실 다른 설정이 있다해도 이것은 무시될 것이다. 중요한 것은 스케쥴러의 이름은 클라이언트와 서버의 설정이 일치해야 한다는 것이다.(예제의 경우 Shed1) 그러면 시작하기 위한 준비는 끝났다. 단지 리다이렉트된 프로퍼티 파일로 클라이언트를 시작하기만 하면 된다:

java -Dorg.quartz.properties
       quartzClient.properties
       net.nighttale.scheduling.rmi.QuartzClient
이제 서버 콘솔에서 첫번째 예제와 같은 결과를 볼 수 있을 것이다.

웹과 엔터프라이즈 환경
웹이나 엔터프라이즈 환경의 솔류션을 개발하고 있다면, 스케쥴러를 시작하기 위한 적절한 곳은 어디인지 의문이 생길 것이다. 이것을 위해 Quartz는 org.quartz.ee.servlet.QuartzInitializerServlet를 제공한다. 할 일은 단지 web.xml 파일에 다음 설정을 추가하는 것 뿐이다:


 
   QuartzInitializer
 
 
   Quartz Initializer Servlet
 
 
   org.quartz.ee.servlet.QuartzInitializerServlet
 
 
   1
 

Job을 EJB 메소드에서 호출하고 싶다면, org.quartz.ee.ejb.EJBInvokerJob 클래스를 JobDetail 에 전달해야 한다. 이러한 기법을 보여주기 위해, ReportGenerator를 세션빈으로 구현하고 서블릿으로부터 generateReport() 메소드를 호출할 것이다.

package net.nighttale.scheduling.ee;

import java.io.IOException;

import javax.servlet.*;
import net.nighttale.scheduling.Listener;
import org.quartz.*;
import org.quartz.ee.ejb.EJBInvokerJob;
import org.quartz.impl.StdSchedulerFactory;

public class ReportServlet implements Servlet {

  public void init(ServletConfig conf)
    throws ServletException {
    JobDetail jobDetail = new JobDetail(
      "Income Report",
      "Report Generation",
      EJBInvokerJob.class
    );
    jobDetail.getJobDataMap().put(
      "ejb",
      "java:comp/env/ejb/Report"
    );
    jobDetail.getJobDataMap().put(
      "method",
      "generateReport"
    );
    Object[] args = new Object[0];
    jobDetail.getJobDataMap().put("args", args);
    CronTrigger trigger = new CronTrigger(
      "Income Report",
      "Report Generation"
    );
    try {
      trigger.setCronExpression(
        "0 0 12 ? * SUN"
      );
      Scheduler sched =
       StdSchedulerFactory.getDefaultScheduler();
      sched.addGlobalJobListener(new Listener());
      sched.scheduleJob(jobDetail, trigger);
      System.out.println(
        trigger.getNextFireTime()
      );
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public ServletConfig getServletConfig() {
    return null;
  }

  public void service(ServletRequest request,
                      ServletResponse response)
    throws ServletException, IOException {
  }

  public String getServletInfo() {
    return null;
  }

  public void destroy() {
  }
}
위에 나온 바와 같이, job에 전달할 필요가 있는 파라미터가 3개가 있다.

ejb: 엔터프라이즈 빈의 JNDI 이름.
method: 실제 호출되야 할 메소드의 이름.
args: 메소드의 인자로 전달되야 할 객체의 배열.
나머지는 Quartz의 일반 사용법과 다른 것이 없다. 간단하게 하기 위해 이 예제를 서블릿의 초기화 메소드에 위치시켰지만, 물론 애플리케이션의 어떠한 위치에 놓아도 상관 없다. Quartz를 성공적으로 실행하기 위해 웹 애플리케이션에 이 EJB를 등록할 필요가 있다. 일반적으로 web.xml 파일에 다음을 추가하면 된다.


  ejb/Report
  Session
  net.nighttale.scheduling.ee.ReportHome
  net.nighttale.scheduling.ee.Report
  ReportEJB

어떤 애플리케이션 서버(Orion과 같은)는 유저 쓰레드를 생성하기 위한 권한을 설정해야 할 필요가 있다. 그러므로 -userThread와 같은 옵션으로 실행해야 할 것이다.

지금까지 살펴 본 것이 Quartz의 엔터프라이즈 기능의 전부는 아니다. 그러나 처음 시작할 때 참고할 만할 것이다. 지세한 정보를 원한다면 Quartz's Javadocs 를 참고하기 바란다.

Web Services
Quartz는 현재 웹 서비스를 위해 내장된 지원은 없지만, XML-RPC 을 통하여 스케쥴러 인터페이스를 반출하는 플러그 인을 찾을 수 있다. 설치 절차는 간단하다. 시작하기 위해 플러그 인 소스를 Quartz 소스 폴더에 압축을 풀고 다시 빌드해야만 한다. 플러그 인은 Jakarta XML-RPC 라이브러리에 의존하므로, 그것이 클래스패쓰에 잡혀 있는지 확인해야 한다. 다음에 프로퍼티 파일에 아래의 내용을 추가한다.

org.quartz.plugin.xmlrpc.class = org.quartz.plugins.xmlrpc.XmlRpcPlugin
org.quartz.plugin.xmlrpc.port = 8080
이제 스케쥴러는 XML-RPC를 통해 사용할 수 있는 준비가 되었다. 이러한 방식으로 PHP나 Perl같은 다른 언어, 또는 RMI가 적합한 해결책이 아닌 분산 환경에서, Quartz의 기능 중의 일부를 사용할 수 있다.

요약
지금까지 자바에서 스케쥴링을 구현하는 두 가지 방법에 대해 살펴 봤다. Quartz는 매우 강력한 라이브러리지만, 간단한 요구사항에 대해서는 Timer API가 시간을 절약하고 시스템에 불필요한 복잡성을 피할 수 있게 해 준다. 스케쥴링이 필요한 작업이 그다지 많지 않고, 프로세스에서 실행시간이 잘 알려져 있을 경우(그리고 불변일 경우) Timer API를 사용할 것을 고려해라. 이러한 상황에서 시스템 중지나 다운 때문에 스케쥴링 작업들을 잃어버릴 것에 대해 걱정할 필요는 없다. 더욱 복잡한 요구사항을 위해서는 Quartz가 우아한 해결책이다. 물론 언제든지 Timer API를 기반으로 당신 자신의 프레임워크를 만들 수 있다. 그러나 이미 당신이 필요로 하는 모든 기능(그리고 그 이상의 기능)을 제공하는 훌륭한 해결책이 존재하는데 왜 그런 귀찮을 일을 하려는가?

한 가지 주목할 만한 이벤트는 IBM과 BEA이 제출한 "Timer for Application Servers" JSR 236 이다. 이 스펙은 스케쥴링에 관련된 표준 API가 부족한 J2EE 환경에서 타이머를 위한 API를 만드는 데 촛점을 두고 있다. IBM's developerWorks site에서 스펙에 관련된 보다 자세한 사항을 찾을 수 있다. 이 스펙은 봄에 일반 사용자에게 공개될 것이다.

샘플 코드



Dejan Bosanac은 DNS 유럽의 소프트웨어 개발자 이다


 Job : 작업을 정의할 때는 이 인터페이스를 구현하고 execute 메소드를 구현해 주어야 합니다.
- SchedulerFactory
   - StdSchedulerFactory : 클래스패스에 quartz.properties 파일을 먹고 삽니다. 프로퍼티 파일의 이름이 이와 같지 않다면, 생성자에 직접 이름을 세팅해 줍니다
- Scheduler : 스케쥴 팩토리에서 얻어 옵니다. JobDetail과 Trigger를 가지고 스케쥴을 정할 수 있습니다.
- JobDetail : 매번 Job의 객체를 만들지 않고, JobDetail을 사용합니다.
- Trigger : 작업을 언제 실행할 지 정의합니다.
   - SimpleTrigger : interval time, repeat times 등을 설정할 수 있습니다.
   - CronTrigger : Linux의 cron 하고 비슷하게, 특정 시간이 되면 발동하게 해줍니다

public static void main(String[] args) {
        try {
            SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();
            sched.start();
           
            JobDetail jobDetail = new JobDetail("ipNotify", "SLT", IpNotifier.class);
//            SimpleTrigger trigger = new SimpleTrigger("ipNotify", "SLT");
//            trigger.setRepeatInterval(1l);
//            trigger.setRepeatCount(100);
            CronTrigger trigger = new CronTrigger("ipNotify", "SLT");
            trigger.setCronExpression("* 0,30 * * * ?");
           
            sched.scheduleJob(jobDetail, trigger);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }




Quartz는 Job, Trigger라는 두 개의 기본 추상 계층을 정의한다.

- Job은 실제 실행되는 작업의 추상 계층이고,

- Trigger는 언제 작업이 실행되어야 하는지를 나타내는 추상 계층이다.



Job은 인터페이스이다. 그러므로 해야할 일은 클래스가 org.quartz.Job 인터페이스를 구현하도록 하고

execute() 메소드를 오버라이드하는 것 뿐이다



Trigger에는 SimpleTrigger 와 CronTrigger의 두 가지의 기본적인 Trigger가 있다:

- SimpleTrigger는 기본적으로 Timer API가 제공하는 것과 같은 기능을 제공한다.

  작업이 시작된 이후에 정해진 간격으로 반복해서 실행되는 경우, SimpleTrigger를 써야 한다.

  SimpleTrigger는 시작일, 종료일, 반복횟수, 실행 주기를 정의할 수 있다.

- 여기서는 더욱 사실적인 바탕에서 작업을 스케쥴링할 수 있는 유연성때문에 CronTrigger를 사용한다.

  CronTrigger를 사용함으로써 "매주 평일 오후 7시" 또는 "토요일과 일요일 매 5분마다"와 같은 스케쥴링 작업을 할 수 있다.


 



 

cron expression ( 표현 순서대로 의미)

Field Name             Allowed Values             Allowed Special Characters

Seconds                 0-59                               , - * /

Minutes                  0-59                               , - * /

Hours                     0-23                                , - * /

Day-of-month         1-31                                , - * ? / L W C

Month                    1-12 or JAN-DEC             , - * /

Day-of-Week          1-7 or SUN-SAT              , - * ? / L C #

Year (Optional)       empty, 1970-2099             , - * /


 

예)

 Expression                      Meaning
"0 0 12 * * ?"                       Fire at 12pm (noon) every day
"0 15 10 ? * *"                   Fire at 10:15am every day
"0 15 10 * * ?"                   Fire at 10:15am every day
"0 15 10 * * ? *"                   Fire at 10:15am every day
"0 15 10 * * ? 2005"             Fire at 10:15am every day during the year 2005
"0 * 14 * * ?"                       Fire every minute starting at 2pm and ending at 2:59pm, every day
"0 0/5 14 * * ?"                   Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day
"0 0/5 14,18 * * ?"             Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day

"0 0-5 14 * * ?"                   Fire every minute starting at 2pm and ending at 2:05pm, every day
"0 10,44 14 ? 3 WED"             Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.
"0 15 10 ? * MON-FRI"             Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday
"0 15 10 15 * ?"                   Fire at 10:15am on the 15th day of every month
"0 15 10 L * ?"                   Fire at 10:15am on the last day of every month
"0 15 10 ? * 6L"                   Fire at 10:15am on the last Friday of every month
"0 15 10 ? * 6L"                   Fire at 10:15am on the last Friday of every month
"0 15 10 ? * 6L 2002-2005"           Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005

"0 15 10 ? * 6#3"                   Fire at 10:15am on the third Friday of every month

구현

구현해 줘야 하는 주요 역할 클래스는
관리용 클래스(Manager역할) : 스케쥴러를 실행 해줄 클래스, Job을 등록할수 있는 인터페이스를 제공해준다.
Job생성 클래스(Factory역할) : 실제 실행할 Job을 생성해줄 빌더를 구현해 준다.
Job 클래스(Executor역할) : 실행 해야 할 목적 프로그램을 처리할 Job구현 클래스JobSchedulingDataProcessor이용시에는 Job관련 정보를 xml로 정의하여 더 유연한 작업 스케쥴 관리를 할수 있다.

SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
JobSchedulingDataProcessor xmlProcessor = new JobSchedulingDataProcessor();
xmlProcessor.processFileAndScheduleJobs("quartz-jobs.xml", scheduler, true);
scheduler.start();

quartz-jobs.xml

<?xml version="1.0" encoding="UTF-8"?>
<quartz xmlns=http://www.opensymphony.com/quartz/JobSchedulingData
 xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
 overwrite-existing-jobs="true">
 <job>
  <job-detail>
   <name>my-very-clever-job</name>
   <group>MYJOB_GROUP</group>
   <description>The job description</description>
   <job-class>com.acme.scheduler.job.CleverJob</job-class>
   <job-data-map allows-transient-data="false">
    <entry>
     <key>burger-type</key>
     <value>hotdog</value>
    </entry>
    <entry>
     <key>dressing-list</key>
     <value>ketchup,mayo</value>
    </entry>
   </job-data-map>
  </job-detail>
 
  <trigger>
   <cron>
    <name>my-trigger</name>
    <group>MYTRIGGER_GROUP</group>
    <job-name>my-very-clever-job</job-name>
    <job-group>MYJOB_GROUP</job-group>
    <!-- trigger every night at 4:30 am -->
    <!-- do not forget to light the kitchen's light -->
    <cron-expression>0 30 4 * * ?</cron-expression>
   </cron>
  </trigger>
 </job>
</quartz>

블로그 이미지

유효하지않음

,
블로그 이미지

유효하지않음

,

원본:http://blog.naver.com/caminoreal?Redirect=Log&logNo=80050941642

우리나라의 현실이 이러합니다.


매국노의 후손들은 아직도 부귀영화를 누리고~~


신채호 선생이 오죽했으면

'혼이 없는 민족'이라고 통탄하였겠는가?

을사5적인 매국노 이완용

식민사학자 이병도인 이병도의 큰아버지(백부)

현 서울대총장 이장무의 큰할아버지(백조)



식민사학자 이병도(이완용의 조카)

역사를 일본에 팔아먹은 역사 왜곡의 주범



   

                                            이장무 서울대총장

                                   (이완용의 증손자,이병도의 손자)      

       

                                                                             
                                      

                                 국립중앙박물관장 이건무

                                   (이장무의 친동생)

 

   

                                      

이영훈 서울대 경제학과장

(이완용의 동생인 이재용의 둘째 아들)

 

 ↓
                                                                   

                                        "일제위안부는 한국책임"

   "정신대 자발적 매춘"

 

 

"일제의 조선강점이 조선을 근대화시켜 주었기에,

일제의 조선지배는 오히려 축복"



그리고,

죽도록 맞았다.   

 

                                                         2006년 11월 30일           

블로그 이미지

유효하지않음

,

Collection Framework

 

만든이: 송지훈
소속: JavaCafe 부시샵
email: johnleen@hanmail.net

 

이번 강좌를 통해 자바의 자료구조인 Collection Framework 에 대해 자세하게 알아보도록 하겠다. 가장 기본적이고 중요한 부분임에도 불구하고 프로그래밍을 공부하는 많은 사람들이 소홀히 생각하는 부분이다. 이 기회에 필자의 강좌를 통해 다시 한번 자바의 컬렉션 프레임워크에 대해 깊이 있는 이해를 할 수 있었으면 한다. 이 강좌에선 1.4에 추가된 새로운 자료구조, 정확하게 2개의 Map 계열 클래스와 1개의 Set 계열 클래스, 총 3가지를 포함해서 그 이전에 존재하는 자료구조 클래스들 모두를 설명한다.

 

1. 자바의 자료구조 Collection Framework 의 구조

다음 그림은 java.util 패키지 안의 컬렉션 프레임워크의 인터페이스를 관계를 나타내는 UML Class Diagram 을 보여주고 있다.
아래 그림에서 실선으로 그려진 화살표는 상속(extend)을 의미한다. 또한 "::" 을 기준으로 왼쪽은 패키지, 오른쪽은 이름을 나타낸다. 네모 안의 보라색 동그라미 안에 i 라고 써있는 것은 해당 객체가 인터페이스라는 것을 의미한다. 또 컬렉션 인터페이스를 구현한 클래스들을 설명할 때 나오겠지만 점선으로 그려진 화살표는 구현(implements)을 의미하고 클래스이기 때문에 녹색 동그라미안에 C 라는 글자가 쓰여있는 것을 보게 될 것이다. (참고로 필자는 이클립스 플러그인으로 제공되는 OMANDO 라는 UML 툴을 사용했다)

사용자 삽입 이미지

그림에서 볼 수 있듯이 자바의 컬렉션 프레임워크는 크게 두가지로 구분된다. 바로 Collection 과 Map 이다. Collection 은 다시 Set 과 List 로 구분된다.

또한 아래 Class Diagram 에서 볼 수 있듯이 Collection 인터페이스를 구현한 클래스들과 연계해서 편리하게 저장된 요소(Element)들을 다룰 수 있는 2가지 인터페이스가 있다.

사용자 삽입 이미지

  • Note :: 요소(Element) 는 객체로 생각해도 무방하다. 그 이유는 자바의 컬렉션 프레임워크의 구성 클래스들에 저장하거나 꺼내오는 요소의 타입이 객체의 최상위 타입인 Object 이기 때문이다. 따라서 int 등의 primary type 데이터는 랩퍼(Wrapper) 클래스로 감싸서 넣어야 한다. 예를 들어, int 의 경우엔 Integer 로 감싸서 클래스로 만들어 넣는 것이다. 아래 컬렉션 계열 인터페이스들을 보면 추가-삭제 메소드들의 파라미터와 리턴값이 모두 Object 인 것을 확인할 수 있을 것이다.

  • Issue :: J2SDK1.5 에선 primary type 도 랩퍼 클래스로 감싸지 않고 자동으로 컬렉션 클래스들에 넣어도 되는 auto-boxing 기능을 제공해 줄 예정이다.(내부에서 자동으로 적절한 타입으로 변환시켜 주게 됨) 참고로 C# 에선 이미 auto-boxing 기능이 제공되고 있다. auto-boxing 에 대한 자세한 내용은 이 글의 후반부에 다루도록 하겠다.

항상 숲과 나무를 같이 볼 수 있는 시야를 갖추어야 무엇이든 제대로, 깊이 있게 이해할 수 있다. 따라서 다시 한번 위에 있는 자바의 컬렉션 프레임워크의 전체 구조를 표현한 Class Diagram 을 살펴보도록 하자. 각 인터페이스들에 대한 자세한 설명은 아래 부분에서 하도록 하겠다.

Top

2. Collection Framework 인터페이스들과 클래스들

그럼 이제 자바의 자료구조인 Collection, Set, List, Map 등은 어떤 특징에 따라 구분한 것인지를 알아보겠다. 아래의 표에 각각의 특성을 정리해놨다. 아래 표를 주의 깊게 보도록 하자.

Package

Definition

 java.util.Collection

 순서 없는 단순한 요소들의 집합

 java.util.Set

 중복을 허용하지 않는 요소들의 집합

 java.util.List

 순차적 나열, 순서지정 가능한 요소들의 집합

 java.util.Map

 Key와 Key에 대응하는 값으로 이루어진 구조

 java.util.SortedSet

 값들이 정렬된 Set

 java.util.SortedMap

 key 가 정렬된 Map

그럼 먼저 Collection 인터페이스를 구현한 어떤 클래스들이 존재하는지 Set, List 로 나눠서 살펴보자. 아래의 표는 Collection 의 구성을 표현한 표다. 표에서 인터페이스와 해당 인터페이스를 구현한 실제 클래스들를 보여주고 있다.

Interface Implementation

Collection 

 Set HashSet LinkedHashSet TreeSet
 List ArrayList LinkedList Vector Stack

아래의 표는 Map 인터페이스를 구현한 클래스들이다. 역시 인터페이스와 그 구현 클래스들을 보여주고 있다.

Interface

Implementation

Map HashMap LinkedHashMap IdentityHashMap WeakHashMap Hashtable TreeMap

이제 해당 인터페이스와 그 구현 클래스들의 특징이 어떤 것이고 어떻게 사용되는를 자세히 살펴보도록 하겠다.

Top

2-1. Collection 인터페이스

우선 먼저 Collection 인터페이스를 살펴보도록 하자.

    package java.util;

    public interface Collection {
        // Query Operations
        int size();
        boolean isEmpty();
        boolean contains(Object o);
        Iterator iterator();
        Object[] toArray();
        Object[] toArray(Object a[]);

        // Modification Operations
        boolean add(Object o);
        boolean remove(Object o);

        // Bulk Operations
        boolean containsAll(Collection c);
        boolean addAll(Collection c);
        boolean removeAll(Collection c);
        boolean retainAll(Collection c);
        void clear();

        // Comparison and hashing
        boolean equals(Object o);
        int hashCode();
    }
 

위 코드에서 주석으로 설명된 부분에서 알 수 있듯이 기능에 따라 4가지 분류의 메소드들로 나눌 수 있다. 그럼 각 기능에 따른 메소드들을 자주 사용되는 것들 위주로 간단히 살펴보도록 하겠다.

첫번째로 쿼리(Query) 오퍼레이션들을 살펴보자. 쿼리 오퍼레이션은 컬렉션 안에 저장된 요소의 개수(size() 메소드)나 저장된 요소가 있는지(isEmpty() 메소드), 컬렉션 안에 해당 메소드 안에 파라미터로 전달한 Object 요소가 들어있는지(contains(Object o) 메소드) 등의 여부를 질의하는 메소드들의 분류다. 나중에 예제 소스에서도 살펴보겠지만 iterator() 메소드는 컬렉션 안에 저장된 요소들을 Iterator 에 순차적으로 저장한 후 그 Iterator 객체를 리턴해준다.

두번째는 변경(Modification) 오퍼레이션들이다. 메소드 이름만으로도 쉽게 알 수 있듯이 하나의 요소를 컬렉션에 추가(add(Object o)), 삭제(remove(Object o)) 하는 메소드들이다.

세번째는 대량으로 요소의 변경을 가하는 오퍼레이션들이다. 여기서 상당히 간편하게 사용될 수 있는 addAll(Collection c) 메소드가 있는데 이것은 파라미터로 들어온 컬렉션 객체가 갖고 있는 요소들 모두를 저장하는 메소드이고 removeAll(Collection c) 메소드는 반대로 파라미터로 들어온 컬렉션 객체가 갖고 있는 요소들 모두를 제거한다. 이외에 clear() 메소드는 해당 컬렉션 객체의 모든 요소를 전부 제거한다.

마지막으로 비교(Comparison) 및 해싱(Hashing)을 위한 오퍼레이션들을 정의하는 메소드다. 자주 쓰이지 않으므로 별도의 언급은 하지 않겠다.

Top

2-2. Set 인터페이스와 구현 클래스들

Set 인터페이스를 살펴보자. Collection 인터페이스를 상속하므로 큰 차이점은 없고 단지 "Set" 은 중복을 허용하지 않는 자료구조였다는 것을 다시 한번 기억하도록 하자.

    package java.util;

    public interface Set extends Collection {
        // Query Operations
        int size();
        boolean isEmpty();
        boolean contains(Object o);
        Iterator iterator();
        Object[] toArray();
        Object[] toArray(Object a[]);

        // Modification Operations
        boolean add(Object o);
        boolean remove(Object o);

        // Bulk Modification Operations
        boolean containsAll(Collection c);
        boolean addAll(Collection c);
        boolean removeAll(Collection c);
        boolean retainAll(Collection c);
        void clear();

        // Comparison and hashing
        boolean equals(Object o);
        int hashCode();
    }
 

위 코드를 보면 Collection 과 동일한 메소드만을 제공해주는 것을 볼 수 있다. 단지 구현 클래스 내부에 equals(Object o) 메소드를 이용해서 중복을 허용하지 않도록 체크하는 기능이 더해져 있다.

그럼 이제부터 Set 을 구현한 클래스들을 살펴보도록 하겠다.

2-2-1. HashSet

 

2-2-2. LinkedHashSet(1.4에서 추가)

 

2-2-3. TreeSet

 

Top

2-3. List 인터페이스와 구현 클래스들

List 인터페이스는 순서 붙일 수 있는 컬렉션이다. 이 인터페이스의 사용자는 List 내의 어디에 각 요소가 삽입될까를 정밀하게 제어 할 수 있다. 사용자는 정수값의 인덱스(List 내의 위치)에 의해 요소에 액세스(access) 하거나 List 내의 요소를 검색할 수가 있다. Set 과는 다르게, 보통 일반적으로 List는 중복하는 요소를 허가한다.

    package java.util;

    public interface List extends Collection {
        // Query Operations
        int size();
        boolean isEmpty();
        boolean contains(Object o);
        Iterator iterator();
        Object[] toArray();
        Object[] toArray(Object a[]);

        // Modification Operations
        boolean add(Object o);
        boolean remove(Object o);

        // Bulk Modification Operations
        boolean containsAll(Collection c);
        boolean addAll(Collection c);
        boolean addAll(int index, Collection c);
        boolean removeAll(Collection c);
        boolean retainAll(Collection c);
        void clear();

        // Comparison and hashing
        boolean equals(Object o);
        int hashCode();

        // Positional Access Operations
        Object get(int index);
       Object set(int index, Object element);
       void add(int index, Object element);
       Object remove(int index);
        // Search Operations
        int indexOf(Object o);
       int lastIndexOf(Object o);


        // List Iterators
        ListIterator listIterator();
       ListIterator listIterator(int index);


        // View
        List subList(int fromIndex, int toIndex);
    }
 

Collection 인터페이스에서 제공해주던 메소드들에 List 인터페이스의 특징인 특정 위치의 요소를 찾거나 특정 위치에 요소를 추가하는 등의 메소드들이 추가되었다. 메소드 이름이 워낙 일관되고 명확하게 잘 지어져 있기 때문에(필자가 자바를 좋아하는 이유 중 하나) 메소드 이름만으로도 대강 어떤 역할을 하는지 짐작할 수 있을 것이다. List 인터페이스에서 추가된 메소드들은 Bold 를 주어 표현해놨다.

2-3-1. ArrayList

 

2-3-2. LinkedList

 

2-3-3. Vector

 

2-3-4. Stack

 

Top

2-4. Map 인터페이스와 구현 클래스들

Map 인터페이스는 키(key)를 값(value)에 매핑(mapping) 한다. 또한 Map은 동일한 키를 복수 등록할 수 없고 각 키는 1 개의 값밖에 매핑 할 수 없다. 즉, 하나의 키 값에 대응하는 하나의 값을 갖는 자료구조다.

    package java.util;

    public interface Map {
         // Query Operations
        int size();
        boolean isEmpty();
         boolean containsKey(Object key);
        boolean containsValue(Object value);
        Object get(Object key);

         // Modification Operations
        Object put(Object key, Object value);
        Object remove(Object key);

        // Bulk Modification Operations
        void putAll(Map t);
         void clear();

         // Views
        Set keySet();
        Collection values();
        Set entrySet();
        interface Entry {
            Object getKey();
            Object getValue();
            Object setValue(Object value);
            boolean equals(Object o);
             int hashCode();
        }

         // Comparison and hashing
        boolean equals(Object o);
        int hashCode();
    }
 

// 설명

2-4-1. HashMap

 

2-4-2. LinkedHashMap(1.4에서 추가)

 

2-4-3. IdentityHashMap(1.4에서 추가)

 

2-4-4. WeakHashMap

 

2-4-5. Hashtable

 

2-4-6. TreeMap

 

Top

2-5. Enumeration 와 Iterator 인터페이스

Collection Framework 에는 Enumeration 와 Iterator 라는 인터페이스가 있다. 사전적인 의미로는 반복, 순환이라는 뜻을 지니고 있다. 어떤 객체들의 모임이 있을 때(Collection 계열 구현 클래스들, Collection 인터페이스에 iterator() 메소드가 있었음을 기억해라) 이 객체들을 어떤 순서에 의해서 하나씩 꺼내 쓰기 위한 인터페이스라고 할 수 있다. 원래 Java 2 이전는 Enumeration 이라는 인터페이스가 많이 사용되었지만 최근에는 Iterator 인터페이스가 더 많이 사용된다. 그 이유는 각 인터페이스를 살펴보며 알아보기로 하겠다.

2-5-1. Enumeration

아래의 Enumeration 인터페이스의 코드를 보자.

    package java.util;

    public interface Enumeration {
         boolean hasMoreElements();
        Object nextElement();
    }
 

이 인터페이스는 단지 두개의 메소드만을 제공한다. 이 인터페이스의 사용은 상당히 간단하다. hasMoreElements() 메소드로 인터페이스 안에 다음 요소가 있는지를 질의한다. 만약 true 가 리턴되었다면(다음 인덱스에 요소가 있다는 의미) nextElement() 메소드로 다음 요소를 꺼내서 사용하면 되는 것이다.

java.util.StringTokenizer 클래스가 Enumeration 인터페이스를 구현하고 있다. 따라서 StringTokenizer 클래스가 제공하는 메소드들 중에서 Enumeration 에서 정의한 2개의 메소드가 제공되는 것을 볼 수 있을 것이다.

2-5-2. Iterator

아래의 코드는 Iterator 인터페이스다.

    package java.util;

    public interface Iterator {
         boolean hasNext();
        Object next();
        void remove();
    }
 

Enumeration 과의 차이점은 단지 remove() 메소드가 추가된 것 뿐이다. hasNext() 와 next() 메소드는 이름만 약간 다를 뿐 Enumeration 인터페이스의 hasMoreElements() 와 nextElement() 와 정확히 일치하는 기능을 한다.

그럼 왜 Enumeration 대신 Iterator 를 Java 2에서 추가해서 사용할까? 그것은 Enumeration 인터페이스는 집합 내에서 요소를 제거할 방법이 없기 때문이다. 그것을 보완하기 위해 나온 것이 Iterator 인터페이스다.

Top

3. J2SDK1.5 에서 추가될 auto-boxing 과 generic

 

Top

4. 자주 사용되는 컬렉션 객체들의 퍼포먼스 표

4-1. Set 객체

  동기화 설명
HashSet no 가장 빠른 집합. HashMap 보다 느리지만 Set 인터페이스를 구현하고 있다. HashMap 은 Set 이 아니라 Map 임.
TreeSet no HashSet보다 느리다. 차례대로 키를 사용할 수 있다. (키가 정렬됨)

4-2. Map 객체

  동기화 설명
HashMap no 가장 빠른 매핑.
Hashtable yes HashMap 보다 느리지만 동기화한 HashMap 보다 빠르다.
TreeMap no Hashtable 과 HashMap 보다 느리다. 차례대로 키를 사용할 수 있다. (키가 정렬됨)

4-3. List 객체

  동기화 설명
ArrayList no 가장 빠른 리스트.
LinkedList no 다른 리스트보다 느리지만 큐로 이용했을 경우 더 빠를 수도 있다. 느린 이유는 ArrayList 나 Vector, Stack 과 달리 array 계열이 아니기 때문.
Vector yes ArrayList 보다 느리지만 동기화한 ArrayList 보다 빠르다.
Stack yes Vector 와 동일한 속도. LIFO 큐 기능을 제공한다.

필자생각 :: HashMap, ArrayList 에 동기화를 걸어 사용하는것 보다 동기화된 Hashtable, Vector 를 사용하는 것이 더 빠른것으로 미루어 짐작컨데 Hashtable, Vector 경우에는 동기화가 되어 있는 내부 메소드들이 JIT 컴파일러에 의해 최적화 되는음.

Top

5. 효율적인 컬렉션 객체들의 사용

*** Vector-Hashtable vs ArrayList-HashMap ***보통 일반적으로 Vector 와 Hashtable 을 주로 사용하고 있을 것이다.
컬렉션 객체들은 모든 메소드가 synchronized 되어 있기 때문에 동시에 여러 스레드가 접근 할 수 없.

반명 동일한 기능을 하는데도 불구하고 ArrayList 와 HashMap 은 메소드가 synchronized 로 되어있지 않아서 스레드들이
해당 객체에 동시접근이 가능다.

은행에서 현금 입출금에 관련된것처럼 반드시 미션크리티컬한 로직이 필요한 곳에선
VectorHashtable을 사용하는게 바람직하고 당연하지만 필자는 초보 분들이 프로그래밍한 코드에서 멀티스레드 접근을 해도 무방한데도 불구하고 모두 Vector니면 Hashtable을 사용하는 것을 많이 봐왔다. 이건 특히나 jsp 처럼 시간을 다투는 프로그램에선 치명타. 동기화가 필요한지 아닌지를 잘 판단해서 정확히 필요한 곳에만 Vector나 Hashtable을 사용하고 그 이외의 부분에선 ArrayList HashMap 을 사용해야 할 것이다.

이미 다 아는 얘기라고 하실지도 모르겠지만 모르시는 분들이 너무 많아서 다시 한번 언급해봤다.

필자의 경우에는 효율을 좀 더 높이기 위해 동기화가 필요한 부분도 ArrayList 나 HashMap 에다가 락을 걸어서 멀티스레드의 폐해를 피해가는 방식을 사용하고 있다. 모든 경우에 이렇게 한다는 것은 아니고 예를 들어 데이터를 넣는 부분은 멀티스레드 접근이 허용되지만 데이터를 꺼낸 후 삭제해야 하는 부분은 동기화가 필요하다고 가정했을 때 동기화가 필요한 "데이터를 꺼낸 후 삭제" 하는부분에만 락을 걸어서 동기화 블럭을 최소화시켜서 좀 더 효율을 가져간다는 것이다.

데이터를
컬렉션 객체에 넣(put) 가져오고(get) 삭제하는(remove) 등의 모든 부분에 동기화가 필요하다면 당연히 그냥 이미 그런 용도로 만들어진 VectorHashtable 을 사용하는 것이 편하 또 이렇게 사용 하는 것이 ArrayList HashMap 의 모든 메소드에 락을 걸어 사용하는 것보다 더 빠르다.

Top

6. 아쉬움을 남긴채 강좌를 마무리하며

자바의 컬렉션 프레임워크는 매년 자바의 가장 훌륭한 라이브러리로 선정되는 파트다. 그만큼 설계적인 측면에서나 구현적인 측면에서 배울 것이 많은 부분이다. 필자는 지금까지 바로 이 자바의 컬렉션 프레임워크에 대해 설명을 했다. 하지만 아쉬움이 남는다. 그 이유는 필자가 "물고기 잡는 법"을 가르쳐 준 것이 아니라 물고기를 잡아서 준 것이기 때문이다. 즉, 어떻게 이런 자료구조(구현 클래스들)를 만들지에 대한 강좌가 아니라 단순히 만들어진 자료구조를 어떻게 이용하는지에 초점을 맞춰서 설명했다는 것이다.

필자가 독자분들에게 한가지 당부를 한다면 이미 만들어져 있는 api 를 단순히 이용하기 보다는 직접 만들어서 사용할 수 있는 능력을 키우라는 것이다. 그게 진정 창조적인 그리고 프로페셔널한 개발자가 되기 위한 길이라고 필자는 생각한다. 따라서 우선 독자분들은 컬렉션 프레임워크의 구현 클래스들이 어떻게 만들어졌는지를 직접 J2SDK 폴더 안의 src.zip 파일의 압축을 풀어서 분석해봤으면 한다. 그리고 나름대로 직접 그런 자료구조를 구현하기 위한 방법들도 생각해보고 가능하다면 직접 구현해보았으면 한다. 그럼 이제 결코 짧지 않았던 컬렉션 프레임워크 강좌를 마무리하겠다.

블로그 이미지

유효하지않음

,




1. 서강대 경영학과 이윤재 여학생 동영상 

(네티즌이 부르는 별칭: 서강대녀, 서강녀, '촛불시위를 반대'하는 이세진씨의 1인 시위에 동참하는 네이버 카페 회원)
 
"강기갑 의원님은 국회의원인데 18대 국회가 문을 닫고 장외투쟁을 하는 것이 국익에 발전이 되는가?"
 
"저희 카페는 광우병에 대한 회원의 의견이 분분하지만 카페회원은 순수하게 불법시위를 반대하고 있다."
 
"법을 제정하는 국회의원께서 불법, 합법을 구분 못하고 있는 것 같다. 어떻게 생각하느냐?"
 
"촛불문화제는 동의한다. 하지만 집회법을 어겼기 때문에 법치국가에서는 법을 지키면서 표현의 자유를 해야 한다."
 
"저는 중도진보 또는 중도성향을 가지고 있다. 강부자, 고소영 내각에 대해서는 반대한다. 저희 카페는 6월 2일에 한 대학생분이 카페를 개설하셔서 1주일만에 1만5천명의 회원이 가입했습니다. 엄청난 이슈인데 중요한 것은 회원의 연령, 계층, 이념을 떠나서 불법시위라 규정하는 틀안에서 다양한 의견을 내고 있습니다. 회원들이 쇠고기, 대운하 문제에 대해서는 의견이 다 다르다."
 
"새로운 생각으로 카페에 와보셨으면 좋겠. 스펙트럼이 다양한 의견을 나누고 있기 때문에 이념을 떠나서 정치인이 방문해 주셨으면 좋겠다."




2. 고려대 사회학과 김지윤 여학생 동영상
 
(네티진이 부르는 별칭: 고려대녀, 고대녀, 고대교수를 감금한 출교생으로 운동권 출신)
 
"전면 재협상 요구한다. 30개월 미만된 소에서도 광우병 발생은 과학적 통계이다."
"OIE에서도 광우병 발생률 통계는 24개월 이상된 소부터 시작된다."
"즉 30개월 미만된 소에서도 광우병이 발생하기 때문이다."  
 
" 협상결과에 따르면 30개월 미만의 소에서도 위험물질 SRM을 다 수입한다."
"연령제한 뿐만 아니라 검역주권문제가 걸려있다. SRM이 들어오느냐 마느냐가 중요한 문제이다."
 
"미국소 20%만이 이력 추적이 가능하다. 치아감별법은 미국교과서에도 불완전하다고 나왔다."
 
"정부는 자율규제만 얘기하는데 예전에 살코기만 수입했을 때 지난 한해에 5번이나 고시(법)를 위반하는 뼈조각이 들어왔다. 법으로 명문화해도 어기는 판국에 자율규제는 속편한 생각이다."
 
"일본은 OIE 보다 강화된 조치로 미국에서 쇠고기를 수입한다."
 
"미국은 자기들의 이익을 위해서라면 국회 비준되고 있는 페루와의 FTA 협정에서도 재협상을 요구했다. 왜 우리나라는 그렇게 못하나?"
 
'서강대 여학생'과 '고려대 여학생'에 대한 여러분의 의견은 어떠신가요? 
블로그 이미지

유효하지않음

,