Whiteship's Note


4. Spring DM을 사용하여 서비스 노출하기.

Spring DM/exercise : 2008.02.06 15:23


1. Spring IDE 설치

http://www.springide.com/updatesite_nightly 를 이용해서 가장 최신의 Spring IDE 플러그인을 받을 겁니다. 그런데 처음 부터 저를 따라하셨으면, Eclipse를 새로 설치하셨을텐데, 그러면 Web Tools Plugin도 설치되어 있지 않아서, Spring IDE 플러그인을 설치하려고 하면, 종속성 문제 때문에 에러가 납니다.

그럴 때는 Eclispe Discovery Site도 같이 업데이트에서 선택한 다음에, Web 폴더를 왕창 선택해 줍니다.  스샷을 찍었어야 하는데, 깜빡하고 지나가서.. pass 합니다.

2. 필요한 번들 추가.

Target Platform 하단의 Pre Defined Target Plattform에서 Spring OSGi를 선택합니다. 그러면 끝입니다.

사용자 삽입 이미지

Spring IDE에서 제공해주는 기능입니다. 저걸 선택하면, 알아서 Spring OSGi를 사용할 때 필요한 번들들을 추가해 줍니다.

id    State       Bundle
0    ACTIVE      system.bundle_3.2.2.R32x_v20070118
1    ACTIVE      org.springframework.osgi.log4j.osgi_1.2.15.SNAPSHOT
2    ACTIVE      org.springframework.bundle.spring.core_2.5.1
3    ACTIVE      org.springframework.osgi.aopalliance.osgi_1.0.0.SNAPSHOT
4    ACTIVE      slf4j.api_1.4.3
5    ACTIVE      org.springframework.osgi.junit.osgi_3.8.2.SNAPSHOT
6    ACTIVE      org.springframework.osgi.source_1.0.0
7    ACTIVE      org.springframework.osgi.backport-util-concurrent.osgi_3.1.0.SNAPSHOT
8    ACTIVE      org.springframework.spring.source_2.5.1
9    INSTALLED   org.springframework.bundle.osgi.test_1.0.0
10    ACTIVE      org.springframework.bundle.spring.context_2.5.1
11    ACTIVE      org.springframework.bundle.osgi.io_1.0.0
12    ACTIVE      jcl104.over.slf4j_1.4.3
13    ACTIVE      org.springframework.bundle.osgi.extensions.annotations_1.0.0
14    ACTIVE      org.springframework.osgi.asm.osgi_2.2.3.SNAPSHOT
15    ACTIVE      org.springframework.bundle.spring.beans_2.5.1
16    ACTIVE      org.springframework.bundle.osgi.core_1.0.0
17    ACTIVE      org.springframework.osgi.cglib-nodep.osgi_2.1.3.SNAPSHOT
18    ACTIVE      org.springframework.bundle.osgi.extender_1.0.0
19    ACTIVE      slf4j.log4j12_1.4.3
20    ACTIVE      org.springframework.bundle.spring.aop_2.5.1
21    ACTIVE      org.springframework.bundle.spring.context.support_2.5.1

osgi.test 번들이 가동이 안 됐는데 뭔가 필요한게 없나 봅니다.

osgi> diag 9
initial@reference:file:plugins/spring-osgi-test-1.0.jar/ [9]
  Missing imported package org.apache.felix.framework_0.0.0.
  Missing imported package org.apache.felix.main_0.0.0.
  Missing imported package org.knopflerfish.framework_0.0.0.
  Missing imported package org.springframework.test_2.5.0.

확인해보니, 테스트 용도라서, 그런 것 같네요. 배포할 때 테스트 번들까지 추가할 필요가 없어서 그런가 봅니다.

암튼 패스하고 계속 진행하겠습니다.

3. 서비스 노출시키기

이전에 서비스를 노출할 때는, context.registerService() 라는 메소드를 사용했었습니다. 그런데 이제는 스프링 설정 파일에 bean으로 등록하면 됩니다.

먼저, MANIFEST.MF 파일을 수정합니다. overview탭에서 Activator를 지웁니다.(소스코드에도 Activator를 지워버립니다.)
사용자 삽입 이미지
Activator 클래스는 아예 지워버립니다.

그리고 META-INF 폴더 밑에 spring 폴더를 만들고 springOSGi.xml 이라는 이름으로 파일을 만들어 줍니다. 꼭 이 이름일 필요는 없습니다. spring 폴더 밑에 있는 xml 파일을 전부 읽어 들일테니까요.

