6.5. 서비스 Exporter와 서비스 Importer의 관계

공개한 서비스가 기능을 수행하기 위해 다른 서비스에 의존할 수 있다. 이때 만약 이들 서비스가 필수 레퍼런스라고 가정하고 해당 서비스들이 없어지고 대체제를 찾지 못해서 unsatisfied 상태가 되면 공개한 서비스는 자동으로 서비스 레지스트리에서 해지가된다. 즉 더이상 클라이언트에서 해당 서비스를 사용할 수 없게 된다. 그러나, 해당 필수 레퍼런스가 다시 가용해지면, 공개한 서비스도 다시 서비스 레지스트리에 등록된다.

이런 공개한 서비스의 자동 해지 및 자동 재등록은 오직 명시적인 선언에 의해서만 사용할 수 있다. 만약 서비스 A 빈을 공개한 서비스 S가 서비스 M을 사용하고 있을 때 아래 처럼 명시적으로 선언을 해 둬야. M이 없어지면 S가 해지되고, M이 사용가능 해지면 S도 다시 서비스 레지스트리에 등록된다.

<osgi:service id=”S” ref=”A” interface=”SomeInterface”/>

<bean id=”A” class=”SomeImplementation”>
   <property name=”helperService” ref=”M”/>
</bean>

<!– the reference element is used to refer to a service
     in the service registry –>
<osgi:reference id=”M” interface=”HelperService”
     cardinality=”1..1″/>

하지만 만약 A에서 M으로 의존성이 명시되어 있지 않고, 런타임 시에 M에 대한 레퍼런스를 만들어서 A로 넘긴다면 스프링 컨테이너는 아무일도 해주지 않는다. 스프링 DM은 의존성을 추적하지 않을 것이다.

6.4. Service importer global defaults

osgi 네임스페이스는 모든 가져올 레퍼런스에 설정한 전역 설정을 선언할 수 있는 두 개의 속성을 제공한다.

따라서, osgi 네임스페이스를 사용할 때 내부에 있는 set, list, reference 엘리먼트는 다음 속성을 사용할 수 있다.

  • default-timeout: 타임아웃을 명시하지 않은 모든 importer에 기본 타임아웃을 설정할 수 있다.
<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”                            (1)
          osgi:default-timeout=”5000″>                                                   (2)

  <reference id=”someService” interface=”com.xyz.AService”/>                             (3)

  <reference id=”someOtherService” interface=”com.xyz.BService”
       timeout=”1000″/>                                                                  (4)

</beans:beans>

(1) osgi 네임스페이스 프리픽스 선언
(2) default-timeout을 루트 엘리먼트에 선언. 만약 기본값이 설정되어 있지 않으면 5분이다. 여기서는 5초로 설정했다. 즉 밀리세컨이라는거..
(3) 이 reference는 기본값을 상속 받아서 타임아웃이 5초다.
(4) 이 reference는 기본값을 재정의해서 1초가 된다.

  • default-cardinality: 연관유형을 설정하지 않은 것들의 기본 연관유형을 설정할 수 있다. 가용한 값은 0..X와  1..X다. X는 런타입시에 reference일 경우에는 1로 list나 set일 경우에는 N으로 바뀐다.
<beans:beans
      xmlns=”http://www.springframework.org/schema/osgi”                                 (1)
      xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
      xmlns:beans=”http://www.springframework.org/schema/beans”                          (2)
      xmlns:osgi=”http://www.springframework.org/schema/osgi”                            (3)
         osgi:default-cardinality=”0..X”                                                 (4)
         default-lazy-init=”false”>                                                      (5)

  <reference id=”someService” interface=”com.xyz.AService”/>                             (6)

  <set id=”someSetOfService” interface=”com.xyz.BService”/>                              (7)

  <list id=”anotherListOfServices” interface=”com.xyz.CService” cardinality=”1..N”/>     (8)

</beans:beans>

(1) 스프링 DM 스키마를 기본 네임스페이스로 선언
(2) 스프링 프레임워크 beans 스키마를 가져오고 해당 네임스페이스 프리픽스를 정한다.(위 예제에선 beans로 했음)
(3) 스프링 DM 스키마를 가져오고 osgi 네임스페이스 프리픽스를 설정한다.
(4) default-cardinality를 루트 엘리먼트에 설정한다. 만약에 기본값을 설정하지 않으면 1..X으로 설정한다. 위의 경우 기본 값을 0..X으로 했고. 이 때 osgi 프리픽스를 붙인것에 주목하라.
(5) beans 엘리먼트 속성(default-lazy-init)은 프리픽스를 사용하지 않았다. since they are declared as being local and unqualified (see the beans schema for more information).
(6) reference 선언은 연관유형을 설정하지 않았기 때문에 기본값을 상속받는다. 즉 0..1이 된다.
(7) set 선언도 연관유형을 설정하지 않았기 때문에 기본값을 상속받는다. 즉 0..N이 된다. 0..1이 아니다
(8) list 선언은 연관유형이 기본값을 재정의하여 설정한대로 1..N 이 된다.

default-* 들을 사용하면 선언을 보다 쉽게 할 수 있고 기본 행위 변경을 간단하게 할 수 있다.(타임아웃 시간 줄이기나 늘리기와 같은..)

6.3. Exporter/Importer listener 베스트 프랙티스

