★ OSGi 에서의 이벤트 시스템
OSGi 라이프사이클 레이어에서는 BundleEvent, FrameworkEvent를 서비스 레이터에서는 ServiceEvent라는 형태의 EventType객체를 정의하고 있다.이벤트 | 설명 |
BundleEvent | 번들의 Life Cycle 변경을 알리기 위한 이벤트 |
ServiceEvent | 서비스의 변경사항등을 알리기 위한 이벤트 |
FrameworkEvent | OSGi 프레임워크의 변경사항을 알리기 위한 이벤트 |
이 이벤트가 발생하면 번들컨텍스트에 각 타입에 대한 리스너를 등록할 수 있다. 이 이벤트들은 주로 OSGi의 동적인 환경을 이용하는 Extender를 개발할 때 주로 쓰이게 된다.
번들이 동적으로 설치되는 환경이라면, 설치할 때 각 번들마다 해줘야 할 일들을 Extender에 만들어 줌으로써 각 번들마다 각각 코딩할 필요가 없게 되어 중복된 코드를 줄이고 한 곳에서 관리할 수 있다.
※ Extender
Extender는 OSGi상에서 종종 사용되는 개념으로, 동적으로 설치되는 다른 번들/서비스의 설치/삭제 시 이벤트를 받아서 특정 동작을 수행하는 형태를 말한다. 특히 SpringDM이 Extender 개념을 아주 잘 활용한 예라고 볼 수 있다.
★ BundleEvent
BundleEvent는 번들의 라이프 사이클 변경을 알려주는 이벤트, BundleListener인터페이스를 구현한 객체를 생성하여 BundleContext.addBundleListener를 호출하여 등록한다.public class MyListener implements BundleListener {
public void bundleChanged(BundleEvent event) {
}
}
또는 Activator에 BundleListener 인터페이스를 구현하면 간단히 핸들링 할 수 있다.public class Activator implements BundleActivator, BundleListener {
public void start(BundleContext context) throws Exception {
context.addBundleListener(this);
}
public void stop(BundleContext context) throws Exception {
context.removeBundleListener(this);
}
public void bundleChanged(BundleEvent event) {
...
...
}
}
번들을 설치할 때는 INSTALLED ▷ RESOLVED ▷ start() 메소드 실행 ▷ STARTED 순으로 이벤트가 진행된다. 번들을 삭제할 때는 stop() ▷ STOPPED ▷ RESOLVED ▷ UNINSTALLED 순으로 이벤트가 진행된다.
★ FrameworkEvent
FrameworkEvent는 프레임워크에서 일어나는 주요 이벤트로, BundleEvent와 마찬가지로 아래와 같이 FrameworkListener 인터페이스를 구현한 객체를 생성한 후 BundleContext.addFrameworkListener메소드를 호출하여 등록한다.
public class MyListener implements FrameworkListener {
public void frameworkEvent(FrameworkEvent event) {
}
}
FrameworkEvent는 일반적으로 잘 일어나지 않는 이벤트이며, 다음과 같은 이벤트가 존재한다.
번호 | 이벤트 | 설명 |
1 | STARTED | 프레임워크를 시작할 때 발생하는 이벤트 |
2 | ERROR | 프레임워크가 오류가 발생했을 때 던져주는 이벤트 |
3 | PACKAGES_REFRESHED | 각 번들이 임포트/익스포트하는 패키지가 리프레시되었을때 발생하는 이벤트 |
4 | STARTLEVEL_CHANGED | 각 번들의 ServiceLevel이 변경되었을 때마다 발생하는 이벤트 |
5 | WARNING | 번들에 관련하여 경고가 있을 때 발생 |
★ ServiceEvent
ServiceEvent는 서비스가 등록/수정/삭제되었을 때 알려주는 이벤트로 BundleEvent/FrameworkEvent와 마찬가지로 ServiceListener를 등록하면 프레임워크로부터 받을 수 있다.
public class MyListener implements ServiceListener {
public void serviceChanged(ServiceEvent event) {
}
}
번호 | 이벤트 | 설명 |
1 | REGISTERED | 서비스가 등록된 후에 발생하는 이벤트 |
2 | MODIFIED | 서비스가 변경된 후에 발생하는 이벤트 |
3 | UNREGISTERING | 서비스가 삭제되기 전에 발생하는 이벤트 |
초기 OSGi 설계당시에는 ServiceListener를 구현함으로써 각 번들은 자신이 원하는 서비스가 등록되었는지를 동적으로 알아내어 사용할 수 있었지만, 지금은 ServiceTracker를 이용하면 간단히 자신이 원하는 서비스에 대한 Tracking이 가능함으로 ServiceListener는 이제 별도 의미가 없어졌다.
★ OSGi 애플리케이션 이벤트
기존 자바에서 사용하던 일반적인 리스너 객체는 아래와 같은 방식이다.그림1. 일반적인 리스너 객체
▶ 절차
1. 이벤트를 받고자 하는 Client가 이벤트 리스너를 생성해서 이벤트를 실제 생성하는 이벤트 객체에 자신을
등록
2. 이벤트 소스는 OSGi 프레임워크가 되고, 작성한 번들의 액티베이터가 이벤트 리스너이다.
3. Add Listener 함수에서 이벤트 소스는 전달받은 이벤트 리스너들은 자신이 관리하는 리스너 객체 리스트
에 추가한다.
4. 이벤트소스는 이벤트 발생시 이벤트 객체를 생성하고, 자신이 관리하는 리스너 리스트에 있는 모든 리스너
객체들의 handleEvent 메소드를 호출하여 이벤트가 발생했음을 통보
위와 같은 구조는 자바에서 많이 사용하는 구조로 AWT와 같은 데에서는 매우 많이 쓰이고 있다. 하지만 OSGi 환경에서는 약간 부담이 되는 구조이다.
문제1. 각각의 이벤트와 리스너를 위해 클래스 또는 인터페이스를 작성해야 하는데, 클래스 개수가 늘어나는
것은 관리상으로나 성능상으로나 문제가 있다.
문제2. 대부분의 경우 이벤트 리스너를 여러 개 관리(Stored Event Listeners)해야 하므로 이벤트 소스마다
이를 저장하기 위한 컬렉션 개체가 하나씩 필요
문제3. 동적인 OSGi에서는 이벤트소스나 이벤트 리스너가 언제라도 없어질 수 있으므로, 이벤트 리스너 번들
이 제거되었을 때 이벤트 소스에서 삭제하는 역할이 반드시 필요하며, 이벤트 소스는 매번 이벤트 리스
너 존재여부를 체크해야 한다.
상기와 같은 문제로 OSGi에서는 주로 화이트보드 패턴을 이용한 이벤트 전달방식을 사용한다.
그림2. 화이트보드 패턴
화이트보드 패턴은 OSGi 프레임워크의 서비스 레지스트리를 이용하는 것으로, 각각의 이벤트 소스들에 이벤트 리스너를 등록하는 방식이 아니라 이벤트를 받고자 하는 번들이 이벤트 리스너를 서비스 레지스트리에 등록하고 이벤트 소스는 이벤트를 발생하고자 할 때, 서비스 레지스트리에서 이벤트를 받은 리스너들을 가져와서 이벤트를 던져주는 방식이다.
장점1. 이벤트 소스와 이벤트 리스너 사이의 직접적인 의존관계가 없다.
장점2. 이벤트 리스너가 모두 서비스 레지스트리에 등록되므로, 관계가 외부에 공개되며 통합적인 디버깅 툴
의 지원이 가능하다.
장점3. 이벤트 리스너에 대한 다양한 테스트가 가능하여 개발작업이 용이하다.
장점4. 공개된 레지스트리를 이용하게 되므로, 이벤트소스나 이벤트 리스너 양측의 소스코드양이 줄어든다.
★ Event Admin 서비스
화이트보드 패턴은 쉽게 사용할 수 있지만 여전히 문제점이 남아 있다.
문제1. 이벤트 소스와 이벤트 리스너가 완벽히 분지되지 않는다. 즉, 이벤트 소스가 특정한 리스너, 특정한 이
벤트 객체를 알고 있어야 한다는 것은 변함이 없다.
문제2. 서비스 레지스트리로부터 리스너 서비스를 얻어오고 트래킹하는 등의 코드를 이벤트 소스에 추가해야
한다.
상기와 같은 문제점을 해결하고자 추가된 서비스가 OSGi Event Admin이다.
그림3. Event Admin 서비스
설명1. 각 번들은 Event Admin 서비스가 제공하는 EventHandler라는 인터페이스를 구현하여 이것을 서비스
레지스트리에 등록한다.
설명2. 이벤트 소스 번들에서 이벤트를 생성하여 Event Admin 서비스에게 Event를 보낸다.
설명3. Event Admin 서비스가 이벤트를 받은 시점에 등록된 이벤트 핸들러들의 스냅샷스냅샷을 만든다는 의미는, 이벤트를 받았을 떄 마치 사진을 찍듯이 등록된 이벤트 핸들러들의 리스트를 복사해두고 전달을 시작하고, 전달하는 작업 중간에 새로운 핸들러가 등록이 되더라도 그 핸들러는 현재 전달중인 이벤트를 받을 수 없다. 을 만들고 그 이벤트
핸들러들에게 이벤트 객체를 전달한다.
상기와 같이 Event Admin 서비스를 구현한 Equinox 구현체는 org.osgi.service.event에 들어 있다.
★ Event Object
이벤트 객체는 다음과 같은 2개의 속성을 가진다.
1. Topic
이벤트 타입을 표시하기 위한 주제 속성. [주로 Event Listener들이 자신이 원하는 이벤트만 받고자 할 때 필터링시 사용]- '/'로 분리된 Reverse Domain 형태의 계층 구조 스트링을 사용- 일반적으로 fully/qualified/package/ClassName/ACTION 형태의 구조를 가진다.
2. Properties
이벤트에 대한 추가적인 속성을 가지는 컬렉션 개체.
다음은 주로 사용하는 속성들이며 이 상수에 대한 정의는 org.osgi.service.event.EventConstants에 정의
값의 형식 | 키 이름 문자열 | 속성 상수 | 설명 |
BUNDLE | bundle | Bundle | 해당 이벤트와 관련된 번들 |
BUNDLE_ID | build.id | Long | 번들의 ID |
BUNDLE_SYMBOLINAME | bundle.symbolicName | String | Manifest에 정의된 번들의 SymbolicName |
EVENT | event | Object | 이벤트 재전송을 위해 쓸 수 있는 현재 이벤트 객체 자체 |
EVENT_TOPIC | event.topics | String[] | 이벤트가 해당하는 Topic 값, Event개체의 topic 변수와 중복되는 값이지만, 필터링할 때 사용할 수 있도록 Properties안에도 들어 있다. |
EXCEPTION | exception | Throwable | 발생한 Exception 또는 Error 개체 |
EXCEPTION_CLASS | exception.class | String | Exception 클래스 이름 |
EXCEPTION_MESSAGE | exception.message | String | Exception.getMessage()에서 얻어지는 Exception 문자열 |
MESSAGE | message | String | 이벤트의 내용을 설명하는 문자열 |
SERVICE | service | ServiceReference | 등록되거나 수정된 Service에 대한 ServiceReference |
SERVICE_ID | service.id | Long | Service의 ID |
SERVICE_OBJECTCLASS | service.objectClass | String[] | 서비스의 실제 객체인 objectClass의 이름배열 |
SERVICE_PID | service.pid | String | 프레임워크에서 부여한 Service의 Persistent ID값 |
TIMESTAMP | timestamp | Long | 이벤트가 일어난 시간 |
1. EVENT_TOPIC
'*'을 지정하면 모든 이벤트를 받을 수 있으며 'org/osgi/framework/BundleEvent/*'과 같이 세부 항목에 '*'을 지정하면 그 패키지에 해당하는 모든 이벤트를 받을 수 있다. 중간에는 '*'이 올 수 없으며 배열형태로 원하는 이벤트만 지정하여 받을 수도 있다.
2. EVENT_FILTER
RFC2254 "The String Representation of LDAPLDAP(Lightweight Directory Access Protocol)은 TCP/IP 위에서 디렉토리 서비스를 조회하고 수정하는 응용프로토콜 Search Filters." 에 기반한 필터 문자열을 지정하여 원하는 속성의 이벤트만 필터링이 가능하다.
RFC2254 "The String Representation of LDAP
예를 들면 SymbolicName이 Google인 것만 : bundle.symbolicName=*.Google로 지정하면 되며 번들 id가 4이하 중에서, symbolic name에 eclipse인 것은 (&(bundle.id<=4)(bundle.symbolicName=*eclipse*))라고 하면 된다.
★ Event Admin에게 이벤트 보내기
Event Admin 서비스에게 이벤트를 전송하기 위해서는, 서비스 레지스트리로 부터 Event Admin 서비스를 가져와야 한다. 이를 위해서는 BundleContext.getServiceReference 함수나 4장에서 배운 ServiceTracker를 이용해서 손쉽게 가져와서 사용할 수 있다.Event Admin의 이벤트 전송 함수는 sendEvent와 postEvent 두 가지가 있다.
1. sendEvent : 동기 전송
sendEvent를 호출할 때, 해당 Event를 받는 쪽에서 모든 처리를 할 동안 호출하는 쪽으로 리턴되지 않는다. 이 경우 받는쪽에 문제가 발생하면 호출하는 쪽까지 위험하므로 반드시 스레드 처리를 해주는 것이 좋다.
2. postEvent : 비동기 전송
postEvent를 호출할 때는 EventAdmin 내부에서 해당 Event를 받을 핸들러로 보내는 비동기처리가 되므로,
postEvent를 호출하는 쪽으로 바로 리턴된다.