[GAE 시리즈] 1. 구글 앱 엔진 + 메이븐 + IntelliJ 프로젝트 세팅

구글 앱 엔진 시작하기 메뉴얼을 보며 예제를 실습해봤다. 그러나.. 내 입맛에 맞지 않는 구석이 몇개 있었다.

1. 라이브러리를 맘대로 추가할 수 있으니 코딩하는데는 문제가 안되는데 빌드가 보통 앤트를 사용하는 듯하다. 이건 불편하다. 그래서 메이븐을 썼다. GAE가 원하는 프로젝트 구조를 건드리지 않으면서도 필요한 라이브러리는 쉽게 가져다 쓸 수 있도록 메이븐을 설정했다. 이전에도 계속 써오던 형태라 대충 복사해서 붙여넣기고 당장 필요없는 라이브러리는 뺐다. (사실 스프링은 남겨뒀다;;)
2. 이클립스 종속적인 가이드였다. 인텔리J도 GAE 플러긴이 있으며 업데이트 사이트를 추가하는 귀찮은 작업 없이도 간편하게 플러그인을 찾아서 설치할 수 있었다.
3. 배포 설정
웹 서버에 배포할 때 프로젝트 이름/out 폴더 밑으로 웹 컨텐츠를 전부 복사해서 배포하게 되어 있는데 난 프로젝트/web에 배포하게 설정했다. 이래야 재배포 하지 않고도 JSP를 수정할 때 마다 바로바로 적용된다. 

EJ2E Item 23. 새로 작성하는 코드에서는 raw type 쓰지 말자

참조: Effective Java 2nd Edition. Item 23:Don’t use raw types in new code

먼저 용어 정리

class List<E> {

}

List<String> stList = ~~~~;

이런 코드가 있을 때…
– List<E>: Generic type
– List<String>: Parameterized type
– E: Formal type parameter
– String: Actual type parameter
– List: Raw type (제목에 쓴게 이거임)
– List<?>: Unbounded wildcard type
– 나머진 패스.

Generic 장점
– 컴파일 시에 타입 안전성을 보장한다.
– 부가적인 장점으로 컬렉션에 들어있는 것을 꺼낼 때 캐스팅할 필요가 없다.

그럼 Generic을 꼭 쓰게 하지 왜 Raw Type으로 쓸 수 있게 해뒀냐?
– 호환성 때문에. 이미 1.5전 버전 기준으로 만들어 놓은 코드가 많으니까.

Generic 특성
– List<String>은 List의
하위 타입이지만 List<Object>의 하위 타입은 아니다.
– 다음 item에서 더 자세히 설명 나옴.

