정확히는 Azul사의 문제는 아니지만 상용 제품인 Zing이 있어 먼저 나에게 타겟이 된 것뿐입니다.

좀 더 정확히는 OpenJDK를 기반으로 하는 또는 TCK(Java Technology Certification Kit)를 통과한 

벤더사별 JDK가 문제가 있는 것 같습니다.

회사 업무상 최신 JDK를 사용할 수 없으나 현재 테스트한(게시한 날짜 기준) 버전은(1.8, 11, 12) 

Linux, Windows O/S상관없이 전부 문제가 있었습니다.

Zulu JDK를 테스트 한 이유는 당연히 Oracle JDK 요료화 때문이지요.


테스트에 문제가 있었던 내용은 바로 DecimalFormat, NumberFormat 클래스등의 RoundingMode 반올림(HALF_UP)에 
심각한 버그입니다.

(부동소숫점 문제를 떠나 같은 소스코드 기준으로 Oracle JDK(1.7)와 Oracle/OpenJDK(1.8)를 기반으로 하는 벤더사별 JDK의 결과 값이 맞지 않는다가 문제입니다. )

Azul 쪽에 메일도 보냈는데 1개월이 지나도 답이 없고 개발 커뮤니티 관련 내용 게시도 했는데 별다른 답변은 없는 것 같습니다.

혹 OpenJDK를 기반으로 하는 각 벤더사별 JDK를 Oracle JDK 대안으로 생각하고 현업에 적용하실 계획이 있는 분들은 

좀 더 지켜보시거나 많은 테스트를 해보신 후 적용해하시기 바랍니다.(일반 커뮤니티는 문제가 없겠지만.. 금융, 평가, 분석 등은 문제가...)



** 테스트 JDK **
Zulu JDK, Zing JDK, AdoptOpenJDK, Amazon Corretto, RedHat JDK, OpenJDK

 

java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)



문제가 되는 아주 간단한 테스트 코드(1.8이상의 JDK에서 문제가 발생하네요. ORACLE JDK도 문제고요)

import java.math.RoundingMode;
import java.text.DecimalFormat;

public class RoundTest
{
    public static void main(String[] args)
    {
        double rnd;

        System.out.println("** DecimalFormat");
        DecimalFormat df = new DecimalFormat("#,###.00");
        df.setRoundingMode(RoundingMode.HALF_UP);
        df.setMaximumFractionDigits(2);

        rnd = 212399.535d;
        System.out.println(df.format(rnd));

        rnd = 112399.405d;
        System.out.println(df.format(rnd));
    }
}

## 결과값 (Oracle JDK)
   ** DecimalFormat
   212,399.54
   112,399.41

## 결과값 (Azul Zulu JDK)
   ** DecimalFormat
   212,399.54
   112,399.40

 

 

https://docs.oracle.com/javase/6/docs/api/java/math/RoundingMode.html

https://devdocs.io/openjdk~8/java/math/rounding
HALF_UP API문서를 보면 문제는 없어 보입니다.

 

만약 이런 문제를 안고라도 OpenJDK를 사용하시겠다면
우선 BigDecimal 변경해서 처리해야 될 것 같습니다.

 

BigDecimal bd = BigDecimal.valueOf(rnd);
bd.setScale(2, RoundingMode.HALF_UP);
System.out.println(df.format(bd));  --> 112,399.41

 

 

Azul사에 한번더 문의하니 바로 답변 해주시네요.

https://bugs.openjdk.java.net/browse/JDK-7131459

블로그 이미지

유효하지않음

,


Java에서 제목과 같은 클래스는 숫자 포맷을 처리할 때 사용하는 클래스입니다.
잘못 사용하는 케이스가 있어 관련 내용 정리합니다.

DecimalFormat/NumberFormat 포맷 클래스는 별도로 RoundingMode를 설정하지 않을 시
디폴트 RoundingMode는 ROUND_HALF_EVEN 사용합니다.
현재 특정 함수중 numberFormat/numFormat 등이 DecimalFormat 클래스를
사용하고 있습니다. 별도의 RoundingMode를 적용하지 않아 디폴트인 ROUND_HALF_EVEN 사용하니
반올림 숫자 대상의 앞자리 숫자가 홀수냐 짝수냐에 따라 반올림이 되고 되지않는 현상이 발생합니다.
  

  예)
   DecimalFormat df = new DecimalFormat("#,###.00"); 
   double rnd = 112399.405d; 
   System.out.println(df.format(rnd));  --> 112,399.40 

   df.setMaximumFractionDigits(2); 
   df.setRoundingMode(RoundingMode.HALF_UP); 
   System.out.println(df.format(rnd));  --> 112,399.41 


  주의) 테스트 해본 결과 현재 Oracle JDK의 경우 정상이나 Zulu JDK(OpenJDK)의 경우는 문제가 있음
        OpenJDK를 사용하여 배포하는 JDK경우는 BigDecimal 클래스를 사용하여 처리해야 될것 같습니다.
        (테스트 JDK : Zulu JDK, Zing JDK, AdoptOpenJDK, Amazon Corretto, RedHat JDK)

 

 

    double rnd = 112399.405d; 

    DecimalFormat df = new DecimalFormat("#,###.00"); 
    df.setRoundingMode(RoundingMode.HALF_UP); 
    df.setMaximumFractionDigits(2); 

    System.out.println(df.format(rnd)); --> 112,399.40 

    BigDecimal bd = new BigDecimal(rnd); 
    BigDecimal bd = BigDecimal.valueOf(rnd); 
    bd.setScale(2, RoundingMode.HALF_UP); 

    System.out.println(df.format(bd));  --> 112,399.41 

 



** 참고(Java외에 다른 언어 또는 DBMS 등) **

   ROUND_UP                   : 무조건 올림(0을 제외한 값)
   ROUND_DOWN            : 무조건 내림
   ROUND_FLOOR            : 무조건 내림 (음수일 경우에 무조건 올림)
   ROUND_CEILING          : 양수인 경우 올림(ROUND_UP), 음수인 경우 내림(ROUND_DOWN)
   ROUND_HALF_UP        : 반올림 ( >= 5보다 크거나 같으면 올림/ROUND_UP, 아니면 ROUND_DOWN)
   ROUND_HALF_DOWN : 반올림 ( >  5보다 크면 올림/ROUND_UP, 아니면 ROUND_DOWN) 
   ROUND_HALF_EVEN   : 반올림 (버릴 부분의 왼쪽 숫자가 홀수인 경우 ROUND_HALF_UP, 짝수인 경우 ROUND_HALF_DOWN)

 

참고 : https://www.ibm.com/support/knowledgecenter/ko/SSMKHH_10.0.0/com.ibm.etools.mft.doc/ak05380_.htm
        https://docs.oracle.com/javase/6/docs/api/java/math/RoundingMode.html

        https://okky.kr/article/595961

블로그 이미지

유효하지않음

,

OKJSP에 회원님께서 MyBatis 인터셉터 관련 내용을 문의했었는데 답변단 내용에


관련 글을 공유코자 올려봅니다.


자체적으로 인터셉터 된다는걸 알고 있지는 않았었는데 질문이 올라와 테스트겸 해 봤는데 잘되네요..ㅎㅎ


뭐든간에 매뉴얼 정독이 필요한 부분이긴 하나 그럴 시간이 부족한 현실이 조금 아쉽네요..ㅋㅋ



     <plugins>
        <plugin interceptor="handler.QueryInterceptor"/>
        <plugin interceptor="handler.UpdateInterceptor"/>
    </plugins>



package handler;


import java.util.Properties;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;


