technologies‎ > ‎

이클립스 플러그인이 OSGi에서 어떻게 동작하는지 이해하기

posted Jun 20, 2010, 9:04 PM by Kuwon Kang

자바(Java™) 개발자들은 대부분 이클립스를 IDE로 사용한다. 이클립스 IDE는 실제로 플러그인이라고 부르는 상호 연관된 컴포넌트로 구성되어 있다. IDE의 기반이 되는 이 플러그인들은 그밖에도 데스크톱 애플리케이션을 만들 때 사용할 수 있다. 이클립스 기반 애플리케이션을 만들기 위해 필요로 하는 최소한의 플러그인 집합을 이클립스 리치 클라이언트 플랫폼(Eclipse Rich Client Platform)이라고 부른다. 하지만 플러그인은 그것들 스스로 동작하지는 못하고 자신들이 구동되고 동작할 수 있는 환경을 필요로 한다. 이클립스는 이런 환경으로 OSGi R4 명세서에 대한 구현체를 제공한다.

이클립스의 핵심이 OSGi에 의해 주도되었기 때문에, 이클립스 플러그인의 개념이 OSGi 프레임워크와 어떤 관련이 있는지 이해하는 것이 중요하다. 본 기사에서, 필자는 이클립스 플랫폼 입장에서 플러그인이라는 용어가 뜻하는 바가 무엇인지 살펴보는 것을 통해 이 관계를 자세히 설명하고자 한다. 그런 다음, 플러그인의 진화 과정을 이클립스 V2.1 플랫폼부터 OSGi 기반 구현체까지를 통틀어 살펴보겠다. 마지막으로 이클립스 플러그인에 적용되는 OSGi 제공 manifest.mf 옵션을 자세히 살펴보겠다.

플러그인이란?

이클립스는 온라인 도움말은 플러그인을 다음과 같이 정의한다.

"플러그인은 구조화된 번들로 시스템의 기능을 수행하는 데 필요한 코드 또는 데이터다. 기능은 코드 라이브러리(퍼블릭 API를 가지고 있는 자바 클래스), 플랫폼 확장 또는 문서 등으로 달성될 수 있다. 플러그인은 확장 지점을 정의할 수 있다. 확장 지점은 다른 플러그인들이 기능을 추가할 수 있도록 잘 정의해둔 지점을 말한다."
집중해서 살펴보아야 할 것은 플러그인들이 구조화된 방법으로 기능을 수행한다는 것이다. 그것들은 로깅과 같은 서비스를 제공하거나 편집기 같은 사용자 인터페이스(UI)에서 가용한 일부 기능을 제공할 것이다. 그것들의 기능에 관계없이, 모든 플러그인은 똑같은 구조적인 방법으로 정의되어 있다.

위로

OSGi로의 진화

언급했다시피, 이클립스는 OSGi를 자신의 플러그인 시스템의 밑바탕으로 사용한다. 하지만 항상 그래왔던 것은 아니다. 이클립스 초기 버전도 플러그인의 집합체로 설계되었지만, 이클립스는 자신만의 사적인 플러그인 시스템을 내장하여 플러그인들의 상호작용을 관리했다. 그런데 이클립스 IDE의 요구사항이 점차 커짐에 따라 더 견고한 솔루션이 필요하다는 것이 명백해졌다. 새로운 시스템의 기본이 되는 요구사항은 동적으로 새로운 플러그인을 처리하고 현존하는 플러그인을 제거하는 것이었다. 많은 조사 끝에, 이클립스 제작자들은 사적인 플러그인 프레임워크를 교체할 OSGi 프레임워크 구현체를 구현하기로 결정했다.

OSGi는 서비스 플랫폼에 대한 규약이다. 이클립스는 이 규약에 대한 많은 가용한 구현체 중 하나를 제공한다. 그리고 최신 OSGi R4 구현체에 대한 참조 구현을 제공한다. OSGi는 자바 기반 프레임워크로 오랜 시간 동작하며, 동적으로 수정을 가하고, 실행 중인 환경에 최소한의 정지를 요구하는 시스템에서 사용하려고 만들었다. 가장 최근에는, 전화기에서 자동차에 이르기까지 다양한 곳에서 찾아볼 수 있다.