그리고 다음과 같이 XML을 작성합니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:osgi="http://www.springframework.org/schema/osgi"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">

    <bean id="greeting" class="service.impl.GreetingImpl" init-method="start" />
   
    <osgi:service interface="service.Greeting" ref="greeting" />
   
</beans>

bean이 만들어졌는지 확인하기 위해서, init-method 속성을 사용했습니다. GreetingImpl에는 start 메소드 하나를 추가해줍니다.

public class GreetingImpl implements Greeting {

    public String hi(String name) {
        return "hi " + name;
    }
   
    public void start(){
        System.out.println("GreetingImpl Bean을 만들었습니다.");
    }

}

그리고 이 번들을 플랫폼에 올리면 다음과 같이 applicationContext가 만들어 진 것을 확인할 수 있습니다.

osgi> log4j:WARN No appenders could be found for logger (org.springframework.util.ClassUtils).
log4j:WARN Please initialize the log4j system properly.
GreetingImpl Bean을 만들었습니다.
ss

Framework is launched.

id    State       Bundle
0    ACTIVE      system.bundle_3.2.2.R32x_v20070118
1    ACTIVE      org.springframework.osgi.log4j.osgi_1.2.15.SNAPSHOT
2    ACTIVE      org.springframework.bundle.spring.context.support_2.5.1
3    ACTIVE      org.springframework.bundle.osgi.core_1.0.0
4    ACTIVE      org.springframework.osgi.aopalliance.osgi_1.0.0.SNAPSHOT
5    ACTIVE      org.springframework.bundle.osgi.extender_1.0.0
6    ACTIVE      org.springframework.bundle.spring.context_2.5.1
7    ACTIVE      org.springframework.osgi.cglib-nodep.osgi_2.1.3.SNAPSHOT
8    ACTIVE      org.springframework.bundle.osgi.extensions.annotations_1.0.0
9    ACTIVE      org.springframework.osgi.junit.osgi_3.8.2.SNAPSHOT
10    INSTALLED   org.springframework.bundle.osgi.test_1.0.0
11    ACTIVE      org.springframework.bundle.osgi.io_1.0.0
12    ACTIVE      org.springframework.osgi.asm.osgi_2.2.3.SNAPSHOT
13    ACTIVE      jcl104.over.slf4j_1.4.3
14    ACTIVE      org.springframework.spring.source_2.5.1
15    ACTIVE      org.springframework.bundle.spring.core_2.5.1
16    ACTIVE      org.springframework.osgi.source_1.0.0
17    ACTIVE      slf4j.log4j12_1.4.3
19    ACTIVE      org.springframework.osgi.backport-util-concurrent.osgi_3.1.0.SNAPSHOT
20    ACTIVE      org.springframework.bundle.spring.beans_2.5.1
21    ACTIVE      slf4j.api_1.4.3
22    ACTIVE      org.springframework.bundle.spring.aop_2.5.1
23    ACTIVE      Store_1.0.0

osgi>

끝! 그냥 서비스로 제공할 클래스를 bean으로 등록한 다음에 <osgi:service/> 엘리먼트의 interface와 ref 속성으로 해당 bean을 서비스로 등록해둡니다.

top


3. OSGi 서비스 노출하기와 들여오기.

Spring DM/exercise : 2008.02.06 12:52


1. Runtime 수정하기.

먼저 service.impl 패키지는 노출시키지 않고, service 패키지만 노출 시킵니다. 그 안에 들어있는 인터페이스만 외부로 노출 시키려는 것이지요.

사용자 삽입 이미지

이거면 됩니다. 그러면 이제 더 이상, StoreManager에서 service.impl 패키지를 import 할 수 없습니다. 따라서 구현체는 사용할 수 가 없게 됩니다. 구현체는 제공하지 말고 서비스를 registry에 등록하겠습니다.

2. Service Registry에 등록하기.

Activator로 이동해서. Store 번들이 ACTIVE 상태가 될 때, 특정 서비스를 등록하도록 코딩합니다.

public class Activator implements BundleActivator {

    public void start(BundleContext context) throws Exception {
        System.out.println("Store 번들을 가동 했습니다.");
        String className = Greeting.class.getName();
        context.registerService(className, new GreetingImpl(), null);
        System.out.println("GreetingImpl 객체를 '" + className + "'으로 서비스 레지스트리에 등록 했습니다.");
    }
   
    public void stop(BundleContext context) throws Exception {
        System.out.println("Store 번들을 멈췄습니다.");
    }

}