@Intercepts
(
    {
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
       ,@Signature(type = Executor.class, method = "query",  args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
       ,@Signature(type = Executor.class, method = "query",  args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
    }
)
public class QueryInterceptor implements Interceptor
{
    private Logger log = LogManager.getLogger(getClass());
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable
    {
        Object[]        args     = invocation.getArgs();
        MappedStatement ms       = (MappedStatement)args[0];
        Object          param    = (Object)args[1];
        BoundSql        boundSql = ms.getBoundSql(param);
        
        System.out.println("====================================");
        System.out.println(invocation.getMethod().getName());
        System.out.println("====================================");
        System.out.println(ms.getId());
        System.out.println("====================================");
        System.out.println(boundSql.getSql());
        System.out.println("====================================");
        System.out.println(param);
        System.out.println("====================================");
        
        log.debug(ms.getId());
       
        return invocation.proceed();
    }


    @Override
    public Object plugin(Object target)
    {
        return Plugin.wrap(target, this);
    }


    @Override
    public void setProperties(Properties properties)
    {
    }
}



package handler;


import java.sql.Statement;
import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;


@Intercepts
(
     {
          @Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
         ,@Signature(type = StatementHandler.class, method = "query",  args = {Statement.class, ResultHandler.class}) 
     }
)
public class UpdateInterceptor implements Interceptor
{
    private Logger log = LogManager.getLogger(getClass());
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable
    {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        
        //쿼리문
        String sql = handler.getBoundSql().getSql();

        //매핑 자료
        String param = handler.getParameterHandler().getParameterObject() != null ? handler.getParameterHandler().getParameterObject().toString() : "";

        System.out.println("--------------------------------");
        System.out.println(sql);
        System.out.println(param);
        System.out.println("--------------------------------");
        System.out.println(invocation.getMethod().getName());
        System.out.println(invocation.getMethod().getAnnotations());
        System.out.println("--------------------------------");
        
        log.debug(sql);
        log.debug(param);
       
        return invocation.proceed();
    }


    @Override
    public Object plugin(Object target)
    {
        return Plugin.wrap(target, this);
    }


    @Override
    public void setProperties(Properties properties)
    {
    }
}


블로그 이미지

유효하지않음

,
var hwpCtrl	= document.frm.HwpCtrl;
var uploadCtrl	= document.frm.UploadCtrl;


var ActiveXDetector = 
{
	//ACTIVEX_OBJECT_NAME: "NextGuideHwpBridge.NextGuideHwpBridgeX",
	getActiveX: function(ProgId)
	{
		try
		{ 
			var obj = new ActiveXObject(ProgId);
			if(obj) return obj;
			else ret = null;
		}
		catch(e)
		{ 
			ret = null;
		}
		
		return ret; 
	}
}

if(ActiveXDetector.getActiveX('NextGuideHwpBridge.NextGuideHwpBridgeX'))
{ 
	alert("ActiveX 설치"); 
}
else
{
	alert("ActiveX 설치 안됨"); 
}


function ActiveXInstalled(ProgId)
{
	var isInstall = false;

	try
	{
		var obj = new ActiveXObject(ProgId);
		if (obj) return true;
	}
	catch (e)
	{
		return false;
	}
	return false;
}

if(ActiveXInstalled('NextGuideHwpBridge.NextGuideHwpBridgeX'))
{ 
	alert("ActiveX 설치"); 
}
else
{
	alert("ActiveX 설치 안됨"); 
}


var bnResult = (typeof(uploadCtrl.GetDOM) == "undefined")? true : false;
//var bnResult   = (typeof(uploadCtrl.GetDOM) == "unknown")? true : false;

if(bnResult)
{ 
	alert("ActiveX 설치"); 
}
else
{
	alert("ActiveX 설치 안됨"); 
}
블로그 이미지

유효하지않음

,

 Bouncy Castle

The Bouncy Castle and unlimited strength JCE policy files add the encryption algorithms required for Lawson Single Sign-on.  During the Lawson installation, you will select the algorithms you want to use. However, not all algorithms are compatible with all versions of JDK. If you select an incompatible algorithm during the installation, an error message will prompt you to choose a different algorithm.  Some customers may want to use a specific algorithm. After completing this section, Lawson recommends that you use the regression tests provided by Bouncy Castle to verify that your algorithm is compatible with your JDK before you begin the installation.  

IMPORTANT Bouncy Castle is added to all JDKs used by the Lawson system before you install any Lawson products. Repeat the steps below for all JDKs consumed by the Lawson system, for example:

TheJDKdefined as JAVA_HOME and LAW_JAVA_HOME on the Lawson server.
• The JDK delivered with and used by the application server.
• Any JDKs consumed by other Lawson products that use Lawson Single Sign-on via the Distributed SSO solution (for example, Lawson Business Intelligence (LBI) applications such as Smart Notification and Framework Services).

 

Download Bouncy Castle files

 1. Log in as root.

 2. Download the following files from the Bouncy Castle download site http://www.bouncycastle.org/latest_releases.html.

• If you are using Java SDK 1.4.2, use Bouncy Castle 1.4 files.
• If you are using Java SDK 5, use Bouncy Castle 1.5 files.

File

Purpose

Bcprov-jdk1X-XXX.jar

Deliver algorithms to the JDK.

Bctest-jdk1X-XXX.jar

Contain regression tests used to verify which algorithms are compatible with your JDK.  Download these files if you want to verify algorithms before installing Lawson.

 3. Download the unlimited strength JCE policy files for the JDK(s) you are using:

JDK

Platform

Policy files to use

Where to find files

Lawson JDK (LAW_JAVA_HOME)

AIX

IBM

https://www6.software.ibm.com/dl/jcesdk/jcesdk-p

Lawson JDK (LAW_JAVA_HOME)

HP

 

Solaris

Sun

For JDK 1.4.2, use the “Other Downloads” link at http://java.sun.com/j2se/1.4.2/download.html

 

For JDK 5, use the “Other Downloads” link at http://java.sun.com/javase/downloads/index_jdk5.jsp

WebSphere JDK

HP

Solaris

For WebSphere 6.0.2.x, use Sun

 

Dor WebSphere 6.0.1.x, use IBM

For WebSphere 6.0.2.x, use the “Other Downloads” link at http://java.sun.com/j2se/1.4.2/download.html

 

For WebSPhere 6.1, use the IBM policy files at https://www6.software.ibm.com/dl/jcesdk/jcesdk-p

WebSphere JDK

AIX

IBM

https;//www6.software.ibm.com/dl/jcesdk/jcesdk-p

 

Note that the IBM policy files are the same for JDK 1.4 and JDK 1.5

   

Install Bouncy Castle

 1. Create a temporary directory to hold the Bouncy Castle regression test .jar file.

mkdir BCTestDir

 2. Copy the regression .jar into the temporary directory.

cp bctest-jdk1X-XXX.jar BCTestDir

 3. Copy the bcprov-jdk1X-XXX.jar to the following location(s).

Configuration

Location

All installations

$JAVA_HOME/jre/lib/ext

WebSphere installations

$WAS_HOME/java/jre/lib/ext

 4. Verify that the file permissions on the Bouncy Castle .jar files allow read and execute access for "owner", "group", and "other".  If they do not, change permissions on the files.

chmod 555 bcprov-jdk1X-XXX.jar
                
chmod 555 bctest-jdk1X-XXX.jar

where X-XXX is the version number of the .jar file

 NOTE The Bouncy Castle .jar file must be owned by the user who runs the servlet container. During the installation process, that user is always root. Once the Lawson installation is complete, you can change the ownership of the file.

 Configure Java Encryption

 1. Extract local_policy.jar and US_export_policy.jar from the unlimited strength policy files and copy them into the following location(s). 

Configuration

Location

All installations

$JAVA_HOME/jre/lib/security

WebSphere installations

$WAS_HOME/java/jre/lib/security

 

2. Verify that the file permissions on the $JAVA_HOME/jre/lib/security/java.security file allow write access for "owner", "group", and "other".  If they do not, change permissions on the file.

chmod 777 java.security

 3. Use a text editor to open the$JAVA_HOME/jre/lib/security/java.security file. Add the following line.

security.provider.ProviderNumber=org.bouncycastle.jce.provider.BouncyCastleProvider
where ProviderNumber is the number that reflects the position where the line appears in the file.

You must change the ProviderNumber for each security.provider that appears after the Bouncy Castle line.

• For Sun JDK, the security.provider line for Bouncy Castle must come AFTER the sun.security.provider.Sun line.

 

Sun JDK Example
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.net.ssl.internal.ssl.Provider
security.provider.3=com.sun.rsajca.Provider
security.provider.4=org.bouncycastle.jce.provider.BouncyCastleProvider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider

• For IBM JDK, the security.provider line for Bouncy Castle must come AFTER the com.ibm.crypto.provider.IBMJCE line.

IBM JDK Example
security.provider.1=com.ibm.crypto.provider.IBMJCE
security.provider.2=com.ibm.jsse.IBMJSSEProvider
security.provider.3=com.ibm.security.jgss.IBMJGSSProvider
security.provider.4=org.bouncycastle.jce.provider.BouncyCastleProvider
security.provider.5=com.ibm.security.cert.IBMCertPath
security.provider.6=com.ibm.crypto.pkcs11.provider.IBMPKCS11

 

4. Change file permissions on the java.security file to read/execute access for "owner", "group", and "other".

chmod 555 java.security

 

5. Repeat these steps for all JDKs used by your system, including the following:

• The WebSphere JDK located in WAS_HOME/java/jre/lib/security

JDKs on machines running applications that connect to Lawson using the Distributed Single Sign-on Solution (DSSO).

 

6. Run the Bouncy Castle algorithm verification tests.

 

Example: 

cd /tmp/dwnlds/bc
Mkdir bctest
cprp t* bctest/
cp -rp bcprov* $JAVA_HOME/jre/lib/ext
cp -rp bcprov* $WAS_HOME/java/jre/lib/ext
chmod 555 $JAVA_HOME/jre/lib/ext/bcprov*
chmod 555 $WAS_HOME/java/jre/lib/ext/bcprov*
chmod 555 /tmp/dwnlds/bc/bctest/bctest*

 

BACK_UP

cd /tmp/dwnlds/bc/
mkdir javabkup
mkdir wasbkup
cp -p $JAVA_HOME/jre/lib/security/* javabkup
cp -p $WAS_HOME/java/jre/lib/security/* wasbkup

jar -xvf unrestrict142.zip
cp -p local_policy.jar $JAVA_HOME/jre/lib/security/
cp -p US_export_policy.jar $JAVA_HOME/jre/lib/security/
cp -p local_policy.jar $WAS_HOME/java/jre/lib/security/
cp -p US_export_policy.jar $WAS_HOME/java/jre/lib/security/

 cd /etc/java5/jre/lib/security
chmod 777 java.security

 EDIT:

vi  $JAVA_HOME/jre/lib/security/java.security

security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProvider

 vi $WAS_HOME/java/jre/lib/security/java.security

security.provider.9=org.bouncycastle.jce.provider.BouncyCastleProvider

 $JAVA_HOME/bin/java -cp bctest-jdk15-137.jar org.bouncycastle.crypto.test.RegressionTest > java.crypto.out 2>&1

 $JAVA_HOME/bin/java -cp bctest-jdk15-137.jar org.bouncycastle.jce.provider.test.RegressionTest > java.jcecrypto.out 2>&1

 $WAS_HOME/java/bin/java -cp bctest-jdk15-137.jar

org.bouncycastle.crypto.test.RegressionTest > was.crypto.out 2>&1

 $WAS_HOME/java/bin/java -cp bctest-jdk15-137.jar org.bouncycastle.jce.provider.test.RegressionTest > was.jcecrypto.out 2>&1

블로그 이미지

유효하지않음

,
[출처] http://www.ibm.com/developerworks/kr/library/os-ecbug/

난이도 : 중급

Chris Aniszczyk, Software Engineer, IBM 
Pawel Leszek, Independent Software Consultant, Freelance

2007 년 6 월 05 일

소프트웨어 프로젝트 디버깅에 Eclipse 플랫폼에 내장된 디버깅 기능을 사용하는 방법을 배워봅시다. 디버깅은 프로그래머들에게는 피할 수 없는 문제입니다. 많은 수행 방법들이 있겠지만 궁극적으로 버그를 일으킨 코드를 찾는 것이 중요합니다. 예를 들어, 리눅스® 애플리케이션에서 가장 일반적인 에러는 세그멘테이션 오류(segmentation fault)입니다. 프로그램이 할당되지 않은 메모리에 액세스를 시도할 때 세그멘테이션 위반으로 인해 종료됩니다. 이러한 유형의 에러를 픽스 하려면 그러한 작동을 일으킨 코드 라인을 찾아야 합니다. 문제의 코드 라인을 찾았다면, 에러가 발생한 정황, 제휴 값, 변수, 메소드에 대해 아는 것도 도움이 됩니다. 디버거를 사용하면 이러한 정보를 매우 간단하게 찾을 수 있습니다.

편집자 주: 본 기술자료는 2003년 5월 Pawel Leszek이 집필하였고, 2007년 4월에 Chris Aniszczyk가 업데이트 하였습니다.

Eclipse 디버거와 Debug 뷰

Eclipse SDK -- 특히, Java™ Development Tools (JDT) 프로젝트-는 단계별 실행을 수행하는 기능, 중단점과 값을 설정하는 기능, 변수와 값을 검사하는 기능, 쓰레드를 중지하고 재시작 하는 기능을 포함하여 표준의 모든 디버깅 기능들을 제공하는 빌트인 자바 디버거들로 구성되어 있다. 게다가, 원격 머신에서 실행되는 애플리케이션들을 디버깅 할 수도 있다. Eclipse 플랫폼은 다른 프로그래밍 언어들도 각각의 언어 런타임에 이 디버그 장치들을 사용할 수 있다는 강점을 갖고 있다. 앞으로 알게 되겠지만, 같은 Eclipse Debug 뷰 역시 C/C++ 프로그래밍 언어에도 적용될 수 있다.

Eclipse Platform Workbench와 툴은 JDT 컴포넌트에 구현되어 다음과 같은 기능들을 Eclipse에 제공한다.

  • 프로젝트 관리 툴
  • 퍼스펙티브와 뷰
  • 빌더, 에디터, 검색, 빌드 함수
  • 디버거

Eclipse 디버거는 그 자체로 표준 플러그인 세트이다. Eclipse에는 또한 특별한 Debug 뷰가 있어서, 이 뷰를 통해서 Workbench에서 프로그램의 디버깅 및 실행을 관리할 수 있다. 디버깅 하고 있는 각 대상에 대해 중지된 쓰레드에 대한 스택 프레임을 디스플레이 한다. 프로그램의 각 쓰레드는 트리에 있는 노드로서 나타나고, Debug 뷰는 여러분이 실행하고 있는 각 대상의 프로세스를 디스플레이 한다. 쓰레드가 중지되면, 스택 프레임은 자식 엘리먼트로서 보여진다.

Eclipse 디버거를 사용하기 전에 Java SDK/JRE (Java VM V1.4를 권장한다.) 와 Eclipse Platform SDK V3.3이 설치되고, 이것들이 아무런 문제 없이 실행되고 있는 것으로 간주하겠다. 일반적으로, Eclipse 샘플을 사용하여 디버깅 옵션을 테스트 하는 것도 좋은 생각이다. C/C++ 프로젝트를 개발 및 디버그 한다면, C/C++ Development Tools (CDT)를 설치해야 한다. Java SDK/JRE, Eclipse 플랫폼과 샘플, CDT에 대한 링크는 참고자료 섹션을 참조하라. 그림 1은 Debug 퍼스펙티브의 모습이다.


그림 1. Eclipse Debug 퍼스펙티브의 뷰
 


자바 프로그램 디버깅

프로젝트를 디버깅 하기 전에, 코드가 깨끗하게 컴파일 및 실행되어야 한다. 애플리케이션에 맞게 실행 설정을 해야 하고 이것이 올바르게 시작되는지를 확인해야 한다. Run > Debug 메뉴를 사용하여 디버그 설정을 한다. 디버거에 의해서 메인 자바 클래스로서 사용될 클래스를 선택해야 한다. (그림 2) 단일 프로젝트에 원하는 만큼의 디버그 설정을 가질 수 있다. (Run > Debug에서) 디버거가 시작될 때, 새로운 창에서 열리며 이제 디버깅을 시작할 준비가 된 것이다.


그림 2. 디버거 설정 시 프로젝트의 메인 자바 클래스 설정하기
 


이제는 Eclipse에서의 일반적인 디버깅 방법들을 설명하도록 하겠다.

중단점 설정하기

디버깅을 위해 애플리케이션을 시작할 때, Eclipse는 자동으로 Debug 퍼스펙티브로 전환한다. 가장 일반적인 디버깅 절차는 조건 문 또는 루프 내에 변수와 값을 검사할 수 있는 중단점을 설정하는 것이다. Java 퍼스펙티브의 Package Explorer 뷰에서 중단점을 설정하려면, 선택된 소스 코드를 더블 클릭하여 에디터에서 연다. 코드를 검사하고 마커 바(에디터 영역의 왼쪽 가장자리)에 있는 커서를 중지된 코드가 있는 라인에 둔다. 더블 클릭하여 중단점을 설정한다.


그림 3. 에디터의 왼쪽 부분에 있는 두 개의 중단점 마커 
 

이제 Run > Debug 메뉴에서 디버깅 세션을 시작한다. 한 라인에 여러 문을 놓지 않는 것이 중요하다. 같은 라인에 있는 한 개 이상의 문에 라인 중단점을 뛰어넘거나 설정할 수 없기 때문이다.


그림 4. 왼쪽 여백에 화살표로 현재 실행되고 있는 것을 보여주고 있다. 
 

모든 중단점들을 관리하는 편리한 Breakpoints 뷰도 있다.


그림 5. Breakpoints 뷰
 


조건적 중단점

에러가 어디에서 발생했는지 알았다면 프로그램이 충돌하기 전에 올바르게 수행되었는지를 확인해야 한다. 한 가지 방법은 문제 지점에 다다를 때까지 한번에 하나씩 프로그램의 모든 문을 검사하는 것이다. 가끔은 코드의 한 섹션을 실행하고 그 지점에서 실행을 멈춰서 그 위치에 있는 데이터를 검사하는 것이 더 나을 때가 있다. 식의 값이 변할 때마다 실행되는 조건적 중단점을 선언할 수도 있다. (그림 6) 게다가, 조건 식을 타이핑할 때 Code Assist를 사용할 수 있다.


그림 6. 조건적 중단점 트리거 설정하기
 


식 계산하기

Debug 퍼스펙티브에 있는 에디터에서 식을 계산하려면, 중단점이 설정된 전체 라인을 선택하고 콘텍스트 메뉴에서 Ctrl+Shift+I 또는 관심 있는 변수를 오른쪽 클릭하여 Inspect 옵션을 선택한다. 식은 현재 스택 프레임 컨텍스트에서 계산되고 결과는 Display 윈도우의 Expressions 뷰에 디스플레이 된다.


그림 7. Inspect 옵션을 이용하여 식 계산하기 
 

활성 코드 스크랩북킹(Scrapbooking)

Display 뷰에서는 스크랩북 형태로 활성 코드를 조작할 수 있다. (그림 8) 변수를 처리하려면 Display 뷰에 변수 이름을 타이핑 하면 익숙한 Content Assist가 나타난다.


그림 8. Display 뷰
 


디버거가 중단점에서 멈추면 Debug 뷰 툴바에서 Step Over 옵션을 선택하여 디버거 세션을 계속 진행할 수 있다. (그림 9) 이것은 하이라이트 된 코드 라인을 건너뛰고 같은 메소드의 다음 라인에서 실행을 계속한다.(또는 현재 메소드가 호출되었던 메소드에서 실행을 계속한다.) 마지막 단계의 결과로 변경된 변수들은 색깔로 하이라이트 된다. (기본 색은 노란색이다.) 색상은 디버그 프레퍼런스 페이지에서 변경할 수 있다.


그림 9. 색상을 변경하는 변수
 


Debug 뷰에서 쓰레드의 실행을 중지하기 위해, 실행 쓰레드를 선택하고 Debug 뷰 툴바에서 Suspend를 클릭한다. 이 쓰레드에 대한 현재 호출 스택이 디스플레이 되고 현재 실행 라인이 Debug 퍼스펙티브의 에디터에서 하이라이트 된다. 쓰레드가 중지되면 커서는 Java 에디터의 변수 위에 놓이고 그 변수의 작은 창으로 디스플레이 된다. 또한, 이 쓰레드의 상단 스택 프레임이 자동으로 선택되고 스택 프레임에 있는 변수들이 Variables 뷰에 디스플레이 된다. 이름을 클릭하여 Variables 뷰에 있는 해당 변수들을 검사할 수 있다.

Hotswap Bug Fixing: 코드 픽스

Java Virtual Machine (JVM) V1.4 또는 이후 버전을 실행한다면, Eclipse는 Hotswap Bug Fixing (JVM V1.3 이하 버전에서는 실행되지 않음)이라고 하는 기능을 지원한다. 디버거 세션 중에 소스 코드를 변경할 수 있는데 이는 애플리케이션을 종료해서 코드를 수정하고 재컴파일 한 다음 또 다른 디버깅 세션을 시작하는 것 보다 낫다. 이 기능을 사용하려면 에디터에서 코드를 수정하고 디버깅을 재시작 한다. 이 기능은 JVM V1.4가 Java Platform Debugger Architecture (JPDA)와 호환되면서 사용할 수 있게 되었다. JPDA는 실행 애플리케이션에서 수정된 코드를 대체하는 기능을 구현한다. 물론 이것은 애플리케이션을 시작하거나 오류가 난 지점으로 가는데 오랜 시간이 걸릴 경우에 특히 유용하다.

디버깅을 끝냈음에도 프로그램이 완전하게 실행되지 않는다면 Debug 뷰의 콘텍스트 메뉴에서 Terminate 옵션을 선택한다. 디버거 세션에 있는 동안 Resume 대신 Debug 또는 Run을 사용하는 실수를 흔히 저지른다. 이것은 현재 세션을 지속하는 것이 아닌 또 다른 디버거 세션을 시작한다.




위로


원격 디버깅

Eclipse 디버거는 원격 애플리케이션을 디버깅 하는데도 재미있는 옵션을 제공한다. 자바 애플리케이션을 실행하는 원격 VM으로 연결하여 애플리케이션에 부착할 수 있다. 원격 디버깅 세션에서 실행하는 것은 로컬 디버깅과 비슷하다. 하지만, 원격 디버깅 설정에는 Run > Debug 윈도우에서 다른 설정을 해야 한다. 왼쪽 뷰에서 Remote Java Application 항목을 선택한 다음 New를 클릭한다. 새로운 원격 시작 설정이 이루어지면 세 개의 탭(Connect, Source, Common)이 보인다.

Connect 탭의 Project 필드에서 (소스 코드 검색용) 시작 참조용으로 사용할 프로젝트를 선택한다. Connect 탭의 Host 필드에서, 자바 프로그램이 실행되는 원격 호스트의 IP 주소나 도메인 이름을 입력한다. Connect 탭의 Port 필드에서, 원격 VM이 연결을 수락하는 포트를 입력한다. 일반적으로, 이 포트는 원격 VM이 시작될 때 지정된다. Terminate 명령어를 원격 세션에서 사용할 수 있는지 여부를 디버거가 결정하도록 하려면 Allow termination of remote VM 옵션을 선택한다. 연결된 VM을 종료하고 싶다면 이 옵션을 선택한다. 이제 여러분이 Debug 옵션을 선택하면, 디버거는 지정된 주소와 포트에 있는 원격 VM으로 연결하고 결과가 Debug 뷰에 디스플레이 된다.

런처(launcher)가 지정된 주소에 있는 VM에 연결되지 않으면 에러 메시지가 나타난다. 일반적으로, 원격 디버깅 기능의 가용성은 원격 호스트에서 실행되는 Java VM에 달려있다.


그림 10. 원격 디버깅 세션을 위한 연결 프로퍼티 설정하기 
 

-Xdebug
-Xnoagent
-Djava.compiler=NONE
-Xrunjdwp:transport=dt_socket,server=y,address=3999,suspend=n




위로


다른 언어 디버깅 하기

자바가 Eclipse에서 가장 일반적으로 사용되지만, Eclipse는 다른 많은 언어들도 지원할 수 있는 확장성 있는 플랫폼이다. Eclipse는 C/C++ Development Tools (CDT) 프로젝트를 통해 C/C++을 지원한다. CDT는 C/C++ 코드를 디버깅하는 기능으로 표준 Eclipse Debug 뷰를 확장하고, CDT Debug 뷰에서는 워크벤치의 C/C++ 프로젝트의 디버깅을 관리할 수 있다. CDT에는 내부 디버거가 없지만 GNU GDB 디버거에 대한 프론트엔드를 제공한다. 이것은 로컬에서만 사용된다. PHP Development Tools (PDT) 같은 프로젝트도 고유의 디버거를 제공한다. (그림 11)


그림 11. PHP 디버거
 





위로


결론

Eclipse 플랫폼은 단계 실행을 수행하는 기능, 중단점과 값을 설정하는 기능, 변수와 값을 검사하는 기능, 쓰레드를 중지 및 시작하는 기능을 포함한, 표준 디버깅 기능들을 갖춘 자바 디버거를 제공한다. 원격 머신에서 실행되는 애플리케이션을 디버깅 하는 데에도 사용될 수 있다. Eclipse 플랫폼은 주로 자바 개발 환경이지만, 같은 Eclipse Debug 뷰가 C/C++, PHP, 기타 프로그래밍 언어에서도 사용될 수 있다.




위로


감사의 말

그림 11"을 만들어 준 Tyler Anderson에게 감사의 말을 전한다.



참고자료

교육

제품 및 기술 얻기

토론
  • Eclipse CDT newsgroups: C/C++ 디버깅 (기본 Usenet 뉴스 리더기 애플리케이션이 시작되면 eclipse.platform이 열린다.)

  • Eclipse ATF newsgroups: JavaScript 디버깅 (기본 Usenet 뉴스 리더기 애플리케이션이 시작되면 eclipse.platform이 열린다.)

  • Eclipse platform newsgroups: 디버깅 및 기타 Eclipse 플랫폼 관련 질문들 (기본 Usenet 뉴스 리더기 애플리케이션이 시작되면 eclipse.platform이 열린다.)

  • Eclipse Platform newsgroups: Eclipse 관련 기본 질문들 (기본 Usenet 뉴스 리더기 애플리케이션이 시작되면 eclipse.platform이 열린다.)

  • Eclipse newsgroups: Eclipse 사용과 확장과 관련한 자료들

  • Participate in developerWorks blogs and get involved in the developerWorks community.



필자소개

Chris Aniszczyk는 IBM Lotus 팀에서 OSGi 관련 개발 분야의 소프트웨어 에지니어이다. 오픈 소스에 대한 강한 열정을 지니고 있으며 Gentoo Linux로 작업하고 있다. 몇몇 Eclipse 프로젝트(PDE, ECF, EMFT)의 커미터로서도 활약하고 있다. 언제라도 오픈 소스와 Eclipse에 대해 이야기할 준비가 되어 있다.


Pawel Leszek(Studio B 저자)은 독립 소프트웨어 컨설턴트이자 Linux/Win/Mac OS 시스템 아키텍처와 관리를 전문으로 다루는 저자이다. 많은 운영 체제, 프로그래밍 언어, 네트워크 프로토콜, Lotus Domino와 DB2에 경험이 있다. "LinuxWorld"의 기자이며, "PC World"의 리눅스 칼럼니스트로 활동하고 있다. Warsaw에서 아내와 딸과 함께 살고 있다. (pawel.leszek@ipgate.pl)


블로그 이미지

유효하지않음

,

ANT

Programming Language/Java 2008. 11. 24. 11:34

[출처] 생각이...??

설치하기 

Linux(ubuntu)에서는 아래 한줄로 내려받기 + 설치 + 환경설정 끝.

  1. sudo apt-get install ant

      

윈도우에서는

  1. 다운로드 받는다.
    http://ant.apache.org/
  2. 압축을 풀어서 적당한 위치로 이동한다.
    C:\Program Files\apache-ant-1.7.1\     이정도 위치에
  3. 시작 > 제어판 > 시스템 > 고급 > 환경변수 로 들어가서
    - PATH 에 C:\Program Files\apache-ant-1.7.1\bin 추가
    - JAVA_HOME 변수 추가 (이미 설치된 자바의 위치 등록)

 

다음 명령으로 잘 설치 되었는지 확인 한다.

ant --version

 

 

SVN 사용하기

 

사이트 : http://subclipse.tigris.org/svnant.html

            http://svnkit.com/kb/user-guide-svnant.html                    

 

  1. 다운 로드    
    SvnAnt 1.2.x 버전을 다운 받는다.

     

  2. 설치
    /lib/*.jar 파일을 $ANT_HOME/lib 로 Copy 하면 끝.

 

예)

  1. <taskdef resource="org/tigris/subversion/svnant/svnantlib.xml"/>
    
  2. <target ..... >
        <svn username="guest" password="">
          <checkout url="http://subclipse.tigris.org/svn/subclipse/trunk/svnant/" revision="HEAD" destPath="svnant" />
        </svn>
    </target>
    

 

위 소스중 다음의 태그를 포함시키지 않으면, "failed to create task or type svn"를 만나게 될 것이다.

<taskdef resource="org/tigris/subversion/svnant/svnantlib.xml"/>


ㅇ Update

  1. <svn>
    <update dir="${target.web}" />
    </svn>


ㅇ commit

  1. <svn username="${svn.userid}" password="${svn.userpwd}">
        <commit message="변경 사항" dir="${target.web}" recurse="true" />
    </svn>


ㅇ auto file add

  1. <svn >
    <add>
    <svnFileSet dir="${target.web}">
    <svnUnversioned/>
    </svnFileSet>
    </add>
    </svn>


ㅇ 더 자세한 내용은

사이트에 직접 상세문서가 없다(나만 못찾는가?) .

antsvn을 설치하기 위해 받은 파일을 열어보면 doc 라는 디렉토리에서 상세 문서를 볼 수 있다


*.jar 를 classpath 로 잡기


웹서버 프로그램 컴파일시 /WEB-INF/lib/*.jar 파일을 classpath로 참조 하게 되는데 ant build시 일일이 적어 줘야 하는 부담이 있다.

특히나, cvs나 svn 같은 버젼관리 프로그램으로 다운받은 프로그램을 컴파일 할 경우 일일이 작성해서 만들 수 는 없는것이다.

그래서 다음과 같은 방법을 통해 동적인 참조를 만들어 본다.


컴파일시 다음과 같이 사용된다.

  1. <target name="compile" >
        <javac destdir="${svn.dest}/WEB-INF/classes" srcdir="${svn.dest}/WEB-INF/src" >
    <classpath >
  2. <fileset dir="${svn.dest}">
        <include name="**/WEB-INF/lib/*.jar" />
        <include name="**/WEB-INF/lib/.*.zip" />
    </fileset>
  3. </classpath> </javac>
    </target>


디렉토리가 없을 조건의 경우 만들기


ㅇ 디렉토리가 없으면 true가 되는 조건

  1. <condition property="check_dir" >
        <not>
            <available file="${svn.dest}/WEB-INF/classes"/>
        </not>
    </condition>


ㅇ위 조건이 참일 경우 디렉토리 만들기

  1. <target name="make_dir_classes" if="check_dir">
    <mkdir dir="${svn.dest}/WEB-INF/classes" />
    </target>


파일들을 이동


ㅇ 파일을 복사

  1. <copy todir="${project.dest}" file="some.file" />


ㅇ파일들을 복사

  1. <copy todir="${project.dest}" > <!-- to dir -->
    <fileset dir="${svn.dest}" /> <!-- from -->
    </copy>


ㅇ 일부 파일만 복사

  1. <copy todir="${project.dest}/WEB-INF/classes/" >
    <fileset dir="${project.dest}/WEB-INF/src/" includes="*.xml" />
    <fileset dir="${project.dest}/WEB-INF/src/" includes="*.propertie"/>
    </copy>


네이티브 명령어 실행

운영체제에 적절한 명령어를 실행 할 경우 <exec> 태그를 사용

네이티브 명령어
(testdb라는 데이타베이스 생성)

  1. psql -U postgres -c 'create database testdb'


ant에서는

  1. <exec executable="/usr/bin/psql"
    dir="." >
    <arg line="-U postgres -c 'create database testdb'" />
    </exec>


Ant Task를 사용하여 Java Class 이용

 
ant 명령어로 처리 할 수 없는 좀더 복잡한 로직에 대해서는 직접 java class를 만들어 이용 할 수 있다.

먼저 ant 소스부터 보자

  1. <target name="change_context_name" >
    <taskdef name="ccn" classname="com.action.ChangeContextName"/> <!-- Class 정의 -->
    <ccn context_name="testsrv"
    file="**/WEB-INF/sun-web.xml" /> <!-- 사용 -->
    </target>


ㅇ <taskdef /> 태그를 이용하여 사용자가 만든 클래스를 사용할수 있도록 정의

ㅇ <ccn />은 <taskdef /> 태그에서 정의된 name이다. 사용자가 임의로 바꿔 사용할 수 있다.


ChangeContextName 를 열어 보면 다음과 같다

  1. package com.action;

    public class ChangeContextName extends org.apache.tools.ant.Task
    {
        String context_name;
        public String getContext_name()
        {
           return context_name;
        }
        public void setContext_name(String context_name)
        {
           this.context_name = context_name;
        }

        public void execute()
        {
           System.out.println("실행이 되었다...... ^^");
           System.out.println("컨텍스트명["+this.context_name+"]");
        }
    }


ㅇ Ant Task가 되려면

  1. org.apache.tools.ant.Task를 상속 받아야 한다.
  2. public void execute(){ .... } 에서 실행이 된다
  3. attribute값을 전달 받으려면
    public void setXXXX(String val) 와 같이 사용한다.


out of memory

ant 구동중 out of memory를 만날경우

다음의 내용을 환경변수에 추가 하면된다.

  1. ANT_OPTS=-Xmx512M


사용자에게 입력받기

<input>태그를 이용하여 사용자로부터 값을 입력받도록 한다

  1. <target name="get-input">
    <input message="작업할 디렉토리명은?"
    addproperty="target.name" />
    <echo message="입력받은값 > [${target.name}]"/>
    </target>


ant-contrib사용(조건문 사용하기)

ㅇ 설치

http://ant-contrib.sourceforge.net/ > download page 접속 > ant-contrib package 클릭 > ant-contrib-1.0b3-bin.zip 다운로드
압출풀면 ant-contrib-1.0b3.jar 파일이 나옴
이파일을 ANT_HOME/lib/ 에 copy 하면 끝.

ㅇ 선언문

<taskdef resource="net/sf/antcontrib/antcontrib.properties" />

정의 해 주면 사용가능 해 진다

ㅇ 사용 태그

<if> <switch> <foreach> <trycatch> 태그가 사용 가능하다.

  • switch
  1. <switch value="${input.value}">
        <case value="1">
            <echo message="1의 경우..."/>
        </case>

        <default>
            <echo message="이도저도 아닌경우.."/>
        </default>
    </switch>
  • if
  1. <if> 
        <equals arg1="${foo}" arg2="bar" /> 
        <then> 
           <echo message="The value of property foo is bar" /> 
        </then> 
    <else> 
           <echo message="The value of property foo is not bar" /> 
    </else> 
    </if> 

  • foreach
  • tyrcatch
블로그 이미지

유효하지않음

,
package com.util.mail;   
 
import java.security.Security; 
import java.util.Date; 
import java.util.Properties;   
 
import javax.mail.Authenticator; 
import javax.mail.Message; 
import javax.mail.MessagingException; 
import javax.mail.PasswordAuthentication; 
import javax.mail.Session; 
import javax.mail.Transport; 
import javax.mail.internet.InternetAddress; 
import javax.mail.internet.MimeMessage;   
 
import org.apache.log4j.Logger;   
 
 public static final void sendMail(String to, String title, String content) { 
  try { 
     Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); 
   final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; 
   Properties props = System.getProperties(); 
   props.setProperty("mail.smtp.host", "smtp.gmail.com"); 
   props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY); 
   props.setProperty("mail.smtp.socketFactory.fallback", "false"); 
   props.setProperty("mail.smtp.port", "465"); 
   props.setProperty("mail.smtp.socketFactory.port", "465"); 
   props.put("mail.smtp.auth", "true"); 
   final String username = "your@gmail.com"; 
   final String password = "your password"; 
   Session session = Session.getDefaultInstance(props, 
     new Authenticator() { 
      protected PasswordAuthentication getPasswordAuthentication() { 
       return new PasswordAuthentication(username, 
         password); 
      } 
     });   
 
   // -- Create a new message -- 
   Message msg = new MimeMessage(session);   
 
   // -- Set the FROM and TO fields -- 
   msg.setFrom(new InternetAddress(to)); 
   msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse( 
     to, false)); 
   msg.setSubject(title); 
   msg.setText(content); 
   msg.setSentDate(new Date()); 
   Transport.send(msg);   
 
  } catch (MessagingException e) { 
   _log.error(e); 
     }   
  } 
}

  1. //set CLASSPATH=%CLASSPATH%;activation.jar;mail.jar
  2. import javax.mail.*;
  3. import javax.mail.internet.*;
  4. import java.util.*;
  5.  
  6. public class Mail
  7. {
  8.          String  d_email = "iamdvr@gmail.com",
  9.             d_password = "****",
  10.             d_host = "smtp.gmail.com",
  11.             d_port  = "465",
  12.             m_to = "iamdvr@yahoo.com",
  13.             m_subject = "Testing",
  14.             m_text = "Hey, this is the testing email using smtp.gmail.com.";
  15.     public static void main(String[] args)
  16.     {
  17.                 String[] to={"XXX@yahoo.com"};
  18.                 String[] cc={"XXX@yahoo.com"};
  19.                 String[] bcc={"XXX@yahoo.com"};
  20.                 //This is for google
  21.                         Mail.sendMail("venkatesh@dfdf.com","password","smtp.gmail.com","465","true",
  22. "true",true,"javax.net.ssl.SSLSocketFactory","false",to,cc,bcc,
  23. "hi baba don't send virus mails..","This is my style...of reply..
  24. If u send virus mails..");             
  25.     }
  26.  
  27.         public synchronized static boolean sendMail(String userName,String passWord,String host,String port,String starttls,String auth,boolean debug,String socketFactoryClass,String fallback,String[] to,String[] cc,String[] bcc,String subject,String text){
  28.                 Properties props = new Properties();
  29.                 //Properties props=System.getProperties();
  30.         props.put("mail.smtp.user", userName);
  31.         props.put("mail.smtp.host", host);
  32.                 if(!"".equals(port))
  33.         props.put("mail.smtp.port", port);
  34.                 if(!"".equals(starttls))
  35.         props.put("mail.smtp.starttls.enable",starttls);
  36.         props.put("mail.smtp.auth", auth);
  37.                 if(debug){
  38.                 props.put("mail.smtp.debug", "true");
  39.                 }else{
  40.                 props.put("mail.smtp.debug", "false");         
  41.                 }
  42.                 if(!"".equals(port))
  43.         props.put("mail.smtp.socketFactory.port", port);
  44.                 if(!"".equals(socketFactoryClass))
  45.         props.put("mail.smtp.socketFactory.class",socketFactoryClass);
  46.                 if(!"".equals(fallback))
  47.         props.put("mail.smtp.socketFactory.fallback", fallback);
  48.  
  49.         try
  50.         {
  51.                         Session session = Session.getDefaultInstance(props, null);
  52.             session.setDebug(debug);
  53.             MimeMessage msg = new MimeMessage(session);
  54.             msg.setText(text);
  55.             msg.setSubject(subject);
  56.             msg.setFrom(new InternetAddress("p_sambasivarao@sutyam.com"));
  57.                         for(int i=0;i<to.length;i++){
  58.             msg.addRecipient(Message.RecipientType.TO, new InternetAddress(to[i]));
  59.                         }
  60.                         for(int i=0;i<cc.length;i++){
  61.             msg.addRecipient(Message.RecipientType.CC, new InternetAddress(cc[i]));
  62.                         }
  63.                         for(int i=0;i<bcc.length;i++){
  64.             msg.addRecipient(Message.RecipientType.BCC, new InternetAddress(bcc[i]));
  65.                         }
  66.             msg.saveChanges();
  67.                         Transport transport = session.getTransport("smtp");
  68.                         transport.connect(host, userName, passWord);
  69.                         transport.sendMessage(msg, msg.getAllRecipients());
  70.                         transport.close();
  71.                         return true;
  72.         }
  73.         catch (Exception mex)
  74.         {
  75.             mex.printStackTrace();
  76.                         return false;
  77.         }
  78.         }
  79.  
  80. }


우선 smtp를 지원하는 메일서비스만 할 수 있습니다.
naver는 지원하긴 하지만, 조낸 써야지 smtp를 사용할 수 있습니다.
저는 일반사용자인데 으뜸사용자가 되야하는 듯 합니다.
그래서 그냥 지원해주는 gmail이랑 daum메일로 테스트를 해봤습니다. 잘 되는군요.
기존에 25포트가 디폴트로 메일을 사용했는데 보안 때문에 SSL을 사용하고, smtps라는 프로토콜로 465번포트로 하는군요.
이건 좀 더 공부를 해봐야할 듯 싶네요. 그냥 기존의 ssl을 사용하지 않는 메일은 host랑 id랑 password만 지정해주면 돼요.


우선 설정파일입니다.
applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
   
xmlns:p="http://www.springframework.org/schema/p"
   
xmlns:context=http://www.springframework.org/schema/context
   
xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
      http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-2.5.xsd"
>
   
<context:component-scan base-package="mailtest" />


   
<!-- 일반용  
    <bean id="mailSender"
        class="org.springframework.mail.javamail.JavaMailSenderImpl"
        p:host="STMP서버주소"
        p:username="아이디"
        p:password="비밀번호" />
    -->

   
   
<!-- gmail, hanmail 용 -->
   
<bean id="mailSender"
       
class="org.springframework.mail.javamail.JavaMailSenderImpl"
       
p:host="한메일: pop.hanmail.net, 지메일:smtp.gmail.com"
       
p:port="465"
       
p:protocol="smtps"
       
p:username="아이디"
       
p:password="비밀번호">
       
<property name="javaMailProperties">
           
<props>
               
<prop key="mail.smtps.auth">true</prop>
               
<prop key="mail.smtps.startls.enable">true</prop>
               
<prop key="mail.smtps.debug">true</prop>
           
</props>
       
</property>
   
</bean>


   
<bean id="templateMessage"
       
class="org.springframework.mail.SimpleMailMessage"
       
p:from="송신자 주소"
       
p:to="수신자 주소"
       
p:subject="안녕!" />
   </beans>






templateMessage는 임시로 메세지를 지정해주는 것으로 미리 지정할 껀 지정하는 겁니다.
물론, 나중에 java코드에서 수정이 가능합니다.

서비스부분입니다.

MailTestService.java


package mailtest;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.MailSender; import org.springframework.mail.SimpleMailMessage; import org.springframework.stereotype.Service;
@Service public class MailTestService {     @Autowired     private MailSender mailSender;  
   
@Autowired     private SimpleMailMessage simpleMailMessage;       public void sendEmail()     {
        SimpleMailMessage msg = new SimpleMailMessage(this.simpleMailMessage);         msg.setText("난 종천이라고해!");         this.mailSender.send(msg);     } }



저기서 simpleMailMessage를 받아와서 text만 설정해줍니다. 저기서 msg.setTo하면 수신자도 설정할 수 있죠. 그리고 그냥 send메소드만 호출해주면 메일이 전송됩니다.

package mailtest;

import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MailTest {     public static void main(String[] args)     {         String configLocation = "applicationContext.xml";         ApplicationContext context = new ClassPathXmlApplicationContext(configLocation);         MailTestService mailTestService = (MailTestService) context.getBean("mailTestService");         mailTestService.sendEmail();     } }

더욱 심화된 기능은 reference를 참조하세요
http://static.springframework.org/spring/docs/2.5.x/reference/mail.html

블로그 이미지

유효하지않음

,

[ This explains how to use gmail to send emails in php using PHPMailer]
1 Download PHPMailer from http://phpmailer.sourceforge.net
2 Extract to folder phpmailer
3 Create a file email.php
4 Paste this code and change the values in blue as you need (I modified the sample code given on the PHPMailer homepage)

IsSMTP(); // send via SMTP
$mail->SMTPAuth = true; // turn on SMTP authentication
$mail->Username = "username@gmail.com"; // SMTP username
$mail->Password = "password"; // SMTP password
$webmaster_email = "username@doamin.com"; //Reply to this email ID
$email="username@domain.com"; // Recipients email ID
$name="name"; // Recipient's name
$mail->From = $webmaster_email;
$mail->FromName = "Webmaster";
$mail->AddAddress($email,$name);
$mail->AddReplyTo($webmaster_email,"Webmaster");
$mail->WordWrap = 50; // set word wrap
$mail->AddAttachment("/var/tmp/file.tar.gz"); // attachment
$mail->AddAttachment("/tmp/image.jpg", "new.jpg"); // attachment
$mail->IsHTML(true); // send as HTML
$mail->Subject = "This is the subject";
$mail->Body = "Hi,
This is the HTML BODY "
; //HTML Body
$mail->AltBody = "This is the body when user views in plain text format"; //Text Body
if(!$mail->Send())
{
  echo "Mailer Error: " . $mail->ErrorInfo;
}
else
{
  echo "Message has been sent";
}
?>

5 Open the file class.smtp.php in phpmailer directory
6 Paste this code
$host = "ssl://smtp.gmail.com";
$port = 465;
before the line 104 #connect to the smtp server
Hint: Search for #connect
7 Open this page in browser and it will send the email using GMail.

Hint: When you want to email the details from a form, set the variables using the form variables.
eg. $mail->Username=$_POST['email']


[원문] http://deepakssn.blogspot.com/2006/06/gmail-php-send-email-using-php-with.html
블로그 이미지

유효하지않음

,

[출처] http://blog.sdnkorea.com/blog/677

 업체에서 사활을 건 싸움을 하고 있다. 그리고 이러한 경쟁 시장에 JavaDB가 도전장을 내밀게 되었다. JavaDB는 제목처럼 경량화 DB를 모토로 만들어 졌으며 이 글은 소개 및 설치하는 방법과 실행 예제 등 기본적인 정보만 다루도록 하겠다.

1. JavaDB의 등장과 기원

여기서 필자는 JavaDB의 등장과 함께 기원이라는 단어를 사용했다. 그럼 JavaDB는 부모가 있다는 것인가? 그렇다. JavaDB의 기원은 1996년으로 거슬러 올라간다. IBM이 Cloudscape라는 프로젝트를 시작 하였으며 1999년에 Informix, 2001년에는 IBM에서 관리 하였으며 2004년에 지금의 Apache에 기부 되었다. Apache는 Apache Derby라는 프로젝트로 오픈 소스 프로젝트를 진행하고 있으며 Apache Derby는 Apache DB subproject라는 이름 또한 가지고 있다. 기존 Database가 무겁고 비싼 것에 비해 Derby는 경량화와 무료 라이센스 그리고 무엇보다 매력적인 오픈 소스를 표방하며 만들어 졌다. 이 프로젝트를 Sun에서 JavaDB라는 이름으로 JavaSE6에 포함시키며 공급하기 시작했다.

2. JavaDB의 특징

JavaDB의 특징은 Apache Derby의 특징과 같다. 이는 기존 Dababase와 성격이 매우 다른데 특징은 다음과 같다.

1) base engine과 JDBC driver 모두 합쳐 2메가바이트
2) 자바와 JDBC, SQL을 기초로 만들어짐
3) client/server 모드를 Derby Network Client JDBC driver and Derby Network Server. 를 통해 지원 가능
4) 설치 및 디플로이, 사용이 편함