위에서 언급했듯이, 스프링 DM 리스너들은 서비스가 묶이고, 풀리고, 등록되고, 해지될 때를 알기 위한 용도다. 이런 리스너들을 다룰 때 다음의 가이드라인이 도움이 될 수 있을 것이다.

  • 오래 걸리는 작업을 리스너 안에서 실행하지 말아라. 만약 그래야만 한다면, 별도의 쓰레드에서 작업하도록 하라. 리스너들이 동기적으로 처리되기 때문에 가능한 빨리 처리되도록 해야 한다. 해당 리스너 안에서 작업을 하는 동안에는 다른 이벤드들과 서비스의 활동이 대기상태가 된다.
  • 왠만하면 커스텀 리스너 콜백을 사용하라. 그래야 스프링 DM API에 묶이지 않으며, 특정 이름을 가용하지도 않는다.
  • bind/unbind 설정을 반복하고 있다면, 빈 설정 상속 기능을 사용하는 것을 한번 고려해 보아라. 공통적인 설정을 재사용할 수 있다.
  • java.util.Dictionary보다는 java.util.Map을 사용하라. 전자는 폐기처분 상태다. 그래도 호환성을 위해서 스프링 DM은 리스너에 필요시 Dictionary로 캐스팅할 수 있는 Map을 제공한다.
  • 과도한 메소드 사용에 주의하라. 원치 않게도 모든 메소드들이 특정 서비스 타입에 모두 호출 될 수 있다.
public class MyListener {
    void register((1)Object service, Map properties);
    void register((2)Collection dataService, Map properties);
    void register((3)SortedSet orderedDataService , Map properties);
}

1    Object type – 이 메소드는 항상 실행된다.
2    Collection type – 이 메소드가 실행되면, 바로 위에 있는 메소드도 실행된다.
3    SortedSet type – 이 메소드가 실행되면, 위에 있는 거 모두 실행된다.

6.3.1. 리스너와 cyclic depencdency

리스너가 자신을 사용하는 서비스를 사용하려는 빈을 참조하려는 경우가 있을 수 있다.

<bean id=”listener” class=”cycle.Listener”>                                              (1)
    <property name=”target” ref=”importer” />                                            (2)
</bean>
   
<osgi:reference id=”importer” interface=”SomeService”>                                   (3)
    <osgi:listener bind-method=”bind” ref=”listener” />                                  (4)
</osgi:reference>

1    Listener bean
2    Dependency listener -> importer
3    Importer declaration
4    Dependency importer -> listener

위 선언은 유효하다. 그러나 리스너를 만들려고 하면 레퍼런스 빈을 만들어야 하고 레퍼런스 빈을 만들려면 리스너를 만들어야 한다. 이 원이 깨져서 어느 한쪽이라고 먼저 생성을 하고 설정되어야 한다. 이런 시나리오는 스프링 DM에서 지원한다. 즉 알아서 만들어 준다. 하지만 다음과 같이 내부 빈을 사용한 경우에는 그렇치 않다.

<osgi:reference id=”importer” interface=”SomeService”>                                   (1)
    <osgi:listener bind-method=”bind”>                                                   (2)
        <bean class=”cycle.Listener”>                                                    (3)
            <property name=”target” ref=”importer” />                                    (4)
        </bean>
    </osgi:listener>
</osgi:reference>

1    OSGi service importer
2    Dependency between importer -> listener
3    Nested listener declaration
4    Dependency nested listener -> importer

내부 빈은 자신의 이름이나 라이프사이클도 없이 외부 빈에 종속적인 삶을 살 뿐이기 때문에, 내부 리스너를 캐시 할 수 없다. 따라서 cyclic 참조 원을 깨트리지못하고 동작하지 않게 되는것이다.

정리하자면, 만약 리스너가 정말로 자신을 사용하는 서비스에 대한 참조가 필요하다면 리스너를 탑 레벨 빈으로 등록하거나 dependency lookup을 사용하라. 대신 후자는 좀 더 많으 설정과 빈 이름을 사용할 것이기 때문에 dependency injection 보다 깨지기 쉽다.

6.2. Defining references to OSGi services

스프링 DM은 OSGi 서비스 레지스트리를 통해 사용할 수 있는 서비스를 나타내는 빈을 선언하는 기능을 제공한다. 이런 방법으로 통해 OSGi 서비스는 애플리케이션 컴포넌트로 주입될 수 있다. 서비스를 룩업할때는 해당 서비스가 지원하는 서비스 인터페이스 타입과 레지스트리에 등록된 서비스 속성에 부합하는 부가적인 필터 표현식을 사용한다.

몇몇 경우에, 간단하게 애플리케이션에서 하나의 서비스만을 필요로 할 때가 있다. 이 때는 reference 엘리먼트를 정의해서 단일 서비스를 참조하도록 정의할 수 있다. 다른 경우, 특히 OSGi 화이트보드 패턴을 사용할 때, 모든 가용한 서비들을 필요로 할 때가 있는데, 스프링 DM은 이거슬 List나 Set 콜렉션으로 이들 집합을 관리할 수 있는 기능을 제공한다.

6.2.1. 단일 서비스 참조하기

reference 엘리먼트는 서비스 레지스트리에 등록된 서비스를 참조할 때 사용한다.

선언한 것에 부합하는 서비스가 여러 개일 수 있기 때문에, BundleContext.getServiceReference에 의해 반환되는 서비스를 참조하게 된다. 이게 무슨 뜻이냐면 가장 높은 랭킹의 서비스가 반환된다는 것이다. 또는 만약 랭킹이 같을 때는 서비스 id가 가장 낮은 순서(프레임워크에 먼저 등록될 수록 id가 낮다.)로 반환된다.(OSGi 스펙에 보면 보다 자세하게 서비스 선택 알고리즘이 설명되어 있다.)

