JavaBeans Programming

장규오

서울시 서대문구 충정로 3가 32-11 충정빌딩 6층

제이씨현시스템(주), 엘림네트 사업본부, 자바팀

E-mail : kojang@elim.net

URL :

http://www.elim.net/~kojang

Abstract:

자바 비쥬얼 컴포넌트 모델인 자바빈(JavaBeans)은 비쥬얼 빌더 툴을 통해서 쉽고 빠르게 자바 소프트웨어를 개발할 수 있다. 자바빈은 JDK 1.1에서부터 지원하고 있으면 BDK의 이름으로 자바빈을 개발해서 테스트할 수 있는 환경을 제공한다. 이러한 자바빈의 사용 방법과 내부 구조에 대해서 알아보고, 실제로 개발하는 방법을 알아본다.

Keywords:

JavaBeans, AWT, Bilder Tool, Event, Property, Introspection, Customization, JDK

1. 자바빈의 소개

자바빈은 비쥬얼 빌더툴을 통해 재사용 가능한 소프트웨어 컴포넌트이다.; Java Bean is a reusable software component that can be visually manipulated in builder tools

아마 여러분은 Windows95/NT 환경의 개발툴인 Delphi, Visual Basic, Mojo, PowerBuilder를 들어보거나 직접 사용해봤을 것이다. 자바빈을 쉽게 이해하려면 이들 개발툴에서 사용하는 컴포넌트인 VBX(Visual Basic Extension)나 OCX(OLE Custom Control)를 연상하면 된다. 최근에는 ActiveX도 포함 되고있다. 한편 X-Windows 환경의 개발자들은 위젯(Widget)이나 툴킷(Toolkit)을 연상하면 된다. 자바빈의 더욱 가까운 예로는 Symantec Visual Cafe에서 제공하는 Visual Component가 여러 가지가 있는데, 이들 컴포넌트는 자바빈을 본따 만든 것들이고, 앞으로 알아보는 것들은 이들 자바 컴포넌트를 어떻게 사용하여 프로그래밍 하고, 자바빈으로써 어떻게 컴포넌트를 등록하는지에 대해서 알아보겠다.

그러나 위의 자바빈의 정의는 광범위한 의미를 포함하고 있다. 비쥬얼 빌더툴은 웹페이지 빌더, 비쥬얼 소프트웨어 빌더, GUI 빌더, 서버 빌더 등을 의미하며 간단하게는 문서 작성 툴의 의미도 포함한다.

어떤 자바빈은 GUI의 일부분으로써 버튼이나 슬라이더일 수 있다. 또, 자바빈은 아주 복잡 한 형태의 컴포넌트로써 데이터베이스 뷰어도 될 수 있다. 반면 전혀 GUI의 형태를 띠지 않은 자바빈도 있다. 그러나, 결국 비쥬얼 툴을 통해서 쉽게 응용 프로그램을 구성할 수 있 다. 다음은 자바 컴포넌트의 예이다. 이들은 크건 작건 자바빈으로 구현되어 있거나 구현될 수 있다.

그림 1. Symantec Visual Cafe의 Visual Components

그림 2. Button Beans

그림 3. Slider Beans

그림 4. Spinbutton Beans

그림 5. Calendar Beans

그림 6. Graphical Diagram Editor

그림 7. Word Processor Beans

빌더툴은 자바빈을 엮는 형태의 완전한 비쥬얼 환경일 수 있고, 개발자가 더욱 유연하게 개 발할 수 있도록 자바 클래스를 직접 작성할 수 있는 것도 있으며, 스크립트를 제공하는 것 도 있다.

JavaBeans API의 궁극적인 목적은 자바로 Software component model을 정의하여 이들 자바 컴포넌트를 조합하는 것만으로 하나의 응용 프로그램을 개발할 수 있도록 하는데 있다.

1.1. 컴포넌트의 형태

자바빈 컴포넌트를 굳이 나누자면 다음과 같다.

자바빈은 응용 프로그램을 구성하는 블록형태이다. 따라서, 사용자는 빌더툴에서 이들 컴 포넌트를 연결하고 몇 개의 자바 빈을 묶어줌으로써 하나의 응용 프로그램으로써 작동한다. 이러한 경우에 AWT 버튼은 자바빈으로 볼 수 있다.

어떤 자바빈은 일반적인 응용 프로그램일 수 있다. 따라서, 웹페이지 안쪽에 스프레스 쉬트 를 끼어넣을 수 있다.

그림 8. Button Beans

그림 9. Spreadsheet Beans

이 두가지 형태는 겹쳐진다. 예를 들어서 스프레드쉬트가 응용 프로그램 안쪽에 놓일 수 있 듯이 스프레드쉬트 안에 응용 프로그램을 끼어 넣을 수 도 있다.

그림 10. Barchart Spreadsheet Beans

