OSGi:没有生命周期管理的服务绑定
OSGi: service binding without lifecycle management
我正在 Equinox OSGi 框架上构建一个 Java 应用程序,我一直在使用 DS(声明式服务)来声明引用和提供的服务。到目前为止,我实现的所有服务消费者恰好也是服务提供者,所以我很自然地让它们成为无状态的(这样它们就可以被多个消费者重用,而不是依附于一个消费者)并让他们成为由框架实例化(默认构造函数,在我的代码中无处调用)。
现在我遇到了不同的情况:我有一个 class MyClass
引用服务 MyService
但它本身不是服务提供商。我需要能够自己实例化 MyClass
,而不是让 OSGi 框架实例化它。然后我希望框架将现有的 MyService
实例传递给 MyClass
实例。像这样:
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
// bind
private void setMyService(MyService myService) {
this.myService = myService;
}
// unbind
private void unsetMyService(MyService myService) {
this.myService = null;
}
public void doStuff() {
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
public class AnotherClass {
public void doSomething(String myString, int myInt) {
MyClass myClass = new MyClass(myString, myInt);
// At this point I would want the OSGi framework to invoke
// the setMyService method of myClass with an instance of
// MyService, if available.
myClass.doStuff();
}
}
我的第一次尝试是使用 DS 为 MyClass
创建组件定义并从那里引用 MyService
:
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="My Class">
<implementation class="my.package.MyClass"/>
<reference bind="setMyService" cardinality="0..1" interface="my.other.package.MyService" name="MyService" policy="static" unbind="unsetMyService"/>
</scr:component>
但是,MyClass
并不是真正的组件,因为我不想管理它的生命周期——我想自己处理实例化。作为 Neil Bartlett points out here:
For example you could say that your component "depends on" a
particular service, in which case the component will only be created
and activated when that service is available -- and also it will be
destroyed when the service becomes unavailable.
这不是我想要的。我想要没有生命周期管理的绑定。
[注意:即使我将基数设置为0..1
(可选且一元),框架仍会尝试实例化MyClass
(并且由于缺少无参数构造函数)]
那么,我的问题是:有没有办法使用 DS 来获得我正在寻找的 "binding-only, no lifecycle management" 功能?如果 DS 无法做到这一点,有哪些替代方案,您会推荐什么?
更新:使用 ServiceTracker
(Neil Bartlett 建议)
重要提示:我在下面发布了一个改进版本作为答案。我只是为了 "historic" 目的而将其保留在这里。
我不确定如何在这种情况下应用 ServiceTracker
。您会使用如下所示的静态注册表吗?
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.register(myService); // any better suggestion?
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.unregister(service); // any better suggestion?
}
}
public class MyServiceRegistry {
// I'm not sure about using a Set here... What if the MyService instances
// don't have proper equals and hashCode methods? But I need some way to
// compare services in isActive(MyService). Should I just express this
// need to implement equals and hashCode in the javadoc of the MyService
// interface? And if MyService is not defined by me, but is 3rd-party?
private static Set<MyService> myServices = new HashSet<MyService>();
public static void register(MyService service) {
myServices.add(service);
}
public static void unregister(MyService service) {
myServices.remove(service);
}
public static MyService getService() {
// Return whatever service the iterator returns first.
for (MyService service : myServices) {
return service;
}
return null;
}
public static boolean isActive(MyService service) {
return myServices.contains(service);
}
}
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
// There's a race condition here: what if the service becomes
// inactive after I get it?
MyService myService = getMyService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
protected MyService getMyService() {
if (myService != null && !MyServiceRegistry.isActive(myService)) {
myService = null;
}
if (myService == null) {
myService = MyServiceRegistry.getService();
}
return myService;
}
}
你会这样做吗?
你能评论一下我在上面评论中写的问题吗?即:
- 如果服务实现没有正确实现
equals
和 hashCode
,Set
就会出现问题。
- 竞争条件:服务可能在我的
isActive
检查后变得不活跃。
不,这不属于 DS 的范围。如果您想自己直接实例化 class,那么您将不得不使用像 ServiceTracker
这样的 OSGi API 来获取服务引用。
更新:
请参阅以下建议代码。显然,有很多不同的方法可以做到这一点,具体取决于您实际想要实现的目标。
public interface MyServiceProvider {
MyService getService();
}
...
public class MyClass {
private final MyServiceProvider serviceProvider;
public MyClass(MyServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
void doStuff() {
MyService service = serviceProvider.getService();
if (service != null) {
// do stuff with service
}
}
}
...
public class ExampleActivator implements BundleActivator {
private MyServiceTracker tracker;
static class MyServiceTracker extends ServiceTracker<MyService,MyService> implements MyServiceProvider {
public MyServiceTracker(BundleContext context) {
super(context, MyService.class, null);
}
};
@Override
public void start(BundleContext context) throws Exception {
tracker = new MyServiceTracker(context);
tracker.open();
MyClass myClass = new MyClass(tracker);
// whatever you wanted to do with myClass
}
@Override
public void stop(BundleContext context) throws Exception {
tracker.close();
}
}
解决方案:使用 ServiceTracker
(根据 Neil Bartlett 的建议)
注意:如果您想了解否决票的原因,请参阅 以及我们在其评论中的反复。
最后我使用 ServiceTracker
和静态注册表 (MyServiceRegistry
) 解决了它,如下所示。
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.getInstance().register(myService);
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.getInstance().unregister(service);
}
}
/**
* A registry for services of type {@code <S>}.
*
* @param <S> Type of the services registered in this {@code ServiceRegistry}.<br>
* <strong>Important:</strong> implementations of {@code <S>} must implement
* {@link #equals(Object)} and {@link #hashCode()}
*/
public interface ServiceRegistry<S> {
/**
* Register service {@code service}.<br>
* If the service is already registered this method has no effect.
*
* @param service the service to register
*/
void register(S service);
/**
* Unregister service {@code service}.<br>
* If the service is not currently registered this method has no effect.
*
* @param service the service to unregister
*/
void unregister(S service);
/**
* Get an arbitrary service registered in the registry, or {@code null} if none are available.
* <p/>
* <strong>Important:</strong> note that a service may become inactive <i>after</i> it has been retrieved
* from the registry. To check whether a service is still active, use {@link #isActive(Object)}. Better
* still, if possible don't store a reference to the service but rather ask for a new one every time you
* need to use the service. Of course, the service may still become inactive between its retrieval from
* the registry and its use, but the likelihood of this is reduced and this way we also avoid holding
* references to inactive services, which would prevent them from being garbage-collected.
*
* @return an arbitrary service registered in the registry, or {@code null} if none are available.
*/
S getService();
/**
* Is {@code service} currently active (i.e., running, available for use)?
* <p/>
* <strong>Important:</strong> it is recommended <em>not</em> to store references to services, but rather
* to get a new one from the registry every time the service is needed -- please read more details in
* {@link #getService()}.
*
* @param service the service to check
* @return {@code true} if {@code service} is currently active; {@code false} otherwise
*/
boolean isActive(S service);
}
/**
* Implementation of {@link ServiceRegistry}.
*/
public class ServiceRegistryImpl<S> implements ServiceRegistry<S> {
/**
* Services that are currently registered.<br>
* <strong>Important:</strong> as noted in {@link ServiceRegistry}, implementations of {@code <S>} must
* implement {@link #equals(Object)} and {@link #hashCode()}; otherwise the {@link Set} will not work
* properly.
*/
private Set<S> myServices = new HashSet<S>();
@Override
public void register(S service) {
myServices.add(service);
}
@Override
public void unregister(S service) {
myServices.remove(service);
}
@Override
public S getService() {
// Return whatever service the iterator returns first.
for (S service : myServices) {
return service;
}
return null;
}
@Override
public boolean isActive(S service) {
return myServices.contains(service);
}
}
public class MyServiceRegistry extends ServiceRegistryImpl<MyService> {
private static final MyServiceRegistry instance = new MyServiceRegistry();
private MyServiceRegistry() {
// Singleton
}
public static MyServiceRegistry getInstance() {
return instance;
}
}
public class MyClass {
private String myString;
private int myInt;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
MyService myService = MyServiceRegistry.getInstance().getService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
如果有人想将此代码用于任何目的,请继续。
我正在 Equinox OSGi 框架上构建一个 Java 应用程序,我一直在使用 DS(声明式服务)来声明引用和提供的服务。到目前为止,我实现的所有服务消费者恰好也是服务提供者,所以我很自然地让它们成为无状态的(这样它们就可以被多个消费者重用,而不是依附于一个消费者)并让他们成为由框架实例化(默认构造函数,在我的代码中无处调用)。
现在我遇到了不同的情况:我有一个 class MyClass
引用服务 MyService
但它本身不是服务提供商。我需要能够自己实例化 MyClass
,而不是让 OSGi 框架实例化它。然后我希望框架将现有的 MyService
实例传递给 MyClass
实例。像这样:
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
// bind
private void setMyService(MyService myService) {
this.myService = myService;
}
// unbind
private void unsetMyService(MyService myService) {
this.myService = null;
}
public void doStuff() {
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
public class AnotherClass {
public void doSomething(String myString, int myInt) {
MyClass myClass = new MyClass(myString, myInt);
// At this point I would want the OSGi framework to invoke
// the setMyService method of myClass with an instance of
// MyService, if available.
myClass.doStuff();
}
}
我的第一次尝试是使用 DS 为 MyClass
创建组件定义并从那里引用 MyService
:
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="My Class">
<implementation class="my.package.MyClass"/>
<reference bind="setMyService" cardinality="0..1" interface="my.other.package.MyService" name="MyService" policy="static" unbind="unsetMyService"/>
</scr:component>
但是,MyClass
并不是真正的组件,因为我不想管理它的生命周期——我想自己处理实例化。作为 Neil Bartlett points out here:
For example you could say that your component "depends on" a particular service, in which case the component will only be created and activated when that service is available -- and also it will be destroyed when the service becomes unavailable.
这不是我想要的。我想要没有生命周期管理的绑定。
[注意:即使我将基数设置为0..1
(可选且一元),框架仍会尝试实例化MyClass
(并且由于缺少无参数构造函数)]
那么,我的问题是:有没有办法使用 DS 来获得我正在寻找的 "binding-only, no lifecycle management" 功能?如果 DS 无法做到这一点,有哪些替代方案,您会推荐什么?
更新:使用 ServiceTracker
(Neil Bartlett 建议)
重要提示:我在下面发布了一个改进版本作为答案。我只是为了 "historic" 目的而将其保留在这里。
我不确定如何在这种情况下应用 ServiceTracker
。您会使用如下所示的静态注册表吗?
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.register(myService); // any better suggestion?
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.unregister(service); // any better suggestion?
}
}
public class MyServiceRegistry {
// I'm not sure about using a Set here... What if the MyService instances
// don't have proper equals and hashCode methods? But I need some way to
// compare services in isActive(MyService). Should I just express this
// need to implement equals and hashCode in the javadoc of the MyService
// interface? And if MyService is not defined by me, but is 3rd-party?
private static Set<MyService> myServices = new HashSet<MyService>();
public static void register(MyService service) {
myServices.add(service);
}
public static void unregister(MyService service) {
myServices.remove(service);
}
public static MyService getService() {
// Return whatever service the iterator returns first.
for (MyService service : myServices) {
return service;
}
return null;
}
public static boolean isActive(MyService service) {
return myServices.contains(service);
}
}
public class MyClass {
private String myString;
private int myInt;
private MyService myService;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
// There's a race condition here: what if the service becomes
// inactive after I get it?
MyService myService = getMyService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
protected MyService getMyService() {
if (myService != null && !MyServiceRegistry.isActive(myService)) {
myService = null;
}
if (myService == null) {
myService = MyServiceRegistry.getService();
}
return myService;
}
}
你会这样做吗? 你能评论一下我在上面评论中写的问题吗?即:
- 如果服务实现没有正确实现
equals
和hashCode
,Set
就会出现问题。 - 竞争条件:服务可能在我的
isActive
检查后变得不活跃。
不,这不属于 DS 的范围。如果您想自己直接实例化 class,那么您将不得不使用像 ServiceTracker
这样的 OSGi API 来获取服务引用。
更新:
请参阅以下建议代码。显然,有很多不同的方法可以做到这一点,具体取决于您实际想要实现的目标。
public interface MyServiceProvider {
MyService getService();
}
...
public class MyClass {
private final MyServiceProvider serviceProvider;
public MyClass(MyServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
}
void doStuff() {
MyService service = serviceProvider.getService();
if (service != null) {
// do stuff with service
}
}
}
...
public class ExampleActivator implements BundleActivator {
private MyServiceTracker tracker;
static class MyServiceTracker extends ServiceTracker<MyService,MyService> implements MyServiceProvider {
public MyServiceTracker(BundleContext context) {
super(context, MyService.class, null);
}
};
@Override
public void start(BundleContext context) throws Exception {
tracker = new MyServiceTracker(context);
tracker.open();
MyClass myClass = new MyClass(tracker);
// whatever you wanted to do with myClass
}
@Override
public void stop(BundleContext context) throws Exception {
tracker.close();
}
}
解决方案:使用 ServiceTracker
(根据 Neil Bartlett 的建议)
注意:如果您想了解否决票的原因,请参阅
最后我使用 ServiceTracker
和静态注册表 (MyServiceRegistry
) 解决了它,如下所示。
public class Activator implements BundleActivator {
private ServiceTracker<MyService, MyService> tracker;
@Override
public void start(BundleContext bundleContext) throws Exception {
MyServiceTrackerCustomizer customizer = new MyServiceTrackerCustomizer(bundleContext);
tracker = new ServiceTracker<MyService, MyService>(bundleContext, MyService.class, customizer);
tracker.open();
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
tracker.close();
}
}
public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer<MyService, MyService> {
private BundleContext bundleContext;
public MyServiceTrackerCustomizer(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public MyService addingService(ServiceReference<MyService> reference) {
MyService myService = bundleContext.getService(reference);
MyServiceRegistry.getInstance().register(myService);
return myService;
}
@Override
public void modifiedService(ServiceReference<MyService> reference, MyService service) {
}
@Override
public void removedService(ServiceReference<MyService> reference, MyService service) {
bundleContext.ungetService(reference);
MyServiceRegistry.getInstance().unregister(service);
}
}
/**
* A registry for services of type {@code <S>}.
*
* @param <S> Type of the services registered in this {@code ServiceRegistry}.<br>
* <strong>Important:</strong> implementations of {@code <S>} must implement
* {@link #equals(Object)} and {@link #hashCode()}
*/
public interface ServiceRegistry<S> {
/**
* Register service {@code service}.<br>
* If the service is already registered this method has no effect.
*
* @param service the service to register
*/
void register(S service);
/**
* Unregister service {@code service}.<br>
* If the service is not currently registered this method has no effect.
*
* @param service the service to unregister
*/
void unregister(S service);
/**
* Get an arbitrary service registered in the registry, or {@code null} if none are available.
* <p/>
* <strong>Important:</strong> note that a service may become inactive <i>after</i> it has been retrieved
* from the registry. To check whether a service is still active, use {@link #isActive(Object)}. Better
* still, if possible don't store a reference to the service but rather ask for a new one every time you
* need to use the service. Of course, the service may still become inactive between its retrieval from
* the registry and its use, but the likelihood of this is reduced and this way we also avoid holding
* references to inactive services, which would prevent them from being garbage-collected.
*
* @return an arbitrary service registered in the registry, or {@code null} if none are available.
*/
S getService();
/**
* Is {@code service} currently active (i.e., running, available for use)?
* <p/>
* <strong>Important:</strong> it is recommended <em>not</em> to store references to services, but rather
* to get a new one from the registry every time the service is needed -- please read more details in
* {@link #getService()}.
*
* @param service the service to check
* @return {@code true} if {@code service} is currently active; {@code false} otherwise
*/
boolean isActive(S service);
}
/**
* Implementation of {@link ServiceRegistry}.
*/
public class ServiceRegistryImpl<S> implements ServiceRegistry<S> {
/**
* Services that are currently registered.<br>
* <strong>Important:</strong> as noted in {@link ServiceRegistry}, implementations of {@code <S>} must
* implement {@link #equals(Object)} and {@link #hashCode()}; otherwise the {@link Set} will not work
* properly.
*/
private Set<S> myServices = new HashSet<S>();
@Override
public void register(S service) {
myServices.add(service);
}
@Override
public void unregister(S service) {
myServices.remove(service);
}
@Override
public S getService() {
// Return whatever service the iterator returns first.
for (S service : myServices) {
return service;
}
return null;
}
@Override
public boolean isActive(S service) {
return myServices.contains(service);
}
}
public class MyServiceRegistry extends ServiceRegistryImpl<MyService> {
private static final MyServiceRegistry instance = new MyServiceRegistry();
private MyServiceRegistry() {
// Singleton
}
public static MyServiceRegistry getInstance() {
return instance;
}
}
public class MyClass {
private String myString;
private int myInt;
public MyClass(String myString, int myInt) {
this.myString = myString;
this.myInt= myInt;
}
public void doStuff() {
MyService myService = MyServiceRegistry.getInstance().getService();
if (myService != null) {
myService.doTheStuff();
} else {
// Some fallback mechanism
}
}
}
如果有人想将此代码用于任何目的,请继续。