저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
자바 1.4에 새롭게 추가된 Preference API의 사용법 및 특징에 대해서 살펴본다.

Preference API 사용하기

자바 1.4에는 설정 정보를 보다 쉽게 관리할 수 있는 Preference API가 추가되었다. Prefenrece API는 java.util.Properties 클래스와 마찬가지로 설정 정보를 읽거나 저장할 수 있는 기능을 제공하는데 그 사용방법은 매우 간단하다. 먼저 어떤 설정 정보를 저장하고 싶다면 다음과 같이 하면 된다. 이 코드는 이름이 "Port"인 키에 int 값 8080을 할당하고, "Document Root"인 키에 String 값 "/usr/local/apache/docs"를 할당한다.

  Preferences prefs = Preferences.userNodeForPackage(SomeClass.class);
  prefs.putInt("Port", 8080);
  prefs.putString("Document Root", "/usr/local/apache/docs");

Preference API에서 실제 설정 정보를 저장하고 읽어오는 기능을 제공하는 클래스는 java.util.prefs.Preferences로서, Preferences 클래스의 인스턴스는 위 코드에서 볼 수 있듯이 Preferences 클래스의 정적 메소드인 userNodeForPackage()를 사용해서 구할 수 있다. 이때, Preferences.userNodeForPackage() 메소드에 파라미터로 Class 인스턴스를 전달하는데, 이 Class 인스턴스를 전달하는 이유에 대해서는 'Preferences API의 트리 구조' 부분에서 설명하도록 하겠다.

설정 정보를 읽어오는 것 또한 간단하다. 앞 코드에서 봤던 putXXX 메소드 대신에 getXXX 메소드를 사용하면 된다. 예를 들면 다음과 같다.

  Preferences prefs = Preferences.userNodeForPackage(SomeClass.class);
  int portNo = prefs.getInt("Port", 8080);
  String documentRoot = prefs.getString("Document Root", "/usr/local/apache/docs");

위 코드에서 Preferences.getInt() 메소드는 인자를 두개 전달받는데, 첫번째 인자는 키값이고, 두번째 인자는 기본값이다. 예를 들어, 키값에 해당하는 값이 존재하지 않는다거나, (getInt() 메소드의 경우) 정수형으로 변환할 수 없다거나, 또는 설정 정보를 저장하고 있는 저장소(파일이나 데이터베이스 등)에 접근할 수 없는 경우에 getInt() 메소드는 두번째로 전달받은 기본값을 리턴하게 된다. 따라서 다음 코드에서 getInt() 메소드는 9090을 리턴한다.

  prefs.putInt("Port", 8080);
  int portNo = prefs.getInt("port1", 9090);

지금까지 살펴본 코드에서 눈여겨 볼 부분은 java.util.Properties 클래스와 달리 String 뿐만 아니라 int 타입도 설정 정보로 사용할 수 있다는 점이다. 실제, Preferences 클래스는 다음과 같은 타입에 대한 설정 정보를 사용할 수 있도록 지원하고 있다.

타입 읽기 메소드 쓰기 메소드
String get(String key, String def) put(String key, String val)
boolean getBoolean(String key, boolean def) putBoolean(String key, boolean val)
int getInt(String key, int def) putInt(String key, int val)
long getLong(String key, long def) putLong(String key, long val)
float getFloat(String key, float def) putFloat(String key, float val)
double getDouble(String key, double def) putDouble(String key, double val)
byte[] getByteArray(String key, byte[] def) putByteArray(String key, byte[] val)

허용되는 키와 허용되는 값

Preferences 인스턴스에 설정 값을 저장하거나 읽어올 때 키를 사용하는데, 이때 키의 이름에는 특별한 제한이 없다. 알파벳만을 쓰던, 한글명을 쓰던, 아니면 숫자와 문자를 복합적으로 사용하든 자바의 String 객체이기만 하면 키로 사용할 수 있다. 단, 주의할 점은 키의 길이가 Preferences.MAX_KEY_LENGTH 보다 작아야 한다는 것이다. 참고로, Preferences.MAX_KEY_LENGTH의 값은 80이다.

앞의 표에서 Preferences는 String, byte[], int, long, float, double, boolean 타입을 지원한다고 했는데, 이중 기본 데이터 타입에 해당하는 int, long, float, double, boolean 에는 값에 제한이 없고, String과 byte[]의 경우에는 값이 null이어서는 안 된다. 값의 크기는 바이트로 Preferences.MAX_VALUE_LENGTH (8196 바이트;8K)보다 작아야 한다.

String과 byte[] 배열을 저장할 때는 주의할 점이 있다. 먼저 자바의 String 객체는 한글자가 2 바이트를 차지하는 유니코드를 사용하기 때문에 실제 String 객체로 저장할 수 있는 값의 총 길이는 4K가 되는 셈이다. byte[]의 경우는 RFC2045에 지정되어 있는 Base64 인코딩 방식을 사용하여 인코딩된다. Base64 인코딩을 사용하여 인코딩된 결과는 실제 배열의 길이보다 그 길이가 길기 때문에 배열의 길이만으로는 8K 제한을 확인할 수 없다. 따라서 8K의 3/4 정도 되는 길이의 byte[] 까지만 값으로 사용하는 것이 좋다.


값의 반복검색 및 제거