또한 JavaDB는 다음과 같은 환경에 적합하다고 소개되어 있다.

1) 자바 애플리케이션 개발 및 테스트 : 사용하기 쉬우며 사용자의 컴퓨터나 메인프레임에서도 잘 돌아감. 
아파치 라이센스하에 무료임.
2) 임베디드 애플리케이션
3) 멀티 플랫폼 환경 : 100% 자바이므로 Java DB에서 다른 오픈 스탠더드 데이터베이스로 마이그레이트 가능함.
4) Web 2.0 기반의 브라우져 based 환경
5) PDA와 같이 J2ME CDC에서 돌아가는 애플리케이션 

먼저 base engine과 JDBC driver를 모두 합쳐서 2메가바이트라는 획기적이고도 믿을 수 없는 용량을 자랑한다. 
또한 이전에 설명했듯이 Pure Java로 만들어 졌으며 설치법 또한 간단하다.

3. JavaDB 설치

JavaDB는 SE6를 설치하면 자동으로 설치되나 수동으로 설치하는 방법도 있다. 

(1) homepage(http://developers.sun.com/javadb/downloads/index.jsp)에 접속하여 다운로드 하기
(2) 설치하기

① 윈도우의 경우

     javadb_<version>.ms를 더블 클릭하거나 msiexec /i javadb_<version>.msi 명령 실행

② 솔라리스의 경우
    1) 다운로드 하기
       javadb-<version>-solaris-<arch>-pkg.sh
    2) 권한 확인하기
       