OSGi는 컴포넌트와 서비스 모델이며, 그 핵심을 그림 1과 같이 나타낼 수 있다. OSGi 명세서는 모듈화의 단위를 번들이라고 정의한다(별도의 언급이 없다면 본 글의 다음부터는 이클립스의 플러그인이라는 용어와 OSGi 용어인 번들을 같은 뜻으로 이해하기 바란다. 이클립스의 번들이 현재 OSGi 번들이기 때문이다). OSGi는 JVM(Java Virture Machine) 수준의 서비스 레지스트리를 제공하여 번들들을 공개하고, 찾고, 서비스로 묶을 수 있도록 한다.

그림 1. 호스트 운영체제, 자바, OSGi에서 레이어들의 상호 작용

OSGi 명세서는 번들의 생명 주기와 번들들이 어떻게 상호작용하는지를 정의한다. 이러한 규칙들은 특별한 자바 클래스 로더를 사용하도록 강제한다. 보통의 자바 애플리케이션에서, CLASSPATH에 있는 모든 클래스는 서로가 서로를 볼 수 있다. 하지만 그와 반대로, OSGi 클래스 로더는 번들간의 클래스의 상호작용을 OSGi 명세와 각각의 번들이 가지고 있는 manifest.mf 파일의 옵션을 사용하여(본 기사의 뒷 부분에서 살펴볼 것이다) 제한한다.

이클립스 IDE는 OSGi의 서브셋 중 모듈화와 번들 생명 주기를 주로 사용한다. OSGi가 제공하는 서비스 중 최소한의 것들만 사용할 수 있다. 그 대신, 이클립스는 자신만의 확장 지점 시스템을 제공하여 번들이 상호작용할 수 있도록 한다. 번들의 기능을 다른 확장 지점에 기여하는 형태로 제공할 수 있다. 번들은 또한 자신만의 확장 지점을 정의하여 다른 번들들이 기여할 수 있도록 할 수 있다. 이클립스에서 확장 지점 사용의 예로 Preference 창을 들 수 있다. 핵심 이클립스 플러그인이 중앙의 창을 제공하고 확장 지점들을 노출(expose)시킨다. 이 확장 지점에 부가적인 환경 설정 페이지가 기여(contribution)할 수 있도록 하는 것이다. 새로운 플러그인이 이클립스에 추가되면, 자신의 페이지를 기여할 수 있다. 이클립스의 확장 지점 모델은 기본이 되는 OSGi 서비스와는 다르다. 번들 확장 지점은 그것을 정의한 번들이 소유한다. 다른 번들들은 단순히 그것들에 기여하는 것이다. 그와는 반대로, 어떠한 번들도 OSGi 서비스를 구현하고 사용할 수 있다.

위로

OSGi를 사용하여 이클립스 구현하기

이클립스 버전 3.1 이전에는, 모든 플러그인의 plugin.xml 파일에 플러그인 종속성과 확장 지점 그리고 그것을 확장하는 것(extension)들을 정의하였다. OSGi를 사용하는 새로운 버전의 이클립스는 의존성 정보를 manifest.xml 파일로 빼내고, plugin.xml 파일에는 오직 확장 지점과 확장들에 대한 정보만 남겨두었다. 이 진화 과정을 보여주는 실제 예제를 살펴보는 게 좋겠다. Listing 1은 이클립스 V3.0의 org.eclipse.pde.ui.plug-in에서 발췌한 것이다.

Listing 1. org.eclipse.pde 플러그인에서 발췌


  id="org.eclipse.pde.ui"
  name="%name"
  version="3.0.2"
  provider-name="%provider-name"
  class="org.eclipse.pde.internal.ui.PDEPlugin">

  
   
  
 

  


    name="%expoint.pluginContent.name"
  schema="schema/pluginContent.exsd"/>
    name="%expoint.newExtension.name"
  schema="schema/newExtension.exsd"/>
name="%expoint.templates.name"
schema="schema/templates.exsd"/>
name="%expoint.samples.name"
schema="schema/samples.exsd"/>


  

  point="org.eclipse.ui.perspectives">

  name="%perspective.name"
  icon="icons/eview16/plugins.gif"
  class="org.eclipse.pde.internal.ui.PDEPerspective"
  id="org.eclipse.pde.ui.PDEPerspective">
   선언은 플러그인 안에 있는 모든 패키지를 노출시켜 다른 플러그인들이 사용할 수 있도록 한다. 플러그인 종속성 추가 구역은 org.eclipse.pde.ui 플러그인이 필요로 하는 필수 플러그인들을 나열한다.다음의 두 구역은 확장 지점을 정의하여 org.eclipse.pde.ui를 다른 플러그인들이 사용할 수 있도록 하고 있으며, 기여(contribution)들이 그것을 사용할 수 있도록 정의한다. 여기서는, 사용자 정의 이클립스 개발 환경(PDE) 작업 환경을 정의하는 것을 볼 수 있다.이클립스 V3.1에 있는 똑같은 플러그인의 정의를 살펴보자. Listing 2는 plugin.xml 파일을 보여준다.

