정보공간_1

[2기 강북 송석호] OSGi 서비스 등록 및 사용하기 본문

IT 놀이터/Elite Member Tech & Talk

[2기 강북 송석호] OSGi 서비스 등록 및 사용하기

알 수 없는 사용자 2012. 10. 23. 04:20

안녕하세요.

강북 멤버십 20-2 송석호입니다.

이번 달에는 equinox 기반으로 OSGi 서비스를 구현 해보도록 하겠습니다.


1. 서비스 등록하기

 드디어 서비스에 대해 알아볼 준비가 되었습니다. 제 생각에 서비스 레이어는 OSGi 의 가장 재미있는 부분입니다. 
 지난 달에 MovieLister 에 의해 영화를 찾기 위해 사용 되는 MovieFinder 인터페이스 예제에 대해 살펴보았습니다.  MovieLister 는 어디서 Movie 에 대한 정보들이 오던지 상관하지 않으므로,  MovieFinder 인터페이스를 이용하여 그런 세부사항들을 숨겼습니다. MovieLister 가 특정 구현이 아닌 인터페이스에만 의존하고 있기 때문에, MovieFinder 구현을 데이타베이스 에서 읽어오거나 심지어는 Amazon Web Service 를 호출하는것으로 교체할수 있다는 것입니다.
 하지만 특정부분에서는 MovieLister 에게 MovieFinder 의 구현을 넘겨주어야만 합니다. 이를 위해 MovieLister 가 lookup하는 메소드를 호출하기 보다는 외부 컨테이너를 하나 두어서 적절한 개체를 MovieLister 에게 넣어주는(push) 방법으로 처리합니다. 이것이 “Inversion of Control” 이라고 부르는 것이죠. PicoContainer, HiveMind, Spring 그리고 EJB 3.0 처럼 수많은 컨테이너들이 개발되었습니다. 그러나 이런 컨테이너들에겐 아직도 하나의 제한이 있습니다. 거의 static 이라는 것이죠. 한번 MovieLister 에게 MovieFinder 가 주어진다면, JVM 의 생명주기동안 연결된채로 남아있는 경향이 있습니다.
 하지만 OSGi동적인 방식으로 IoC 패턴을 구현하도록 해줍니다. MovieLister에게 동적으로 MovieFinder의 구현을 제공할수 있고, 후에 제거할수도 있습니다. 따라서 평범한 텍스트 파일에서 영화를 찾는 어플리케이션에서 Amazon Web Service 를 통해 영화를 찾는 Application 으로 실행중 교체(Hot-swap)가 가능합니다.

Service계층이 이런 일을 할 수 있도록 도와줍니다. 간단히 말해 MovieFinder 를 Service Registry 에 서비스로 등록합니다. 후에 MovieFinder 서비스는 MovieLister 에게 제공될수 있습니다. 서비스는 그냥 자바 개체와 특별히 다를게 없으며 Java Interface 이름으로 등록됩니다.
 이번에는 레지스트리에 서비스를 등록해 볼것입니다. 차후에 레지스트리에서 서비스를 가져오고 어떻게 이것이 어떻게 MovieLister에게 제공되는지를 살펴보겠습니다.
 지난달에 만들었던 BasicMovieFinder 번들을 추가할것입니다. 기존 클래스를 수정할 필요는 없고, 단지 번들 Activator 만 추가하면 됩니다. 아래 내용을 osgitut/movies/impl/BasicMovieFinderActivator.java 파일에 작성하세요.

BasicMovieFinderActivator.java

---------------------------------------------------------------------------------------------------------------

package osgitut.movies.impl;

import org.osgi.framework.*; import osgitut.movies.*;
import java.util.Properties;
import java.util.Dictionary; public class BasicMovieFinderActivator implements BundleActivator {
   
private ServiceRegistration registration;

   
public void start(BundleContext context) {

        MovieFinder finder =
new BasicMovieFinderImpl();

        Dictionary props =
new Properties();
        props.put(
"category", "misc");

        registration = context.registerService(
                               MovieFinder.class.getName(),
                               finder, props);
   
}


   
public void stop(BundleContext context) {
        registration.unregister();
   
}
}