자바빈은 간단한 컴포넌트부터 웹페이지와 같은 복합된 문서까지 중점을 두며 디자인되었 다. 그러나, ClarisWorks나 Microsoft Office와 같은 아주 복잡한 형태의 문서 시스템은 지 원하지 않을 계획이다. 따라서, 현재 지원하는 API는 OLE 컨트롤이나 ActiveX API와 유사 한 API이고, 워드 프로세서 문서나 스프레드쉬트 문서와 같은 주요 문서들을 자바빈으로 지 원할 것이다.

일반적으로 자바빈 컴포넌트는 어디에서든 쉽게 사용할 수 있도록 작거나 중간 크기로 개발 하는 것이 좋다.

1.2. 이동성

자바빈 구조의 가장 큰 목적은 플렛폼 중립적인 컴포넌트 구조를 제공하는데 있다. 자바빈 이 다른 자바빈의 안쪽으로 겹치게 되더라도 모든 플렛폼에서 모든 기능을 지원한다. 그러 나, 자바빈이 Word, Visual Basic, ClarisWorks, Netscape Navigator와 같은 곳에 삽입될 때 자바빈 API는 지정된 로컬 플렛폼의 구조에 맞게 합성되도록 한다.

예를 들어서, Microsoft 플렛폼 환경에서 자바빈 API는 COM, ActiveX 사이에서 교량역할 을 한다. 마찬가지로 자바빈은 Netscape Navigator상에서는 LiveConnect와 함께 연결 되거나, Live Object처럼 처리된다.

따라서, 단 하나의 자바빈은 아주 다양한 형태로 작용한다. 지정된 환경에서 다른 어떤 컴포 넌트처럼 이벤트를 발생시키고, 메소드를 호출한다.

여기에서 구체적인 자바빈 브릿지에 대해서 다루지는 않는다. 브리지는 각 플렛폼에 맞도록 나름대로 자바빈 API를 지원하며 개발되어야 하기 때문이다.

1.3. 균일하고 고성능인 API

서로 다른 플렛폼에서 자바빈 API를 지원하는 능력 또한 다양하다. 그러나, 해당 플렛폼에 서 자바빈의 기능을 최대로 제공하지 못할 수도 있으므로, 자바빈을 사용하는데 별 무리 가 따르지 않도록 기본적으로 적절한 기능을 제공해야 한다.

예를 들어서, 해당 플렛폼에서 메뉴바를 합치는데 문제가 있다면 이 메뉴바를 팝업으로 띄우는 형태의 기능을 제공해야 한다.

이 말은 자바빈 개발자는 일관된 API를 사용하여 어느 곳에서 제대로 실행되도록 프로그래 밍 해야 한다는 것이다. 대신, 자바빈이 해당 플렛폼에서 구체적으로 어떤 기능으로 사용될 것인지 고민할 필요는 없다는 것이다.

1.4. 단순성

자바빈 API는 비교적 쉽게 되어있다. 중량의 컴포넌트 보다는 경량의 컴포넌트를 만드는데 중점을 두어야 한다.

일반적으로 java.beans.Everything 클래스를 상속받은 형태의 자바빈 컴포넌트는 좋지 않다. 자바빈이 상속을 통해 실행중에 허용되지 않은 어떠한 형태의 기본적인 기능을 지원하기 보 다는, java.beans.Something 인터페이스를 통해 주어진 특정 역할을 하는 것이 바람직하다.

또 하나의 자바빈의 특징은 개발자가 자바빈의 기본적인 개념들을 빠르게 이해하여, 약간의 노력으로도 쉽고 빠르게 자바빈을 개발할 수 있도록 자바빈은 구성되었다.

2. Visual Cafe의 간단한 프로그래밍

자바빈을 큰 안목으로 이해하기 위해 Visual Cafe를 예로 들겠다. 그러나, 비단 Visual Cafe 만이 아니고 모든 자바 빌더 툴은 이러한 형태를 띠게 된다. 오히려 지금 예를 들고 있는 Visual Cafe 1.0은 아직 완벽한 자바빈의 개념을 설명하지는 않지만, 자바빈의 발단이 이렇 게 된다는 것을 이해할 수는 있다.

Visual Cafe로 자바빈을 설명할 때 가장 중요한 3가지 요소는 Component Window or Componet Palette, Property Window, Form or Client Window이다. 각각은 다음과 같이 되 어있다.

그림 11. Component Window

그림 12. Component Palette

그림 13. Form or Client Window

그림 14. Property Window

위의 그림에서 Component Palette는 컴포넌트를 모두 모아놓은 Componet Window의 몇 개를 메뉴로 등록 시켜놓은 것이다.

소프트웨어를 개발할 때 개발자는 Component Window로부터 컴포넌트를 선택해 Client Window에 놓는다. 그리고, 선택한 컴포넌트의 속성값들을 지정할 수 있다. 쉽게 생각해서 어쩌면 이것이 자바 프로그래밍 또는 자바빈을 사용하는 전부가 될 수 있다. 아래 그림은 폼에(Client Window)에 두 개의 버튼과 하나의 리스트를 추가했다. 버튼의 속성에서 레이블 값을 바꾸고, 리스트 컴포넌트의 속성으로는 리스트의 아이템을 추가하였다.

