我正在Equinox OSGi框架上构建Java应用程序,我一直在使用DS(声明性服务)来声明引用和提供的服务 . 到目前为止,我实施的所有服务消费者也恰好都是服务提供商,因此我很自然地将它们变为无状态(这样它们可以被多个消费者重用,而不是被一个消费者连接)让他们成为由框架实例化(默认构造函数,在我的代码中无处调用) .
现在我有一个不同的情况:我有一个类 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指出here:
例如,您可以说您的组件“依赖于”特定服务,在这种情况下,只有在该服务可用时才会创建和激活组件 - 并且当服务变得不可用时它也将被销毁 .
这不是我想要的 . 我想要没有生命周期管理的绑定 . [注意:即使我将基数设置为 0..1
(可选和一元),框架仍将尝试实例化 MyClass
(并且由于缺少no-args构造函数而失败)]
所以,我的问题是:有没有办法使用DS来实现我正在寻找的“仅绑定,无生命周期管理”功能?如果DS无法做到这一点,有哪些替代方案,您会推荐什么?
更新:使用ServiceTracker(Neil Bartlett建议)
IMPORTANT: I've posted an improved version of this below as an answer. I'm just keeping this here for "historic" purposes.
在这种情况下,我不确定如何应用 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
检查后服务可能会变为非活动状态 .
2 回答
不,这不属于DS的范围 . 如果您想自己直接实例化该类,那么您将不得不使用像
ServiceTracker
这样的OSGi API来获取服务引用 .Update:
请参阅以下建议的代码 . 显然,根据您实际想要实现的目标,有很多不同的方法可以做到这一点 .
...
...
解决方案:使用ServiceTracker(由Neil Bartlett建议)
注意:如果您想查看downvote的原因,请参阅Neil's answer以及我们的评论中的来回 .
最后,我使用
ServiceTracker
和静态注册表(MyServiceRegistry
)解决了它,如下所示 .如果有人想将此代码用于任何目的,请继续 .