context에, 서비스를 등록합니다. 인터페이스의 이름으로 구현체를 등록합니다. JNDI와 비슷한 것 같네요.

자 이제 이 번들을 다시 Export 하고, start 시켜봅니다.

ACTIVE 상태가 된 다음에 서비스가 등록 된 것을 확인할 수 있습니다.

3. Dependencies 수정하기.

이제 StoreManager를 수정해야 합니다. 먼저 Depedencies에서 service.impl을 제거해 줍니다. 인터페이스만 알고 있으면 되기 때문에, service만 남겨둡니다.

사용자 삽입 이미지

이렇게 수정해면, GreetingImpl 클래스를 참조할 수 없기 때문에 에러가 발생하는데, 이 때 이상하게 Greeting 까지 못 찾는다고 에러가 납니다. 이건 Eclipse의 버그 같습니다. 이럴 때는 그냥 Eclipse를 껐다가 다시키면, Greeting 인터페이스를 참조할 수 있습니다.

이제 Activator에서 GreetingImpl 객체를 직접 만들어 사용하던 코드를 context에서 서비스를 가져오도록 수정해야 합니다.

서비스를 가져오려면 ServiceTracker를 사용해야 하는데, 그러려면, StoreManager에 org.osgi.util.tracker 패키지를 import 해주어야 합니다.

사용자 삽입 이미지

4. ServiceTracker 사용하기

이제 구현해 줍니다.

public class Activator implements BundleActivator {
   
    private ServiceTracker greetingServiceTracker;

    public void start(BundleContext context) throws Exception {
        System.out.println("StoreMaganer 번들 가동했습니다.");

        String serviceName = Greeting.class.getName();
        greetingServiceTracker = new ServiceTracker(context, serviceName, null);
        greetingServiceTracker.open();
        Greeting greeting = (Greeting) greetingServiceTracker.getService();
        System.out.println("'" + serviceName + "' 서비스를 가져왔습니다.");
       
        System.out.println(greeting.hi("토비"));
    }
   
    public void stop(BundleContext context) throws Exception {
        System.out.println("StoreManager 번들 멈춥니다.");
       
        greetingServiceTracker.close();
        greetingServiceTracker = null;
       
        System.out.println("Service Tracker 닫습니다.");
    }

}

ACTIVE 상태가 되면, 서비스 트래커를 만들고, 열어 줍니다. RESOLVED 상태가 될 때(stop)는 닫은 다음에 null로 만들어 줍니다.

그리고 Store 번들을 시작한 다음에 StoreManager를 시작하면 잘 동작합니다.

osgi> ss

Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.3.1.R33x_v20070828
1    RESOLVED    StoreManager_1.0.0
2    RESOLVED    Store_1.0.0

osgi> start 2
Store 번들을 가동 했습니다.
GreetingImpl 객체를 'service.Greeting'으로 서비스 레지스트리에 등록 했습니다.

osgi> start 1
StoreMaganer 번들 가동했습니다.
'service.Greeting' 서비스를 가져왔습니다.
hi 토비

osgi> ss

Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.3.1.R33x_v20070828
1    ACTIVE      StoreManager_1.0.0
2    ACTIVE      Store_1.0.0

osgi>

자 이 상태에서 Store를 stop하고, StoreManager도 stop한 다음에 StoreManaegr를 먼저 start 시킬 수 있습니다. 그럴 수도 있겠죠. 아직 작업이 마무리 되지 않았는데, 접속을 시도하고 있다고 생각해시면 그럴 수도 있다는 것이 상상되실 겁니다. 이런 상황에서 어떤 일이 벌어지는지 보겠습니다.

osgi> ss

Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.3.1.R33x_v20070828
1    ACTIVE      StoreManager_1.0.0
2    ACTIVE      Store_1.0.0

osgi> stop 2
Store 번들을 멈췄습니다.

osgi> stop 1
StoreManager 번들 멈춥니다.
Service Tracker 멈춥니다.

osgi> ss

Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.3.1.R33x_v20070828
1    RESOLVED    StoreManager_1.0.0
2    RESOLVED    Store_1.0.0

osgi> diag 1
file:/c:/plugins/StoreManager_1.0.0.jar [1]
  No unresolved constraints.