Listing 2. Plugin.xml



    name="%expoint.pluginContent.name"
  schema="schema/pluginContent.exsd"/>
    name="%expoint.newExtension.name"
  schema="schema/newExtension.exsd"/>
    name="%expoint.templates.name"
  schema="schema/templates.exsd"/>
    name="%expoint.samples.name"
  schema="schema/samples.exsd"/>


  

  point="org.eclipse.ui.perspectives">

  name="%perspective.name"
  icon="icons/eview16/plugins.gif"
  class="org.eclipse.pde.internal.ui.PDEPerspective"
  id="org.eclipse.pde.ui.PDEPerspective">
 익스포트(export)와 임포트(import) 정보가 사라진 것을 확인하라. 이 정보는 이제 Listing 3에 보이는 manifest.xml 파일로 옮겨갔다.Listing 3. Manifest.xmlManifest-Version: 1.0
Bundle-Name: %name
Bundle-SymbolicName: org.eclipse.pde.ui; singleton:=true
Bundle-Version: 3.1.0
Bundle-ClassPath: org.eclipse.pde.ui_3.1.0.jar
Bundle-Activator: org.eclipse.pde.internal.ui.PDEPlugin
Bundle-Vendor: %provider-name
Bundle-Localization: plugin
Require-Bundle: org.eclipse.core.runtime,
 org.eclipse.ui.ide,
 org.eclipse.ui.views,
 org.eclipse.jface.text,
 org.eclipse.ui.workbench.texteditor,
 org.eclipse.ui.editors,
 org.eclipse.ant.core,
 org.eclipse.core.resources,
 org.eclipse.debug.core,
 org.eclipse.debug.ui,
 org.eclipse.jdt.core,
 org.eclipse.jdt.debug.ui,
 org.eclipse.jdt.launching,
 org.eclipse.jdt.ui,
 org.eclipse.pde,
 org.eclipse.pde.build,
 org.eclipse.search,
 org.eclipse.team.core,
 org.eclipse.ui,
 org.eclipse.update.core,
 org.eclipse.ui.forms,
 org.eclipse.ant.ui,
 org.eclipse.jdt.junit,
 org.eclipse.ui.intro,
 org.eclipse.ui.cheatsheets,
 org.eclipse.update.configurator,
 org.eclipse.help.base
Bundle-ManifestVersion: 2
Eclipse-AutoStart: true
Export-Package: org.eclipse.pde.internal.ui;x-internal:=true,
 org.eclipse.pde.internal.ui.build;x-internal:=true,

. . .

org.eclipse.pde.ui,
 org.eclipse.pde.ui.internal.samples;x-internal:=true,
 org.eclipse.pde.ui.templates

여러 플러그인 추가 정보는 필요한 번들(Required-Bundles)이라는 이름으로 서술하고 있다. 그리고 * 패키지 노출은 명시적으로 노출할 패키지 목록으로 대체되었다.

플러그인 수준에서 의존성을, 명시적인 패키지 노출과 임포트를 필요로 하는 의존성으로 옮긴 것은 이클립스가 그 사실을 발표했을 때 상당히 많은 소동을 일으켰다. 근본적인 불만사항은 이전 버전의 이클립스에 존재했던 와 같은 무언가가 없다는 것이었다. 하지만 이것을 뺀 이유는 상당히 많다. 가장 중요한 이유는 노출할 패키지와 들여올 패키지를 기술함으로써 얻는 속도였다. 이전 버전의 이클립스는 jar 파일이 어떤 클래스 파일을 가지고 있는지 확인하기 위해 일일히 파일을 열고 찾아봐야했다. * 노출을 지원하지 않음으로써 원하지 않는 클래스를 노출하는 것을 한 번 방지할 수 있다. 플러그인 개발자들은 외부에서 사용 가능한 플러그인을 만들기 위해 신중하게 선택을 해야 한다. 이런 제약으로 인해 내부에서만 사용할 패키지들은 내부에 남아있게 된다.