Preferences 인스턴스가 저장하고 있는 모든 <키, 값> 쌍을 구하고 싶다면 keys() 메소드를 사용하면 된다. key() 메소드는 키값을 갖는 String 배열을 리턴한다. 다음은 keys() 메소드를 사용해서 모든 <키, 값> 쌍을 출력하는 예이다.

  Preferences pref = Preferences.userNodeForPackage( someClass.class);
  try {
      String[] keys = pref.keys();
  
      for (int i = 0 ; i < keys.length ; i++) {
        System.out.println(keys[i] + " = " + pref.get(keys[i], "none");
      }
  } catch(BackingStoreException ex) {
      ....
  }

Preferences 인스턴스에 있는 <키, 값> 쌍을 지울 때는 remove() 메소드나 clear() 메소드를 사용하면 된다. 먼저 remove() 메소드는 다음과 같이 사용한다. 이 코드는 이름이 "Some Key"인 키에 해당하는 쌍을 삭제한다.

  Preferences pref = Preferences.userNodeForPackage( someClass.class);
  pref.remove("Some Key");

remove() 메소드가 특정 키에 대한 삭제 처리를 한다면 clear() 메소드는 모든 키에 대한 삭제 처리를 해준다. 따라서, 모든 키에 대한 값을 제거하고 싶다면 clear() 메소드를 사용하면 된다.

사용자 설정 정보와 시스템 설정 정보

윈도우 2000이나 XP를 보면 사용자에 상관없이 시스템 전체에 적용되는 환경 변수를 지정할 수 있고 또한 각 사용자마다 별도의 환경 변수를 지정할 수 있도록 되어 있는 것을 알 수 있다. 비슷하게 리눅스나 유닉스 같은 시스템도 마찬가지로 전체 시스템에 적용되는 시스템 환경 변수와 각 사용자마다 다르게 적용되는 환경 변수를 지정할 수 있다. Preference API도 이러한 다중 사용자 시스템과 마찬가지로 시스템 전체에 영향을 미치는 설정 정보와 사용자 각각에 영향을 미치는 설정 정보를 별도로 처리할 수 있도록 하고 있으며, 이들을 각각 시스템 설정 정보(system Preference)와 사용자 설정 정보(user Preference)라 부른다.

Preference API는 사용자 설정 정보를 담고 있는 Preference 인스턴스와 시스템 설정 정보를 담고 있는 Preference 인스턴스를 리턴해주는 static 메소드를 제공하고 있으며, 이들 메소드는 다음과 같다.

  • Preference static userRoot()
  • Preference static userNodeForPackage(Class c)
  • Preference static systemRoot()
  • Preference static systemNodeForPackage(Class c)
위의 메소드 중에서 userRoot()와 userNodeForPackage(Class c)는 사용자 설정 정보를 담고 있는 Preference 인스턴스를 리턴하고, systemRoot()와 systemNodeForPackage(Class c)는 시스템 설정 정보를 담고 있는 Preference 인스턴스를 리턴한다.

트리로 구성된 설정 정보

앞에서 사용자 Preference 인스턴스를 구하는 메소드에는 userRoot()와 userNodeForPackage(Class c)가 있다고 했는데, 이때 Root와 Node라는 이름에서 Preference 인스턴스가 트리 형태로 구성되어 있다고 유추해볼 수 있을 것이다. 실제로 Preference API는 트리 형태로 preference 인스턴스들을 구성하고 있다. 각각의 Preference 인스턴스는 트리의 한 노드가 되며, 이 트리의 계층 구조는 패키지 이름을 통해서 결정된다. 다음 그림은 Preference 인스턴스의 트리가 어떻게 구성되는 지를 보여주고 있다.


타원-노드, 사각형-<키,값> 쌍위 그림이 사용자 Preference 인스턴스 트리를 보여주고 있다고 가정할 경우 위 그림에 있는 각각의 키에 대하여 값을 읽어오는 방법은 다음과 같다.

  Preferences rootPref = Preferences.userRoot();
  String val = rootPref.getString("somekey", "default value");
  
  Preferences javacanPref = Prefereces.userNodeForPackage(com.javacan.Description.class);
  val = javacanPref.getString("key", "val");
  
  Preferences mailPref = Preferences.userNodeForPackage(com.javacan.mail.MailManager.class);
  String smtpServer = mailPref.getString("smtp", "mail.javacan.com");
  
  Preferences dbPref = Preferences.userNodeForPackage(com.javacan.db.PoolManager.class);
  String driver = dbPref.getString("driver", "Oracle");
  String pools = dbPref.getString("pools", "test");

그림과 위 코드를 통해서 알 수 있는 것은 각 노드마다 그에 알맞은 Preferences 객체가 존재한다는 것이다. 그리고 각 Preferences 노드는 자신들만의 <키, 값> 목록을 갖고 있다. 따라서 같은 이름을 갖는 키라도 그 키가 어떤 노드에 포함되느냐에 따라서 다른 값을 가질 수 있게 된다.

노드의 경로 및 노드 처리

트리상의 각 노드는 경로를 사용하여 표현할 수 있다. 예를 들어, 앞의 그림에서 이름이 mail인 노드의 경로는 '/com/javacan/mail'로 표현되며, 루트 노드의 경우는 '/'로 표현된다. 이때 경로명은 절대 경로와 상대 경로로 표현할 수 있다. 먼저, 절대 경로는 루트 경로부터 차례대로 표시한다. 즉, mail 노드를 '/com/javacan/mail'로 표시하는 것이 바로 절대 경로이다. 반면에 상대 경로는 특정 노드에 대해서 상대적인 경로를 나타낸다. 예를 들어, '/com' 노드 입장에서 마지막에 있는 mail 노드는 'javacan/mail'과 같이 표현할 수 있다. 상대 경로를 표현할 때는 하위 트리에 포함되어 있는 노드만 표현할 수 있다. 예를 들어, '/com' 노드에서 상대 경로를 사용하여 '/org/jcore' 노드를 표현할 수는 없다는 얘기다.

Preferences 클래스는 node(String pathname) 메소드를 제공하는데, 이 메소드는 경로를 사용하여 다른 Preferences 인스턴스를 구해준다. 예를 들어, 다음과 같이 node() 메소드에 상대 경로 또는 절대 경로를 전달하여 그에 알맞은 Preferences 인스턴스를 구할 수 있다.

  Preferences rootPref = Preferences.userRoot(); // 루트('/') 노드
  Preferences comPref = rootPref.node("com"); // '/com' 노드
  Preferences mailPref = comPref.node("javacan/mail"); // '/com/javacan/mail' 노드
  Preferences jcore = comPref.node("/org/jcore"); // '/org/jcore' 노드

또한, node() 메소드를 사용하면 존재하지 않는 노드를 생성할 수도 있다. 예를 들어, '/child/pref' 노드가 존재하지 않는 상황에서 comPref.node("/child/pref")를 실행하면 '/child/pref' 노드가 생성된다.

일단 노드를 구하면 parent() 메소드를 사용하여 부모 노드를 구할 수 있다.

  // '/com/javacan/db' 노드
  Preferences node = Preferences.userNodeForPackage(com.javacan.db.Pool.class);
  
  Preferences parent = node.parent(); // '/com/javacan' 노드

자식 노드의 경우는 앞에서 설명한 node() 메소드를 사용하여 구할 수 있다는 것을 이미 알고 있을 것이다. Preferences 클래스는 자식 노드의 이름을 알 수 있도록 childrenNames() 메소드를 제공하고 있는데, 이 메소드는 자식 노드의 이름을 String 배열로 리턴한다.

  try {
      Preferences root = Preferences.userRoot();
      String[] childNames = root.childrelNames();
    
      for (int i = 0 ; i < childNames.length ; i++) {
          Preferences child = root.node(childNames[i]);
          ...
      }
  } catch(BackingStoreException ex) {
      ...
  }

removeNode() 메소드를 사용하면 노드를 삭제할 수도 있다. 단, 하위 노드까지 모두 삭제된다. 예를 들면 다음과 같다.

  try {
      Preferences comNode = Preferences.userRoot().node("/com");
      comNode.removeNode();
  } catch(BackingStoreException ex) {
      ...
  }

위와 같이 하면 /com 노드를 비롯한 그 하위에 있는 /com/javacan 을 비롯한 모든 노드를 삭제한다.

flush()/sync() 그리고 이벤트 리스너

flush()와 sync()

Preferences API는 내부적으로 사용하는 설정 정보 저장 장치에 상관없이 동작하도록 설계되었다. 즉, Preferences API는 노드 정보와 <키, 값> 정보를 저장하기 위해 데이터베이스를 사용하든 파일을 사용하든, 또는 원격 서버와 통신을 하든 사용자에게는 동일하게 동작한다. 그런데, 만약 putXXX() 계열의 메소드를 사용하여 설정 정보를 지정할 때마다 또는 새로운 노드를 생성할 때 마다 매번 데이터베이스, 파일, 또는 원격 서버에 그 결과를 저장한다면 성능상에서 문제가 발생할 수 있을 것이다.

그래서 Preference API는 노드의 변화나 키, 값의 변화가 발생할 때 마다 매번 저장하지 않고 대신 변경된 내용을 메모리상에 저장해두었다가 flush() 메소드를 사용하여 한번에 저장하도록 하고 있다.

  Preferences pref = Preferences.userRoot();
  
  pref.putInt("X", 95);
  pref.putInt("Y", 100);
  
  ... // 변경 내용이 아직 저장장소에 적용되지 않는다.
  pref.flush();
  
  // 변경 내용이 저장장소에 적용되었다.

단, 주의할 점은 반드시 flush() 메소드를 사용해야만 저장되는 것은 아니라는 점이다. 예를 들어, 어떤 JDK 구현체는 쓰레드를 사용하여 일정 주기로 변경된 데이터를 실제 저장소에 반영하도록 구현되어 있을 수도 있다. 중요한 건 flush() 메소드를 사용하면 확실하게 저장소에 변경된 내용이 반영된다는 점이다. 변경된 내용이 곧바로 반영되지 않는다는 것은 서로 다른 어플리케이션이 동시에 같은 시스템 노드에 접근할 때 데이터가 동기화되지 않는다는 것을 의미한다.

비슷하게, getXXX() 메소드를 호출할 때에도 데이터를 읽어오기 위해 매번 저장소에 접근하는 것은 아니기 때문에 다른 어플리케이션에서 flush() 메소드를 호출하여 변경 내용을 저장소에 반영한다 하더라도 반영된 데이터를 읽어오지 못하는 경우가 발생한다. Preference API는 반영된 내용을 getXXX() 메소드를 사용할 때 읽어올 수 있도록 하기 위해서 sync() 메소드를 제공한다. sync() 메소드를 수행하면, 다음번에 데이터를 읽어올 때 저장소에 있는 실제 내용을 읽어올 수 있게 된다. 즉, flush() 메소드와 sync() 메소드는 서로 보완적인 기능을 제공하고 있는 것이다.

flush() 메소드와 sync() 메소드를 호출할 때 알아야 할 점은 Preferences 인스턴스가 나타내는 노드 및 그 하위 노드에 대해서만 적용된다는 점이다. 예를 들어, '/com/javacan' 노드에 해당하는 Preferences 인스턴스의 flush() 메소드를 호출하였다면, '/com' 노드의 변경된 <키, 값> 쌍은 적용되지 않고 '/com/javacan' 노드나 '/com/javacan/mail' 노드와 같이 서브트리에 속해 있는 노드에 대해서만 변경된 결과가 적용된다. sync() 메소드도 마찬가지로 Preferences 인스턴스 노드를 상위노드로 하는 서브트리에 대해서만 변경된 데이터를 읽어온다.

이벤트 리스너

Preference API는 키값이 변경되거나 자식노드가 추가 또는 삭제될 경우 이벤트를 발생하여 어플리케이션에서 알맞게 처리할 수 있도록 해 주고 있다. 이 두 이벤트는 PreferenceChangeEvent와 NodeChangeEvent이며, 이 두 이벤트는 PreferenceChangeListener와 NodeChangeListener에 전달된다.

PreferenceChangeListener

PreferenceChangeListener는 설정 정보가 새롭게 추가, 제거 또는 변경되는 경우에 알맞은 처리를 하고 싶을 때 사용된다. (AWT의 이벤트 위임 모델과 같은 이벤트/리스너 방식을 사용한다.) 각각의 노드마다 리스너를 등록할 수 있는데, 이는 노드마다 별도로 이벤트 처리를 할 수 있다는 것을 의미한다.

이벤트 처리를 하려면 먼저 PreferenceChangeListener 인터페이스를 구현한 클래스를 작성하고, preferenceChange() 메소드를 알맞게 구현하면 된다. 다음은 PreferenceChangeListener 인터페이스를 구현한 예를 보여주고 있다.

  class RootNodeListener implements PreferenceChangeListener {
      public void preferenceChange(PreferenceChangeEvent pcs) {
          System.out.println(pce.getKey() + " = " + pce.getNewValue());
      }
  }

위 코드에서 보듯이 PreferenceChangeListener 인터페이스를 구현한 클래스는 preferenceChange() 메소드를 구현해주어야 한다. PreferenceChange() 메소드는 PreferenceChangeEvent 객체를 파라미터로 전달받는다. PreferenceChangeEvent 객체는 변경된 키와 그 값을 저장하고 있으며, 키와 값은 각각 getKey() 메소드와 getNewValue() 메소드를 사용하여 구할 수 있다. 위에서 작성한 이벤트 리스너는 다음과 같이 Preferences.addPreferenceChangeListener() 메소드를 사용하여 등록할 수 있다.

  Preferences userPref = Preferences.userRoot();
  userPref.addPreferenceChangeListener(new RootNodeListener());

  userPref.put("someKey", "someValue");
  userPref.put("somekey", "value2");
  userPref.remove("someKey");

위 코드를 수행했을 때 출력되는 결과는 다음과 같다. 이 출력 결과를 보면 삭제된 키에 대한 값은 null이라는 것을 알 수 있다.

  someKey = someValue
  somekey = value2
  someKey = null

PreferenceChangeEvent 클래스는 또한 getNode() 메소드를 제공하는 데, 이 메소드는 이벤트가 발생한 노드의 Preferences 인스턴스를 리턴한다.

NodeChangeListener

NodeChangeListener는 자식 노드가 추가되거나 삭제될 때 알맞은 처리를 할 수 있도록 해 준다. 다음은 NodeChangeListener 인터페이스를 구현하여 이벤트를 알맞게 처리할 수 있도록 해주는 클래스의 한 예이다.

  class RootNodeListener implements NodeChangeListener {
      public void childAdded(NodeChangeEvent nce) {
          System.out.println(
              nce.getParent().getName()+" 노드에 자식 추가:"+nce.getChild());
      }
      
      public void childRemoved(NodeChangeEvent nce) {
          System.out.println(
              nce.getParent().getName()+" 노드의 자식 저게:"+nce.getChild());
      }
  }

NodeChangeEvent 클래스는 getParent() 메소드와 getChild() 메소드를 제공하는데, getParent() 메소드는 제거되거나 삭제된 노드의 부모 노드에 해당하는(즉, NodeChangeListener를 등록한) Preferences 인스턴스를 리턴하고, getChild() 메소드는 추가되거나 삭제된 노드에 해당하는 Preferences 인스턴스를 리턴한다.

NodeChangeListener를 등록할 때는 다음과 같이 addNodeChangeListener() 메소드를 사용하면 된다.

  Preferences pref = Preferences.userRoot();
  pref.addNodeChangeListener(new RootNodeListener());
  
  ...

NodeChangeListener와 관련해서 알아두어야 할 점은 바로 하위 노드의 추가/제거에 대해서만 이벤트가 발생한다는 점이다. 에를 들어, 다음 코드를 보자.

  Preferences rootNode = Preferences.userRoot();
  rootNode.addNodeChangeListener(new RootNodeListener());
  
  // /madvirus 노드가 추가되므로
  // RootNodeListener의 childAdd() 메소드가 호출
  Preferences madvirusNode = pref.node("madvirus");
  
  // /madvirus/homepage 노드가 추가되지만,
  // rootNodeListener의 childAdd() 메소드는 호출되지 않음
  Preferences homepageNode = madvirusNode.node("homepage");

위 코드에서 루트 노드에 NodeChangeListener를 등록하였는데, 루트 노드의 바로 하위 노드인 '/madvirus'를 추가할 때는 NodeChangeListener가 사용되지만 바로 하위 노드가 아닌 '/madvirus/homepage'가 추가될 때는 NodeChangeListener가 사용되지 않는다.

결론

이 글에서 우리는 자바 1.4에 새롭게 추가된 Preferences API를 통해서 설정과 관련된 보다 다양한 기능을 사용할 수 있다는 것을 배웠다. Preferences API가 기존의 java.util.Properties 클래스에 비해 더욱 다양한 기능을 제공하게 된 이유는 Preferences API를 설계할 때 다음과 같은 것들을 목적으로 삼았기 때문이다.

  • 트리 구조의 데이터를 저장하는 계층적 구조를 제공해야 한다.
    계층적 구조를 사용함으로써 개발자들은 보다 유연하고 체계적으로 설정 정보를 사용할 수 있게 된다.

  • 파일의 위치를 기억할 필요가 없어야 한다.

  • 설정 정보 저장소가 사용 가능하지 않더라도 동작해야 한다.
    예를 들어, 설정 정보를 데이터베이스에 저장했을 때 데이터베이스가 동작을 멈춘다 해도 설정 정보를 사용하는 어플리케이션은 알맞게 수행되어야 한다.

  • 사용자 데이터와 시스템 데이터를 구분할 수 있어야 한다.

  • 다른 패키지 내지 어플리케이션의 데이터와 독립적이어야 한다.

  • 명시적으로 데이터를 저장하거나 읽을 필요가 없어야 한다.
    즉, 설정 정보를 저장하고 있는 파일의 위치를 기억할 필요가 없으며, 데이터의 읽고 쓰기가 API에 의해 알맞은 때 수행된다.
실제로 필자는 현재 개발하고 있는 일부 모듈에서 Preferences API를 사용하여 설정 정보를 활용하고 있는데, 위에서 언급한 설계 목적이 그대로 설정 정보 관리의 편리함으로 이어지고 있음을 느끼고 있다. 특히, 패키지이름을 사용하여 계층 구조로 설정 정보를 관리할 수 있다는 것은 설정 정보를 유연하면서도 체계적으로 관리할 수 있게 해준다. 게다가 java.util.Properties가 String 타입만 가능한 것에 비해 Preferences API는 기본 데이터 타입도 사용할 수 있다는 장점을 갖고 있다. 만약 위에서 언급한 설계 목적이 여러분이 필요로 하는 설정 정보 처리 시스템에 부합된다면, Preferences API를 사용해볼 것을 강력하게 권한다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. Yarmini 2011.10.13 16:09 신고  댓글주소  수정/삭제  댓글쓰기

    Java Preference 사용법이 상당히 잘되어있네요 감사합니다. _ _

  2. ㅊㅊㅊ 2015.10.06 12:56 신고  댓글주소  수정/삭제  댓글쓰기

    감사합니다!!

Java 1.4는 비윈도우 환경에서도 이미지를 생성할 수 있다.

자바1.4-Headless 기능 지원

이미지 처리와 관련해서 1.4 이전버전까지 자바의 문제점 중의 하나는 비윈도우(non-window) 환경에서 AWT나 Swing과 같은 GUI 컴포넌트를 사용할 수 없다는 점이었다. 예를 들어, 다음 클래스를 살펴보자.

    import java.awt.*;
    
    public class TestGUI {
        public static void main(String[] args) {
            Toolkit toolkit = Toolkit.getDefaultToolkit();        }
    }
    

TestGUI 클래스를 비윈도우 환경의 리눅스나 유닉스 명령행에서 실행하면 다음과 비슷한 예외가 발생한다.

    [madvirus@wolf temp]$ java TestGUI
    Exception in thread "main" java.lang.InternalError: 
    Can't connect to X11 window server using
    ':0.0' as the value of the DISPLAY variable.
        at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)
        at sun.awt.X11GraphicsEnvironment.<clinit>(X11GraphicsEnvironment.java:126)
        ....
        at TestGUI.main(TestGUI.java:5)