그림 15. 폼 디자인

한가지 더 추가하자면 각 컴포넌트간에 이벤트를 설정할 수 있다는 것이다. 예를 들어서 Add Item 버튼이 선택 되었을 때 리스트에 "chaconne@hei.co.kr" 문자열의 아이템을 추가 하고, Clear List 버튼이 선택 되었을 때 리스트의 모든 아이템을 삭제하는 기능을 비쥬얼하 게 프로그래밍 할 수 있다. Visual Cafe의 경우에는 Palette Window의 왼쪽 하단에 두 개 의 플러그가 그려져 있는 버튼을 선택한 후 먼저 이벤트를 발생(Event Source)시키는 컴포 넌트를 지정하고(여기에서는 Add Item 버튼), 그 다음 그 이벤트를 받아(Event Listener) 새로운 행동을 취하는 컴포넌트를 지정하면 된다. Visual Cafe에서는 이것을 Interaction이라 고 이야기 하는데, 결국은 이벤트의 연결이다. 다음은 그 실행 예이다.

그림 16. 이벤트 연결

그림 17. Source Event와 Target의 기능 설정; 버튼클릭과 아이템 추가선택

그림 18. 추가하려는 아이템의 문자열 지정

모든 것이 자동으로 이루어졌다. 이것으로 모든 자바 프로그래밍이 끝났다. 이렇게 비쥬얼하 게 프로그래밍한 루틴은 자동으로 소스코드가 생성되어 무난하게 실행된다. 만약 파워 프로 그래머인 경우 소스코드를 보고자 한다면 물론 소스크드를 수정할 수 도 있다. 이러한 기능 은 앞에서 말한바와 같이 개발툴에 따라 스크립트를 지원하는것도 있다. 다음은 다른 개발 툴인 Mojo에서의 소스크드 편집 화면이다.

그림 19. 소스코드 편집

그러면, 지금까지 설명한 자바빈의 변형의 원형을 알아보고 과연 자바빈은 프로그램으로 어 떻게 구현되는지 알아보겠다.

3. 자바빈의 속성(Property)

자바빈의 속성값은 public으로 선언되어 읽고/쓰기, 읽기, 쓰기가 가능하다. 이들 속성의 종 류에는 값의 입력 형태와 쓰임에 따라 단순형(simple property), 배열형(indexed property), 경계형(bound property), 속박형(constrained property)이 있다.

그림 20. BDK의 BeanBox를 이용한 비쥬얼 프로그래밍 (I)

그림 21. BDK의 BeanBox를 이용한 비쥬얼 프로그래밍 (II)

3.1. 단순형 속성값(Simple property)

단순형 속성값은 하나의 값으로 표현되며 get/set으로 시작하는 이름의 메소드로 짝을 이루 고 속성의 이름은 이들 메소드에 의해서 알려진다. 예를 들어서 setX, getX 메소드가 있다 면 이것은 속성 이름이 X임을 나타낸다. 그리고, isX라는 이름의 메소드가 있다면 X는 boolean 값을 가짐을 알리는 것이다. 다음은 단순형 속성값을 사용하는 간단한 예제이다.

public class kojang1 extends Canvas {

String ourString = "Hello";

public kojang1() {

setBackground(Color.red);

setForeground(Color.blue);

}

public void setString(String newString) {

outString = newString;

}

public String getString() {

return outString;

}

public Dimension getMinimumSize() {

return new Dimension(50, 50);

}

}

setString(String newString)메소드로 속성값을 설정하고, getString() 메소드로 속성값을 얻 어낸다.

3.2. 배열형 속성값(Indexed Property)

배열형 속성값은 배열의 값을 가진다. 속성의 원소들은 get/set 메소드로부터 정수형 인텍스 를 얻어내어 배열의 한 원소의 값을 설정하거나 얻어낼 수 있고, 전체 배열의 값도 마찬가 지로 설정하거나 얻어낼 수 있다. 다음은 사용 예이다.

public class kojang2 extends Canvas {

int[] dataSet = {1, 2, 3, 4, 5, 6};

public kojang2() {

setBackground(Color.red);

setForeground(Color.blue);

}

pubilc void setDataSet(int[] x) {

dataSet = x;

}

public void setDataSet(int index, int x) {

dataSet[index] = x;

}

public int[] getDataSet() {

return dataSet;

}

public int getDataSet(int x) {

return dataSet[x];

}

}

setDataSet(int[] x) 메소드로 배열 전체 값을 설정하고, getDataSet()메소드를 통해서 전체 배열 값을 얻어낸다. 또한, setDataSet(int index, int x) 메소드로 배열의 한 원소의 값을 설 정하고, getDataSet(int x) 메소드로 배열의 한 원소의 값을 얻어낸다.

3.3. 경계형 속성값(Bound Property)

경계형 속성값은 자신의 값이 바뀌면 다른 객체에게 알린다. 값이 매번 바뀔 때 마다 속성 의 이름, 이전 값과 새로운 값에 대한 정보를 실은 PropertyChange 이벤트를 발생시킨다.