chmod +x javadb-<version>-solaris-<arch>-pkg.sh

    3) 압축풀기
       
./javadb-<version>-solaris-<arch>-pkg.sh

    이 명령을 실행하고 나면 javadb-<version> 디렉토리 밑에 여러 개의 다음의 SVR4 package 디렉토리들이 생성된다

SUNWjavadb-common
SUNWjavadb-client
SUNWjavadb-core
SUNWjavadb-demo
SUNWjavadb-docs
SUNWjavadb-javadoc
SUNWjavadb-service

      4) su – root등을 사용하여 수퍼 권한자로 변경하기 
      5) 기존 JavaDB 삭제하기
         기존에 JavaDB가 설치되어 있다면(디폴트 설치 경로는 /opt/SUNWjavadb 이다) 새 버전을 설치하기 전에 제거해야 한다. 현재 돌아가고 있는 패키지를 보는 방법은 다음과 같다.
    
pkginfo | grep SUNQjavadb-

          기존 패키지를 제거하기           
        
pkgrm SUNWjavadb-client SUNWjavadb-core SUNWjavadb-demo SUNWjavadb-docs SUNWjavadb-javadoc SUNWjavadb-service SUNWjavadb-common

      6) 설치하기
        
cd javadb-<version>
pkgadd
-d . SUNWjavadb-common SUNWjavadb-client SUNWjavadb-core SUNWjavadb-demo SUNWjavadb-docs SUNWjavadb-javadoc SUNWjavadb-service

      7) 새 JavaDB 패키지 설치하기
         