자바 1.3 버전까지는 비윈도우 환경에서 Toolkit과 같은 AWT/Swing 관련 클래스를 사용할 수 없었기 때문에, 웹 어플리케이션에서 동적으로 이미지를 생성해야 하는 경우 BufferedImage를 곧바로 사용하지 못하고 별도의 이미지 처리 라이브러리를 구매하거나 작성해야만 했다.

시간이 지날수록 웹 어플리케이션에서 동적으로 이미지를 생성해야 하는 요구사항이 증가하기 시작했으며, 따라서 비윈도우 환경에서 GUI 클래스를 마음대로 사용하지 못하는 자바의 문제점은 개발자들을 귀찮게 하는 요인중의 하나가 되었다. 이를 처리해줄 수 있는 별도의 라이브러리를 작성하거나 구매해서 사용해야 했다.

그래서, 이러한 개발자들의 불만을 해소시키기 위해 자바 1.4 버전에는 비윈도우 환경에서도 GUI 클래스를 사용할 수 있는 기능이 추가되었다. 이 기능은 매우 간단해서 명령행에서 다음과 같이 시스템 프로퍼티를 지정해주면 된다.

    java -Djava.awt.headless=true com.some.RunningClass
    

"java.awt.headless"는 자바 1.4에 새롭게 추가된 시스템 프로퍼티로서 비윈도우 환경에서 실행되는지의 여부를 설정할 때 사용된다. 이 프로퍼티의 값을 "true"로 지정하면 비윈도우 환경에서도 GUI 관련 클래스를 사용할 수 있게 된다. 따라서, 앞에서 작성한 TestGUI 클래스를 다음과 같이 실행하면 예외가 발생하지 않게 된다.

    java -Djava.awt.headless=true TestGUI

