태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

The Power of Byte Code Instrumentation in Java - Part 3

Enterprise Java 2007.08.05 15:49

Part2에 이어...

One Simple but Powerful Example of BCI

ASM 라이브러리를 이용한 Byte Code Instrumentation의 아주 간단한 예제를 하나 작성해보자.

비록 매우 심플한 예제이지만, 실제로는 매우 강력한 방법이다.

이 방법에 익숙해지면 아마 그 용도의 무궁무진함에 놀라게 될 것이다.

이런 질문에 한번 답해보자.

"우리 회사에서 사용 중인 WAS Application에서 발생하는 Exception을 체계적으로 수집하고 정리하고 싶다. 그 Exception이 Catch되어서 처리되고 있는지에 무관하게.... "

(사실은 이 문제에 대해 블로그의 "Aspected Oriented Programming in Java"에서 다룬 바 있다)

대부분의 사람들이 본능적으로 다음과 같은 대답을 떠올린다.

즉, Application에서 사용 중인 모든 소스 코드에 try catch 구문을 사용해 Exception을 Catch해서 적절히 조작하는 것이다. 이 접근법은 다음과 같은 문제점들이 있다.

  • 그 많은 소스들을 언제 다 바꿀 것인가?
  • 내가 직접 작성하지 않은 Java Core Library(java.*, javax.*, ...)나 3rd Party Library는 어떻게 할 것인가?
  • Exception 처리 정책이 바뀌었을 때는 또 어떻게 할 것인가?
  • 무엇보다 엄청나게 지저분해질 코드는 또 어떻게 할 것인가?

Business Logic을 처리하는 코드에 Exception 수집을 위한 코드를 넣는 것은 가장 위험하고 비효율적인 방법임이 분명하다.

이런 문제를 해결하기 위해 AOP와 같은 방법론이 등장했을 정도로...

이 문제를 해결하는 가장 간단한 방법을 생각해보자....

나의 머리 속에 떠오른 방법은 이것이다.

  • "java.lang.Excetpion" 클래스가 모든 Exception의 부모 클래스 아닌가?"
  • "즉, 어떤 종류의 Exception이라도 반드시 java.lang.Exception이 제공하는 생성자와 메소드를 공유하고 있다"
  • "따라서 Java.lang.Exception의 생성자에 Exception 수집 코드를 삽입하면 모든 것이 해결된다"

빙고!!

문제는 java.lang.Exception 클래스의 생성자를 어떻게 조작하느냐는 것이다.

여기가 바로 BCI, 즉 Byte Code Instrumentation이 등장하는 곳이다.

우선 java.lang.Exception 클래스가 어떤 코드로 이루어져 있는지 Java Decompiler를 이용해 코드를 살펴 보자.

Decompiler를 통해서 Exception 클래스의 소스 파일은 다음과 같다.

package java.lang;

// Referenced classes of package java.lang:
// Throwable, String

public class Exception extends Throwable
{

public Exception()
{
}

public Exception(String s)
{
super(s);
}

public Exception(String s, Throwable throwable)
{
super(s, throwable);
}

public Exception(Throwable throwable)
{
super(throwable);
}

static final long serialVersionUID = 0xd0fd1f3e1a3b1cc4L;
}

ASM Bytecode Outline Plugin을 통해서 본 Byte Code는 아래와 같다.

/ class version 49.0 (49)
// access flags 33
public class Exception extends Throwable {


// access flags 24
final static long serialVersionUID = -3387516993124229948

// access flags 1
public () : void
ALOAD 0
INVOKESPECIAL Throwable.() : void
RETURN
MAXSTACK = 1
MAXLOCALS = 1

// access flags 1
public (String) : void
ALOAD 0
ALOAD 1
INVOKESPECIAL Throwable.(String) : void
RETURN
MAXSTACK = 2
MAXLOCALS = 2

// access flags 1
public (String,Throwable) : void
ALOAD 0
ALOAD 1
ALOAD 2
INVOKESPECIAL Throwable.(String,Throwable) : void
RETURN
MAXSTACK = 3
MAXLOCALS = 3

// access flags 1
public (Throwable) : void
ALOAD 0
ALOAD 1
INVOKESPECIAL Throwable.(Throwable) : void
RETURN
MAXSTACK = 2
MAXLOCALS = 2
}

만일 Exception 클래스의 생성자에서 다음과 같은 코드가 삽입되도록 한다면?

public class Exception extends Throwable
{

public Exception()
{
ExceptionCallBack.exceptionOccurred(this);
}

public Exception(String s)
{
super(s);
ExceptionCallBack.exceptionOccurred(this);
}

public Exception(String s, Throwable throwable)
{
super(s, throwable);
ExceptionCallBack.exceptionOccurred(this);
}

public Exception(Throwable throwable)
{
super(throwable);
ExceptionCallBack.exceptionOccurred(this);
}

static final long serialVersionUID = 0xd0fd1f3e1a3b1cc4L;
}

이렇게 되면 어떤 Exception이 발생하든지 항상 ExceptionCallBack.exceptionOccurred(this) 코드에 의해 수집이 이루어진다.심지어 Java Core Library를 사용하는 과정에서 내부적으로 발생하는 Exception도 다 수집할 수 있다.

Part3에서 ASM을 이용해 java.lang.Exception 클래스의 바이트 코드를 직접 수정하는 예제를 보게 될 것이다.

(참조)

이런 반문을 할 지 모르겠다. "java.lang.Exception" 클래스를 디컴파일한 소스를 직접 수정해서 새로운 java.lang.Exception 클래스를 직접 만들면 안되나?"

가능한 방법이다. 하지만 이런 문제점이 있다.

  • JVM의 Version이나 Vendor에 따라 소스는 모두 다를 수 있다. 따라서 타겟이 바뀔 때마다 디컴파일을 수행해서 소스를 생성한 후 작업을 해야 한다.
  • 디컴파일러가 모든 클래스 파일을 다 완벽하게 디컴파일해내는 것은 아니다.

이런 이유들 때문에 소스 코드를 직접 수정하는 방법은 정말 특별한 경우가 아니면 권장되지 않는다. 더구나 Java Core Library의 소스 코드를 변경해서 사용하는 것은 법적(?)인 문제를 일으킬 수도 있다.

신고
Trackback 0 : Comment 0

Write a comment

티스토리 툴바