본문 바로가기

Dev.../플밍 관련 자료

[펌] Struts MessageResources에서 한글 문제 해결법

펌 : www.javaservice.net

 

JDK의 ResourceBundle을 쓰건, Struts의 MessageResources를 쓰건 자바의 Properties
파일을 이용하는 이상 인코딩 문제는 피해갈 수가 없습니다. 실제 대부분의 프로퍼티
파일들은 각 시스템의 기본 인코딩으로 작성되는 반면 자바의 Properties는 무조건
ISO-8859-1 인코딩으로만 인식하고 읽기 때문에 이를 기반으로 한 대부분의 클래스들
역시 인코딩의 문제를 피해갈 수 없습니다.(ISO-8859-1로 인식할 수 없는 문자에
대해서는 유니코드로 인식합니다.) 따라서 원죄는 Properties에 있는 것이고 근본적인
해결책은 Properties에 현재의 load, store를 빨리 deprecated시키고 Reader, Writer
혹은 nio를 통해 설정파일에 입출력할 수 있는 메쏘드가 추가되는 것입니다.

그러나, 현실적으로 프로퍼티를 사용하는 곳은 너무나 많고 이를 모두 수정하기는
불가능에 가까운 일입니다. 따라서, 프로퍼티에서 읽을 때 인코딩 변환을 해주는
방법이 가장 현실적인 대안이라고 할 수 있겠습니다. 그렇다면 차선책으로 가장 좋은
것은 Struts에서 이 점을 반영한 패치를 내놓는 것입니다. 이건 동아시아권
개발자들이 참여해야할 부분이라고 생각됩니다. 그러나, 그렇다고 패치가 나오기까지
무작정 기다릴 수는 없겠죠. 그 패치가 다른 나라 개발자들의 동의를 얻을 수
있을지도 미지수이고요. (이 문제는 우리가 유니코드만 쓴다면 아무 문제가 안되는
것입니다. 사실 저 역시 우리도 어서 UTF-16이든 UTF-8이든 유니코드 기반의
인코딩으로 모든 소스들을 전환해야한다고 생각합니다.) 결국 마지막 남은 방법은
우리 개발자들의 몫이겠죠.

다행스럽게도 Struts의 MessageResources 클래스는 어느 정도 추상화가 되어
있습니다. 실제 구현하는 클래스는PropertyMessageResources입니다. 이놈의 코드를
살펴보면, 어떤 로케일로 getMessage를 호출했을 때 해당하는 로케일로 읽어둔
메세지가 없으면 그 때 해당 로케일의 프로퍼티 파일을 찾아서 읽게 되어 있습니다.
근데, 그냥 읽어서 Properties 객체로 갖고 있는 것이 아니라 새로운 HashMap 객체를
생성해서 이곳에 담습니다. (이 개발자도 Properties에서 Hashtable을 사용하는 것은
못마땅했던 모양입니다.) 덕분에 우리는 큰 퍼포먼스 저하 없이 쉽게 인코딩 변환할
할 방법을 찾을 수 있게 되었죠. HashMap으로 옮겨 담는 부분에서 Configuration
등에서 읽어온 인코딩으로 변환을 시켜주면 됩니다. 사실 이 작업을 getMessage를
오버라이드해서 해줄 것인지 HashMap에 옮겨 담는 과정에서 해줄 것인지 고민하긴
했습니다만 결국 중요한 것은 프로퍼티 파일 작성자의 의도가 담긴 객체로 남아 있는
것이라 생각해서 HashMap에 옮겨 담을 때 인코딩 변환을 하도록 구현했습니다.

아직도 문제는 남았습니다. 그렇다면 이 과정으로 Struts 소스를 고쳐서 새로
컴파일하고 사용하는 것이 바람직할까요? 제 생각엔 No입니다. 개발자 자신이
유지보수하는 것이 아닌 패키지를 제공 받아 사용할 때 이를 함부로 수정해서
사용하는 것은 많은 문제를 낳습니다. 당장 Struts 버전업할 때마다 새로 수정,
컴파일, 패키징 과정을 거쳐야하죠. 다행스럽게도 Struts의 개발자들은 이런 부분에
대해 세심한 배려를 해두었습니다. Factory 클래스를 교체할 수 있게 한 것이죠. 이런
배려는 여기 뿐 아니라 Jakarta 프로젝트 전반에 걸쳐 나타나고 있죠.(사실 Abstract
Factory 메쏘드 패턴을 사용할 때 당연히 따라오는 장점이기도 합니다.) 따라서
우리는 PropertyMessageResources와 PropertyMessageResourcesFactory를 상속 받아서
위에서 설명한 부분을 오버라이드하고 이 팩토리를 struts-config.xml에 설정해주면
됩니다. 다음과 유사하게 설정하면 됩니다.