java.awt.headless 시스템 프로퍼티의 기본값은 "false"이다.

동적으로 이미지 생성하기

java.awt.headless 시스템 프로퍼티의 값을 "true"로 지정하면 다음과 같이 java.awt.image.BufferedImage와 java.awt.Grahpics 클래스 그리고 자바 1.4에 새롭게 추가된 javax.imageio.ImageIO 클래스를 사용하여 동적으로 GIF/JPEG 이미지를 생성할 수 있다. 동적으로 이미지를 생성하는 순서는 다음과 같다.

  1. BufferedImage 클래스를 사용하여 원하는 크기의 버퍼 이미지를 생성한다.
  2. 생성된 버퍼 이미지와 관련된 Graphics 객체를 구한다.
  3. Graphics 객체를 사용하여 원하는 이미지를 그린다.
  4. ImageIO 클래스를 사용하여 버퍼 이미지를 GIF 내지 JPEG 이미지 파일로 변환한다.
실제 코드로 보면 다음과 같다.

    
    int width = 200; // 이미지 세로 크기
    int height = 150; // 이미지 가로 크기
    
    BufferedImage image = new BufferedImage(width, height,
        BufferedImage.TYPE_3BYTE_BGR);    
    Graphics g = tempBuffer.getGraphics();    // 또는 
    // Graphics2D g = tempBuffer.createGraphics();
    
    ... // Graphics 객체를 사용하여 원하는 이미지를 그린다.
    
    // 파일을 생성한다.
    File outFile = new File("c:\temp.jpg");
    ImageIO.write(image, "jpg", outFile);    