위로

OSGi manifest 옵션

현재 OSGi R4 명세서는 거의 300쪽에 달하는 PDF 문서다. 이 명세서의 모든 부분을 살펴보는 것은 본 기사의 범위를 벗어난다. 이클립스 플러그인 개발자들을 위해 필요한 OSGi manifest.xml 옵션만을 살펴보겠다.

Bundle-Activator
이 클래스는 번들을 시작하고 멈출 때 사용한다. 위의 플러그인 예제에서, org.eclipse.pde.internal.ui.PDEPlugin 클래스라고 설정되어 있다. 이 클래스는 org.eclipse.core.runtime.Plugin을 상속받았고 BundleActivator 인터페이스를 구현하였다.
Bundle-ClassPath
이 속성은 번들이 사용할 CLASSPATH를 기술한다. 이 속성은 번들 jar 파일에 들어있는 디렉터리나 jar 파일에 대한 참조를 포함하고 있을 것이다. 마침표를 사용해 번들의 루트를 나타낼 수 있다. 이클립스 PDE 번들 예제의 경우, 번들 jar 파일에 들어있는 org.eclipse.pde.ui_3.1.0.jar 파일을 기술했다. 플러그인의 소스 버전을 워크스페이스로 가져오면, 가져오는 과정 중에 번들 CLASSPATH를 Bundle-ClassPath:에 추가해 준다. 이렇게 함으로써, 개발 중인 플러그인 버전이 컴파일된 번들 클래스를 선택하도록 할 수 있다.
Bundle-Version
이 속성은 번들의 버전 번호를 기술한다. 패키지 추가와 필요한 번들 명세에는 번들 버전 번호를 포함하도록 되어 있다.
Export-Package
이 속성은 다른 플러그인들이 사용할 수 있도록 외부로 노출할 모든 패키지를 기술한다.
Import-Package
이 속성은 필요로 하는 플러그인에서 사용할 패키지들을 명시적으로 기술한다. 기본적으로, 번들이 기능을 시작하려면 모든 패키지가 반드시 의존성을 해결한 상태(resolved)여야 한다. 패키지가 존재하지 않은 경우에 대비해 특정 패키지들은 부가적인 것으로 설정할 수도 있다. 명시적으로 가져오는 클래스들은 Require-Bundle 플러그인들에서 가져온 것들보다 먼저 의존성을 해결한다.
Require-Bundle
이 속성은 해당 번들에서 사용하기 위해 가져올 번들과 해당 번들들이 노출한 패키지를 기술한다. 기술되어 있는 번들들은 명시적 패키지 추가 작업 이후에 처리한다.
이클립스에서 제공하는 부가적인 manifest 옵션

버디 클래스 로더 옵션

하이버네이트 플러그인을 만든다고 해보자. 그리고 나서 하이버네이트가 의존성을 가지는 도메인 클래스들을 포함하고 있는 플러그인을 만든다. 그리고 하이버네이트 플러그인에 다음과 같은 줄을 추가한다. Eclipse-BuddyPolicy: registered.

그리고 다음을 도메인 클래스와 리소스를 가지고 있는 플러그인의 manifest 파일에 추가한다. Eclipse-RegisterBuddy: hibernate.

해당 설정으로 플러그인은 선언적으로 그 자신을 하이버네이트 플러그인에 노출시켰다. 이 때 하이버네이트 플러그인은 그 사실을 전혀 알지 못한다. 이제 하이버네이트 플러그인은 자신이 필요한 것들을 가져오겠다고 명시하지도 않았지만 그것들을 사용할 수 있다.

OSGi 명세서가 가지고 있는 manifest.mf 설정 옵션으로는 이클립스 플랫폼이 필요로 하는 모든 기능을 제공하지 못한다. 그 결과, 이클립스 제작진은 몇몇 확장 옵션을 추가했다(그리고 이것들을 OSGi 명세서 다음 버전에 추가할 수 있도록 제안했다).