osgi> start 1
StoreMaganer 번들 가동했습니다.
'service.Greeting' 서비스를 가져왔습니다.
org.osgi.framework.BundleException: Exception in storemanager.Activator.start() of bundle StoreManager.
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:1018)
    at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:974)
    at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:346)
    at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:260)
    at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:252)
    at org.eclipse.osgi.framework.internal.core.FrameworkCommandProvider._start(FrameworkCommandProvider.java:260)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.eclipse.osgi.framework.internal.core.FrameworkCommandInterpreter.execute(FrameworkCommandInterpreter.java:150)
    at org.eclipse.osgi.framework.internal.core.FrameworkConsole.docommand(FrameworkConsole.java:291)
    at org.eclipse.osgi.framework.internal.core.FrameworkConsole.console(FrameworkConsole.java:276)
    at org.eclipse.osgi.framework.internal.core.FrameworkConsole.run(FrameworkConsole.java:218)
    at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.NullPointerException
    at storemanager.Activator.start(Activator.java:22)
... 생략

재밌습니다. 분명 StoreManager가 Resolved 상태였는데(종속성이 모두 해결되고 Start 될 수 있는 상태) start를 시키면... NullPointerException이 발생합니다.

왜 그러죠?


당연하죠. 서비스 레지스트리에 StoreManager의 Activator에서 찾으려는 Service가 없기 때문이죠.

왜 없죠?

Store 번들이 stop 으로 인해 RESOLVED 상태가 되면서, 등록됐던 서비스들이 사라져버려서 그렇습니다.
증거는 아래를 열어보세요.

뭔가 멋지지가 않습니다.

5. 좀 더 멋지게 ServiceTracker 사용하기.

일단 StoreManager가 Resolved 상태가 됐다면, Start 시켜서, AVTIVE 상태로 만들고 싶습니다.(서비스가 없더라도 말이죠.) 그러다가 원하는 서비스가 등록되면, 그 때 서비스를 얻어낸 다음 할 일을 처리하게 하고 싶습니다.

public class Activator implements BundleActivator {

    public static class GreetingServiceTracker extends ServiceTracker {

        public GreetingServiceTracker(BundleContext context) {
            super(context, Greeting.class.getName(), null);
        }

        public Object addingService(ServiceReference reference) {
            Greeting greeting = (Greeting) context.getService(reference);
            System.out.println(greeting.hi("토비"));
            return greeting;
        }
    }

    private ServiceTracker greetingServiceTracker;

    public void start(BundleContext context) throws Exception {
        System.out.println("StoreMaganer 번들 가동했습니다.");

        greetingServiceTracker = new GreetingServiceTracker(context);
        greetingServiceTracker.open();
    }

    public void stop(BundleContext context) throws Exception {
        System.out.println("StoreManager 번들 멈춥니다.");

        greetingServiceTracker.close();
        greetingServiceTracker = null;

        System.out.println("Service Tracker 멈춥니다.");
    }

}

ServiceTracker의 addingService() 메소드는 ServiceTracker 객체를 만들 때 필요한 ServiceTrackerCustomizer 파라미터가 null일 때 호출 됩니다. 그래서 위처럼 생성자에서 super를 사용해서 세 번째 인자에 null을 넣어 주어야 addingService() 메소드가 호출되고, 그럼 이 메소드 안에서 context.getService(ServiceReference);를 사용해서 서비스 객체를 가져옵니다. 여기서 비밀이 있는 것 같은데 우선은 복잡한 건 제끼고 예제부터 실행해 보겠습니다.

이번에도 위에서 했던 것처럼, Export 한 다음, StoreManager와 Store를 stop 시키고, StoreManager를 먼저 start 시켜보겠습니다.

osgi> ss

Framework is launched.

id    State       Bundle
0    ACTIVE      org.eclipse.osgi_3.3.1.R33x_v20070828
1    RESOLVED    StoreManager_1.0.0
2    RESOLVED    Store_1.0.0

osgi> start 1
StoreMaganer 번들 가동했습니다.

osgi> start 2
Store 번들을 가동 했습니다.
hi 토비
GreetingImpl 객체를 'service.Greeting'으로 서비스 레지스트리에 등록 했습니다.

osgi>

와우~~ 멋지지 않나요.

뭐가 멋진지 모르겠다구요??

분명 StoreManager를 먼저 실행 시켰는데 에러가 발생하지 않았습니다. 멋지지 않아요??? 물론 여기까진 별로 멋지지 않을 수도 있습니다. 그런데.. Store를 start 시키자 마자. StoreManager에서 해야 했던 일이 실행됐습니다.

이건 정말 멋진겁니다.

그런데 코딩이 정말... 피곤하죠. 이제 Spring DM이 등장할 차례 입니다.

참조 : http://www.eclipsezone.com/eclipse/forums/t91059.html
top