6.2.1.1. 가져온 서비스의 인터페이스 제어하기

interface 속성은 해당 서비스가 반드시 구현한 인터페이스를 나타낸다. 예를 들어, 다음의 선언은 messageService 빈 하나를 등록한건데, 이것은 MessageService 인터페이스를 제공하는 서비스를 서비스 레지스트리로부터 질의하여 반환받은 서비스가 된다.

<reference id=”messageService” interface=”com.xyz.MessageService”/>

service 선언과 마찬가지로, 여러개의 인터페이스를 기술할 떄는 interface 속성 대신에 내부 엘리먼트로 interfaces를 사용하면 된다.

<osgi:reference id=”importedOsgiService”>
  <osgi:interfaces>
     <value>com.xyz.MessageService</value>
     <value>com.xyz.MarkerInterface</value>
  </osgi:interfaces>
</osgi:reference>

interface 속성과 interfaces 엘리먼트를 둘 다 사용하는 것은 문법에 어긋나며, 항상 둘 중 하나만 사용하도록 한다.

reference 엘리먼트로 정의한 빈은 번들이 참조할 수 있는 서비스의 인터페이스들을 구현하고 있다.(greedy proxing 이라고 한다.) 만약 등록된 서비스 인터페이스가 자바 클래스 타입을 포함하고 있다면(인터페이스가 아니라), 해당 타입들은 스프링 AOP를 따르게 되ㅇㄴ다. 간략하게 말하자면, 명시된 인터페이스들이 인터페이스가 아니라 클래스라면, cglib이 반드시 가용한 상태여야 하며, final 메소드가 없어야 한다.

6.2.1.2. filter 속성

부가적인 속성 filter는 OSGi 필터 표현식을 명시하고 서비스 레지스트리 룩업을 할 때 오직 주어진 필터에 해당하는 것들에서만 찾도록 제약을 가할 수 있다.

<reference id=”asyncMessageService” interface=”com.xyz.MessageService”
  filter=”(asynchronous-delivery=true)”/>

이것은 asynchronous-delivery 속성이 true이고 MessageService 인터페이스로 등록해둔 OSGi 서비스만 참조할 것이다.

6.2.1.3. bean-name 속성

bean-name 속성은 service 엘리먼트를 사용하여 공개한 빈의 이름에 해당하는 서비스를 찾아주는 필터 표현식의 단축키 정도에 해당한다.

<bean id=”(1)messageServiceBean” scope=”bundle” class=”com.xyz.MessageServiceImpl”/>
<!– service exporter –>
<osgi:service id=”messageServiceExporter” ref=”(1)beanToBeExported” interface=”com.xyz.MessageService”/>

<osgi:reference id=”messageService” interface=”com.xyz.MessageService”
   bean-name=”(1)messageServiceBean”/>

1    the name used with bean-name attribute

위에 선언한 reference 빈은 MessageService 인터페이스로 등록된 서비스 중에서 org.springframework.osgi.bean.name 속성에 이름이 messageServiceBean 인 OSGi 서비스를 찾는다. 다시 간략하게 말하자면, 모든 공개된 빈 중에서 MessageService 인터페이스를 구현했고, 빈의 이름이 messageService인 빈을 참조하게 된다.

6.2.1.4. cardinality 속성

cardinality 속성은 항상 해당하는 서비스가 존재해야 하는지 아닌지를 기술할 때 사용한다. cardinality 값이 1..1(이게 기본값) 이면 해당하는 서비스가 반드시 가용해야 한다는 것을 뜻한다. cardinality 값이 0..1이면, 해당하는 서비스가 항상 필요한 것은 아니라는 것을 뜻한다.(4.2.1.6에 자세히 나와있다.) reference에 cardinality 1..1로 설정되어 있는 것은 필수mandatory 서비스 레퍼런스라고도 하며, 기본적으로 해당 레퍼런스가 참조 가능할 때까지 애플리케이션 컨텍스트 생성이 지연된다.

노트

해당 번들 자신이 공개한 서비스를 필수 서비스 레퍼런스로 참조하는 것은 에러다. 이런 행위로 인해 애플리케이션 컨텍스트 생성이 데드락이나 타임아웃으로 실패하게 될 것이다.

6.2.1.5. depends-on 속성

dependes-on 속성은 서비스 레지스트리에서 해당 서비스 레퍼런스를 룩업하기 전에 이 속성에 명시한 빈을 먼저 생성하라는 것이다.

6.2.1.6. context-class-loader 속성

OSGi 서비스 플랫폼 코어 스펙(현재 4.1 작성중)에는 서비스 레지스트리에서 얻어온 서비스를 통하여 컨텍스트 클래스 로더에서 가용한 타입이나 리소스들을 명시하고 있지 않다. 따라서 어떤 서비스들은 컨텍스트 클래스 로더로부터 특정 라이브러리가 가용하리라고 예상하고 작성하기도 한다. 스프링 DM은 서비스 호출 시점에 컨텍스트 클래스 로더 제어대해 명시적으로 제어한다. reference 엘리먼트의 context-class-loader 속성으로 이를 달성할 수 있다.