위와 같이 하면 Grahpics 객체를 통해서 그린 이미지를 c: emp.jpg 파일로 저장할 수 있다. 만약 JPEG이 아닌 GIF 포맷으로 저장하고 싶다면, ImageIO.write() 메소드의 두번째 파라미터에 "jpg" 대신 "gif"를 전달하면 된다.

ImageIO.write() 메소드에는 File 객체 대신 OutputStream을 파라미터로 전달받는 것도 존재한다. 즉, 다음과 같이 출력할 수도 있다.

    FileOutputStream outStream = new FileOutputStream("c:\temp.jpg");
    ImageIo.write(image, "jpg", outStream);
    outStream.close();

위에서 OutputStream을 사용할 수 있다는 것은 서블릿에서 이미지를 동적으로 생성하여 웹브라우저에 출력할 수 있다는 것을 의미한다. 서블릿에서 동적으로 이미지를 생성하고자 하는 경우 다음과 같이 코드를 작성하면 된다.

    public class ImageCreator extends HttpServlet {
        public void doGet(HttpServletRequest request,
                          HttpServletResponse response)
            throws IOException, ServletException {
            
            response.setContentType("image/jpeg");
            ServletOutputStream outStream = response.getOutputStream();            
            BufferedImage image = BufferedImage(width, height,
                BufferedImage.TYPE_3BYTE_BGR);
            Graphics g = image.getGraphics();
            
            // Graphics 객체를 사용하여 원하는 이미지를 생성한다.
            ...
            
            ImageIO.write(image, "jpg", outStream);            
            outStream.close();
        }
    }

위와 같이 서블릿 클래스를 사용했다면 다음과 같이 서블릿을 호출하여 웹브라우저에서 이미지를 볼 수 있다.

    <img src="http://some.com/../servlet/ImageCreator" width="100" height="100">

결론

자바 1.3 버전까지만 해도 비윈도우 환경에서 Image 객체를 출력 스트림으로 보내기 위해서는 비표준 클래스인 com.sun.image.codec.jpeg.JPEGImageEncoder를 사용하거나 별도의 라이브러리가 필요했으나, 이제 자바 1.4의 java.awt.headless 시스템 프로퍼티와 javax.imageio.ImageIO 클래스를 사용함으로써 비윈도우 환경에서도 얼마든지 GIF와 JPEG 이미지를 생성할 수 있게 되었다.

이제 자바 1.4도 출시된 지 1년정도 되었고, 지금은 안정화된 1.4.1 버전이 출시된 상태이다. 따라서 새롭게 추진되는 프로젝트에서 비윈도우 환경에서 이미지를 동적으로 생성해야 하는 경우 별도의 라이브러리가 없어도 그리고 비표준 API를 사용하지 않고도 이미지를 생성할 수 있을 것이다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

1.4에 새롭게 추가된 Assertion이 무엇이며 어떻게 사용하는지에 대해서 살펴본다.

Assertion 기본

자바 1.4 버전은 1.3 버전에 보안을 비롯한 다양한 확장 API를 추가하여 그 크기가 방대해졌을 뿐만 아니라 NIO와 로깅 등 새로운 기능을 추가함으로써 완벽한 개발 플랫폼으로 확장되었다. 이러한 새로운 기능들 중에서 자바에서는 전혀 새로운 기능이라고 할 수 있는 게 Assertion 기능이다.

Assertion은 무엇인가

JSR(Java Specifiaction Request) 41을 보면 다음과 같이 Assertion에 대해서 정의를 내리고 있다.

Assertion은 불리언 식(expression)을 포함하고 있는 문장으로서, 프로그래머는 그 문장이 실행될 경우 불리언 식이 참이라고 단언할 수 있다.
다시 말해서 개발자가 자신이 개발한 프로그램에서 가정하고 있는 사실이 올바른 지 검사할 수 있도록 하기 위해서 자바 1.4에 추가된 기능이 바로 Assertion인 셈이다. 예를 들어, 이러한 가정에는 이자율에 해당하는 변수가 0보다 커야 한다는 것 등이 있을 수 있다. Assertion은 그러한 가정이 올바른 지 검사할 수 있도록 함으로써 프로그램이 부드럽게 수행될 수 있도록 도와준다. 이러한 검사 기능은 예외(Exception)를 사용해서도 구현할 수 있긴 하지만, Assertion은 예외와는 다른 의미의 검사 기능을 제공한다. 이에 대해서는 이 글을 진행하면서 살펴볼 것이다.

왜 Assertion을 사용하는가?

사실, 예외를 사용하면 에러 검사 기능을 어느 정도 처리할 수 있다. 그런데 왜 Assertion이라는 새로운 검사 기능을 도입한 것일까? 이제 이 질문에 답을 해 보도록 하자. 먼저 자바의 예외는 주로 프로그램을 실행하는 도중에 예상하지 못한(또는 드물게 발생하는) 상태를 처리하는 데 사용된다. 예를 들어 파일을 열수 없다던가, 네트워크의 연결이 끊어졌다던가 하는 등의 이상 상태를 처리할 때 주로 예외가 사용된다. 반면에 Assertion은 개발자가 참이라고 가정하는 상태를 명시하기 위해서 사용된다. 에를 들어, 어떤 메소드가 파라미터로 양수만 입력받아야 한다고 확신한다면, Assertion을 사용하여 그 사실을(즉, 파라미터가 양수라는 것을) 명시할 수 있다.