Export-Package Header Extensions
이클립스는 두 개의 OSGi resolver 메서드 default와 strict를 가지고 있고 이것은 osgi.resolver가 기술할 수 있는 속성들이다. 이클립스는 Export-Package에 두 개의 확장인 x-internal과 x-friends를 가지고 있다. Strict 모드를 사용할 때만 이 두 속성을 반드시 명시해야 한다.
x-internal
이 속성의 기본값은 false다. 이 옵션을 사용하여 내부 패키지를 true로 설정하면 이클립스 PDE가 그것들을 사용하는 것을을 막아준다.
x-friends
이 옵션은 x-internal과 비슷하다. 하지만 이 옵션을 가지고 있는 번들들은 노출된 패키지를 사용할 수 있도록 허가한다. 다른 번들들은 사용할 수 없다. x-internal 옵션이 x-friends에 비해 우선시된다.
Eclipse-AutoStart
기본적으로, 이클립스는 필요할 때 번들을 읽어온다. 따라서 번들들은 자신이 가지고 있는 클래스가 그것을 추가한 번들에 의해 필요해질 때 로딩된다. 이 속성의 값을 ??로 설정하면 이클립스 시작시에 해당 번들을 로딩한다. 또한 번들이 가지고 있는 예외 클래스, 리소스 그리고 무엇이든지 그것들을 가지고 있는 번들을 시작할 필요 없이 로딩하도록 명시할 수 있다.
Eclipse-PlatformFilter
이 속성은 번들을 시작할 때 반드시 true 값이 나와야 하는 조건을 기술한다. 표현식에는 다음 정보를 포함시킬 수 있다.
언어를 뜻하는 osgi.nl
운영체제를 뜻하는 osgi.os
아키텍처를 뜻하는 osgi.arch
윈도우 기반 시스템을 뜻하는 osgi.ws
이 속성을 사용하는 예제로 SWT_AWT 브릿지(bridge)를 사용하는 번들이 동작하기 전에 운영체제가 MAC OS X이 아님을 확인하는 것이 가능하다(본 기사를 작성하는 시점에 MAC OS X의 SWT 구현체는 이 기능을 지원하지 않았다).
Eclipse-BuddyPolicy
이 옵션은 번들의 클래스 로딩 정책을 기술한다. 일반적으로 번들은 자신의 내부 클래스들과 외부에서 가져온 것들만을 볼 수 있다. 이클립스 뉴스그룹에서 버디 클래스 로딩을 설명할 때 사용한 가장 유명한 예제는 하이버네이트다. 하이버네이트 프레임워크는 사용자가 작성한 클래스와 리소스를 봐야지 하이버네이트 자신의 일부를 봐야 하는 것이 아니다. 이런 경우 중 하나가 하이버네이트 쿼리 언어(HQL)에 동적으로 클래스를 채워넣는 프로젝트를 사용할 때다. 기본적으로, 하이버네이트는 하이버네이트 jar 파일을 가지고 있는 플러그인의 외부 클래스들은 볼 수가 없다. 그리고 하이버네이트 맵을 허용하지 않는 클래스를 가지고 있는 플러그인 생성 때문에 하이버네이트 플러그인의 수정을 요구하게 된다. 다행히도, 이 문제를 버디 클래스 로더 옵션 절에서 설명한 버디 클래스 로더 옵션으로 해결할 수 있다.

위로

이클립스와 OSGi의 미래

이클립스는 OSGi 사용으로 인해 큰 장점을 얻었다. 동적으로 컴포넌트의 생명 주기를 관리하는 견고한 시스템이 되었다. 동적으로 WAR 파일을 서버에 배포하여 서블릿, JSP 그리고 이클립스 스타일 플러그인들으로 된 HTTP 리소스가 매일 같이 새롭게 개발되고 있다.

이클립스 재단은 OSGi 명세서를 주도하여 스스로가 사용하고 다른 참여자들도 OSGi를 사용할 수 있도록 하는 핵심 역할을 맡게 되었다. 내부적인 이클립스 플러그인 프레임워크에서 OSGi로 진화하면서, OSGi 명세서에 많은 부가적인 것들을 만들어냈고 그것들이 배포된 OSGi R4 명세서의 일부가 되었다. 그 결과, 이클립스 Equinox 프로젝트는 앞서가는 OSGi 레퍼런스 구현체가 되었다. 이런 진화는 OSGi를 제어하기 위한 자바 표준 요청(JSR) 291이 만들어진 것과 같이 이클립스와 OSGi의 파트너십이 계속해서 성공적으로 이어나갈 것임을 보장한다.

Comments