public class kojang3 extends Canvas {

String ourString = "Hello";

private PropertyChangeSupport change =

new PropertyChangeSupport(this);

public kojang3()

{

setBackground(Color.red);

setForeground(Color.blue);

}

public void setString(String newString) {

String oldString = ourString;

ourString = newString;

changes.firePropertyChange("string", oldString, newString);

}

public String getString() {

return outString;

}

public Dimension getMinimumSize() {

return new Dimension(50, 50);

}

public void addPropertyChangeListener(PropertyChangeListener l) {

changes.addPropertyChangeListener(l);

}

public void removePropertyChangeListener(

PropertyChangeListener l) {

changes.removePropertyChangeListener(l);

}

}

우선 ProperyChangeSupport 객체를 생성하고 만약 속성값이 바뀌게 되면(setString 메소드 가 호출되면) 이벤트 수신자(listener)에게 이벤트를 발생시켜 정보를 전달한다. 한편, addPropertyChangeListener, removePropertyChangeListener 메소드를 통해서 이벤트 수신 자를 추가하거나 삭제할 수 있다.

3.4. 속박형 속성값(Constrained Property)

속박형 속성값을 가지는 객체는 다른 객체로 하여금 자신의 속성값을 바꾸는 것을 거부할 수 있다. 속박형 속성값을 수신하는 객체는 값을 바꾸지 않고 PropertyVetoException을 수행 (throw)하면서 다른 루틴을 실행시킨다.

public class JellyBean extends Canvas {

private PropertyChangeSupport changes =

new PropertyChangeSupport(this);

private VetoableChangeSupport vetos =

new VetoableChangeSupport(this);

......

public void setPriceInCents(int newPriceInCents)

throws PropertyVetoException {

int oldPriceInCents = ourPriceInCents;

vetos.fireVetoableChange("priceInCents",

new Integer(oldPriceInCents),

new Integer(newPriceInCents));

ourPriceInCents = newPriceInCents;

changes.firePropertyChange("priceInCents",

new Integer(oldPriceInCents),

new Integer(newPriceInCents));

}

public void addVetoableChangeListener(VetoableChangeListener l) {

vetos.addVetoableChangeListener(l);

}

public void removeVetoableChangeListener(

leChangeListener l) {

}

......

}

위의 예제 프로그램에서 변수 값을 바꿀 수 있는 경우는 vetoableChange 이벤트로 인해 등 록된 모든 VetoableChangeListener객체가 속성값 변경을 허락할 경우에만 값을 변경하고 propertyChange 이벤트를 발생시켜 마무리를 할 수 있다. 만약, VetoableChangeListener 객체 하나라도 허락하지 않으면 PropertyVetoException이 발생하여 속성값을 변경할 수 없게 된다.

4. 이벤트(Event)

4.1. AWT의 새로운 이벤트 모델

AWT에 새롭게 정의한 이벤트 모델은 JavaBeans에서도 사용하는 이벤트 모델로써 많은 부 분에서 개선되었다. 이전의 이벤트 모델은 여러 가지 문제점을 내재하고 있었다. 우선, 이벤 트를 처리하기 위해서는 AWT 컴포넌트를 상속 받아야만 가능했고, GUI 컴포넌트의 구조 에 따라 이벤트가 전달 되었다. 모든 이벤트는 handleEvent와 action에서 모두 처리되므로 코드가 혼란스러웠고, 따라서 오류가 발생하기 쉬운 환경이었다.

새롭게 구성된 이벤트 모델은 배우기 쉽고, 훨씬 구조적으로 디자인 되었다. 특히 GUI 코드 와 확실하게 구분되어 혼돈으로 인한 프로그램 오류를 방지하고, 비쥬얼 툴을 사용해서 쉽 게 프로그래밍 할 수 있게 되었다. 또한 하나의 Event 클래스에서 모든 이벤트를 정수 형태 의 ID로 표현하지 않고 훨씬 다양한 형태이면서 이전 코드와 호환성 있도록 디자인 되었다.

새로운 AWT의 이벤트는 하위레벨의 이벤트와 상위레벨의 이벤트로 나누어져 있다.

하위레벨의 이벤트

java.util.EvnetObject

java.awt.AWTEvent

(윈도우의 이동, 크기 변화등에 의한 이벤트)

java.awt.event.ComponentEvent

(키보드 포커스에 의한 이벤트)

java.awt.event.FocusEvent

java.awt.event.InputEvent

(키보드 입력에 의한 이벤트)

java.awt.event.KeyEvent

(마우스 입력에 의한 이벤트)

java.awt.event.MouseEvnet

상위레벨의 이벤트

java.util.EventObject

java.awt.event.AWTEvent

(명령의 실행에 따른 이벤트)

java.awt.event.ActionEvent

(값의 변화에 따른 이벤트)

java.awt.event.AdjustmentEvent

(아이템의 상태에 따른 이벤트)

java.awt.event.ItemEvent