예외는 프로그램을 수행하는 도중에 발생하는 비정상적인 상태를 처리하기는 하지만 프로그램의 코드가 수행되는 도중에 예외가 발생한다. 반면에 Assertion은 프로그램이 올바르게 수행될 수 있는 조건을 명시해주어 그 조건을 만족시키는 경우에만 코드가 실행될 수 있도록 해 준다. 따라서 Assertion은 프로그램이 올바르게 실행되도록 해주는 효과적인 도구가 될 수 있으며, 프로그램의 안정성을 높여줄 수 있다.

Assertion의 문법

Assertion 문장은 다음과 같은 두가지 형태를 취한다.

  assert 불리언식;  assert 뷸라온식:수식;

첫번째 형태는 간단한 형태로서 불리언식을 인자로 받는다. 불리언 식은 개발자가 참(true)이라고 생각하는 식이다. 만약 그 가정이 틀렸다면(즉, 불리언식이 false가 된다면), 불리언식은 Assertion이 틀렸음을 나타내는 false 값을 리턴하고, 불리언식이 성공한 경우에는 프로그램의 수행이 정상적으로 진행된다.

두번째 형태는 좀더 상황에 맞는 설정을 하는 것으로서, 첫번째 인자에는 불리언 식이 오며, 두번째 수식에는 Assertion이 틀렸을 때 사용할 수식이 온다. 두번째 수식은 값이 될 수도 있고, 또는 메소드를 수행한 결과값이 올 수도 있다. 두번째 수식이 void 값을 리턴할 경우 컴파일러는 에러를 발생시킨다.

첫번째 형태와 두번째 형태 모두 불리언식이 틀렸을 때, 즉 불리언식이 false를 리턴하면 AssertionError를 발생시킨다. 일반적인 예외와 마찬가지로 AssertionError도 스택 트레이스의 한 부분으로서 보여진다. 두번째 형태에서 지정한 수식의 결과값은 AssertionError의 생성자에 전달되어 스택 트레이스에 출력되는 메시지로 사용된다.

다음은 몇가지 assert의 사용 예를 보여준 것이다.

    assert i<0;
    assert (!pageNo.equals(""));
    assert age > 0 : "나이는 음수가 될 수 없습니다:"+age;
    assert ((i/2*23-12)>0):checkValue();

위의 예에서 첫번째 assert 문장은 i가 0보다 작지 않을 경우 AssertionError를 발생시킨다. 세번째 assert 문장의 경우는 age가 0보다 크지 않은 경우 AssertionError를 발생시키며, 그때 AssertionError의 예외 메시지는 "나이는 음수가 될 수 없습니다:"와 age 변수를 연결한 문자열이 된다. 네번째 예제의 checkValue() 메소드는 void가 아닌 값을 리턴해야만 한다.

Assertion의 사용

Assertion은 예외를 대체하진 않지만 예외보다 효과적인 기능을 제공할 수가 있다. 프로그램을 매끄럽게 실행하기 위해서는 Assertion의 위치가 중요하다. 만약 Assertion의 위치가 올바르지 않다면 Assertion이 아무 소용이 없기 때문이다.

전위조건으로 Assertion 사용하기

Assertion을 사용하기 가장 좋은 경우는 어떤 동작을 수행하기 전에 수행에 필요한 값들의 조건을 검사하는 것이다. 예를 들어, 회원 가입을 처리하는 register(MemberInfo member)라는 메소드를 생각해보자. register() 메소드는 다음과 같이 전달받은 파라미터를 검사할 수 있을 것이다.

    private void register(MemberInfo member) {
        assert Member != null:"MemberInfo cannot be null";
        assert member.getId() != null && !member.getId().equals("");
        
        ...
    }

후위조건으로 Assertion 사용하기

어떤 동작을 수행하기 전에 값들을 검사할 때 Assertion을 사용하는 것과 반대로, 동작을 수행한 후의 결과값들을 검사하기 위해서도 Assertion을 사용할 수 있다. 예를 들어, 어떤 값을 리턴하기 전에 assert 문장을 사용하여 리턴할 값이 올바른지의 여부를 판단할 수 있다.

Assertion을 사용해선 안 되는 경우

예외가 모든 경우에 알맞은 것이 아니듯이, Assertion도 모든 경우에 알맞지는 않다. 다음의 경우에는 Assertion을 사용하지 않는 것이 좋다.

  • public 메소드의 파라미터를 검사하는 경우
  • 올바른 수행을 위해 필요한 작업을 수행하는 경우
먼저 public 메소드의 파라미터를 검사하는 경우를 살펴보자. 일반적으로 public 메소드에 전달된 파라미터를 검사할 때에는, 파라미터의 값이 잘못된 경우에 메소드에 알맞은 의미를 나타내지 않는 AssertionError 보다는 IllegalArgumentException과 같이 알맞은 의미를 갖는 예외를 발생시키는 것이 좋다.

또한, 올바른 수행을 위해서 필요한 작업을 assert 문장에서 실행하면 안된다. 예를 들어, 다음과 같은 코드를 생각해보자.

    assert checkName();

1.4버전의 자바가상환경(JRE)는 기본적으로 Assertion 기능을 사용하지 않는다. 따라서, 기본적으로 기능을 사용하도록 명시하지 않는다면 위의 메소드는 실행되지 않을 것이다. 따라서, 올바른 수행을 하는 데 반드시 필요한 코드는 assert 문장에 넣어서는 안 되며, 다음과 같이 해 주어야 한다.

    boolean checked = checkName();
    assert checked;

Assertion을 사용하는 소스코드의 컴파일 및 실행

1.4 버전의 컴파일러는 컴파일하는 동안에 Assertion 기능을 사용하지 않기 때문에, Assertion 기능이 사용되도록 컴파일 하기 위해서는 컴파일러를 실행할 때 다음과 같이 -source 옵션을 사용해야 한다.

    javac -source 1.4 AssertionTest.java 

assert는 1.4 버전에 새롭게 추가된 키워드이기 때문에, assert를 포함한 소스 코드를 1.4 이전 버전의 컴파일러를 사용하여 컴파일할 경우에는 컴파일 에러가 발생한다.

Assertion 기능을 사용할 수 있도록 컴파일 하는 것으로 Assertion을 사용할 수 있는 것이다. 1.4 버전이 자바실행환경(JRE)은 클래스가 Assertion 기능이 사용되도록 컴파일됐는지의 여부에 상관없이 기본적으로 Assertion 기능을 사용하지 않는다.

Assertion 기능을 사용하거나 사용하지 않으려면 명령행에서 -ea 또는 -enableassertions 옵션과 -da 또는 -disableassertions 옵션을 사용하면 된다. -ea 옵션은 Assertion 기능을 사용할 수 있도록 해 주고, 반대로 -da 옵션은 Assertion 기능을 사용할 수 없도록 해 준다. 예를 들어, 클래스를 실행할 때 Assertion 기능을 사용가능하도록 하기 위해서는 다음과 같이 하면 된다.

    java -ea AssertionTest