context-class-loader에 가용한 값은 다음과 같다.

  • client – 서비스 호출 기간중에, 컨텍스트 클래스 로더는 서비스를 호출하고 있는 번들의 클래스패스에 있는 타입들을 볼 수 있다. 기본값이다.
  • service-provider – 서비스 호출 기간중에, 컨텍스트 클래스 로더는 서비스를 공개한 쪽 번들의 클래스패스에 있는 타입들을 볼 수 있다.
  • unmanaged – 서비스 호출 기간중에 컨텍스트 클래스 로더 관리를 하지 않음

6.2.1.7. reference 엘리먼트 속성

지금까지 살펴본 속성들 표 생략.

6.2.1.8. reference와 OSGi 서비스 동적특성Dynamics

reference 엘리먼트로 정의한 빈은 애플리케이션 컨텍스트의 생명주기 동안 바뀌지 않는다. 하지만, 해당 빈이 참조하는 OSGi 서비스는 언제든지 서비스 레지스트리에서 나가고 등록될 수 있다. 필수 서비스 레퍼런스(cardinality 1..1)인 경우에는, 해당 서비스가 가용할 떄 까지 애플리케이션 컨텍스트 생성을 일정 시간 지연시킨다. 부가 서비스 레퍼런스(cardinality 0..1)인 경우에는 현재 해당 서비스가 가용한지 여부와 관계없이 해당 빈이 바로 생성된다.

만약 참조해야 하는 서비스가 없어지면, 스프링 DM은 해당 reference에 정의한 것에 해당하는 다른 서비스로 기존 서비스를 교체 시도를 한다. 애플리케이션은 리스너를 등록하여 자신이 참조하는 서비스의 변경을 알아차려야 한다. 만약 해당하는 서비스가 없다면, reference는 unsatisfied가 되고, 이 reference에 의존하는 다른 공개된 서비스들을 해당 reference의 의존성이 해결될때까지 서비스 레지스트리에서 해지한다. 자세한건 6.5에서..

만약 unsatistied 상태인 reference 빈에 어떤 요청이 발생했다면, 해당 요청은 레퍼런스가 다시 satisfied 상태가 될때까지 잠시 대기시킨다. timeout 속성에 이 때 대기할 시간(millisesecond)을 설정할 수 있다. 몇시한 타임아웃 값이 지나고 나서도 해당 빈이 가용하지 않다면 ServiceUnavailableException을 던진다.

6.2.1.9. 관리하고 있는 서비스 레퍼런스 참조하기

스프링 DM은 자신이 관리하고 있는 서비스 레퍼런스를 ServiceReference 타입으로 자동변환해준다. 따라서, ServiceReference 타입의 속성을 가지고 있는 빈에 해당 서비스를 (명시된 인터페이스 타입이 아니라 ServiceReference 타입으로)injection 할 수 있다.

public class BeanWithServiceReference {
    private ServiceReference serviceReference;
    private SomeService service;
           
    // getters/setters ommitted
}

<reference id=”service” interface=”com.xyz.SomeService”/>
       
<bean id=”someBean” class=”BeanWithServiceReference”>
  <property name=”serviceReference” ref=”service”/>                                      (1)
  <property name=”service” ref=”service”/>                                               (2)
</bean>

1    Automatic managed service to ServiceReference conversion.
2    Managed service is injected without any conversion

노트

주입된 ServiceReference 타입의 빈은 참조하고 있는 OSGi 서비스 객체가 바뀔 때마다 같이 바뀐다.


6.2.2. 서비스 콜렉션 참조하기

때때로 애플리케이션은 간단하게 특정 범위 중에서 하나의 서비스를 참조할 뿐만 아니라, 범위 해당하는 모든 서비스를 참조해야 할 때도 있다. 스프링 DM은 이를 List 또는 Set(정렬은 부가적으로) 엘리먼트로 지원한다.

List와 Set의 차이는 오직 동일성뿐이다. 레지스트리에 등록되어 있는 두 개 이상의 서비스들(각자 다른 서비스 id를 가지고 있을 것이다.)이 해당 서비스의 equals 메소드 구현에 따라 같을 수도 있는데. Set에는 그들 중에 하나만 가지고 있을 것이고, List는 전부다 반환해 준다.

set과 list 스키마 엘리먼트는 서비스 집합체를 나타낼 때 사용한다.

이 엘리먼트들도 interface, filter, bean-name, cardinality 그리고 context-class-loader를 지원한다. 단 cardinality의 값으로는 0..n 과 1..n 만 사용할 수 있다.

0..n은 해당 콜렉션에 대응하는 서비스가 하나도 없어도 상관없다는 것이고 1..n은 필수 레퍼런스를 나타낸다.

list는 java.util.List 타입으로 정의되고, set 엘리먼트는 java.util.Set 타입의 빈으로 정의된다.

다음의 예제는 EventListener 인터페이스를 구현한 모든 등록된 서비스들을 List 타입으로 반환할 것이다.

<list id=”myEventListeners” interface=”com.xyz.EventListener”/>

리스트의 요소들은 스프링에의해 동적으로 관리될 것이다. 대응하는 서비스가 레지스트리에 등록되고 해지됨에 따라 콜렉션의 구성요소도 갱신될 것이다.

스프링 DM은 정렬된 콜렉션도 지원한다. set과 list 둘다.

정렬하는 방법은 두 가지인데, 하나는 Comparator를 사용하는 방법(custom ordering)이고 다른 하나는 Comparable 인터페이스를 구현한 것 끼리 비교하는 것(natural ordering)이다.