<message-resources parameter="messages" factory="com.hangame.jwdf.util.NativePropertyMessageResourcesFactory"/>


상속 받아 추가된 클래스는 다음과 유사하게 작성하면 됩니다.

NativePropertyMessageResourcesFactory.java
package com.hangame.jwdf.util;import org.apache.struts.util.MessageResources;import org.apache.struts.util.PropertyMessageResourcesFactory;public class NativePropertyMessageResourcesFactory extends PropertyMessageResourcesFactory {		public NativePropertyMessageResourcesFactory() {		super();	}	public MessageResources createResources(String config) {		return new NativePropertyMessageResources(this, config, this.returnNull);	}}


NativePropertyMessageResources.java
package com.hangame.jwdf.util;import net.javaservice.jdf.util.CharConversion;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.struts.util.MessageResourcesFactory;import org.apache.struts.util.PropertyMessageResources;import com.hangame.jwdf.ExceptionHandler;import com.hangame.jwdf.config.Configuration;import com.hangame.jwdf.config.ConfigurationException;import java.io.IOException;import java.io.InputStream;import java.io.UnsupportedEncodingException;import java.util.Iterator;import java.util.Locale;import java.util.Properties;public class NativePropertyMessageResources extends PropertyMessageResources {	Log log = LogFactory.getLog(NativePropertyMessageResources.class);	/**	 *	 */	public NativePropertyMessageResources(MessageResourcesFactory factory, String config) {		super(factory, config);	}	/**	 *	 */	public NativePropertyMessageResources(MessageResourcesFactory factory, String config, boolean returnNull) {		super(factory, config, returnNull);	}	/**	 * Load the messages associated with the specified Locale key.  For this	 * implementation, the config property should contain a fully	 * qualified package and resource name, separated by periods, of a series	 * of property resources to be loaded from the class loader that created	 * this PropertyMessageResources instance.  This is exactly the same name	 * format you would use when utilizing the	 * java.util.PropertyResourceBundle class.	 *	 * @param localeKey Locale key for the messages to be retrieved	 */	protected synchronized void loadLocale(String localeKey) {		if (log.isTraceEnabled()) {			log.trace("loadLocale(" + localeKey + ")");		}		// Have we already attempted to load messages for this locale?		if (locales.get(localeKey) != null) {			return;		}		locales.put(localeKey, localeKey);		// Set up to load the property resource for this locale key, if we can		String name = config.replace('.', '/');		if (localeKey.length() > 0) {			name += ("_" + localeKey);		}		name += ".properties";		InputStream is = null;		Properties props = new Properties();		// Load the specified property resource		if (log.isTraceEnabled()) {			log.trace("  Loading resource '" + name + "'");		}		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();		if (classLoader == null) {			classLoader = this.getClass().getClassLoader();		}		is = classLoader.getResourceAsStream(name);		if (is != null) {			try {				props.load(is);			} catch (IOException e) {				log.error("loadLocale()", e);			} finally {				try {					is.close();				} catch (IOException e) {					log.error("loadLocale()", e);				}			}		}		if (log.isTraceEnabled()) {			log.trace("  Loading resource completed");		}		// Copy the corresponding values into our cache		if (props.size() < 1) {			return;		}		synchronized (messages) {			Iterator names = props.keySet().iterator();			while (names.hasNext()) {				String key = (String) names.next();				if (log.isTraceEnabled()) {					log.trace("  Saving message key '" + messageKey(localeKey, key));				}				String convertedMessage = props.getProperty(key);				try {					Configuration conf = Configuration.getInstance();					convertedMessage = CharConversion.convert(props.getProperty(key), "ISO-8859-1", conf.get("native.encoding"));				} catch (UnsupportedEncodingException e) {					log.fatal("Invalid Encoding..");				} catch (ConfigurationException e) {					log.fatal("Configuration Error..");				}				messages.put(messageKey(localeKey, key), convertedMessage);			}		}	}			/**	 *	 */	public String getMessage(String key) {		String message = super.getMessage(key);		try {			Configuration conf = Configuration.getInstance();			Locale locale = new Locale(conf.get("native.locale.language"), conf.get("native.locale.country"));			message = getMessage(locale, key);		} catch (ConfigurationException e) {			ExceptionHandler.handle(e);		}		return message;	}}


좀더 생각해보면 더 좋은 해결책도 있을 것입니다. 일단 이 정도만 해도 struts에서
별 불편 없이 메세지를 그대로 사용할 수 있을 것입니다.
제목 : Re: xml 은 어떨까요N 글쓴이: 이희승(anoripi)   2003/10/30 23:08:06  조회수:26  줄수:21
잘 이해가 안가는데요 그러니까...프로퍼티즈 파일을 직접 에디터로 수정해서 한글 메시지 같은 것을 넣을 수 없다는문제점을 지적하신 것 같은데요. (\uXXXX 로 표현되는..)더 좋은 방법은 리소스 파일을 xml로 만들어서 쓰는게 아닐까 하네요.일단 XML 은 파서 측에서 엔코딩에 맞게 해석하고, 사람이 직접 수정할 수도 있고, XML 의 장점도 그대로 가져갈 수 있어서 좋다고 봅니다.  JSF cardemo 를 보면 JSF 의 메시지 리소스를 구현해 만든 XML 리소스 클래스가 있습니다.  그것을 참고해도 좋을 것 같네요.--what we call human nature in actually is human habit.--http://gleamynode.net/
제목 : Re: 글쎄요...N 글쓴이: 박영록(poci)   2003/10/31 00:00:02  조회수:8  줄수:12
움. 논쟁 거리가 될 수도 있는 문제를 끄집어내시는군요-_-개인적인 생각으론, XML도 충분히 좋은 선택이나 그렇다고해서 Properties보다 나은선택이라고 말할 수는 없다..정도인 것 같습니다.XML을 쓸 때는 아무래도 그 무게와 복잡성을 고려하지 않을 수 없고, 또 XML을 쓰면서Properties 수준의 key-value 구조만 사용한다면 XML을 쓴 이유가 희석되겠죠.근데 사실 제가 제기한 문제는 이게 아니죠. Properties의 문제를 지적하긴 했지만그건 struts MessageResources가 뜻대로 동작하지 않는 원죄가 Properties에 있다는걸 말하기 위한 거지 Properties 자체의 문제를 해결하자는 것이 목적인 건 아니었죠.struts에서 bean:message 태그를 한글 문제 걱정 없이 쓰기 위한 방편 정도로 보면잘 이해가 안간다는 말을 하실 정도는 아닐 겁니다.
제목 : Re: 글쎄요...N 글쓴이: 이희승(anoripi)   2003/10/31 00:51:50  조회수:11  줄수:13
잘 이해가 안간다는 말은 음..> 프로퍼티즈 파일을 직접 에디터로 수정해서 한글 메시지 같은 것을 넣을 수 없다는> 문제점을 지적하신 것 같은데요. (\uXXXX 로 표현되는..)이게 긴가 민가 하다는 이야기였습니다. ^^--what we call human nature in actually is human habit.--http://gleamynode.net/
제목 : Re: XML 사용해서 성공했습니다N 글쓴이: 박원호(guest)   2003/11/03 10:05:35  조회수:62  줄수:33
메세지 리소스 문제로 저도 고민하다가,XML 가지고 해결하는 방향으로 했습니다.PropertyMessageResources 클래스 대신에MessageResource 클래스를 상속받은 XMLMessageResource 라는 클래스를 작성해서해결했습니다.위에 박영록님이 말씀하신데로Property 정도의 수준으로 XML을 이용하기에는 낭비같지만,다국어 처리에는 이게 가장 쉽고 편한 방법인 것 같습니다.그리고, 데이터 형태도 "CDATA" 타입을 사용하면메세지 안에 내용도 HTML과 긴 내용도 사용이 가능합니다.리소스 관리 측면에서는,처음에 Resource가 초기화 될때 한번만 XML 파싱을 하고,로딩이 끝나면 내용을 "List" 구조에 넣어 두게 되므로성능에서는 큰 차이는 없을 듯 합니다.소스 파일은 첨부된걸 참고하세요.<message-resources null="false"   factory="com.em.struts.util.XMLMessageResourcesFactory"   parameter="com.em.strutsfile.ApplicationResources" />struts-config.xml 파일에 위의 내용을 추가해주셔야 합니다.그리고, 현재 첨부된 소스 파일은 JDOM을 이용해서 구현하였습니다.(XML 처리를 쉽게 하기 위해서.)필요하시다면 JAXP 등으로 구현하셔도 별로 어려움은 없을 듯 합니다.참고하세요.

Download XMLMessageResources.zip (3120 Bytes) XMLMessageResources.zip (3120 Bytes)
Download ApplicationResources.xml (1448 Bytes) ApplicationResources.xml (1448 Bytes)