때에 따라서는 특정 패키지에 속한 클래스나 특정 클래스에 대해서만 Assertion 기능을 적용하고 싶은 경우가 있을 수 있다. 이런 경우에는 -ea 옵션 다음에 다음과 같은 값을 입력해주면 된다.

  • 패키지명... : 패키지와 그 하위 패키지에 있는 모든 클래스에 적용
  • 클래스명 : 특정 클래스에 대해서만 적용
예를 들어, com.javacan.member 패키지에 있는 모든 클래스에 대해서만 Assertion 기능을 수행하고 싶다면 다음과 같이 실행하면 된다.

    java -ea:com.javacan.member... ViewMemberInfo

-da 옵션도 -ea 옵션과 마찬가지로 패키지 단위 또는 특정 클래스에 대해서 Assertion 기능을 수행하지 않게 하고 싶은 경우에 위와 같은 방식으로 값을 지정할 수 있다. 예를 들어, com.javacn.member 패키지에 있는 모든 클래스에 대해서 Assertion 기능을 수행하고 싶지만, com.javacan.member.MemberUtil 클래스에 대해서는 Assertion 기능을 수행하고 싶지 않다면 다음과 같이 실행하면 된다.

    java -ea:com.javacan.membmer... -da:com.javacan.member.MemberUtil ViewMemberInfo

결론

JDK 1.0의 이벤트 모델을 사용하던 개발자가 JDK 1.1부터 적용된 위임형 이벤트 모델을 사용해야 하는 경우와 비슷하게, Assertion을 사용하는 것은 아직 많은 개발자에게 익숙하지 못한 기능이다. 물론, Assertion 개념을 포함하고 있던 언어를 사용해본 경험이 있는 개발자들은 자바의 Assertion 기능을 적극적으로 사용하겠지만, 자바 1.4 이전 버전의 예외 처리 방식에 익숙한 개발자들의 경우는 Assertion 기능을 알맞게 사용하기 까지 시간이 걸릴 것이다.

하지만 Assertion 기능을 사용하기까지 시간이 걸린다 하더라도, 콜렉션 API가 그랬던 것처럼 Assertion 기능도 점차 많은 개발자들에게서 사용될 것으로 생각되며 차기 버전에서는 보다 발전된 모습으로 다가올 것이라 믿는다. 지금부터라도 Assertion 기능을 활용하는 습관을 들인다면 깔끔한 코드를 작성할 수 있는 개발자로 거듭날 수 있게 될 것이다.

강제적으로 Assertion 기능을 사용하도록 만드는 방법 자바실행환경은 기본적으로 Assertion 기능을 사용하지 않기 때문에, 강제적으로 Assertion 기능을 사용하도록 유도하는 방법이 필요하다. 이런 경우에 사용할 수 있는 코드가 바로 다음과 같다.

    static {
        boolean assertsEnabled = false;
        assert assertsEnabled = true;        if (!assertsEnabled)
            throw new RuntimeException("Assertion 기능이 사용가능해야 합니다!");
    }    

위 코드를 main() 메소드가 있는 클래스에 추가하면 Assertion 기능을 사용하도록 유도할 수 있다. 예를 들어, Assertion 기능을 사용하지 않는 경우 위 코드의 assert 문장이 실행되지 않으므로 assertsEnabled 변수의 값이 false인 상태로 남게 될 것이며 RuntimeException이 발생하게 될 것이다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. soul_동이 2009.03.20 15:21 신고  댓글주소  수정/삭제  댓글쓰기

    많은 도움이 되었습니다. 퍼갈께요~ㅎ

  2. 2010.03.28 07:23  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  3. 광후니 2013.02.19 00:03 신고  댓글주소  수정/삭제  댓글쓰기

    많은 도움 되었습니다. ^^

  4. java 2013.05.18 22:34 신고  댓글주소  수정/삭제  댓글쓰기

    저도 많은 도움되었습니다.
    assertion이 javascript 에서도 가능한지요?

JDK1.4! Comming Soon!

Java 2001.06.01 12:50
JDK 1.4의 새로운 특징에 대해서 살펴본다.

JDK1.4의 새로운 특징들