6.2.2.1. Greedy Proxing

스프링 DM 서비스 콜렉션으로 가져온 모든 OSGi 서비스는 interface 속성에 설정한 클래스에 타입 호환가능하다. 하지만, 때때로 서비스가 부가적으로 설정한 클래스들을 사용하고 시을 수 있다.

그런 경우, 스프링 DM 콜렉션은 greedy-groxing 속성을 지원하는데, 이 속성을 사용하여 참조하는 서비스에 부가적으로 설정한 모든 클래스들을 사용할 수 있도록 프록시들을 생성하게 할 수 있다. 따라서, 가져온 프록시를 interface에 설정한 클래스가 아닌 다른 타입으로 캐스팅을 할 수 있다. 다음의 예제를 보자.

<list id=”services” interface=”com.xyz.SomeService” greedy-proxying=”true”/>

다음과 같이 할 수 있다.

for (Iterator iterator = services.iterator(); iterator.hasNext();) {
    SomeService service = (SomeService) iterator.next();
    service.executeOperation();
    // if the service implements an additional type
    // do something extra
    if (service instanceof MessageDispatcher) {
        ((MessageDispatcher)service).sendAckMessage();
    }
}

노트

greedy proxy와 instanceof를 사용하기 전에 다른 인터페이스나 클래스 사용을 고려해 보아라. 보다 나은 다형성과 객체 지향 스러운 서비스를 구성할 수 있을 것이다.

6.2.2.2. 콜렉션(list와 set) 엘리먼트 속성

timeout 이 없는거만 뺴면 reference 랑 똑같다.

6.2.2.3. list/set 그리고 OSGi 서비스 동적특성

OSGi 서비스들의 집합은 OSGi 공간의 상태를 반영해야 하기 때문에 애플리케이션 컨텍스트의 라이프사이클 동안 계속해서 변한다. 서비스가 등록되고 해지되는 동안 콜렉션에 해당 서비스들이 추가 또는 삭제 된다.

reference 선언은 자신이 참조하는 서비스가 해지 되면 대체제를 찾는 반면, 콜렉션은 그냥 서비스를 콜렉션에서 제거한다. reference와 마찬가지로, 1..n 인 cardinality는 필수 레퍼런스라고 하며, 0..n은 부가적인 레퍼런스라고 한다. 만약에 대응하는 서비스가 없다면 필수 콜렉션만 unsatisfied 상태가 되고 ServiceUnavailableException을 던진다.

6.2.2.4. Iterator 제약사항과 서비스 콜렉션

콜렉션을 순회하는 방법중에 권장하는 방법은 Iterator를 사용하는 것이다. 하지만, OSGi 서비스는 언제든지 등록되고 해지될 수 있으므로, 콜렉션 또한 그에 따라 변경되어야 한다. 스프링 DM은 깔끔하게Transparenlty 사용자가 참조하는 모든 Iterator들을 수정해 준다. 따라서 사용자는 콜렉션이 변경되는 와중에도 안전하게 순회할 수 있다. 게다가, 그 Iterator들은 콜렉션의 모든 변경 사항을 반영한다. 변경이 Iterator 객체를 만든 후에 발생했더라도 반영된다. 만약 순회를 시작하자 마자 서비스들이 왕창 내려갔다고 했을때, 이 상태에서 순회를 계속하면 “죽은” 서비스들을 호출하게 될 것이다. 스프링 DM은 그래서 이 Iterator들이 스냅샷이 아니라 가장 최신 콜렉션 상태를 반영한 것이 된다. 순회가 얼마나 빠르고 느리냐는 상관없다.

서비스 수정은 Iteraor에서 순회하기 전에 있는 것들에만 반영이 된다는 것에 주의하자. 이미 순회를 마친 서비스에는 어쩔 도리가 없다. 만약 이미 해지된 서비스에 어떤 동작을 요구한다면 ServiceUnavailableException이 발생한다.

정리하자면, reference 선언은 자신이 참조하는 서비스가 해지되면 대체제를 찾지만 콜렉션은 대체제를 찾지 않는다. 그냥 자신의 콜렉션에서 해당 서비스를 제거할 뿐이다. 다음 순회할 때 그들을 사용하지 않도록.

Iterator 제약사항은 next() 메소드가 항상 hasNext() 호출 결과를 따른다는 것을 알아두자.

hasNext()가 true일 때 next()를 하면 항상 null이 아닌 값을 반환한다.
hasNext()가 false일 때 next()를 하면 NoSuchElementException을 던진다.

간단하게 Iterator를 리프레쉬하려면 hasNext()를 다시 호출하면 된다. 그럼 Iterator가 현재 콜렉션 에서 다음 순회 엔트리를 확인할 것이다.

6.2.3. 가져온import OSGi 서비스의 동적특성 다루기

reference나 set, list를 사용할 때 스프링 DM이 뒷단의 서비스를 관리할 것이다. 하지만, 하지만 때론 애플리케이션이 뒷단의 서비스 변경을 알고 싶을 수도 있다.

언제 reference 빈이 묶이고 풀리는지 알고 싶은 그런 애플리케이션들은 내부 엘리먼트로 하나 이상의 listener 엘리먼트를 등록할 수 있다. 이 엘리먼트는 reference, set, list에서 사용할 수 있다. 서비스 공개 리스너 설정과 비슷하다. listener 엘리먼트에 org.springframework.osgi.service.importer.OsgiServiceLifecycleListener 인터페이스를 구현한 빈을 참조하도록 설정하면, 해당 인터페이스의 bind와 unbind 메소드를 호출하게 된다. 커스텀 바인드 언바인드 메소드를 사용하려면 메소드 이름을 사용하면 된다.

