Context 와 ThreadLocal
J2EE에서 DB와 같은 리소스를 연동하고 트랜잭션을 관리하기 위해서 J2EE에서는 Context라는 객체를 활용한다. 이러한 Contex는 리소스에 대한 위치 투명성을 제공하고 Two Phase Commit과 같은 분산 트랜잭션을 위한 기본적인 환경을 제공한다.
이러한 Context의 기능을 이용해서 작업을 추적해서 로그를 출력하는 모듈을 개발할 수 있다. 그러면 이러한 Context를 간단하게 구현하려면 어떻게 해야 할까?
이러한 문제를 해결하려면 문제를 조금 다른 관점에서 살펴보는 안목이 필요하다.
Web 프로그램은 Muiti Thread 환경에서 실행되는데 프로그램이 각각의 Thread에서 실행된다.
이러한 각각의 Thread에 변수를 저장하는 방법이 있다면 Context와 비슷한 기능을 하는 모듈을 작성할 수 있다
그리고 이러한 모듈을 활용해서 앞에서 제시한 문제를 해결할 수 있다.
그러면 어떻게 Thread에 변수를 저장할 수 있을까? 바로 java.lang.ThreadLocal이라는 Class를 활용해서 작성할 수 있다.
ThreadLocal은 각각의 Thread별로 변수를 생성할 수 있는 기능을 제공하고 또한 각각의 Thrad에서 접근할 수 있는 API를 제공해 준다. 이러한 ThreadLocal을 이용해서 Thread에 따라 구분되는 Data를 저장하고 활용할 수 있다.
아래는 간단한 ThreadLocal 예제이다.
public static void main(String[] args) {
ThreadLocal local = new ThreadLocal();
local.set("Thread Local변수");
System.out.println("ThreadLocal변수 : " + local.get() );
}
}
위와 같이 일반적인 Class와 동일하게 객체를 생성하고 값을 설정하고 읽어온다.
다만 내부적으로 Thread.currentThread()라는 함수로 현재 Thread 객체를 찾아와서 객체를 기준으로 Map에서 값을 읽어오도록 구현이 되어 있다. J2SE 1.4.1의 ThreadLocal의 get()과 set()을 살펴보면 아래와 같다.
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return map.get(this);
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
Object value = initialValue();
createMap(t, value);
return value;
}
public void set(Object value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
여기서 중요한 부분은 Thread.currentThread()를 이용하는 부분이다. 사실 Thread.currentThread() 함수를 이용하면 ThradLocal을 어렵지 않게 직접 구현할 수도 있다.
그러면 이러한 ThreadLocal을 이용해서 디버그 flag를 저장하고 읽어오는 함수를 구현해 보자.
private static DebugContext context = new DebugContext();
private DebugContext() {
}
// singleton객체 반환 함수
public static DebugContext getContext() {
return context;
}
public void setDebug( boolean debug ) {
info.set( debug + "" );
}
public boolean getDebug() {
return Boolean.valueOf( info.get() ).booleanValue();
}
}
그러면 이 함수를 이용해서 로깅을 하는 함수를 구현해 보자. 로깅은 단순하게 System.out.println을 이용하겠다.
public static void printLog( String s ) {
if( DebugContext.getContext().getDebug() ) {
System.out.println( s );
}
}
}
테스트 시나리오에서 A.jsp라는 페이지에서 BizTask.getInfo()를 호출하고 BizTask.getInfo()에서는 DAO.select()라는 class를 호출한다고 가정하겠다. 이경우 A.jsp와 두개의 Class에서 출력되는 로그를 한번에 Enable/Disable하기 위해서 위에서 만든 Logger와 DebugContext를 이용해 보겠다.
A.jsp는 아래와 같다
String debug = request.getParameter("debug");
if( debug != null && debug.equals("true") {
DebugContext.getContext().setDebug( true );
} else {
DebugContext.getContext().setDebug( false );
}
Logger.printLog( "현재 Thread에서 실행되는 모든 로그 출력 시작");
Logger.printLog( "BizTask.getInfo() 호출 시작");
task.getInfo();
Logger.printLog( "BizTask.getInfo() 호출 완료");
DebugContext.getContext().setDebug( false ); //
Logger.printLog( "로그 테스트");
%>
BizTask.java는 아래와 같다.
Logger.printLog("BizTask 객체 생성");
}
public void getInfo() {
DAO dao = new DAO();
Logger.printLog("DAO.select() 호출 시작");
dao.select();
Logger.printLog("DAO.select() 호출 완료");
}
}
DAO.java는 아래와 같다.
Logger.printLog("DAO 객체 생성");
}
public void select() {
Logger.printLog("DAO.select() 실행");
}
}
A.jsp?bebug=true 라는 주소로 호출하면 아래와 같은 로그가 나오게 된다.
BizTask 객체 생성
BizTask.getInfo() 호출 시작
DAO 객체 생성
DAO.select() 호출 시작
DAO.select() 실행
DAO.select() 호출 완료
BizTask.getInfo() 호출 완료
현재 Thread에서 실행되는 모든 로그 출력 종료
A.jsp아래쪽에 있는 로그 테스트 라는 부분은 이미 Context에서 debug플래그가 제거되었기 때문에 출력되지 않는다. 또한 A.jsp?debug=false로 호출하면 아무런 로그도 출력되지 않는다.
이렇게 해서 Context를 구현하는 핵심 방법인 ThreadLoca을 알아보고 ThreadLocal을 이용해서 특정 Thread만 debug flag를 활성화 시키는 방법을 알아보았다.
아마 코딩 경험이 있으신 분들은 눈치 채었겠지만 ThreadLocal을 이용하는 방법은 참 다양하다.
예를 들어 JDBC Connection을 개발자가 반환하는 것이 아니라 응용프로그램이 종료될 때 일괄로 반환해 주는 기능을 많은 Framework에서 제공하는데 이러한 기능도 ThreadLocal을 이용한다.