cd javadb-<version>
          pkgadd
-d . SUNWjavadb-common SUNWjavadb-client SUNWjavadb-core SUNWjavadb-demo SUNWjavadb-docs SUNWjavadb-javadoc SUNWjavadb-service



③ 리눅스의 경우
   1) 다운로드 하기
      javadb-<version>-linux-rpm.bin
   2) 권한 확인하기
        
chmod +x javadb-<version>-linux-rpm.bin

       3) 압축풀기
          
/javadb-<version>-linux-rpm.bin

    이 명령을 실행하고 나면 javadb-<version> 디렉토리 밑에 여러 개의 RPM 패키지
Sun-javadb-*.i386.rpm이 생긴다. 

       4) 수퍼 권한자로 변경하기
       5) 기존 JavaDB 삭제하기
          만약 기존에 JavaDB 설치가 되어 있으면(디폴트 설치 경로는 /opt/sun/javadb 이다)
          이를 새 버전을 설치하기 전에 삭제해야 한다.
          현재 돌아가고 있는 JavaDB 패키지의 리스트를 보는 방법은 다음과 같다.
          
rpm qa | grep sun-javadb-

          기존 패키지를 제거하기        
         
rpm -ev sun-javadb-common sun-javadb-client sun-javadb-core sun-javadb-demo sun-javadb-docs sun-javadb-javadoc


        6) 새 JavaDB 패키지 설치하기

cd javadb-<version>
rpm
-ivh sun-javadb-*.rpm

        
위와 같이 JavaDB를 설치하면 demo, frameworks, javadoc, docs 그리고 lib라는 서브 디렉토리가 
생긴다.
이들 서브 디렉토리가 어떤 정보를 가지고 있는지 살펴보자.

1) demo : 2개의 데모 프로그램을 가지고 있으며 database 폴더는 임베디드 애플리케이션을 어떻게 만드는 지에 대한 데모이며 programs 폴더는 클라이언트-서버 환경에서 JavaDB를 사용하는 데모이다. 이 데모는 밑에서 실행해 보도록 하겠다.
2) frameworks : 환경 변수, 데이터베이스 생성 및 작업의 셋팅을 위한 유틸리티를 가지고 있다.
3) javadoc : 예상 했겠지만 API 관련 문서가 있으며 jdbc3와 jdbc4 폴더로 나뉘어져 있다.
4) docs : JavaDB의 셋업 및 어드민, 레퍼런스 가이드가 있다.
5) lib : JAR 파일과 같은 패키지 된 JavaDB 라이브러리가 있다.

4. JavaDB 실행하기

설치를 마쳤으면 이제 JavaDB를 가동시켜 보자. 가동하기 전 현재 환경 셋업이 잘 되었는지 테스트 해야 한다. 테스트 하는 명령어는 다음과 같다.

java org.apache.derby.tools.sysinfo -cp embedded SimpleApp.class


명령어를 실행하면 다음과 같은 결과 화면이 나온다.

 



이 테스트는 먼저 클래스 경로를 찾고 라이브러리와 클래스를 찾는다.
이 테스트가 성공적으로 끝나면 화면과 같은 메시지가 나온다.

그럼 이제 Derby 프로그램을 실행시켜 보자.
우선 실행할 프로그램은 SimpleApp라는 자바 프로그램이다. 이 프로그램은 설치 시 예제로 들어가 있으며 기본적인 커넥션 얻는 법부터 SQL 문을 수행하는 예제이다. 소스는 다음과 같다.


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class SimpleApp
{
   
/* the default framework is embedded*/
   
public String framework = "embedded";
   
public String driver = "org.apache.derby.jdbc.EmbeddedDriver";
   
public String protocol = "jdbc:derby:";

   
public static void main(String[] args)
   
{
       
new SimpleApp().go(args);
   
}

   
void go(String[] args)
   
{
       
/* parse the arguments to determine which framework is desired*/
        parseArguments
(args);

       
System.out.println("SimpleApp starting in " + framework + " mode.");

       
try
       
{
           
/*
               The driver is installed by loading its class.
               In an embedded environment, this will start up Derby, since it is not already running.
             */

           
Class.forName(driver).newInstance();
           
System.out.println("Loaded the appropriate driver.");

           
Connection conn = null;
           
Properties props = new Properties();
            props
.put("user", "user1");
            props
.put("password", "user1");

           
/*
               The connection specifies create=true to cause
               the database to be created. To remove the database,
               remove the directory derbyDB and its contents.
               The directory derbyDB will be created under
               the directory that the system property
               derby.system.home points to, or the current
               directory if derby.system.home is not set.
             */

            conn
= DriverManager.getConnection(protocol +
                   
"derbyDB;create=true", props);

           
System.out.println("Connected to and created database derbyDB");

            conn
.setAutoCommit(false);

           
/*
               Creating a statement lets us issue commands against
               the connection.
             */

           
Statement s = conn.createStatement();

           
/*
               We create a table, add a few rows, and update one.
             */

            s
.execute("create table derbyDB(num int, addr varchar(40))");
           
System.out.println("Created table derbyDB");
            s
.execute("insert into derbyDB values (1956,'Webster St.')");
           
System.out.println("Inserted 1956 Webster");
            s
.execute("insert into derbyDB values (1910,'Union St.')");
           
System.out.println("Inserted 1910 Union");
            s
.execute(
               
"update derbyDB set num=180, addr='Grand Ave.' where num=1956");
           
System.out.println("Updated 1956 Webster to 180 Grand");

            s
.execute(
               
"update derbyDB set num=300, addr='Lakeshore Ave.' where num=180");
           
System.out.println("Updated 180 Grand to 300 Lakeshore");

           
/*
               We select the rows and verify the results.
             */

           
ResultSet rs = s.executeQuery(
                   
"SELECT num, addr FROM derbyDB ORDER BY num");

           
if (!rs.next())
           
{
               
throw new Exception("Wrong number of rows");
           
}

           
if (rs.getInt(1) != 300)
           
{
               
throw new Exception("Wrong row returned");
           
}

           
if (!rs.next())
           
{
               
throw new Exception("Wrong number of rows");
           
}

           
if (rs.getInt(1) != 1910)
           
{
               
throw new Exception("Wrong row returned");
           
}

           
if (rs.next())
           
{
               
throw new Exception("Wrong number of rows");
           
}

           
System.out.println("Verified the rows");

            s
.execute("drop table derbyDB");
           
System.out.println("Dropped table derbyDB");

           
/*
               We release the result and statement resources.
             */

            rs
.close();
            s
.close();
           
System.out.println("Closed result set and statement");

           
/*
               We end the transaction and the connection.
             */

            conn
.commit();
            conn
.close();
           
System.out.println("Committed transaction and closed connection");

           
/*
               In embedded mode, an application should shut down Derby.
               If the application fails to shut down Derby explicitly,
               the Derby does not perform a checkpoint when the JVM shuts down, which means
               that the next connection will be slower.
               Explicitly shutting down Derby with the URL is preferred.
               This style of shutdown will always throw an "exception".
             */

           
boolean gotSQLExc = false;

           
if (framework.equals("embedded"))
           
{
               
try
               
{
                   
DriverManager.getConnection("jdbc:derby:;shutdown=true");
               
}
               
catch (SQLException se)
               
{
                    gotSQLExc
= true;
               
}

               
if (!gotSQLExc)
               
{
                   
System.out.println("Database did not shut down normally");
               
}
               
else
               
{
                   
System.out.println("Database shut down normally");
               
}
           
}
       
}
       
catch (Throwable e)
       
{
           
System.out.println("exception thrown:");

           
if (e instanceof SQLException)
           
{
                printSQLError
((SQLException) e);
           
}
           
else
           
{
                e
.printStackTrace();
           
}
       
}

       
System.out.println("SimpleApp finished");
   
}

   
static void printSQLError(SQLException e)
   
{
       
while (e != null)
       
{
           
System.out.println(e.toString());
            e
= e.getNextException();
       
}
   
}

   
private void parseArguments(String[] args)
   
{
       
int length = args.length;

       
for (int index = 0; index < length; index++)
       
{
           
if (args[index].equalsIgnoreCase("jccjdbcclient"))
           
{
                framework
= "jccjdbc";
                driver
= "com.ibm.db2.jcc.DB2Driver";
                protocol
= "jdbc:derby:net://localhost:1527/";
           
}
           
if (args[index].equalsIgnoreCase("derbyclient"))
           
{
                framework
= "derbyclient";
                driver
= "org.apache.derby.jdbc.ClientDriver";
                protocol
= "jdbc:derby://localhost:1527/";
           
}
       
}
   
}
}

 
이 소스를 실행하면 다른 DB에 접속하는 법과 다른 점은 없다.
 


