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: 왜이리 길어...