<reference id=”someService” interface=”com.xyz.MessageService”>
  <listener ref=”aListenerBean”/>
</reference>

<reference id=”someService” interface=”com.xyz.MessageService”>
  <listener bind-method=”onBind” unbind-method=”onUnbind”>
     <beans:bean class=”MyCustomListener”/>
  </listener>
</reference>

만약 OsgiServiceLifecycleListener와 커스텀 메소드 둘다 등록되어 있다면 인터페이스 구현체 먼저 호출하고 그 다음 커스텀 메소드를 호출한다.

커스텀 메소드의 시그너쳐는 다음과 같다.

public void anyMethodName(ServiceType service, Dictionary properties);

public void anyMethodName(ServiceType service, Map properties);

public void anyMethodName(ServiceReference ref);

ServiceType 자리에는 어떤 타입이든 선언할 수 있다. 해당 타입에 해당하는 서비스가 묶이거나 풀릴때 호출된다. 만약 모든 타입에 대해 콜백을 호출하고 싶으면 java.lang.Object 타입으로 선언하면 된다.

properties 파라미터는 등록된 서비스가 가지고 있는 속성들의 집합을 나타낸다.

리스너가 reference에 등록되어 있다면:

  • 레퍼런스가 초기에 서비스와 묶일 때와 서비스가 새로운 서비스로 교체될 때마다 bind 메소드가 실행된다.
  • 현재 서비스가 해지되거나, 그 즉시 교체가능한 대체 서비스가 없을 때 unbind 콜백이 호출된다.(물론 해당 reference는 unsatisfied 상태가 된다.)

리스너가 콜렉션에 등록되어 있다면:

  • 새로운 서비스가 콜렉션에 추가되면 bind 메소드를 호출한다.
  • 서비스가 해지되고 콜렉션에서 제거될 때 unbind 메소드를 호출한다.

콜렉션에는 서비스 교체가 없다는 것을 주목하라. 콜렉션은 그냥 서비스를 추가하고 제거할 뿐이다.

뒷단의 OSGi 서비스에 대한 OSGi serviceChanged 이벤트 처리의 일부로 바인드와 언바인드 콜백은 동기적으로 처리된다.

아래의 테이블은 reference listener 서브 엘리먼트로 가용한 속성들이다.

생략

6.2.4. 리스너와 서비스 프록시들

임폴트 리스너들은 특정 시점에 묶이는 OSGi 서비스에 접근할 수 있는 방법을 제공하지만, 여기서 중요한 것은 해당 메소드로 넘어온 아규먼트가 실제 서비스 객체가 아닌 프록시라는 것이다. 프록시를 사용하는 이유는 언제 어떻게 바뀔지 모른느 객체에 대하여 강력한 레퍼런스를 가지는 것을 방지하기 위함이다. 서비스들을 추적하는 것이 주목적인 리스너들은 instance equality나 object equlity에 연연해서는 안된다. 이 들 메소드가 해당 인터페이스나 클래스의 public 메소드로 구현한 것이 아니라면, 프록시의 메소드가 호출될 것이다.