지금까지 JavaDB의 기본적인 설명을 다루었다. JavaDB를 사용해보면서 느낀 점은 가볍다는
것이었다. 향후 임베디드 시장에서의 활약을 기대해 보지만 이를 뚫기 위해 넘어야 할 난관이
많으며 이 중 오라클에서 Oracle Berkeley DB라는 오픈소스 기반 Lightweight 
데이터베이스와의 경쟁이 그 하나이다. 오라클은 Derby와 Oracle Berkeley DB의 성능을 비교한
문서를 공개하며 성능 이슈를 재기하고 있다. 임베디드 시장에서의 두 데이터베이스간의 활약이
기대하며 기고를 마친다.

참조 문헌 :
JavaDB 설치: http://developers.sun.com/javadb/downlo ··· ons.html
아파치 Derby 소개: http://db.apache.org/derby/docs/dev/getstart/
오라클 버클리 DB 소개: http://www.oracle.com/technology/produc ··· dex.html

블로그 이미지

유효하지않음

,
[출처] http://www.wearethebest.co.kr/zbxe/?mid=CodeGear_Lecture_Capture&category=6065&document_srl=3752&sort_index=readed_count&order_type=desc

브라우저 빈창 하나띄우고(1번) Ctrl+N해서 새창 띄우고(2번) 2번창에 axtivexform이는 경로 페이지 로드하고
2번창에서 Ctrl+N해서 새창 띄우고(3번) 2번 창 닫고
3번창을 리로드하거나 3번창에서 새창을 띄우면
에러납니다.
여기에 있는 방법 말고도 Borland QC(3315)에 있는 해봤는데도
똑같네요

------------------------------------------------------------------------------------
[답변]DAXParkingWindow란 무엇인가여?  
Parking Windows는 델파이에서 ActiveX 컨트롤이나 ActiveForm을 사용시
컨트롤들을 구성,제어하기 위해 생성하는 윈도우 입니다.
AxCtrls.pas에 보시면 그 부분이 나와있는데요..
두가지 문제가 많이 발생한다고 합니다.

첫번째는 컨트롤 위에 컨트롤이 존재하는 형태일 경우 생성 순서의 문제에 의해
발생하는 경우로 소스단 에서 생성 순서대로 재배열 해주시는 방법이구요..
(이 부분은 직접 확인하지 않은...발취한 자료에 있는 내용 입니다.)

두번째는 예전 한델에서 발취한 부분입니다.
(http://www.delphi.co.kr/zboard/view.php?id=tips&page=1&sn1=&divpage=1&sn=off&ss=on&sc=on&keyword=dax&select_arrange=headnum&desc=asc&no=1328)

보시면 자세히 내용히 나와 있지만...간단히 풀어보면..
ActiveX나 ActiveForm을 생성하면 Parking Window를 하나 Windows에 등록(registerClass)을 하게 됩니다.
첫번째 OCX가 로드시 Parking Window를 등록하면 문제가 없지만 두번째가 로드시에
만약 첫번째가 언로드 중이라면 두번째 OCX는 이미 등록되어 있다고 판단하고 처리
루틴에 접근하면 Access violation 에러를 내게 됩니다.
위의 본문에도 나와있지만 해결책의 하나로서 자신만의 Parking Window를 사용하도록
수정하는 것이지요.
델6 까지 이 문제가 존재했던걸로 압니다.
델7에서는 ActiveForm을 건들지 않아 확인하지 못했습니다.


전대식 wrote:
> DAXParkingWindow란
>
> 어떤 윈도우를 말하는 것인가여?
>
> Activex를 제작중인데.. 다음과 같은 에러가 발생해서요.
>
>
> 1. Explorer 부모창에서 자식창을 띄웁니다.
>
> 2. 자식창에서 ActiveX 컨트롤이 있어서 구동됩니다.
>
> 3. 자식창에서 ActiveX를 종료하고 Destroy하는것까지 확인했습니다.
>
> 4. 자식창이 종료된 후에 부모창이 먹통이 되는 현상이 발생합니다.
>
> 에러 원인이 어떤건지 도대체 모르겠습니다.
> 부모창 자체에서 ActiveX를 테스트 해보면 아무 이상이 없는데..
> 팝업(자식창)에서 테스트해보면 이런 현상이 간혹 발생합니다.
>
> 이유를 모르니 답답하기만 합니다.
>
> 위와 같은 원인을 해결하신 분의 고견을 부탁드립니다.
>
> 그럼 수고하세요.



[답변]DAXParkingWindow란 무엇인가여?  
답변 정말 감사드립니다.

위의 방법으로도 수행을 했습니다.
지금 코드도 그렇게 생성이 되어 있구여.. 이젠 DAX Error는 발생하지 않는것 처럼
보입니다만

자식창이 종료된 후에 부모창이 먹통이 되는 현상이 발생합니다.

은 여전히 존재합니다.

이런 원인이 어떤건지 참 궁금합니다.

아시는분은 도움 부탁드립니다.

그럼 오늘도 즐프하세요.. ^^;;;


-------------------------------------------------------------------------------------------

[팁]ActiveX 제작시 DAX 에러가 발생하는 경우중 해결법 하나...  
안녕하신지요...

얼마전에 발견한것이었는데..지금 올립니다.

제가 해결한 방법일뿐이구요. 그래도 가끔은 해당되시는 분들이 계실듯해서...

한국 델파이 개발자 홈페이지에 가시면 팁란에 이광수님의 자세한 설명이 있구요...

지금 말씀드리는 것은 너무나 간단한거라서...

Visual Component 등을 ActiveForm 에 올려 놓으실때 간혹 개발과정중에 화면구성상

맨위로 올라와야 하는 컴포넌트가 그 밑에 깔리는 컴포넌트보다 먼저 생성이 되는 경우

가 있을 수 있습니다. 특히 소스 정리를 하다가도 그렇게 될수도 있구요...

이것을 제일 뒤(바닥)에 있는 것부터 생성하도록 다음부분에서 정리를 해주세요..


type
TMyActiveImgCtrl = class(TActiveForm, IMyActiveImgCtrl, IOleInPlaceActiveObject)
   Bevel1: TBevel;
   Label1: TLabel;
   Bevel2: TBevel;
   Label4: TLabel;
   Label5: TLabel;
   Label7: TLabel;
   CutImg: TImage32;
   OpenPictureDialog1: TOpenPictureDialog;
   Bitmap32List1: TBitmap32List;
   PreImg0: TImage32;
   ~~~~~~생략~~~~~


이부분의 컴포넌트들을 대충 순서대로 정리를 해 주었더니 에러가 사라졌다는...

제가 아직 초보인관계로 자세한 설명은 못하지만 액티브폼위의 비쥬얼컴포넌트들이

상속되는 순서에 있어서 자신의 Parent 가 있는데에도 그보다 먼저 생성되게 되면

에러가 나오는듯 하더군요.. 그냥 일반 어플리케이션에서는 별상관이 없고

ActiveX Form 에서만 그런듯 합니다. 허접한 팁이기는 해도 혹시나 하는 마음에 올립니

다..

즐겁게 코딩하세요...
    
--------------------------------------------------------------------------------


델마당 자료실엔 없길레
한국 델파이 개발자 모임에서 퍼왔습니다.
문제가 된다면 바로 삭제 조치 하겠습니다.
아직도 델7에서 여러 ActiveX를 맹글어 개발하는 관계로
DAX 에러때문에 고생을 많이 했는데 사용하여 보니 별 문제?!가 없더군요 -_-;

----------------------------------------------------------------------------
Delphi 7 VCL Bug-fix Pack

D7VCL.bpg를 열어서 전체 빌드하시면

Packing 디렉토리에 생기는 파일을 델파이 디렉토리에 복사하시면 끝입니다. ^^

1. axctrls.pas

1) access violation해결

2. 한영 전환 문제 해결책 적용

--------------------------------------

사용법을 잘모르시겠다는 메일이 와서 추가합니다.


delphi.co.kr에 자료실에 올린 파일을 받아서 압축을 풀고

D7VCL.bpg 파일을 델파이에서 엽니다.



메뉴에서



Project - Build All Project를 눌러서

전체 프로젝트를 컴파일 합니다.



압축을 푼 디렉토리에서 Packing  디렉토리에 보면 lib, Source 디렉토리가 보입니다.



이 2개 디렉토리를 전체를 선택해서 복사한후



델파이7 설치 디렉토리에 붙여 넣습니다.

(일반적으로 C:\Program File\Boralnd\Delphi7)



이런 후 다시 엑티브 엑스를 컴파일하시면 됩니다.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Delphi로 ActiveX 콘트롤이나 ActiveForm을 작성하면 IE상에서
DAX error : Access violation을 만들어 냅니다.

이 에러는 98에서는 발생하는 것을 본적이 없으며,
win2k에서는 아주 가끔, Win Xp에서는 빈번하게 발생합니다.

문제의 원인은 axctrls.pas의 ParkingWindow펑션에서 일어납니다.

소스 코드를 보시면 파킹윈도우를 만들기 위해서 registerclass를 하게 됩니다.
보통 윈도우 클래스는 윈도우를 처리하기 위한 프로시져를 가지는데요.
이게 처음 메모리에 로딩되는 ocx의 것을 사용하게 됩니다.

그런데 ie와 같이 프로세스는 하나이고 빈번하게 ocx가 로딩되고
언로드 되는 과정에서 등록한 클래스의 처리 루틴이 메모리에 로딩되고
다시 떨어질때 문제가 생기는 것으로 보입니다.

즉 2개 이상의 delphi ocx를 사용하는 경우에
ie상에서 새창에 ocx를 띄울 때

1번 ocx가 올라 갈때 는 문제가 없다가
2번 ocx가 올라 갈때 문제가 생깁니다. 이때 1번 ocx가 언로드 중이라면
2번 ocx는 클래스는 레지스트 된것으로 생각하고, 윈도우 proc을 1번
ocx것을 사용하게 되는데 1번 ocx는 메모리에서 언로드 된 상황이라
access violation을 일으키게 됩니다. (에러의 정확한 위치는 CreateWindowEx에서 생기게 됩니다.)

일반 어플리케이션에서는 보통 초기에 모든 ocx 로딩이 끝나고
실행에 들어가는 경우가 많으므로 문제점이 나타나지 않았고,
win xp에 경우는 작업의 스위칭? 문제인지 os의 문제인지
특별히 잘나타나게 됩니다.

문제의 해결책은 간단합니다. 각 ocx가 자신의 parkingwindow처리 class와
proc을 사용하면 됩니다. 다음 소스를 axctrls.pas에 반영하고
소스 컴파일후 lib에 넣어주시면 처리 됩니다.


  1. function ParkingWindow: HWND;   
  2. var  
  3.   TempClass: TWndClass;   
  4.   ParkingName : String;   
  5. begin  
  6.   Result := xParkingWindow;   
  7.   if Result <> 0 then Exit;   
  8.   
  9.   // fix DAX error : accessviolation (win2k, win xp)   
  10.   ParkingName := 'DAXParkingWindow_' +   Format('%p', [@ParkingWindowProc]);   
  11.   
  12.   FillChar(TempClass, sizeof(TempClass), 0);   
  13.   if not GetClassInfo(HInstance, PChar(ParkingName), TempClass) then // fix DAX error : accessviolation (win2k, win xp)   
  14.   begin  
  15.     TempClass.hInstance := HInstance;   
  16.     TempClass.lpfnWndProc := @ParkingWindowProc;   
  17.     TempClass.lpszClassName := PChar(ParkingName); // fix DAX error : accessviolation (win2k, win xp)   
  18.     if Windows.RegisterClass(TempClass) = 0 then  
  19.       raise EOutOfResources.Create(SWindowClass);   
  20.   end;   
  21.   xParkingWindow := CreateWindowEx(WS_EX_TOOLWINDOW, TempClass.lpszClassName, nil,   
  22.      WS_POPUP, GetSystemMetrics(SM_CXSCREEN) div 2,   
  23.      GetSystemMetrics(SM_CYSCREEN) div 20000, HInstance, nil);   
  24.   SetWindowPos(xParkingWindow, 00000, SWP_NOACTIVATE or SWP_NOREDRAW   
  25.      or SWP_NOZORDER or SWP_SHOWWINDOW);   
  26.   Result := xParkingWindow;   
  27. end;  