최근에 많은 자바 개발자들이 기다려오던 자바2 SDK 1.4 베타 버전이 발표되었다. 이는 올해 안에 자바2 SDK 1.4 버전이 정식으로 발표될 것이라는 것을 나타내기도 한다. 필자는 자바 매니아의 한 사람으로서 자바2 SDK 1.4 버전의 특징을 새로 추가된 기능에 중점을 두어 나열하고자 한다. 참고로 필자는 자바2 SDK 1.4 Documentation 에 포함되어 있는 Summary of New Features (http://java.sun.com/j2se/1.4/docs/relnotes/features.html)에 기반하여 이 글을 작성했음을 밝힌다.

자바2 SDK 1.4 버전은 서버사이드를 제외한 클라이언트에서 필요로 하는 대부분의 API가 포함되어 있다고 해도 무리가 없을 정도로 그 크기가 방대해졌다. 또한, 서버사이드에서 기본적으로 필요로 하는 기능 역시 추가되었다. 자바2 SDK 1.4 버전은 그야말로 완벽한 개발도구를 지향하고 있다고 할 수 있다. 이 글에서는 모든 새로 추가된 특징이나 향상된 점에 대해서는 다루기 힘드므로 필자 나름대로 중요하다고 생각되는 것을 중점적으로 살펴보도록 하겠다.

JAXP API 포함

가장 먼저 살펴볼 내용은 자바에서 XML 처리할 수 있도록 해주는 API인 JAXP(Java API for XML Processing) API가 포함되었다는 점이다. 아직까지 XML을 응용한 제품이 극소수이긴 하지만, 이미 XML은 IT 업계의 중심에 자리잡을 기술로 인식되고 있다. 특히 대부분의 B2B 업체는 XML을 자신들의 영역에 도입하기 위해 부단한 노력을 기울이고 있으며, 정부차원에서 진행되는 프로젝트 역시 차기 프로젝트의 주요 모델로 XML을 선택하고 있다.

이러한 시점에서 발표되는 자바2 SDK 1.4에 XML을 처리할 수 있는 API인 JAXP가 추가된 것은 선택이 아닌 필수라고 볼 수 있다. 특히 JAXP API는 JAXP API 하부에 존재하는 DOM 파서나 SAX 파서를 교체할 수 있는 구조를 갖고 있어서 언제든지 원하는 파서를 사용할 수 있도록 하고 있다.

보안 관련 확장 API 추가

자바2 SDK 1.3 버전까지 J2SE(Java 2 Standard Edition)가 제공하는 보안 기능은 매우 빈약했었다. 물론 간단한 암호화 정도는 프로그래밍할 수 있었으나 대규모 프로젝트에서 사용하기에는 부족했던 것이 사실이었다.

자바2 SDK 1.4는 이처럼 취약한 자바2의 보안 기능을 대폭 강화하기 위해 기존에 존재하던 확장 API인 JCE(Java Cryptography Extension), JSSE(Java Secure Socket Extension) 그리고 JAAS(JavaTM Authentication and Authorization Service) API를 J2SE에 포함시켰다. 이 API를 통해서 개발자들은 클라이언트 사이드에서도 강력한 보안 기능을 손쉽게 제공할 수 있게 되었으며 서버 사이드 역시 별다른 확장없이 강력한 보안 기능을 구현할 수 있게 되었다.

또한 이와 더불어 새로운 시큐리티 API인 Java GSS API와 Java Certification Path API 를 제공하고 있다.

로깅API

JDK 1.4 이전까지 JDK는 로깅과 관련된 어떤 기능도 제공하지 않았다. 이로 인해 개발자들은 늘 로깅 API를 직접 구현하거나 log4j와 같은 로깅 API를 사용해야만했었다. 1.4 버전은 새롭게 로깅 API를 추가하였으며, 따라서 개발자들은 이제 로깅과 관련된 부분을 걱정할 필요가 없게 되었다. 참고로, 1.4 버전에서 제공하는 로깅 API는 로깅 기능이 필요로 하는 대부분의 기능을 제공하고 있다.

네트워크 기능 강화

네트워크와 관련해서 가능 큰 변화는 역시 IPv6를 지원한다는 점이다. IPv6의 지원뿐만 아니라 Unconnected/Unbound 소켓 지원, 연결된 UDP 소켓 지원, java.net.URI 클래스를 통한 URI(Uniform Resource Indentifier) 지원을 비롯한 실제 네트워크 프로그래밍에서 필요로 하는 다양한 기능들이 추가되었다.

자바 웹 스타트 제품(Java Web Start Product)

자바가 클라이언트 영역에서 힘을 못내고 있는 주요 이유중의 하나는 클라이언트 프로그램을 배포하는 것이 불편하다는 점이었다. 자바 웹 스타트는 자바 어플리케이션을 간단한 방법을 배치할 수 있도록 해 줌으로써, 클라이언트 영역에서 자바의 쓰임새가 증가할 수 있는 기반을 만들어주고 있다. 자바 웹 스타트를 이용하면, 간단히 웹 페이지에 있는 링크를 클릭함으로써 어플리케이션을 실행시킬 수 있다. 이 때, 클라이언트 머신에 어플리케이션이 설치되어 있지 않을 경우 자바 웹 스타트는 자동적으로 필요한 파일을 다운로드 받으며, 알맞게 파일들을 캐싱한다. 또한, 언제라도 어플리케이션을 다시 다운로드 받을 수 있도록 하고 있다. 즉, 실행시에 어플리케이션을 손쉽게 교체할 수 있는 기능을 제공하고 있는 것이다.

아직 자바 웹 스타트가 클라이언트 분야에서 맥을 못추고 있는 자바에 힘을 줄지는 모르지만, 자바 어플리케이션을 손쉽게 배포할 수 있는 방법이라는 것만은 확실하다.

새로운 I/O API

NIO(New I/O) API는 기존의 I/O API에 비해 다음과 같은 특징을 제공하고 있다.

  • 기본 데이터 타입을 위한 버퍼
  • 캐릭터 셋 인코더와 디코더
  • 정규 표현식에 기반한 패턴 매칭 기능
  • 새로운 I/O 개념인 채널
  • 락(lock)과 메모리 매핑을 지원하는 파일 인터페이스
JDBC 3.0 API

JDBC 3.0 API가 추가되었다. 이제 개발자들은 JDBC 3.0 API를 사용하여 관계형 데이터베이스에서부터 스프레드쉬트 파일에 이르기까지 가상의 어떠한 데이터 자원에도 접근할 수 있게 되었다.

Preferences API

자바를 사용하여 설정 API를 작성해본 사람이라면 java.util.Properties 클래스를 유용하게 사용했던 기억이 날 것이다. 1.4 버전에서는 Properties 클래스보다 한층 강화된 Preferences API를 제공하고 있다. Preferences API는 어플리케이션이 사용자별로 설정 정보를 관리할 수 있도록 해 주고 있다. 실제로 Preferences API는 java.util.Properties 클래스를 대체할 것으로 목적으로 하고 있으며, java.util.Properties 클래스의 단점은 개정하고 장점은 그대로 유지하고 있다.

정규 표현식(Regular Expressions)

JDK1.4 버전에 새롭게 추가된 것 중에 흥미로운 것 중의 하나가 바로 정규 표현식을 처리할 수 있는 API가 추가되었다는 점이다. 정규 표현식을 사용할 경우 사용자가 입력한 값이 올바른지의 여부를 매우 간단하게 처리할 수 있게 된다. 예를 들어, 전화번호와 같이 그 값이 일정한 구조를 가져야 하는 값들을 검사할 때 기존에는 개발자가 일일이 검사해주어야 했지만, JDK1.4 부터는 java.util.regex 패키지에 제공되는 Pattern 클래스와 Matcher 클래스를 사용하여 매우 간단하게 검사할 수 있다.

엔터프라이즈 기능의 강화

앞에서 살펴본 JDBC 3.0의 지원을 비롯한 인터넷 DNS(Domain Naming System) 서비스 프로바이더가 1.4에 포함되었으며, LDAP(Lightweight Directory Access Protocol)의 보안 기능이 강화되었다.

GUI 기능 강화

기존의 AWT와 Swing 사이에서 호환되지 못하던 부분을 포커스 관리와 같은 일부 기능을 개선했으며 새로운 API인 Full-Screen Exclusive Mode API를 지원하고 있다. Full-Screen Exclusive Mode API는 스크린에 직접 그릴 수 있는 기능을 제공함으로써 고성능의 그래픽 기능을 제공하고 있다. 또한 휠 마우스 역시 지원하고 있다.

Swing 역시 형식에 맞춰 텍스트를 입력받을 수 있도록 하는 JFormattedTextField 컴포넌트를 비롯해 다양한 컴포넌트와 다양한 특징이 새로 추가되었다.

또한 두 어플리케이션 사이에서 데이터를 드래그 앤 드롭 할 수 있는 기능이 추가되었다.

성능 향상

전체적으로 성능이 향상되었다. 자바 가상 머신, 입출력 스트림, 네트워크, 자바 2D를 비롯한 많은 부분에서 성능 향상이 이루어졌다.

결론

JDK 1.4는 많은 기업들이 공동으로 참여하여 완성된 것이다. 이는 다양한 기업들이 공동으로 필요로 하는 기능이 JDK 1.4에 포함되었다는 것을 의미하기도 한다. 아직 JDK 1.4가 베타 버전이고 또한 지금 상업용 제품에서 지원하고 있는 JDK 버전이 1.2.x 이긴 하지만, 앞으로 JDK 1.4가 정식으로 출시되면 많은 기업들이 JDK 1.4를 지원하는 제품을 내 놓을 것이다. 이 때를 위해서 자바 개발자들은 지금부터 미리 JDK 1.4를 공부하는 것도 좋을 것이다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요