따라서 추적은 그냥 서비스 인터페이스나, 서비스 속성(org.osgi.framework.Constants#SERVICE_ID 참조) 또는 서비스 노티(바인드/언바인드)로만 하는 것을 추천한다.

6.2.5. 호출하는 BundleContext에 접근하기

가져온 서비스가 특정 시점에 어떤 번들을 사용하고 있는지 알고 싶을 수 있다. 이런 시나리오를 돕기 위해, 스플이 DM이 가져온 서비스는 가져온 번들 BundleContext를 LocalBundleContext 클래스로 공개한다. 가져온 것에 대한 메소드가 호출될 때마다, ThreadLocal을 사용하여 호출하는 BundleContext를 사용할 수 있다. getInvokerBundleContext()를 통해서..

단 이걸 사용할 때 해당 클래스가 스프링 DM 코드에 의존하게 된다는 것을 주의해야 한다.

6.1. Exporting a Spring bean as an OSGi service

service 엘리먼트를 사용해서 OSGi 서비스로 공개할 빈을 설정할 수 있다. 최소한 공개시킬 빈과 서비스 인터페이스를 설정해야 한다.

<service ref=”beanToPublish” interface=”com.xyz.MessageService”/>

위의 설정은 beanToPublish라는 빈을 com.xyz.MessageService 인터페이스를 통해서 사용할 수 있도록 서비스로 공개하겠다는 것이다. 그렇게 공개한 서비스는 org.springframework.osgi.bean.name 이라는 속성을 타겟 빈의 이름(여기서는 beanToPublish)을 설정한다.

서비스 엘리먼트로 정의한 빈은 org.osgi.framework.ServiceRegistration 타입의 빈이고 즉 서비스 레지스트리에 ServiceRegistration 객체가 등록된다. 이 빈에 id를 설정하고 다른 빈에서 해당 ServiceRegistration 객체를 참조 하도록 설정할 수 도 있다.

<service id=”myServiceRegistration” ref=”beanToPublish”
    interface=”com.xyz.MessageService”/>

서비스로 공개할 빈 이름을 참조하는 대신에 서비스 빈 내부에 익명 내부 빈으로 등록할 수도 있다. 최상위 네임스페이스가 bean일 경우에 아래와 같을 것이다.

<osgi:service interface=”com.xyz.MessageService”>
  <bean class=”SomeClass”>
     …
  </bean>
</osgi:service>

org.osgi.framework.ServiceFactory 인터페이스
를 구현한 빈을 공개하면 OSGi Service Platform Core Specification 5.6에 있는 ServiceFactory 제약대로 동작한다.(해당 서비스 객체를 요구할 때마다 서비스팩토리에서 만들어서 줌) OSGi API를 구현하는 대신, 스프링 DM이 도입한 새로운 bean 스콥을 사용할 수도 있다. bundle 스콥으로 해당 빈을 이 스콥으로 OSGi 서비스로 공개하면 각각의 클라이언트(OSGi 서비스 레지스트리에서 서비스를 import하는 번들들)마다 독립적인 객체를 만들어서 제공해 줄 것이다. 해당 빈을 서비스로 임포트한 번들이 동작을 멈추면, 해당 빈 객체는 사라질 것이다. bundle 스콥을 사용하려면 다음과 같이 설정하면 된다.

<osgi:service ref=”beanToBeExported” interface=”com.xyz.MessageService”/>

<bean id=”beanToBeExported” scope=”bundle” class=”com.xyz.MessageServiceImpl”/>

6.1.1. Controlling the set of advertised service interfaces for an exported service

OSGi 서비스 플랫폼 코어 스팩에는 “서비스 인터페이스”라는 용어를 정의하고 있는데, 이는 서비스의 public 메소드들 규약을 표현하는 용어다. 일반적으로 자바 인터페이스가 되지만, 스펙에서는 클래스 이름으로 서비스 객체를 등록 할 수도 있도록 지원한다. 따라서, “서비스 인터페이스”라는 말은 클래스와 인터페이스 둘 모두를 가리킬 수 있다.

공개할 서비스의 서비스 인터페이들을 기술하는데 몇가지 옵션들이 있다. 가장 간단하게는 interface 속성에 전체 페키지 경로가 붙은 인터페이스 이름을 설정할 수 있다. 해당 서비스를 여러 인터페이스에 등록하려면 interfaces라는 내부 엘리먼트를 사용한다.

<osgi:service ref=”beanToBeExported”>
  <osgi:interfaces>
     <value>com.xyz.MessageService</value>
     <value>com.xyz.MarkerInterface</value>
  </osgi:interfaces>
</osgi:service>

동시에 둘 다 사용하는 것은 문법에 어긋난다. 둘 중 하나만 사용하라.

6.1.1.1. Detecting the advertised interfaces at runtime

auto-export  속성을 사용하면 명시적으로 서비스 인터페이스들을 등록할 필요가 없다. 스프링 DM이 클래스 상속구조와 인터페이스를 분석하여 설정할 것이다.

auto-export 속성은 네 개중 하나의 값을 가질 수 있다.

  • disabled: 기본값으로, 자동으로 찾지 않으므로, interface 또는 interfaces 설정을 해야 한다.
  • interfaces: 해당 빈이 구현한 모든 인터페이스를 등록한다.
  • class-hierarchy: 빈이 구현한 모든 타입과 슈퍼 타입을 등록한다.
  • all-classes: 위에 두개 합친거

auto-export와 interfaces 옵션은 상호배타적이지 않다. 두 설정을 동시에 사용할 수 있다. 하지만, 대부분의 경우 다음의 설정이 유용할 것이다.

<service ref=”beanToBeExported” auto-export=”interfaces”/>

이렇게 설정하여 인터페이스 상속구조에서 모든 인터페이스 타입으로 서비스를 참조할 수 있다.

public interface SuperInterface {}

public interface SubInterface extends SuperInterface {}

이런 코드에서 SupoerInterface로 등록한 서비스는 SubInterface로 참조할 수가 없는데, 이런 이유로 인해 해당 서비스에 auto-export=”interfaces”로 설정하여 모든 인터페이스를 지원하도록하는 것이 베스트 프랙티스다.

6.1.2. 공개할 서비스에 프로퍼티 설정하기

앞서 언급했듯이, 공개한 서비스는 항상 org.springframework.osgi.bean.name 서비스 속성을 공개할 빈의 이름으로 설정되어 있다. service-properties 내부 엘리먼트를 사용하여 부가적인 속성을 설정할 수 있다. service-properties 엘리먼트는 키-값 쌍을 가지고 있는데 이는 서비스에서 속성을 사용할 수 있다. 키는 반드시 문자열이어야 하고 값은 OSGi 필터가 인식할 수 있는 타입이어야 한다. OSGi 서비스 플랫폼 코어 스팩 5.5를 보면 필터 표현식과 프로퍼티 값이 어떻게 대응하는지 참조할 수 있다.

service-properties 엘리먼트는 반드시 최소한 하나의 entry 엘리먼트를 가지고 있어야 한다.

<service ref=”beanToBeExported” interface=”com.xyz.MyServiceInterface”>
  <service-properties>
    <beans:entry key=”myOtherKey” value=”aStringValue”/>
    <beans:entry key=”aThirdKey” value-ref=”beanToExposeAsProperty”/>
  </service-properties>
</service>

스프링 DM 로드맵에는 OSGi Configuration Administration 서비스에 안에 등록되어 있는 속성들을 등록된 서비스의 속성으로 공개하는 기능을 포함하고 있다. Appendix F. 로드맵에 자세한 내용이 있다.

6.1.3 depends-on 설정

스프링은 명시적으로 서비스 요소들간의 의존성을 관리한다. 예를 들어 서비스로 공개한 빈이 공개하기 전에 완전히 설정을 마치고 만든 다음에 공개한다. 만약에 서비스가 다른 컴포넌트(다른 서비스를 포함한)에 의존성을 가지고 있다면 단드시 해당 서비스를 공개하기 전에 그것들을 초기화 해야 하는데, 그럴 때 depends-on 속성을 사용여 그들의 의존성을 표현할 수 있다.

<service ref=”beanToBeExported” interface=”com.xyz.MyServiceInterface”
     depends-on=”myOtherComponent”/>

6.1.4 context-class-loader 속성

OSGi 서비스 플랫폼 코어 스펙(현제 4.1이 작성 중이다.)에는 서비스 레지스트리에서 받아온 서비스에 어떤 요청을 했을 때 context class loader를 통해서 가용한 리소스나 타입을 명시하고 있지 않다. 따라서, 몇몇 서비스들은 context class loader에 특정 가정을 한 상태에서 라이브러리를 사용하고 있다. 스프링 DM은 서비스 실행 중에 context class loader를 명시적으로 제어할 수 있는 방법을 제공한다. context-class-loader 속성을 통해 제어할 수 있다.

context-class-loader 속성에는 unmanaged(기본값)와 service-provider를 사용할 수 있다. service-provider 값을 사용하여 스프링 DM으로 하여금 context class loader가 번들이 공개한 서비스의 클래스패스에 있는 모든 리소스를 참조할 수 있다는 것을 보장해준다.

context-class-loader를 service-provider로 설정하면, 클래스로더를 다루기 위해 서비스 객체가 프록시로 된다. 이 때 만약 서비스가 구현 클래스 일 경우 CGLIB을 필요로 한다.

6.1.5. ranking 속성

서비스 레지스틀에 여러 개의 같은 종류의 서비스가 존재할 경우, 우선 순위가 높은 서비스를 사용하게 된다.(OSGi 서비스 플랫폼 스펙 5.2.5) 이 값을 ranking 속성으로 설정할 수 있다. 기본값은 0

<service ref=”beanToBeExported” interface=”com.xyz.MyServiceInterface”
  ranking=”9″/>

6.1.6. service 엘리먼트 속성

요약할 겸, 위에서 살펴본 모든 속성들과 그 값들을 표로 나타내면 다음과 같다.

[표 생략]

6.1.7. 서비스 등록과 해지 라이프사이클

service 엘리먼트로 등록된 서비스느는 애플리케이션 컨텍스트가 처음 만들어 질때 OSGI 서비스 레지스크리에 등록된다. 번들이 멈추면 서비스가 자동적으로 해지되고 애플리케이션 컨텍스트가 소멸한다.

만약 의존성이 충족되지 않아서 등록시 또는 해지시에 어떤 행위를 해야하는 상황이 필요할 수 있다. 그럴 때는 registration-listener 엘리먼트를 사용해서 리스너 빈을 정의할 수 있다.

<service ref=”beanToBeExported” interface=”SomeInterface”>
  <registration-listener ref=”myListener”                                                (1)
    registration-method=”serviceRegistered”                                              (2)
    unregistration-method=”serviceUnregistered”/>                                        (2)
  <registration-listener
     registration-method=”register”>                                                     (3)
     <bean class=”SomeListenerClass”/>                                                   (4)
  </registration-listener>
</service>

(1) 최상위 빈 정의를 참조하는 리스너 선언
(2) 등록과 해지 메소드 설정
(3) 해당 리스너의 등록 메소드만 설정
(4) 내부 리스너 빈 등록

registration-method와 unregistration-method 속성은 리스너 빈에 정의한 메소드 이름을 나타내고 이들은 등록 및 해지시에 자동으로 호출된다. 등록과 해지는 콜백 메소드로 반드시 다음의 시그너처 중 하나를 따라야 한다.

public void anyMethodName(ServiceType serviceInstance, Map serviceProperties);

public void anyMethodName(ServiceType serviceInstance, Dictionary serviceProperties);

serviceType은 공개할 서비스의 인터페이스에 상응하는 어떤 타입이든지 될 수 있다.

등록 콜백은 서비스가 초기에 시작시 등록될 때 호출되고, 계속해서 다시 등록될 때마다 호출된다. 해지 콜백은 이유에 상관없이 서비스 해지 과정 중에 호출된다.

스프링 DM은 ServiceType 인자 타입을 보고 그에 상응하는 서비스가 등록/해지 될 때에만 등록/해지 메소드를 호출한다.

serviceProperties는 등록/해지 서비스의 모든 속성을 담고 있는 맵을 나타낸다. OSGi 스펙에 호환하기 위해 이 인자는 필요시에 java.util.Dictionary로 캐스팅될 수도 있다.

6.1.7.1. OsgiServiceRegistrationListener 인터페이스

위에서 설명한 방법말고 OsgiServiceRegistrationListener 인터페이스를 직접 구현하는 방법도 있는데, 이렇게 하면 XML 설정은 줄어드는 대신 스프링 DM에 종속적인 코드가 생긴다.

둘 다 사용할 수 있는데, 그럴 때는 먼저 OsgiServiceRegistrationListener 인터페이스의 메소드가 먼저 호출되고, 그 다음 커스텀 메소드가 호출된다.

ps: 왜이리 길어…