참고로 axctrls에는 3가지 정도의 문제 점이 있는데요.

1. DAX error : accessvioaltion
2. active x의 스크롤시 화면 깨지는 문제점
3. activeForm에서 OnDestroy의 call안됨입니다.

delphi 6에서는 1번을 제외한 나머지는 수정 된 것으로 보입니다.

from 마형

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

이전의 에러 해결 방법에 이어서 메인 쓰레드가 아닌 창에서
ActiveX 의 에러를 해결 한 방법을 소개합니다.

델파이 7 기준이며 이전 버젼도 적절히 고치면 사용이 가능합니다.

자료실에 업로드하겠습니다.

문제의 원인은 역시 파킹윈도우 인데요.

메인쓰레드에서 파킹 윈도우가 생성되면 문제가 없습니다만.
메인 윈도우가 아닌 gui 쓰레드에서 OCX가 로딩되고
다시 이 컨트롤이 이용되면

2번째 생긴 gui 쓰레드가 종료되면 3번째 gui의 컨트롤이 DAX에러를 일으 킵니다.

문제의 원인은 메인이 아닌 gui쓰레드에서 윈도우가 만들어 지면
정상적인 종료시에도 그 gui에서 생성된 모든 윈도우가 제거 됩니다.

따라서 2번째 3번째 스레드가 사용하던 파킹윈도우가 2번 쓰레드가 제거 되면 윈도우 자체가 파괴되고 이로 인해 3번째 쓰레드의 컨트롤이

에러를 만들어 내게 됩니다.

해결책은 각 쓰레드당 1개의 파킹 윈도우를 만들어 주면 되며, 이러한 방법을 통해서 제가 아는 한에 대해서는 일단 보여지는 에러는 없어지게 됩니다.

참고로 소스의 수정 부분은 bug-fix를 찾아보시면 되며, 한영 전환 문제에 대한 수정부분도 적용했습니다.

개인적으로는 이전에 한 약속을 지키게 되서 기쁘고, 이제는 에러없는 엑티브 엑스를 만들어 낼수 있어서 기쁘네요.

블로그 이미지

유효하지않음

,

 

난이도 : 초급

Peter HaggarIBM

2002 년 5 월 01 일
2003 년 1 월 07 일 수정

모든 프로그래밍 언어에는 고유의 이디엄이 있다. 이중 대부분이 유용하다. 문제는 몇몇 이디엄의 경우 원래 표명했던 것이 아니라는 것이 나중에 입증되거나 설명한대로 작동하지 않다는 점이다. 자바에는 많은 유용한 이디엄이 있다. 하지만 결코 사용되어서는 안되는 이디엄도 있다. Double-checked locking이 바로 그것이다. 이글에서는 double-checked locking 이디엄의 근원부터 살펴본다.

Singleton creation pattern은 일반적인 프로그래밍 이디엄이다. 다중 쓰레드와 함께 사용할 때 동기화 유형을 사용해야 한다. 좀더 효율적인 코드를 만들기위한 노력으로 자바 프로그래머들은 코드가 동기화 되는 것을 제한하기위해 Singleton creation pattern과 함께 쓰일 double-checked locking 이디엄을 만들었다. 하지만 자바 메모리 모델에 대한 이해의 부족으로 double-checked locking 이디엄은 작동을 보장할 수 없다. 게다가 작동 실패의 이유는 명확하지 않고 자바 메모리 모델과 밀접하게 연관되어 있다. 때문에, double-checked locking으로 인한 코드 작동 실패의 원인을 검사하기 힘들다. 이 글을 통해 단지 그것이 어디서 고장이 났는지를 이해할 수 있도록 double-checked locking 이디엄을 연구해본다.

Singleton creation 이디엄

double-checked locking 이디엄이 어디서 기원했는지를 이해하려면 일반적인 singleton creation 이디엄을 알아야 한다. (Listing 1):


Listing 1. Singleton creation 이디엄

import java.util.*;
class Singleton
{
  private static Singleton instance;
  private Vector v;
  private boolean inUse;

  private Singleton()
  {
    v = new Vector();
    v.addElement(new Object());
    inUse = true;
  }

  public static Singleton getInstance()
  {
    if (instance == null)          //1
      instance = new Singleton();  //2
    return instance;               //3
  }
}

 

이 클래스의 디자인은 단지 하나의 Singleton 객체가 만들어졌다는 것을 확인시켜주고 있다. 생성자는 private으로 선언되고 getInstance() 메소드는 단지 하나의 객체를 만든다. 이것은 단일 쓰레드 프로그램에 적합하다. 하지만 다중 쓰레드가 개입되면 동기화를 통해서 getInstance() 메소드를 방어해야 한다. getInstance() 메소드가 방어되지 않으면 Singleton 객체의 두 개의 다른 인스턴트를 리턴할 수 있다. getInstance() 메소드를 동시에 호출하고 다음 이벤트를 따라가는 두 개의 쓰레드를 생각해보자:

  1. Thread 1은 getInstance() 메소드를 호출하고 //1에서 그 instance null이라는 것을 결정한다.
  2. Thread 1은 if 블록으로 들어가지만, //2의 라인을 실행하기 전에 thread 2에 선점된다.
  3. Thread 2는 getInstance() 메소드를 호출하고 그 instance가 //1에서 null 이라는 것을 결정한다.
  4. Thread 2는 if 블록으로 들어가서 새로운 Singleton 객체를 만들고 instance 변수를 이 새로운 //2에 있는 새로운 객체에 할당한다.
  5. Thread 2는 //3에 있는 Singleton 객체 레퍼런스를 리턴한다.
  6. Thread 2는 thread 1에 선점된다.
  7. Thread 1는 남겨진 곳에서 부터 시작하고 다른 Singleton 객체가 만들어지는 결과가 된 //2 라인을 실행한다.
  8. Thread 1은 //3에서 이 객체를 리턴한다.

결과는 getInstance() 메소드가 단지 하나의 객체를 만들어야 하는데 두개의 Singleton 객체를 만들었다. 이 문제는 단지 하나의 쓰레드가 한 번에 코드를 실행하도록 getInstance() 메소드를 동기화시켜 수정할 수 있다 (Listing 2):


Listing 2. 쓰레드 방지 getInstance() 메소드

public static synchronized Singleton getInstance()
{
  if (instance == null)          //1
    instance = new Singleton();  //2
  return instance;               //3
}

 

Listing 2의 코드는 getInstance() 메소드로 멀티쓰레드 액세스에 잘 작동한다. 하지만 이것을 분석해보면 동기화는 메소드의 첫 번째 호출에만 필요하다는 것을 깨닫게 된다. 지속적인 호출은 동기화를 필요로하지 않는다. 첫 번째 호출이 //2에서 코드를 실행하는 유일한 호출이기 때문이다. 이 라인은 동기화가 필요한 유일한 라인이다. 모든 다른 호출들은 instance null이 아니라는 것을 결정하고 이것을 리턴한다. 다중 쓰레드는 첫 번째 것을 제외하고는 모든 호출에 대해 일관성 있게 실행할 수 있다. 하지만, 메소드가 synchronized 될 때, 메소드의 모든 호출에 대해 동기화의 대가를 지불해야한다.

이 메소드를 좀더 효율적으로 만들기 위해서 double-checked locking이라는 이디엄이 만들어졌다. 첫 번째 것을 제외한 모든 메소드 호출에 대해 동기화를 피하려는 생각이다. 동기화의 비용은 JVM 과는 다르다. 초기에 이 비용은 매우 높았다. 향상된 JVM이 등장함에 따라, 동기화의 비용은 감소했지만 여전히 synchronized 메소드 또는 블록에 들어가고 나오는 것에 대한 퍼포먼스 패널티는 존재한다. JVM 기술의 발전과 관계없이 프로그래머들은 불필요하게 프로세스 시간을 낭비하기를 결코 원하지 않는다.

Listing 2의 //2 행만이 동기화가 필요하기 때문에, 이것을 동기화 블록으로 래핑한다 (Listing 3):


Listing 3. getInstance() 메소드

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {
      instance = new Singleton();
    }
  }
  return instance;
}

 

Listing 3의 코드는 다중 쓰레드에 나타났던 문제와 같다. 두 개의 쓰레드는 instance null이면 동시적으로 if 문 내부에서 얻어질 수 있다. 그리고나서 하나의 쓰레드가 instance를 초기화하기 위해서 synchronized 블록으로 들어간다. 그러는 동안 다른 것들은 블록화된다. 첫 번째 쓰레드가 synchronized 블록을 종료할 때 기다리고 있는 쓰레드가 들어가서 다른 Singleton 객체를 만든다. 두 번째 쓰레드가 synchronized 블록에 들어갈 때, instance이 non-null인지를 검사하지 않는다.

 


위로



Double-checked locking

Listing 3의 문제를 해결하려면 instance를 검사해야한다. double-checked locking 이디엄을 Listing 3에 적용하면 Listing 4의 결과가 나온다.


Listing 4. Double-checked locking 예제

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {  //1
      if (instance == null)          //2
        instance = new Singleton();  //3
    }
  }
  return instance;
}

 

double-checked locking 이론은 //2에서의 두번째 체크가 두 개의 다른 Singleton 객체들이 Listing 3에 나타난 것처럼 만들어질 수 없도록 한다는 것이다:

  1. Thread 1은 getInstance() 메소드로 들어간다.
  2. Thread 1은 instance null이기 때문에 //1에 있는 synchronized 블록으로 들어간다.
  3. Thread 1은 thread 2에 선점된다.
  4. Thread 2는 getInstance() 메소드로 들어간다.
  5. Thread 2는 instance가 여전히 null이기 때문에 //1에서 lock 얻기를 시도한다. 하지만 thread 1이 lock을 보유하고 있기 때문에 thread 2는 //1에서 블록한다.
  6. Thread 2는 thread 1에 선점된다.
  7. Thread 1은 실행하고 인스턴스가 //2에서 여전히 null 이기 때문에, Singleton 객체를 만들고 이것의 레퍼런스를 instance에 할당한다.
  8. Thread 1은 synchronized 블록을 종료하고 getInstance() 메소드에서 인스턴스를 리턴한다.
  9. Thread 1은 thread 2에 선점된다.
  10. Thread 2는 //1에서 lock을 얻어서 instance null인지를 점검한다.
  11. instance가 non-null이기 때문에, 두 번째 Singleton 객체는 만들어지지 않고 thread 1에 만들어진것이 리턴된다.

double-checked locking 이론은 완벽하다. 안타깝게도 현실은 그와 반대라는 것이다. double-checked locking과 관련한 문제는 이것이 단일 또는 다중 프로세서 머신에서 작동하는 것을 보장할 수 없다는 점이다.

double-checked locking 실패는 JVM의 버그 때문이 아니고 현재의 자바 플랫폼 메모리 모델 때문이다. 메모리 모델은 "난잡한 작성"을 허용하고 이것이 이디엄이 실패하는 주요 이유이다.

 


위로



out-of-order write

이 문제를 설명하기 위해서, Listing 4의 //3행을 다시한번 살펴보아라. 이 코드는 Singleton 객체를 만들고 객체를 참조하기 위해서 instance 변수를 초기화한다. 이 코드의 문제는 instance 변수가Singleton 생성자의 바디가 실행하기 전에 non-null 이 될 수 있다는 점이다.

이것은 여러분이 가능하다고 생각했던 모든것과 배치가 될 수 있다. 하지만 가능한 현실이다.이것이 어떻게 발생했는지를 설명하기전에 어떻게 이것이 double-checked locking 이디엄을 고장냈는지를 살펴보면서 이러한 현실을 받아들이자:

  1. Thread 1은 getInstance() 메소드로 들어간다.
  2. Thread 1은 //1의 synchronized 블록으로 들어간다. instance null이기 때문이다.
  3. Thread 1은 //3 으로 가서 non-null 인스턴스를 만든다. 생성자가 실행하기 전이다.
  4. Thread 1은 thread 2에 선점된다.
  5. Thread 2는 인스턴스가 null인지를 점검한다. null이 아니기 때문에, thread 2는 instance 레퍼런스를 완전히 만들어졌지만 부분적으로 초기화된 Singleton 객체로 리턴한다.
  6. Thread 2는 thread 1에 선점된다.
  7. Thread 1은 생성자를 실행함으로서 Singleton 객체의 초기화를 완료하고 레퍼런스를 리턴한다.

thread 2는 한 객체를 리턴하는데, 그 객체의 생성자는 실행되지 않았다.

이를 설명해줄 다음의 가상 코드를 살펴보자: instance =new Singleton();

mem = allocate();             //Allocate memory for Singleton object.
instance = mem;               //Note that instance is now non-null, but
                              //has not been initialized.
ctorSingleton(instance);      //Invoke constructor for Singleton passing
                              //instance.

 

Listing 5의 코드를 보자. getInstance() 메소드를 간략하게 나타냈다. "double-checkedness"를 제거했다. instance=new Singleton(); 라인을 JIT 컴파일러가 어떻게 컴파일하는지에만 관심이 있다. 어셈블리 코드에서 생성자가 실행된다는 것을 확인하기 위해 간단한 생성자도 제공했다.


