자바에서 한글 처리 문제 - 아직도냐...

자바는 모든 문자열 처리를 유니코드 기반으로 한다. 초창기에 한글 지원이 제대로 구현되어 있지 않은 적도 있다 하나 지금은 한글 지원에 별 문제가 없는 걸로 알고 있다.  폰트나 IME 연동 등의 문제를 갖고 있는 데스크탑 프로그램 개발이라면 모를까 JEE 개발에서 한글 처리는 더 이상 큰 문제가 되지 않는다고 생각한다.

그렇지만 현실은 별로 그런 거 같지 않다. 우연일진 모르겠지만 결제 연동을 비롯한 여러 가지 외부 서비스 연동에서 한글 문제 관련하여 문제가 안 일어났던 적은 거의 없었다. 거의 100% 연동을 위해 제공한 라이브러리에서 한글을 잘못 처리한다.

하도 그런 경우를 많이 봐서 한글이 깨지는 경우가 발생하면 무엇 때문에 그러는지 짐작이 가게 된다.  그리고 라이브러리를 역컴파일해서 보면 거의 짐작했던 대로이다.

자바는 문자열이 유니코드로 저장되기 때문에 메서드 호출이나 문자열 조작 단계에서 결코 한글이 깨지지 않는다.  모든 문제는 문자열을 바이트 배열로, 또는 바이트 배열을 문자열로 변환할 때 발생한다.  이런 경우는 다음과 같은 때이다.
  • 문자열 객체의 getBytes() 메서드로 바이트 배열을 얻을 때
  • new String(byte[])로 바이트 배열에서 문자열 객체를 얻을 때
  • IO(파일과 Socket IO)에서 - 정확히는 Reader/Writer를 이용할 때
  • JDBC로 데이터베이스와 통신할 때
  • 외부 프로그램을 실행할 때 - java.util.Runtime 객체의 exec 메서드를 호출할 때
  • 프로퍼티 파일을 읽을 때 즉 java.util.Properties 객체의 load 메서드를 호출할 때
문자열과 바이트 배열간 변환에서 반드시 문자셋(charset)이 개입된다. 문자셋을 명시적으로 정할 수 있느냐 아니면 묵시적으로 정해져 있느냐의 차이가 있을 뿐 모든 경우에 문자셋에 따라 변환이 일어나게 된다.

따라서 이런 변환이 있을 것으로 예상되는 부분에 문자셋을 지정하는 부분이 없다면 어떤 문자셋이 묵시적으로 지정되는지를 알아봐야 한다.  만약 이것이 실행하는 환경에 따라 바뀐다면(많은 경우 그렇다) 윈도우 데스크탑인 개발 머신에서는 문제 없던 코드가 리눅스 운영 체제인 실 서버에서 한글이 깨지는 등의 문제가 발생되는 경우가 되는 거다.

실제로 한 결제 모듈에서는 대략 다음과 같은 식으로 서버와 통신을 하는 코드가 있었다.

....

private OutputStream out;

public void send(String message, ...) {
    ....
    byte[] buf = message.getBytes();
    out.write(buf);
    ....
}

위 코드의 문제점은 getBytes() 메서드가 문자열을 바이트 배열로 변환할 때 file.encoding 이라고 하는 시스템 프로퍼티에 지정된 문자셋으로 변환한다는 점이다. 보통 file.encoding 프로퍼티는 실행 환경에 따라 결정된다. 한글 윈도우에서는 cp949(또는 ms949)이며 리눅스 머신에서는 LANG 변수를 어떻게 지정하느냐에 따라 달라진다.  콘솔 환경을 선호하는 SE들은 posix 또는 ascii로 지정하기도 하고 en_US.8859_1으로 지정하는 경우도 있다. 한글 환경으로 설정하더라도 ko_KR.euc-kr인 경우도 있고 ko_KR.utf-8인 경우도 있다.

그래서 이렇게 구현된 코드는 사용 환경에 따라 다르게 작동한다.  따라서 한글 윈도우에서는 문제없던 코드가 리눅스 머신에서는 문제를 일으키는 것이다.  여기에 문제를 해결한답시고 다음과 같은 메서드를 쓰면 문제를 더욱 복잡하게 만들게 된다.

public static String toKor(String src) {
    byte[] b = src.getBytes("8859_1");
    return new String(b, "EUC-KR");
}

위와 같은 식의 처치법은 지금도 검색하면 꽤 해결책이라고 나온다. 그렇지만 이런 코드를 이용하여 한 곳에서 제대로 돌아가게 땜빵을 하면 어김없이 다른 곳에서 문제를 일으키게 된다.

위의 경우 자바를 기동할 때 -D 옵션으로 file.encoding 을 지정하는 방법으로 해결할 수 있다.  그렇지만 이런 해결책은 제공하는 라이브러리를 개발할 때 사용해서는 안 된다.  만약 두 개의 서로 다른 라이브러리가 각기 다른 file.encoding 설정을 요구한다면 어떻게 되겠는가.

많은 경우 문자셋은 직접 지정할 수 있다. 따라서 다음과 같은 규칙을 지키는 것이 좋다.
  • String 객체의 getBytes() 를 쓰지 않고 getBytes(String charsetName)을 사용
  • new String(byte[] b) 가 아니라 new String(byte[] b, String charsetName)을 사용
  • 파일 입출력을 할 때에는 FileReader, FileWriter 클래스를 사용하지 말고 FileInputStream, FileOutputStream 으로 파일을 연 후 InputStreamReader, OutputStreamWriter 를 이용하여 charset을 명시하여 Reader/Writer로 변환할 것.
  • JDBC로 데이터베이스를 연결하는 경우 데이터베이스마다 문자셋을 지정하는 경우가 다르므로 관련 매뉴얼을 참조할 것. 그러나 반드시 어떤 방식으로든 문자셋 지정이 포함된다는 것은 잊지 말 것.
  • java.lang.Runtime.exec 의 경우 문자셋을 외부에서 지정할 수 있는 방법이 없음. C로 되어 있는 외부 프로그램인 경우 핵심 기능이 구현된 라이브러리가 있다면 JNI를 통해 호출할 수 있는지 알아보고 그것이 불가능하다면 한글 처리에 신경을 써야 한다(한글을 base64로 인코딩하여 보내고 외부 프로그램에서 이를 디코딩한다던지)
  • java.util.Properties의 경우 프로퍼티 파일은 무조건 iso-8859-1 문자셋으로 읽게 된다. 한글은 native2ascii 유틸리티를 이용하여 변환해야 한다.
  • Servlet이나 JSP의 경우 좀더 복잡하지만 결국 원리는 같다. IO가 일어나는 부분에서 어떤 문자셋이 지정되는지에만 신경을 쓰면 된다.
그렇지만 아직도 많은 코드들이 실행 환경에 따라 한글이 깨지게 구현되어 돌아가고 있다.  그러면서 이런 말을 하기도 한다. 자바의 "Write Once Run AnyWhere"는 거짓말이라고. 하지만 그 정도 수준의 개발자가 그런 말 할 자격은 없지 않을까.

나는 어떻게 해결하고 있을까? 라이브러리를 역컴파일하여 문제점을 찾아서 버그 패치를 해도 반영해 주는 곳을 보지 못했다.  나의 경우 아예 한글을 쓰지 않는 방법으로 문제를 해결하고 있다.

by Corund | 2008/11/26 16:24 | 트랙백 | 덧글(0)

트랙백 주소 : http://corund.egloos.com/tb/2192664
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