Google AdSense (text)

hidden logo stop

Moving

거지 같은 이글루스 광고노출 정책이 싫어서,
새 보금자리(http://blog.leocat.kr/)로 이사감.

[Java] InputStream 조심해서 쓰자 TㅅT Computer & Program

jar 파일 안에 있는 클래스 파일을 동적으로 로딩해서 사용하고 jar 파일을 지워야 했다. 아주 간단하게는 JarClassLoader를 사용하면 되지만, 이 클래스를 사용하면 jar 파일을 한 번 물고 놓아주지 않아 jar 파일의 삭제가 불가능하다. 그래서 해결책을 찾고 물어물어 좋은 방법이 있는 포스트를 발견했었다. 이 방법.. 잘 된다..

아.. 지난주까지는 말이다. =ㅅ=;;; 지난주까지는 아무 문제 없이 동작했다. 동적으로 로딩할 클래스 파일의 크기가 작을 때 까지는 말이다.. 헌데 이번주 들어서 점점 클래스가 커져서 클래스 로딩이 안되는 것이다. (커져봐야 600줄도 안되는 간단한 녀석이지만 @ㅅ@) 이녀석이 작으면 로딩이 되는데 커지면 로딩이 실패하는게 아닌가!!

발생한 예외는 java.lang.ClassFormatError로 동일한데 실패 메시지가 컴파일할 때 마다 아주 다양하게 나왔다.

Code segment has wrong length in class file [CLASS_FILE]
Invalid code attribute name index 0 in class file [CLASS_FILE]
Name index 0 in LocalVariableTable has bad constant type in class file [CLASS_FILE]

원인을 찾아 백방 돌아다녔으나 나와는 관계 없는 내용만.. 계속된 구글링 도중 ZipEntry의 InputStream으로 읽으면 8192 바이트밖에 안 읽힌다는 버그 리포트를 발견했다. 헌데 버그 리포트의 처리 결과가 버그가 아니라는 것(Not a bug.)이다!! 어라?? 이게 버그가 아니면 내가 사용한 JarEntry(ZipEntry를 상속 받았음)의 InputStream도 파일을 전부 안 읽을 수 있다는 것인가??

그리하야 InputStream이 안 읽히는 경우를 찾아 검색했더니.. 나같은 사람이 의외로 많네?? 그것도 8192 바이트(8k 바이트)만 읽힌다는 사람이 많다. 나는 11k 바이트 정도만 읽힌 것 같다. 분명 끝이 아닌데 파일을 중간까지만 읽고 말아버리는 것이다. 왜 그런지도 모르겠고.. 답답할 노릇이었는데.. 역시 우리의 API 형님.. 모든 것을 알고 계신다. TㅅT

API에서 우연히 InputStream.available()을 보니 다른 스레드에서 blocking하기 전까지 읽을 수 있는 문자열 개수를 반환한단다. 어라?? blocking이 되면 읽는게 중단되는건가?? 그래서 block되기 전에 available()의 개수만큼만 문자를 읽으라는건가??

그럼 다시 API의 InputStream.read(byte []) 메소드를 보자. 설명이 뭐라고 적혀있나~~ input stream에서 몇 개의 바이트를 읽어 배열에 저장한다.. 응?? 몇 개의 바이트라니?? byte 배열 개수만큼 읽는거 아니었어?? InputStream.read(byte [])의 소스코드를 보면 아래와 같이 되어 있어서 배열의 길이만큼 읽는 것처럼 보이는데.. 그게 아닌 것 같다.

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

그래서 부랴부랴 찾은게.. BufferedInputStream이었다. 부모 클래스인 FilterInputStream의 read(byte [])를 override 없이 사용하는데.. 메소드 설명을 보면, input stream에서 byte.length 만큼 읽어 배열에 넣는단다. 오호라~~ 이게 내가 찾던거로구나~~ 파일 전체를 읽어서 배열에 차곡 차곡 넣어주렴~~

BufferedInputStream으로 바꿔주니 원하는 결과를 낸다. 사실 지금까지 별 신경 안 쓰고 스트림을 사용해 왔는데 API 안 보고 쓰면 이런 낭패도 볼 수 있다는 아주 무서운 교훈을 얻었다. (사실 영어라 해석하기가 귀찮았.. 퍽!! #ㅅ#) 하긴.. 나도 javadoc 주석으로 메소드의 주의점들을 엄청나게 적어놓지만 하나 하나 쓸 때 마다 읽기는 귀찮지 아니한가?? ㅋㅋ 하지만 앞으로는 잘 보고 써야겠다.


이 방법의 원본 포스트에 있는 loadClasses(String) 메소드는 다음과 같이 InputStream이 아닌 다른 스트림을 사용하도록 수정되어야 겠다. 아.. 이왕이면 스트림도 닫아줄까나?? 어차피 파일이 닫히면 함께 닫히겠지만 좋은 코딩 습관을 위해~~


/**
 * 주어진 경로의 jar 파일을 읽어 파일 내의 class를 loading한다.
 *
 * @param jarFilePath class를 loading할 jar 파일 경로
 * @throws FileNotFoundException 주어진 jar 파일이 존재하지 않는 경우
 * @throws IOException jar 파일을 열거나 읽을 때 IOException이 발생하는 경우
 */
private void loadClasses(String jarFilePath) throws FileNotFoundException, IOException
{
    JarFile jarFile = null;
    try
    {
        jarFile = new JarFile(jarFilePath);

        Enumeration <JarEntry> entries = jarFile.entries();
        JarEntry entry = null;
        while(entries.hasMoreElements())
        {
            entry = entries.nextElement();

            if(entry.getName().endsWith(".class"))
            {
                BufferedInputStream jarInputStream = null;
                try
                {
                    String className = getClassName(entry);

                    jarInputStream = new BufferedInputStream(jarFile.getInputStream(entry));
                    byte [] data = new byte [(int) entry.getSize()];

                    jarInputStream.read(data);
                    defineClass(className, data, 0, data.length);
                    this.loadedClasses.add(className);
                } // end of try
                finally
                {
                    if(null != jarInputStream) try {jarInputStream.close();} catch (Exception e ) {/* ignore */} finally {jarInputStream = null;}
                }
            } // end of if class 파일만..
        } // end of while jar 파일에 있는 모든 파일에 대해
    } // end try
    finally
    {
        if(jarFile != null) try { jarFile.close(); } catch(Exception e) { /* ignore */ } finally {jarFile = null;}
    }
}


+ 코드가 길어져서 보기 힘드네 TㅅT 복사해서 메모장에?? ㅋㅋ

덧글

  • 나안 2009/11/06 14:09 # 삭제 답글

    패킷이 MTU사이즈 단위로 잘려버리는 경우를 간혹 발견해서
    원인이 뭔가 찾아다녀보니 InputStream에 이런게 있었던 것이군요~

    잘 참조해보겠습니다.
  • Sigel 2009/11/06 15:27 #

    예.. 그렇더라구요.. 특히 파일 보다 네트웍 스트림인 경우 더 심하더군요..
    네트웍이 느리거나 문제가 있어 늦게 전송되는 경우 내가 예상하는 부분까지 스트림을 읽지 못 하고 중간까지만 읽히더군요.. 그리고 스트림에는 데이터가 또 쌓이고 있고요.. 조심조심 끝까지 읽어줘야 하더군요..

    도움이 되시면 좋겠네요..
  • 지나가다가 2010/01/14 17:39 # 삭제 답글

    read메소드가 읽은 사이즈를 리턴해주는 이유가 배열길이 만큼 다 읽는다는것을 보장하지 않기 때문입니다.

    그래서 while문을 많이 씁니다. 예제코드는 다음과 같이.

    int left = buf.length;
    int pos = 0;
    while(left >0){
    int n = is.read(buf, pos, left);
    left -= n;
    pos += n;
    }

    즉, left가 0이 될때까지 읽도록 루프를 도는것이죠.
  • Sigel 2010/01/14 17:40 #

    그러게요..
    로컬에서 짧은 내용만 확인하다가 갑자기 길어지니 짤리네요..
    항상 조심해야겠습니다..
  • 사랑합니다 2015/08/22 16:27 # 삭제

    안녕하세요 약 5년전인데.. 제가 얼마전까지 해당 문제로 엄청 앓고있었는데
    지나가다님 덕에 해결했습니다..
    정말감사합니다 ㅠㅠ
  • kris 2010/02/09 23:45 # 삭제 답글

    자료 잘 읽었습니다.

    테스트를 해보니 abstract class인 경우 defineClass메서드에서 에러가 발생하는군요..

    아마도 Instance를 찾으려다 보니 못찾는걸로 보이네요..
  • Sigel 2010/02/10 13:43 #

    abstract class로 하면 안되나요??
    defineClass 메소드는 상속받아서 사용하는 protected 메소드인데요.. 이 녀석에 문제가 있는건가 @ㅅ@
댓글 입력 영역

Google AdSense (text/image)