static initializer

출처 : http://blog.naver.com/parnx/140054010993

다음은 흔히 싱글턴singleton 패턴에서 애용되는 형태의 코드이다.


public class MySingleton {
   private static final MySingleton INSTANCE = new MySingleton();

   public static MySingleton getInstance() {
       return INSTANCE;
   }

   private MySingleton() {
      // 초기화 작업…
   }

   // … 나머지 코드
}

이 글에서 말하고 싶은 것은 흔히 싱글턴 패턴에서의 스레드 관련 문제가 아니라, 이 클래스가 가져올 수도 있는 재앙 내지 밤샘디버깅이다. 경험상 이런 코드가 말썽을 일으키는 경우를 종종 볼 수 있었으며(현재 프로젝트에서도 발생), 그 때의 디버깅은 같은경험이 없다면 정말 많은 시간이 걸릴 수 있다.

이 클래스가 사람 밤 새게 만드는 에러 메시지는 다음과 같다.

java.lang.NoClassDefFoundError: sample.MySingleton
……

디버깅 세션에 돌입하게 되면 NoClassDefFoundError가 발생하면 당연히 해당 클래스가 존재하는지 부터 찾기 시작할것이다. jar 파일을 뒤지고, 해당 jar 파일이 CLASSPATH 혹은 기타 로딩될 수 있는 위치에 있는지 찾아보고…그런데 간혹 분명히 존재함에도 NoClassDefFoundError가 발생하는 경우가 있다는 사실은 잘 알려지지 않았다.

시나리오는 이렇다.


1. MySingleton 클래스는 설정파일을 읽어들이거나 하는 등의 이유로 정적 초기화 과정이 다소 복잡하다. 위에서는 static 멤버변수를 통해 생성자 호출을 통한 객체생성까지 끝나야만 정적 초기화가 끝난다.

2. 그리고 초기화 영역에서 예외가 발생하는 환경(설정파일이 없다거나…)이 우발적으로 만들어진다.

3. 해당 클래스가 처음 참조될 때 초기화 영역에서 NullPointerException든 뭐든 예외가 발생하여 처리되지 않고 throw 된다.

4. 클래스의 정적 초기화 과정에서 예외가 발생하였으므로 클래스 로딩이 실패한다.

5. 시스템은 정지하지 않고 해당 처리만 예외를 발생시키고 계속 진행한다.

6. 이후 이 클래스에 액세스하는 모든 코드는 이 클래스가 로딩되지 않았기 때문에 NoClassDefFoundError를 유발한다.


이러한 시나리오대로 진행된다면, 아마 주의깊게 로깅을 남기고 그것을 발견해 내지 않는 이상 정말 찾기 어렵다. 클래스 찾아 삼만리 작업… 원인은 다른데 있는… 디버깅 세션이 시작되는 것이다.

이런 상황을 방지하기 위한 방법은 – 내가 아는 한 – 아래와 같이 코드를 작성하는 것이다.

public class MySingleton {
   private static final MySingleton INSTANCE;
   private static Throwable thrownLoadingThisClass;

    static {
       // 모든 예외를 잡아내야 한다.
       try {
           INSTANCE = new MySingleton();
       } catch (Throwable t) {
           thrownLoadingThisClass = t;
           t.printStackTrace(System.err);
       }
   }

   public static MySingleton getInstance() {
       if (
thrownLoadingThisClass == null) {
           return INSTANCE;
       } else {
           throw new IllegalStateException(
               “This class has not initialized properly. Check the root cause.”
               ,
thrownLoadingThisClass);
       }
   }

   private MySingleton() {
      // 초기화 작업…
   }
   // … 나머지 코드
}


정적 초기화 영역이 조금이라도 위험성이 있다고 판단되면 무조건 전체를 try-catch로 감싸서, 예외 발생시 static변수에 저장한다. 그리고 이 클래스 액세스 하는 모든 메소드는 이 예외 변수(thrownLoadingThisClass)를 체크해야 한다. 여기서는 getInstance() 메소드라는 유일한 진입메소드가 존재하므로 여기서만 체크하면 된다. 만약 이러한코드가 번거롭다면 AOP를 활용하는 것도 생각해 볼 수 있을 것이다.


한 줄 요약 – NoClassDefFoundError가 발생하면 해당 클래스의 static initializer 영역에서 예외 혹은 에러가 발생하지 않는지 부터 확인하라.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다