AWT의 이벤트는 이벤트를 발생시키는 것을 EventSource라고 하고 이벤트를 수신하는 것을 EventListener라고 부른다. EventListener는 AWT의 새로운 이벤트의 형태에 따라 다음 과 같이 구분되어 있다.

하위레벨의 Event Listener

java.util.EvnetListener

java.awt.event.ComponentListener

java.awt.event.FocusListener

java.awt.event.KeyListener

java.awt.event.MouseListener

java.awt.event.MouseMotionListener

java.awt.event.WindowListener

상위레벨의 이벤트

java.util.EventListener

java.awt.event.ActionListener

java.awt.event.AdjustmentListener

java.awt.event.ItemListener

또한, EventSource는 컴포넌트의 특성에 따라 발생 시킬 수 있는 이벤트가 정해있다. 다음은 각각의 컴포넌트에 따른 EventSource를 등록하는 메소드들이다.

* java.awt.Component

addComponentListener(ComponentListener l)

addFocusListener(FocusListener l)

addKeyListener(KeyListener l)

addMouseListener(MouseListener l)

addMouseMotionListener(MouseMotionListener l)

* java.awt.Dialog

addWindowListener(WindowListener l)

* java.awt.Frame

addWindowListener(WindowListener l)

* java.awt.Button

addActionListener(ActionListener l)

* java.awt.Choice

addItemListener(ItemListener l)

* java.awt.Checkbox

addItemListener(ItemListener l)

* java.awt.CheckboxMenuItem

addItemListener(ItemListener l)

* java.awt.List

addActionListener(ActionListener l)

addItemListener(ItemListener l)

* java.awt.MenuItem

addActionListener(ActionListener l)

* java.awt.Scrollbar

addAdjustmentListener(AdjustmentListener l)

* java.awt.TextField

addActionListener(ActionListener l)

대부분의 EventListener는 여러 개의 이베트를 처리한다. 한 예로 마우스의 이벤트는 mouse-down, mouse-up, mouse-enter 등 다양한데 모두 MouseListener에서 처리한다. 따 라서, AWT 여러개의 어댑터 클래스를 재정의 하여 사용함으로써 필요한 이벤트만 처리할 수 있도록 하고 있다. 이러한 어댑터 클래스에는 다음과 같다.

java.awt.event.ComponentAdapter

java.awt.event.FocusAdapter

java.awt.event.KeyAdapter

java.awt.event.MouseAdapter

java.awt.event.MouseMotionAdapter

java.awt.event.WindowAdapter

그러면, 이러한 새로운 이벤트 구조를 사용하는 예제 프로그램을 하나 살펴보겠다.

import java.awt.*;

import java.awt.event.*;