---------------------------------------------------------------------------------------------------------------

이제 BasicMovieFinder.mf 파일의 내용을 아래와 같이 작성하세요.

BasicMovieFinder.mf

---------------------------------------------------------------------------------------------------------------Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Basic Movie Finder
Bundle-SymbolicName: BasicMovieFinder
Bundle-Version: 1.0.0
Bundle-Activator: osgitut.movies.impl.BasicMovieFinderActivator
Import-Package: org.osgi.framework,
osgitut.movies;version=
"[1.0.0,2.0.0)"
 ---------------------------------------------------------------------------------------------------------------
  지난 달에 비해 두 가지가 Manifest 에 추가되었습니다. 프레임워크에게 Activator 를 알려주는 Bundle-Activator 부분 그리고 Import-Package  org.osgi.framework 를 추가했습니다. 지난번 번들에선 프레임워크와 연동할 일이 없었으므로, OSGi API 패키지가 필요없었기 때문입니다.

이제 BasicMovieFinder.jar 를 다시 만들수 있습니다.

> javac -classpath equinox.jar;MoviesInterface.jar osgitut/movies/impl/*.java
> jar cfm BasicMovieFinder.jar BasicMovieFinder.mf osgitut/movies/impl/*.class


 OSGi 콘솔로 돌아가면, 아마 아직 지난번의 BasicMovieFinder.jar 가 설치되어 있을것입니다. 그럼 OSGi 에게 번들을 업데이트 할수 있도록 update N 을 입력해보세요. N 은 ss 를 입력했을때 보이는 번들의 ID 번호입니다. 자 이제 start N 을 입력해서 번들을 시작해 보세요. 그런데 별로 일어난 일이 없습니다.

실제로 첫번째 서비스를 OSGi 서비스 레지스트리에 등록했습니다만, 아직 이 서비스의 사용자가 없으므로 등록작업이 눈에 보이는 효과를 가져오지 못합니다. 코드가 정말로 무엇을 했는지 확인하려면 좀더 깊게 들어가야 하므로 아래의 명령을 입력해 봅니다.

osgi>  services (objectClass=*MovieFinder)

 

{osgitut.movies.MovieFinder}={category=misc, service.id=22}
  Registered by bundle: file:BasicMovieFinder.jar [4]
  No bundles using service.

 

위와 같은 출력을 볼수 있습니다.

서비스가 등록되어 있네요. services 뒤의 괄호안의 문자열을 빼고 입력해 보세요. 그 문자열은 관심있는 서비스들만 볼 수 있도록 출력될 서비스의 숫자를 줄여주는 필터 입니다. 필터가 없다면 등록된 모든 서비스들을 보실수 있습니다. 놀라울 정도로 많은 서비스가 있을겁니다!



2. 서비스 사용하기

이제 그 서비스를 찾고 다른 번들에서 그 서비스를 이용하도록 작업해 봅시다.
MovieFinder 를 서비스로 만들어서 서비스 레지스트리에 등록했습니다. 이젠 특정 감독에 의해 제작된 영화를 찾기위해 MovieFinder 를 사용하는 MovieLister를 만들어볼 것입니다.

 그리고 MovieLister 자체도 GUI Application 같은 다른 특정번들에 사용되는 서비스가 될것이라고 가정합니다. 여기서 문제점은, OSGi 서비스는 동적이라 등록되고 사라지고 한다는것입니다.

 즉, MovieFinder 서비스를 호출하고자 할때, 서비스가 불가능할수도 있습니다.
 그러면, 만약 MovieFinder 서비스가 존재하지 않는다면 MovieLister 는 무엇을 해야할까요?  MovieLister 가 하는 일 중에 MovieFinder 에 대한 호출이 매우 중요한 것이라는것은 확실하므로, 우리가 선택할수 있는 옵션은 몇가지 밖에 없습니다.

1. 에러를 낸다. (Null 을 리턴하거나 Exception 을 내거나)

2. 기다린다.

3. 처음부터 아예 설치가 안되도록 한다. ( Don’t be there in the first place )


이 글에선 쉬운 앞에 2가지 방법만을 살펴보도록 합니다. 세번째 방법은 아직 어떤 의미가 있게 보이진 않겠지만, 앞에 2가지를 보고나면 알수 있게 되실겁니다.

첫번째로 할일은 MovieLister 서비스에 대한 인터페이스를 작성하는것입니다. osgitut/movies/MovieLister.java를 작성하세요.

 

MovieLister.java

---------------------------------------------------------------------------------------------------------------

package osgitut.movies;

import java.util.List;

public interface MovieLister {
    List listByDirector(String name);
}

--------------------------------------------------------------------------------------------------------------

 

이제 osgitut/movies/impl/MovieListerImpl.java 파일을 작성합니다.

MovieListerImpl.java

---------------------------------------------------------------------------------------------------------------package osgitut.movies.impl;

import java.util.*;
import osgitut.movies.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;


public class MovieListerImpl implements MovieLister {
    private final ServiceTracker finderTrack;


    public MovieListerImpl(ServiceTracker finderTrack) {
        this.finderTrack = finderTrack;
    }

    public List listByDirector(String name) {

        MovieFinder finder = (MovieFinder) finderTrack.getService();
        if(finder == null) {
            return null;
        } else {

            return doSearch(name, finder);
        }
    }

    private List doSearch(String name, MovieFinder finder) {
        Movie[] movies = finder.findAll();
        List result = new LinkedList();
        for (int i = 0; i < movies.length; i++) {

            if(movies[i].getDirector().indexOf(name) > -1) {
                result.add(movies[i]);
            }
        }

        return result;
    }
}

---------------------------------------------------------------------------------------------------------------

이것이 아마 지금까지 샘플중 가장 긴 코드일겁니다. 여기선 무슨일이 일어나고 있을까요첫번째로 실제 영화를 찾는 로직이 doSearch(String,MovieFinder) 메소드로 분리되어, OSGi 에 관련된 코드를 독립시키도록 도와주고 있습니다. 검색을 하는 방법이 비효율적이지만, 그건 이 튜토리얼에는 크게 중요하지 않습니다. 어차피 우리 데이터베이스에는 2개의 영화만 있으니까요.
 흥미로운건 서비스 레지스트리로부터 MovieFinder 를 찾기위해 ServiceTracker를 사용하는 listByDirector(String name) 메소드 입니다. ServiceTracker는 OSGi API 의 하위레벨 코드를 추상화시켜주는 매우 유용한 클래스입니다. 어쨌든 정말 서비스가 존재하는지 체크할 필요가 있습니다. ServiceTracker는 생성자로 전달되었다고 가정합니다.
 
ServiceTracker 를 생성하기에 좋은곳은 bundle activator 입니다.

osgitut/movies/impl/MovieListerActivator.java를 작성하세요.

MovieListerActivator.java

---------------------------------------------------------------------------------------------------------------package osgitut.movies.impl;

import java.util.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.ServiceTracker;
import osgitut.movies.*;


public class MovieListerActivator implements BundleActivator {

    private ServiceTracker finderTracker;
    private ServiceRegistration listerReg;


    public void start(BundleContext context) throws Exception {
        // Create and open the MovieFinder ServiceTracker
        finderTracker = new ServiceTracker(context, MovieFinder.class.getName(), null);
        finderTracker.open();


        // Create the MovieLister and register as a service
        MovieLister lister = new MovieListerImpl(finderTracker);
        listerReg = context.registerService(MovieLister.class.getName(), lister, null);

        // Execute the sample search
        doSampleSearch(lister);
    }


    public void stop(BundleContext context) throws Exception {
        // Unregister the MovieLister service
        listerReg.unregister();
       
        // Close the MovieFinder ServiceTracker

        finderTracker.close();
    }

    private void doSampleSearch(MovieLister lister) {
        List movies = lister.listByDirector("Miyazaki");
        if(movies == null) {

            System.err.println("Could not retrieve movie list");
        } else {
            for (Iterator it = movies.iterator(); it.hasNext();) {
                Movie movie = (Movie) it.next();
                System.out.println("Title: " + movie.getTitle());
            }

        }
    }
}
---------------------------------------------------------------------------------------------------------------

 처음에 start 메소드에서 MovieLister 가 사용할 ServiceTracker 개체를 생성합니다. 그리고는 ServiceTracker 를 엽니다.  이건 MovieFinder 서비스의 Instance를 레지스트리에서 추적하는걸 시작하라는 것이죠. 그리고는 MovieLister 인터페이스 이름으로 서비스 레지스트리에 MovieListerImpl 개체를 생성하고 등록합니다. 마지막으로 번들을 시작했을 때 MovieLister 를 이용하여 간단한 검색을 실행하고 결과를 출력합니다.
 이제 이 번들을 만들고 설치해야 합니다. 자세한 설명은 하지 않도록 하겠습니다.  이전 블로깅을 참고하충분히 하실수 있을겁니다. 
 MovieLister.jar 를 Equinox 런타임안에 설치했다면, 시작할 수 있습니다. 지난번에 만든 BasicMovieFinder 번들이 동작하고 있는지에 따라 다음 2개의 메시지중 하나를 볼수 있습니다. 만약

동작중이 아니라면,

osgi> start 2
Could not retrieve movie list

아직 동작중이라면

osgi> start 2
Title: Spirited Away


각 번들을 중지시키고 시작시켜보면, 두개의 메시지를 원하는 대로 보실수 있습니다. 이것이 이번 연재의 거의 다입니다만, 제가 서비스가 사용 불가능할때(not available) 할수 있는 일중에 기다리는 방법이 있다고 한거 기억하시나요 ? 가지고 있는 코드만 있으면 이건 간단합니다. MovieListerImpl 의 16번째 라인을 getService() 메소드 대신 ServiceTracker waitForService(5000)으로 변경하시고, InterruptedException 에 대한 try/catch 블록을 추가하세요.

이것은 listByDirector() 메소드가 5000 밀리세컨드동안 MovieFinder 서비스가 나타날때까지 기다리도록 할것입니다. 만약 MovieFinder 서비스가 그 시간내에 설치된다면 (물론 이미 있을때에도) 우린 바로 서비스를 가져와서 사용이 가능할 것입니다.

일반적인 경우에 저는 이런식으로 중지하고 있는 쓰레드를 사용하라고 권고하지만, 이 경우엔 listByDirecor() 메소드가 프레임워크 쓰레드로 부터 호출되는 Bundle activator 의 start 메소드 에서 호출이 되었기 때문에 위험할수 있습니다. 번들이 활성화 될때 여러 일들이 일어나야할 필요가 있기때문에 Activator 들은 빨리 리턴해야 합니다. 최악의 경우에는 다른것에 의해 이미 lock 되었을지도 모르는 프레임워크 소유개체의 synchronized 블록에 들어갔으므로 deadlock 이 발생할 수도 있습니다. 기본 가이드라인은, 프레임워크에 의해 호출되는 모든코드 또는 번들의 activator 에 있는 start 메소드에서는 시간이 오래 걸리는 작업이나 블록킹 오퍼레이션을 하지 말라는 것입니다.


 

3. Reference

§ “OSGi Service Platform Core Specification Release 4, Version 4.3”, The OSGi Alliance, Apr. 2011.

§ Song J.H., “Design and Implementation of Management Bundle for OSGi Framework”, Soongsil Univ., 2008.

§ 권정혁, “실전 OSGi & Spring DM”, 위키북스, 2009.

§ Guru`s Blog, http://xguru.net/

§ OSGi Alliance site, www.osgi.org

§ Open OSGi middleware project site, www.knopflerfish.org