자바에서는 Spring Framework 이 거의 표준이라서 별 생각없이(?) 잘 쓰고 있습니다. 다만 한가지 아쉬운 점은 같은 Thread Context 별로 다른 Bean (Component) 를 사용하고 싶은데... 할 수 있는 것이라곤...
scope 가 singleton 이거나 prototype 이란 거... 즉 프로세스에 오직 하나의 인스턴스만 있거나, 요청 시 매번 새로운 인스턴스를 준다는 것이지요. 물론 웹에서는 PerSession 등이 있습니다만, 내부 라이브러리에서는 Thread Context 별로 Bean을 제공하는 방식이 추가되었으면 좋겠다 싶었습니다.
Windsor Castle 에는 thread 라고 지정해주면 Thread Context 별로 독립된 인스턴스를 제공해 주거든요^^
그래서 만들어 봤습니다.
우선 Bean 을 정의하는데, 일반적으로 단순 생성해서 주는 게 아니라. TheadLocal을 이용하여, 생성된 인스턴스를 보관하여, 같은 ThreadContext 에서 Bean을 요청하는 경우, 기존 인스턴스를 제공하는 것입니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static ThreadLocal<MongoOgmDao> MongoOgmDaoLocal = | |
new ThreadLocal<MongoOgmDao>() { | |
@Override | |
public MongoOgmDao initialValue() { | |
log.debug("현 ThreadContext 에 저장소를 생성합니다..."); | |
return new MongoOgmDao(); | |
} | |
}; | |
@Scope("prototype") | |
public MongoOgmDao hibernateOgmDao() { | |
return MongoOgmDaoLocal.get(); | |
} |
이렇게 하면, Thread Context 별로 다른 인스턴스를 사용하여, 독립적인 작업을 수행할 수 있고, 같은 Thread Context에서는 반복적인 조회에도 같은 인스턴스를 제공할 수 있습니다.
Thread 별로 제공해야 하는 Bean 이 많은 경우에는 위와 같은 작업이 번거로울 수 있습니다. 이럴 때에는 thread 별로 hash map 을 제공하는 것이 좋습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.core; | |
import com.google.common.collect.Maps; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import java.util.HashMap; | |
import static kr.debop4j.core.Guard.shouldNotBeNull; | |
/** | |
* Thread Context 별로 격리된 저장소를 제공합니다. | |
* | |
* @author sunghyouk.bae@gmail.com | |
* @since 12. 9. 12 | |
*/ | |
public class Local { | |
private static final Logger log = LoggerFactory.getLogger(Local.class); | |
private Local() { } | |
private static ThreadLocal<HashMap> threadLocal = | |
new ThreadLocal<HashMap>() { | |
@Override | |
public HashMap initialValue() { | |
log.debug("현 ThreadContext 에 저장소를 생성합니다..."); | |
return Maps.newLinkedHashMap(); | |
} | |
}; | |
private static HashMap getMap() { | |
return threadLocal.get(); | |
} | |
public static Object get(Object key) { | |
shouldNotBeNull(key, "key"); | |
return threadLocal.get().get(key); | |
} | |
@SuppressWarnings("unchecked") | |
public static <T> T get(Object key, Class<T> clazz) { | |
assert key != null; | |
return (T) threadLocal.get().get(key); | |
} | |
@SuppressWarnings("unchecked") | |
public static void put(Object key, Object value) { | |
assert key != null; | |
if (log.isTraceEnabled()) | |
log.trace("Local 저장소에 key=[{}], value=[{}]를 저장합니다.", key, value); | |
threadLocal.get().put(key, value); | |
} | |
public static void clear() { | |
threadLocal.get().clear(); | |
log.debug("Local 저장소의 모든 정보를 삭제했습니다."); | |
} | |
} |
이렇게 Local 을 이용하여 기존 ThreadLocal 방식을 다시 구현하면
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static final String MONGO_OGM_DAO_CLASS_NAME = MongoOgmDao.class.getName(); | |
@Override | |
@Bean | |
@Scope("prototype") | |
public MongoOgmDao hibernateOgmDao() { | |
MongoOgmDao dao = Local.get(MONGO_OGM_DAO_CLASS_NAME, MongoOgmDao.class); | |
if (dao == null) { | |
dao = new MongoOgmDao(sessionFactory()); | |
Local.put(MONGO_OGM_DAO_CLASS_NAME, dao); | |
if (log.isDebugEnabled()) | |
log.debug("현 스레드에서 새로운 MongoOgmDao 인스턴스를 생성했습니다. ThreadId=[{}]", Thread.currentThread().getId()); | |
} | |
return dao; | |
} |
과 같습니다. Bean 마다 ThreadLocal 을 따로 만들지 않아 좋은 장점이 있습니다.