public class App {

public void search() { // 검색 기능이 있는 것으로 한다....}

public void sort() { // 정렬 기능이 있는 것으로 한다....}

static public void main(String args[]) {

App app = new App();

GUI gui = new GUI(app);

}

}

class Command implements ActionListener {

static final int SEARCH = 0;

static final int SORT = 1;

int id;

App app;

public Command(int id, App app) {

this.id = id;

this.app = app;

}

public void actionPerformed(ActionEvent e) {

switch(id) {

case SEARCH :

app.search();

break;

case SORT :

app.sort();

break;

}

}

}

class GUI {

public GUI(App app) {

Frame f = new Frame();

f.setLayout(new FlowLayout());

Command searchCmd = new Command(Command.SEARCH, app);

Command sortCmd = new Command(Command.SORT, app);

Button b;

f.add(b = new Button("Search"));

b.addActionListener(searchCmd);

f.add(b = new Button("Sort"));

b.addActionListener(sortCmd);

List l;

f.add(l = new List());

l.add("Alphabetical");

l.add("Chronological");

l.addActionListener(sortCmd);

f.pack();

f.show();

}

}

이벤트는 AWT 컴포넌트에서 자동 발생되는 것만이 있는 것이 아니라, 개발자가 필요에 따 라서 자신의 이벤트를 정의해서 이벤트를 전달하고 수신할 수 있다. 이전 AWT에서는 postEvent를 이용해서 Event 객체를 전달하고 handleEvent에서 수신하는 형태이었는데, 새 로운 AWT에서는 지금까지 설명한 이벤트 구조와 비슷한 형태의 새로운 이벤트 구조를 가 진다. 다음은 이러한 이벤트 구조를 사용하는 예제 프로그램이다.

public class TextCanvas extends Canvas {

boolean haveFocus = false;

public TextCanvas() {

enableEvents(AWTEvent.FOCUS_EVENT_MASK);

...

}

protected void processFocusEvent(FocusEvent e) {

switch(e.getId()) {

case FocusEvent.FOCUS_GAINED :

haveFocus = true;

break;

case FocusEvent.FOCUS_LOST :

haveFocus = false;

}

repaint();

super.processFocusEvent(e);

}

public void paint(Grahpics g) {

if(haveFocus) {

// 키보드 포커스를 갖는 화면 갱신

}

}

... 중략 ...

}

enableEvents에서 자신이 필요한 포커스 이벤트를 허용함을 알리고, processFocusEvent에서 이에 따른 이벤트를 처리한다. 이러한 메소드로는

protected void processEvent(AWTEvent e)

protected void processActionEvent(ActionEvent e)

protedted void processItemEvent(ItemEvent e)

가 있다. 한편, 위의 예제에서 자신이 처리하지 못하는 부분에 대해서는 상위 클래스의 processFocusEvent를 호출함으로써 해결하고 있다.

JDK 1.1 AWT는 이벤트 큐(Event Queue)를 제공하는데 java.awt.EventQueue로 구현되어 있다. 이벤트 큐는 getEventQueue, postEvent, getNextEvent, peekEvent 등의 메소드를 통 해서 직접 이벤트 큐를 조작할 수 있다.

4.2. 자바빈의 이벤트

이벤트는 source 객체와 타겟이 되는 listener 객체 사이의 상태 변화를 알리는 메커니즘으 로, 컴포넌트를 개발툴과 엮어서 실행할 수 있는 메커니즘을 제공한다. 이벤트는 아주 다양 하게 사용되지만 일반적으로 마우스의 입력이나, 키보드 입력, 위젯의 갱신을 알리는데 주로 사용되고 있다. 여기에서는 WaterSource, Valve, Pipe 자바빈의 예제 프로그램을 보면서 이 벤트의 이해를 돕겠다.

그림 18. JavaBean의 객체간의 이벤트 흐름

위의 그림에서 WaterSource는 초당 하나의 WaterEventObject를 WaterListener에게 전달한다. WaterListener는 여러 개가 될 수 있는데 여기에서는 Valve와 Pipe가 그 역할을 담당하 고 있다. Valve가 열리면 WaterEventObject를 자신의 WaterListener에 다시 전송하게 되고, 닫혀있으면 WaterEventObject는 전달되지 않는다. 그리고, Pipe는 열려있는 Valve의 역할을 하고 있다.

다음은 물의 역할로써 객체들간에 전송되는 이벤트인 WaterEventObject를 정의한 것이다. 재부적으로는 시간 정보만을 가지고 있다.

public class WaterEventObject extends EventObject {

long timeOfEvent;

public WaterEventObject(Object o) {

super(o);

timeOfEvent = System.currentTimeMillis();

}

public long getTimeOfEvent() {

return timeOfEvent;

}

}

다음 클래스는 물의 근원지로써 이벤트를 발생시키는 이벤트의 근원이 되는 클래스이다. waterListeners는 이벤트를 전송할 객체들을 저장하고 있는 Vector객체이다. 개발툴은 addWaterListener와 removeWaterListener 메소드를 통해서 listener를 등록할 수 있고, splash 메소드 에서는 등록된 listener에게 물에 해당하는 1초에 한번씩 이벤트를 전송하는 기능을 가지고 있다.

public class WaterSource extends Canvas implements Runnable {

private Vector waterListeners = new Vector();

Thread thread;

public WaterSource() {

setBackground(Color.blue);

thread = new Thread(this);

thread.start();

}

public Dimension getMinimumSize() {

return new Dimension(15, 15);

}

public void run() {

while(true) {

splash();

try {

thread.sleep(1000);

}catch(Exception e) {}

}

}

public synchronized void addWaterListener(WaterListener l) {

waterListeners.addElement(l);

}

public synchronized void removeWaterListener(WaterListener l) {

waterListeners.removeElement(l);

}

private void splash() {

Vector l;

WaterEventObject weo = new WaterEventObject(this);

synchronized(this) {

l = (Vector)waterListeners.clone();

}

for(int i=0 ; i<l.size() ; i++) {

WaterListener wl = (WaterLIstener)l.elementAt(i);

wl.handleSplash(weo);

}

}

}

밸브는 물의 흐름을 조절할 수 있는 장치이다. 마찬가지로 여기에서 Valve는 이벤트의 흐름 을 조절하는 역할을 담당하고 있다. Valve는 위에서 받은 이벤트를 다시 자신과 연관된 listener에 전달하게 되는데, 이들 listener 객체를 저장하는 객체가 waterListeners이고, 마지 막 이벤트는 lastWaterEvent에 저장한다. 논리형 open 변수는 현재 밸브의 개폐 상태를 저 장하고 있다.

isOpen, setOpen 메소드로 속성값을 변경하거나 얻을 수 있고, 위의 WaterSource와 마찬가 지로 개발툴을 통해 listener를 등록하거나 삭제하는 메소드로 addWaterListener, removeWaterListener가 있다.

handleSplash 메소드는 WaterListener 인터페이스에 의해 정의한 메소드로써, 물(이벤트)을 WaterSource로부터 전송받아 현재 자신의 밸브 상태가 열려있으면 화면을 파란색으로 칠하 고, 자신의 listener에 물을(이벤트)를 splash메소드를 통해서 다시 전달한다. 한편, 스레드에 의해서 호출되는 run메소드에서는 현재 자신의 밸브 상태에 따라 화면을 다시 흰색으로 돌 리는 일을 한다.

public class Valve extends Canvas implements WaterListener, Runnable {

private Vector waterListeners = new Vector();

private WaterEventObject lastWaterEvent;

private boolean open = true;

Thread thread;

public Valve() {

setBackground(Color.white);

thread = new Thread(this);

thread.start();

}

public boolean isOpen() {

return open;

}

public void setOpen(boolean x) {

open = x;

}

public Dimension getMinimumSize() {

return new Dimension(20, 30);

}

public void handleSplash(WaterEventObject e) {

lastWaterEvent = e;

if(isOpen()) {

setBackground(Color.blue);

repaint();

splash();

}

}

public void run() {

while(true) {

try {

thread.sleep(1000);

} catch(Exception e) {}

}

if(lastWaterEvent != null) {

long dt = System.currentTimeMillis() -

lastWaterEvent.getTimeOfEvent();

if((dt > 2000) || (!isOpen())) {

setBackground(Color.white);

repaint();

}

}

}

public synchronized void addWaterListener(WaterListener l) {

waterListeners.addElement(l);

}

public synchronized void removeWaterListener(WaterListener l) {

waterListeners.removeElement(l);

}

private void splash() {

Vector l;

WaterEventObject weo = new WaterEventObject(this);

synchronized(this) {

l = (Vector)waterListeners.clone();

}

for(int i=0 ; i<l.size() ; i++) {

WaterListener wl = (WaterListener)l.elementAt(i);

wl.handleSplash(weo);

}

}

}

다음에 정의한 Pipe는 물을 흘러내리는 통로로써 밸브와 비교하면 항상 열려진 밸브라고 볼 수 있다. 따라서, 기능적인 측면에서 Pipe는 Valve 클래스의 축소된 기능을 가지고 있다. 마찬가지로, listener의 정보를 담고 있는 객체는 waterListeners이고, 마지막 전송받은 이벤 트는 lastWaterEvent가 저장한다. handleSplash에서 전송받은 이벤트가 있으면 화면을 파란 색으로 갱신하는 일을 한다. 그리고, run 메소드에서는 시간이 조금 지남에 따라 화면을 다 시 흰색으로 전환한다.

public class Pipe extends Canvas implements WaterListener, Runnable {

private Vector waterListeners = new Vector();

private WaterEventObject lastWaterEvent;

Thread thread;

public Pipe() {

setBackground(Color.white);

thread = new Thread(this);

thread.start();

}

public Dimension getMinimumSize() {

return new Dimension(150, 10);

}

public void handleSplash(WaterEventObject e) {

lastWaterEvent = e;

setBackground(Color.blue);

repaint();

splash();

}

public void run() {

while(true) {

try {

thread.sleep(1000);

} catch(Exception e) {}

}

if(lastWaterEvent != null) {

long dt = System.currentTimeMillis() -

lastWaterEvent.getTimeOfEvent();

if(dt > 2000) {

setBackground(Color.white);

repaint();

}

}

}

public synchronized void addWaterListener(WaterListener l) {

waterListeners.addElement(l);

}

public synchronized void removeWaterListener(WaterListener l) {

waterListeners.removeElement(l);

}

private void splash() {

WaterEventObject weo = new WaterEventObject(this);

for(int i=0 ; i<waterListeners.size() ; i++) {

WaterListener wl =

(WaterListener)waterListeners.elementAt(i);

wl.handleSplash(weo);

}

}

}

이제 이벤트의 개념에 대해서 잘 이해했을 것이다. 이벤트는 각각의 객체 사이에서 이벤트 의 수신자와 송신자가 존재하고 특정 입력이나 시간에 따라 이들 객체의 상태를 변환시키는 도구로 사용되는 메커니즘이다.

5. 자바빈을 보다 쉽게

5.1. Introspection

앞에서 설명한 Property와 Event를 디자인 하는데에는 규칙이 있었다. 그래야만, 빌더 툴에 서 자바빈을 제대로 분석해서 충분하게 자바빈의 모든 기능을 지원할 수 있기 때문이다. 이 러한 것을 내성(Introspection)이라고 하는데 다음과 같은 디자인 패턴을 따른다.

Simple properties

public  get();

public void set( a);

Boolean properties

public boolean is();

public void set( a);

Indexed properties

public  get(int a);

pubic void set(int a,  b);

Event Design Pattern

public void add( a)

pubic void remove( a)

5.2. Customization

자바빈은 앞에서 말한 바와 같이 비쥬얼 개발 툴을 위해 개발된 라이브러리이다. 따라서, 자 바빈을 개발 툴에서 보다 효율적이고 쉽게 사용할 수 있도록 해야 한다. 그러면 지금부터 자바빈의 형태나 행동 양식을 개발툴이 원하는 형태, 즉 개발자가 사용하기 쉽게 고쳐보도 록 하겠다.

제일 먼저 java.beans.Customizer 인테페이스를 사용하는 방법이다. 이 인터페이스를 사용함 으로써 addPropertyChangeListener와 removePropertyChangeListener 메소드를 정의하게 된다. 그러면, 자바빈을 지원하는 개발툴들은 기뻐할 것이다. 이들 메소드를 통해서 개발툴은 보다 정확하게 자바빈을 지원할 것이다. 다음은 실제 사용 예제이다.

public OutButtonCustomizer extends Panel implements Customizer {

public PropertyChangeSupport support =

new PropertyChangeSupport(this);

public void addPropertyChangeListener(PropertyChangeListener l) {

support.addPropertyChangeLIstener(l);

}

public void removePropertyChangeListener(

PropertyChangeListener l) {

support.removePropertyChangeListener(l);

}

}

다음은 속성값 편집을 위한 인터페이스이다. 이때 사용하는 클래스 는

java.beans.PropertyEditorSupport 클래스이다. 다음은 사용 예제이다.

public class MoleculeNameEditor

extends java.beans.PropertyEditorSupport {

public String[] getTage() {

String results[] = {

"HyaluronicaAcid",

"benzene",

"buckminsterfullerine",

"cyclohexane",

"ethane",

"water"};

return result;

}

}

각각의 자바빈 클래스는 BeanInfo 클래스를 가질 수 있다. 이 클래스를 사용하면 비쥬얼 개 발툴에서 자바빈이 표현되는 형태를 정의할 수 있다. BeanInfo 클래스는 속성값, 메소드, 이 벤트를 화면에 출력 가능하게 한다. 다음은 사용 예제이다.

public class MoleculeBeanInfo extends SimpleBeanInfo {

public PropertyDescriptor[] getPropertyDescriptors() {

try {

PropertyDescriptor pd = new PropertyDescriptor(

"moleculeName", Molecule.class);

pd.setPropertyEditorClass(MoleculeNameEditor.class);

PropertyDescriptor result[] = { pd };

return result;

} catch (Exception ex) {

System.err.println(

"MoleculeBeanInfo : unexpected exception: "+ex);

return null;

}

}

}

6. 패키징 (Packaging)

자바빈은 JAR파일의 형태로 제공된다. JAR파일은 ZIP 형태의 합축 파일로, 파일의 단위가 많은 자바 프로그램과 웹서버의 특성을 고려해 속도 개선과 모듈관리를 위해 제공되는 파일 기법이다. 이 JAR파일에는 일반적인 자바 코드와 자바빈을 구분하기 위해서 MANIFEST파 일이 필요하다. 다음은 이벤트를 알기 위해 사용했던 모듈들의 MANIFEST파일을 작성한 예이다. MANIFEST파일은 확장자가 ".mk"로 저장되어야 하고, 내용에서 가장 중요한 것은 아마도 "Java-Bean: True" 문장일것이다. 이 문장이 해당 클래스를 화면에 보일지 말지를 결정하기 때문이다.

CLASSFILES = \

sunw\demo\valves\WaterListener.class \

sunw\demo\valves\WaterSource.class \

sunw\demo\valves\Valve.class \

sunw\demo\valves\Pipe.class \

sunw\demo\valves\WaterEventObject.class

JARFILE= ..\jars\valves.jar

.SUFFIXES: .java .class

all: $(JARFILE)

# Create a JAR file with a shitable manifest.

$(JARFILE): $(CLASSFILES) $(GIFFIELS)

jar cfm $(JARFILE) <<manifest.tmp sunw\demo\valves\*.class $(GIFFILES)

Name: sunw\demo\valves\WaterListener.class

Java-Bean: False

Name: sunw\demo\valves\WaterSource.class

Java-Bean: True

Name: sunw\demo\valves\Valve.class

Java-Bean: True

Name: sunw\demo\valves\Pipe.class

Java-Bean: True

Name: sunw\demo\valves\WaterEventObject.class

Java-Bean: False

<<

# Rule for compiling a normal .java file

{sunw\demo\valves}.java{sunw\demo\valves}.class :

set CLASSPATH=..\classes;.

javac $<

clean:

-del sunw\demo\valves\*.class

-del $(JARFILE)

5. 결론

지금까지 JDK 1.1의 JavaBeans의 모습들을 알아보았다. 지금까지 설명한 자바빈은 기본개 념 에 불과하다. 자바빈은 확장 AWT와 Object Serialization, Java Refrection, JAR, Java Security, JDBC 등과 함께 보다 폭넓은 형태로 사용될 수 있다. GUI 컴포넌트를 이용해서 프로그래밍 한다는 것은 아주 신나는 일이다. 자바빈을 개발하기 힘들면 남들이 만들어놓은 자바빈을 그냥 이용할 줄 아는 것만으로도 큰 도움이 될것이다.

References

JavaBeans 1.1 API Specification

http://www.java.sun.com/beans/

Using the Beans Development Kit 1.0 April 1997 Tutorial

http://www.javasoft.com/beans/

JavaBeans Tutorial

http://www.javasoft.com/nav/read/Tutorial/beans/index.html

AWT Enhancements

http://www.javasoft.com/

Last-Modified : 97-05-03