본문 바로가기

Daylogs/Java

다중 클래스로더 환경에서의 싱글턴 (BeanUtils의 예)

발생일: 2009.07.30

문제:
친구 홍이 얘기한다.

apache commons의 BeanUtils 에서 getSimpleProperty 메서드의 소스를 보면...

-- BeanUtils class
public static String getSimpleProperty(Object bean, String name)
        throws IllegalAccessException, InvocationTargetException,
        NoSuchMethodException {

    return BeanUtilsBean.getInstance().getSimpleProperty(bean, name);
}

근데 여기에서 왜 getSimpleProperty 를
static 으로 선언하지 않고, 굳이 BeanUtilsBean을 생성해서 싱글턴 방식으로 호출할까?

왜 그럴까....?


해결책:
'BeanUtils를 싱글턴으로 만들었다가 사용편의성을 위해 static으로 따로 만들었던 걸까' 라고...
우리끼리 추측하고 있다가 홍이 소스를 뜯어보고는-
BeanUtilsBean 클래스에 보면 왜 저렇게 처리했는지 주석으로 달아놓았다고 한다.

 * <p>Occasionally it is necessary to store data in "global" variables
 * (including uses of the Singleton pattern). In applications which have only
 * a single classloader such data can simply be stored as "static" members on
 * some class. When multiple classloaders are involved, however, this approach
 * can fail; in particular, this doesn't work when the code may be run within a
 * servlet container or a j2ee container, and the class on which the static
 * member is defined is loaded via a "shared" classloader that is visible to all
 * components running within the container. This class provides a mechanism for
 * associating data with a ClassLoader instance, which ensures that when the
 * code runs in such a container each component gets its own copy of the
 * "global" variable rather than unexpectedly sharing a single copy of the
 * variable with other components that happen to be running in the same
 * container at the same time (eg servlets or EJBs.)</p>

데이터를 전역적으로 저장하기 위해 스태틱 또는 싱글턴을 사용할 때에,
다중 클래스로더 환경에서 제대로 동작하지 않을 수 있어서 위와 같은 메커니즘으로 구성했다고 한다.

결국 static 이냐, singleton 이냐의 문제가 아니라
'다중 클래스로더 환경에서의 정상적인 데이터 공유'의 문제였던 건가보다.


그래서 검색을 하다 보니 싱글턴 패턴에 대해 심도있게(비기너라고 써있긴 하지만..) 잘 정리해 둔 블로그가 있다.


위 블로그에서 다중 클래스로더 환경에서 싱글턴이 제대로 동작할 수 있도록
클래스로더를 직접 지정하는 방식에 대해 이야기한 부분이 있다.
그래서 BeanUtilsBean 의 getInstance() 의 소스를 확인해보니,
아니나 다를까 비슷한 방법으로 클래스로더를 직접 지정하고 있다.


public synchronized Object get() {
    // synchronizing the whole method is a bit slower
    // but guarantees no subtle threading problems, and there's no
    // need to synchronize valueByClassLoader
   
    // make sure that the map is given a change to purge itself
    valueByClassLoader.isEmpty();
    try {
       
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (contextClassLoader != null) {
           
            Object value = valueByClassLoader.get(contextClassLoader);
            if ((value == null)
            && !valueByClassLoader.containsKey(contextClassLoader)) {
                value = initialValue();
                valueByClassLoader.put(contextClassLoader, value);
            }
            return value;
           
        }
       
    } catch (SecurityException e) { /* SWALLOW - should we log this? */ }
   
    // if none or exception, return the globalValue
    if (!globalValueInitialized) {
        globalValue = initialValue();
        globalValueInitialized = true;
    }//else already set
    return globalValue;
}


그래서 그렇게 했나보다..... 라고는 하고 있는데,
아직 클래스로더나 다중 클래스로더 환경에 대한 이해가 부족해서 정확하게는 잘 모르겠다.


팁:
그 이전에 static 과 singleton 의 차이에 대해 깔끔하게 정리해둔 블로그가 있으니 참고하자.