Raw Type을 사용했을 때 발생할 수 있는 문제

    public static void main(String[] args) {
        List<String> strings = new ArrayList<String>();
        unsafeAdd(strings, new Integer(42));
        String s = strings.get(0); // Compiler-generated cast
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

굵은 글씨에서 타입을 명시하지 않았기 때문에 list.add(o); 여기서 일단 컴파일 경고가 발생하지만 일단 무시하고 실행할 수는 있다 막상 실행하면 ClassCastException 발생한다. 하지만 만약 저기서 List<String>이라고 타입을 명시했다면 애초에 컴파일도 못했을 것이다.

어떤 종류가 들어있는지 모르는 컬렉션을 처리하는 메서드에서는 unbounded wildcard type을 사용하자.
   
    //위험
    static int numElementsInCommon(Set s1, Set s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

    //안전
    static int numElementsInCommon(Set<?> s1, Set<?> s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

위에껀 왜 위험하고 아래껀 왜 안전할까? 안전한 녀석에는 무언가 추가할 수가 없다. 위험한 녀석에는 아무 객체나 넣을 수 있지만 unbounded wildcard type을 사용한 경우에는 컬렉션에 아무것도 추가할 수 없다. 컴파일 에러다.

왜? Actual type parameter가 뭔지 모르는데 뭘 집어 넣을 수 있을까.. 생각해보면 당연하다.

Type parameter를 사용할 수 없는 경우
– List<String>.class
– instanceof

[ClassLoader] Thread의 getContextClassLoader()

pdf: http://www.theserverside.com/tt/articles/content/dm_classForname/DynLoad.pdf

먼저 간단한 퀴즈로 시작하는.. 긴 문서..


이 코드가 문제를 일으킬 수 있는 상황은 저 클래스를 자바 Extentions 폴더 (<JDK>/jre/lib/ext 또는 <JRE>/lib/ext)에 놓았을 경우입니다. 그렇지 않고 그냥 CLASSPATH에 둘었다면 별 문제가 없을 가능성이 큽니다. 대체 저런식으로 동적으로 로딩하려는 클래스들이 App CL의 CLASSLOADER에 있기 마련일 테니까요.

하지만, Extention 폴더로 이동하면 무슨일이 벌어질까요. ClassNotFoundExcepion이 발생합니다. 그 원인을 정확히 모르신다면 ClassLoader에 대한 아주 기본적인 개념이 부족한거라 봐도 무방하겠습니다.

이 글에서 그에 대한 해결책 두 개를 제공해주며, 그 중 하나가 바로 쓰레드 컨텍스트 클래스로더를 사용하는 것입니다.

http://java.sun.com/javase/6/docs/api/java/lang/Thread.html#getContextClassLoader%28%29

Returns the context ClassLoader for this Thread. The context ClassLoader is provided by the creator of the thread for use by code running in this thread when loading classes and resources. If not set, the default is the ClassLoader context of the parent Thread. The context ClassLoader of the primordial thread is typically set to the class loader used to load the application.

First, if there is a security manager, and the caller’s class loader is not null and the caller’s class loader is not the same as or an ancestor of the context class loader for the thread whose context class loader is being requested, then the security manager’s checkPermission method is called with a RuntimePermission(“getClassLoader”) permission to see if it’s ok to get the context ClassLoader.

별짓을 하지 않는다면 쓰레드 컨텍스트 클래스로더는 System(또는 Application) CL이 됩니다. 따라서 저 위의 코드를 자바 extentions 폴더에 넣고 실행하더라도 쓰레드 컨텍스트 클래스로더를 가져와서 loadClass를 호출하면 아무런 문제없이 동작합니다.

안그래도 대체 Thread Context ClassLoader가 왜 생긴건지 궁금했는데 이 글을 통해서 짐작할 수 있게 됐습니다. 클래스로더 계층 구조를 만들 때, 예를 들어 톰캣의 Common CL 같은 녀석은 자기 하위의 CL인 WebAPP CL에 어떤 클래스들이 들어올지 모를겁니다. 하지만 그런 상태에서 Common CL에서 WebAPP CL에서만 로딩할 수 있는 어떤 클래스를 로딩할 필요가 있다면… WebAPP CL을 직접 참조하지 않고 바로 Thread Context ClassLoader를 사용해서 로딩하도록 코딩을 하면 로딩할 수 있게 되는거죠.

흠.. 하지만 이.. 미적지근한 느낌은 뭘까요.. 왠지 상위 CL에서 하위 CL의 클래스패스에 있는걸 로딩해버리면 왠지 꼬여버릴 것만 같은 이 기분… 아.. 불안해. 왜 그럴까나..

[ClassLoader 퀴즈 5] Whiteship은 언제 로딩 될까?

public class SpringSprout {

    Whiteship2 whiteship;

    public void makeWhiteship(){
        whiteship = new Whiteship2();
    }
}

이런 클래스가 있을 때 SpringSprout가 참조하는 Whiteship2는 과연 언제 클래스로더에 의해 로딩이 될까요? SpringSprout를 로딩할 때? SpringSprout 객체를 생성할 때? Whiteship2 인스턴스를 생성할 때. 이 중 하나가 아닐까요?

   @Test
    public void whenDoseTheDependeciesWillBeLoaded() throws Exception {
        FileUrlClassLoader fUCL1 = new FileUrlClassLoader(WIN_TEMP_CLASSPATH);
        Class springSproutClass1 = fUCL1.loadClass(TEMP_SPRING_SPROUT);
        Object springSprout1 = springSproutClass1.newInstance();

        assertThat(fUCL1.isLoadedClass(TEMP_WHITESHIP), is(B1));

        Object whiteship1 = springSproutClass1.getMethod(“makeWhiteship”).invoke(springSprout1, null);

        assertThat(fUCL1.isLoadedClass(TEMP_WHITESHIP), is(B2));

        System.out.println(fUCL1.loadClass(TEMP_WHITESHIP).getClassLoader());
        System.out.println(fUCL1.loadClass(TEMP_SPRING_SPROUT).getClassLoader());
    }

그래서 위와 같은 테스트를 만들어봤습니다.
이 테스트가 통과하려면 B1과 B2에는 각각 어떤 값이 들어가야 할까요?

기타 조건은 이전 글과 동일합니다.

[ClassLoader 퀴즈 1] Whiteship은 대체 누가 데려온 것일까?

어제 면접 볼 때 받은 질문이기도 하다. 지금 내가 있는 회사에 어떻게 들어가게 됐으며 누가 데려갔고 그 사람을 어떻게 알게 됐느냐는 질문을 받았었다. 당시 난 클래스로더가 떠올랐다. 대체 누가 날 로딩한 걸까. 사부? Toby? 그 둘은 같은 사람일까?

    @Test
    public void appClassLoader() throws Exception {
        URL url = new URL(“file:C:/intellij9-workspace/springsprout2/test/sandbox/classloader”);
        URLClassLoader uCL1 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass1 = uCL1.loadClass(“sandbox.classloader.Whiteship”);
       
        URLClassLoader uCL2 = new URLClassLoader(new URL[]{url});
        Class whiteshipClass2 = uCL2.loadClass(“sandbox.classloader.Whiteship”);
       
        System.out.println(“URLClassLoader1: ” + uCL1); // C1
        System.out.println(“URLClassLoader2: ” + uCL2); // C2
        System.out.println(“whiteshipClass1 loaded by: ” + whiteshipClass1.getClassLoader()); // C3
        System.out.println(“whiteshipClass2 loaded by: ” + whiteshipClass2.getClassLoader()); // C4
    }

편의상 콘솔에 찍히는 객체 레퍼런스를 C1, C2, C3, C4라고 표기하겠다.
test 이하의 폴더는 현재 클래스패스로 잡혀있는 상태이다.

다음 중 참인 것은?
1. C1과 C3은 같다.
2. C2와 C4는 같다.
3. C1과 C3, C4가 같다.
4. C2와 C3, C4가 같다.
5. C3과 C4는 같다.

정답은 다음 글에서 공개. To be continued!