Listing 5. out-of-order write를 표현한 Singleton 클래스

class Singleton
{
  private static Singleton instance;
  private boolean inUse;
  private int val;  

  private Singleton()
  {
    inUse = true;
    val = 5;
  }
  public static Singleton getInstance()
  {
    if (instance == null)
      instance = new Singleton();
    return instance;
  }
}

 

Listing 6에는 getInstance() 메소드의 바디를 위한 Sun JDK 1.2.1 JIT 컴파일러에서 만들어진 어셈블리 코드가 포함되어 있다.


Listing 6. Listing 5 코드에서 만들어진 어셈블리 코드

;asm code generated for getInstance
054D20B0   mov         eax,[049388C8]      ;load instance ref
054D20B5   test        eax,eax             ;test for null
054D20B7   jne         054D20D7
054D20B9   mov         eax,14C0988h
054D20BE   call        503EF8F0            ;allocate memory
054D20C3   mov         [049388C8],eax      ;store pointer in 
                                           ;instance ref. instance  
                                           ;non-null and ctor
                                           ;has not run
054D20C8   mov         ecx,dword ptr [eax] 
054D20CA   mov         dword ptr [ecx],1   ;inline ctor - inUse=true;
054D20D0   mov         dword ptr [ecx+4],5 ;inline ctor - val=5;
054D20D7   mov         ebx,dword ptr ds:[49388C8h]
054D20DD   jmp         054D20B0

 

이 어셈블리 코드는 getInstance() 메소드를 호출하는 테스트 프로그램을 실행하여 만들어졌다. 프로그램이 실행되는 동안 Microsoft Visual C++ 디버거를 실행하고 이것을 자바 프로세스에 붙였다.

어셈블리 코드의 처음 두 개의 행은 049388C8 메모리 로케이션에서 instance 레퍼런스를 eax 로 로딩하고 null을 테스트 한다. 이것은 Listing 5의 첫 행의 getInstance()메소드와 상응한다. 이 메소드가 처음 호출되고 instance null이면 코드는 B9으로 진행된다. BE에 있는 코드는 Singleton 객체용 힙에서 메모리를 할당하여 eax의 메모리에 포인터를 저장한다. C3 eax 에서 포인터를 가져다가 049388C8 메모리 로케이션에 인스턴스 레퍼런스에 저장한다. 결과적으로 인스턴스는 현재 non-null이고 유효한 Singleton 객체를 참조한다. 하지만 이 객체용 생성자는 아직 실행되지 않았고 그것은 double-checked locking이 문제가 있다는 것이다. C8 라인에서,instance 포인터는 역참조되고 ecx에 저장된다. CA D0 행은 true 5 값을 Singleton 객체에 저장하는 인라인 생성자를 나타낸다. C3 행을 실행하고 나서 다른 쓰레드에 의해 이 코드가 인터럽트된다면 생성자를 완료하기 전에 double-checked locking은 실패한다.

모든 JIT 컴파일러가 위와 같은 코드를 생성하는 것은 아니다. 어떤것은 생성자가 실행된 후 바로 인스턴스가 non-null이 되는 코드를 만든다. IBM SDK for Java technology, version 1.3과 Sun JDK 1.3 모두 이와 같은 코드를 만든다. 하지만 이러한 인스턴스에 double-checked locking을 사용해야 된다는 것을 말하는 것은 아니다. 이것이 실패할 수 있는 다른 이유들이 있다. 게다가 어떤 JVM에서 여러분의 코드가 실행될지를 항상 알고있는것은 아니지 않는가? 그리고 JIT 컴파일러는 이 이디엄을 중단시키는 코드를 만들기위해 언제나 변화할 수 있다.

 


위로



Double-checked locking: Take two

현재 double-checked locking 코드가 실행되지 않는다면 다른 코드 버전을 제안하겠다 (Listing 7). out-of-order write 문제를 방지하기 위함이다.


Listing 7. out-of-order write 문제 해결

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          inst = new Singleton();        //4
        }
        instance = inst;                 //5
      }
    }
  }
  return instance;
}

 

Listing 7의 코드를 보게되면 상황이 우습게 되고있다는 것을 알게된다. 기억해야할 것은 double-checked locking은 간단한 세 라인의 getInstance() 메소드의 동기화를 막을 수단으로 만들어졌다는 것이다. Listing 7의 코드는 손에서 떠났다. 이 코드는 문제를 해결하지 않는다. 천천히 그 이유를 살펴보자.

out-of-order write 문제를 방지하기 위해 시도되었다. 이를 inst 로컬 변수와 두 번째 synchronized 블록을 이용하여 수행한다:

  1. Thread 1 getInstance() 메소드로 들어간다.
  2. instance null이기때문에, thread 1은 //1에서 첫 번째 synchronized 블록으로 들어간다.
  3. 로컬 변수inst instance 값을 가지고 이것은 //2 에서 null이다.
  4. inst null 이기 때문에, thread 1은 //3의 두 번째 synchronized 블록으로 들어간다.
  5. Thread 1은 //4에서 코드 실행을 시작하면서 Singleton 생성자가 실행되기 전에 inst를 non-null로 만든다. (이것이 out-of-order write 문제이다.)
  6. Thread 1은 Thread 2에 선점된다.
  7. Thread 2는 getInstance() 메소드로 들어간다.
  8. instance null이기 때문에, thread 2는 //1에 있는 첫 번째 synchronized 블록으로 들어가는 것을 시도한다. thread 1이 현재 이 lock을 보유하고 있기 때문에, thread 2는 블록된다.
  9. Thread 1은 //4의 실행을 완료한다.
  10. Thread 1은 완전히 생성된 Singleton 객체를 //5의 instance 변수에 할당하고 synchronized 블록 모두를 종료한다.
  11. Thread 1은 instance를 리턴한다.
  12. Thread 2는 실행해서 instance를 //2의 inst에 할당한다.
  13. Thread 2는 instance가 non-null이라는 것을 확인하고 이를 리턴한다.

핵심 라인은 //5 이다. 이 라인은 instance null 이거나 완전히 생성된 Singleton 객체를 참조한다는 것을 확인하도록 되어있다. 문제는 이론과 진실이 직교하며 실행되는 곳에서 발생한다.

Listing 7의 코드는 메모리 모델의 현재 정의 때문에 작동하지 않는다. Java Language Specification (JLS)은 synchronized 블록 안에서 작성된 코드가 synchronized 블록 밖으로 이동하지 못하도록 되어있다. 하지만, synchronized 블록에 없는 코드가 코드가 synchronized 블록 안으로 이동할 수 없다는 것을 뜻하는 것은 아니다.

JIT 컴파일러는 여기에서 최적화 기회를 본다. 최적화는 //4에 있는 코드와 //5 에 있는 코드를 제거하여 이를 조합하여 Listing 8과 같은 코드를 만든다:


Listing 8. Listing 7의 최적화 코드

public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          //inst = new Singleton();      //4
          instance = new Singleton();               
        }
        //instance = inst;               //5
      }
    }
  }
  return instance;
}

 

최적화가 되면 이전과 같은 out-of-order write 문제가 생긴다.

 


위로



volatile

inst 변수와 instance 변수에 volatile 키워드를 사용하는 경우도 있다. JLS (참고자료)에서 volatile이 선언된 변수는 영속성이 있어 재정리 되지 않아야 한다. double-checked locking 과 관련한 문제를 해결하기 위해 volatile을 사용할 때 두 가지 문제가 발생한다:

  • 여기서의 문제는 순차적 영속성에 있지 않다. 코드는 이동되고 재정리 되지 않는다.
  • 많은 JVM이 순차적 영속성을 정확히 고려한 volatile 을 구현하지 않는다.


Listing 9. volatile의 순차적 영속성 

class test
{
  private volatile boolean stop = false;
  private volatile int num = 0;

  public void foo()
  {
    num = 100;    //This can happen second
    stop = true;  //This can happen first
    //...
  }

  public void bar()
  {
    if (stop)
      num += num;  //num can == 0!
  }
  //...
}

 

JLS에 의하면, stop num volatile로 선언되기 때문에, 그들은 순차적으로 영속적이여야 한다. 이는, stop이 언제나 true이면, num 100으로 설정되어야 한다는 것을 의미한다. 하지만, 많은 JVM이 volatile의 순차적 영속성 기능을 구현하지 않기 때문에 이러한 것에 의존할 수 없다. 따라서 thread 1이 foo를 호출하고 thread 2가 bar를 동시에 호출하면, thread 1은 num  100으로 설정되기 전에 stop true로 설정해야한다. 이렇게 되면 thread 2에서 stop true로 설정되고 num은 여전히 0으로 설정된다.

 


위로



솔루션

모든 JVM 구현에서 작동한다는 것을 보장할 수 없기 때문에 어떤 형태로든 double-checked locking은 사용되어서는 안된다. JSR-133은 메모리 모델 관련한 문제를 언급하고 있지만 double-checked locking은 새로운 메모리 모델에서 지원되지 않는다. 따라서 다음 두 가지 옵션이 있다:

  • getInstance() 메소드의 동기화를 수락한다. (Listing 2).
  • 동기화를 그만두고 static 필드를 사용한다.

Option 2는 Listing 10에 나와있다:


Listing 10. static 필드를 이용한 Singleton 구현

class Singleton
{
  private Vector v;
  private boolean inUse;
  private static Singleton instance = new Singleton();

  private Singleton()
  {
    v = new Vector();
    inUse = true;
    //...
  }

  public static Singleton getInstance()
  {
    return instance;
  }
}

 

Listing 10의 코드는 동기화를 사용하지 않고 static getInstance() 메소드에 호출이 있을때까지 Singleton 객체가 만들어지지 않는다는 것을 확실히 하고있다.

 


위로



스트링은 변하지 않는다!

out-of-order writes 문제가 있는 String 클래스와 생성자 실행에 앞서 non-null 이 되는 레퍼런스가 이상할 것이다. 다음 코드를 보자:

private String str;
//...
str = new String("hello");

 

String 클래스는 변하지 않는다. 하지만 out-of-order write 문제가 발생할 수 있는가? 문제는 그럴 수 있다는 것이다. String str로 액세스 하는 두 개의 쓰레드를 생각해보자. 한 쓰레드는 str 레퍼런스가 생성자가 실행하지 않는 곳에서 String 객체를 참조한다는 것을 본다. 사실 Listing 11에는 이러한 것이 발생하는 것을 보여준다. 이 코드는 내가 테스트 한 JVM 로만 고장을 일으킨다는 것을 명심해라. IBM 1.3과 Sun 1.3 JVM은 변하지 않는 String을 만든다


Listing 11. Mutable String 예제

class StringCreator extends Thread
{
  MutableString ms;
  public StringCreator(MutableString muts)
  {
    ms = muts;
  }
  public void run()
  {
    while(true)
      ms.str = new String("hello");          //1
  }
}
class StringReader extends Thread
{
  MutableString ms;
  public StringReader(MutableString muts)
  {
    ms = muts;
  }
  public void run()
  {
    while(true)
    {
      if (!(ms.str.equals("hello")))         //2
      {
        System.out.println("String is not immutable!");
        break;
      }
    }
  }
}
class MutableString
{
  public String str;                         //3
  public static void main(String args[])
  {
    MutableString ms = new MutableString();  //4
    new StringCreator(ms).start();           //5
    new StringReader(ms).start();            //6
  }
}

 

이 코드는 //3의 두 개의 쓰레드에 의해 공유된 String 레퍼런스를 포함하는 //4의 MutableString 클래스를 만든다. //5와 //6라인에 두 개의 개별 쓰레드에 StringCreator StringReader라는 두 개의 객체가 만들어지면서 MutableString 객체에 레퍼런스를 전달한다. StringCreator 클래스는 무한 루프로 들어가고 //1에서 "hello" 값을 가진 String 객체를 만든다. StringReader는 무한 루프로 들어가고 현재 String 객체가 //2에서 "hello" 값을 갖고 있는지를 확인한다. 그렇지 않다면, StringReader 쓰레드는 메시지를 프린팅하고 정지한다. String 클래스는 변하지 않고 이 프로그램에서 어떤 아웃풋도 볼 수 없다.

Sun JDK 1.2.1같은 오래된 JVM 에서 이 코드를 실행하면 out-of-order write 프로그램이 되고 non-immutable String이 된다.

 


위로



요약

singleton의 비싼 동기화를 피하기위해 특별히 천재적인 프로그래머들은 double-checked locking 이디엄을 개발했다. 약한 메모리 모델의 분야를 재정의하는 작업이 진행중이다. 하지만 아무리 새로운 메모리 모델이어도 double-checked locking은 작동하지 않을 것이다. 이 문제에 대한 최상의 솔루션은 동기화를 수락하거나 static field를 사용하는 것이다.

 


위로



참고자료

 


위로



필자소개

Peter Haggar

Peter Haggar는 IBM의 소프트웨어 엔지니어이다. Practical Java Programming Language Guide (Addison-Wesley)의 저자이며 자바 프로그래밍 관련하여 많은 글을 집필했다. 개발 툴, 클래스 라이브러리, OS 등 광범위한 분야에 많은 경험을 갖고 있다.




위로





출처: http://www.ibm.com/developerworks/kr/library/j-dcl.html

블로그 이미지

유효하지않음

,

[출처] 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>

블로그 이미지

유효하지않음

,