OSGi:没有生命周期管理的服务绑定

Posted

技术标签:

【中文标题】OSGi:没有生命周期管理的服务绑定【英文标题】:OSGi: service binding without lifecycle management 【发布时间】:2015-08-11 23:28:26 【问题描述】:

我正在 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(并且由于缺少无参数构造函数而失败)]

所以,我的问题是:有没有办法使用 DS 来获得我正在寻找的这种“仅绑定,无生命周期管理”功能?如果 DS 无法做到这一点,有哪些替代方案,您会推荐什么?


更新:使用 ServiceTracker(由 Neil Bartlett 建议)

重要提示:我在下面发布了一个改进版本作为答案。我只是出于“历史”目的而将其保留在这里。

我不确定在这种情况下如何申请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;
    


你会这样做吗? 你能评论一下我在上面的 cmets 中写的问题吗?那就是:

    如果服务实现未正确实现 equalshashCode,则 Set 会出现问题。 竞争条件:服务可能会在我的isActive 检查之后变为非活动状态。

【问题讨论】:

【参考方案1】:

解决方案:使用ServiceTracker(由 Neil Bartlett 建议)

注意:如果您想查看投反对票的原因,请参阅Neil's answer 以及我们在其 cmets 中的来回讨论。

最后我使用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
        
    


如果有人想将此代码用于任何目的,请继续。

【讨论】:

【参考方案2】:

不,这不属于 DS 的范围。如果您想自己直接实例化该类,那么您将不得不使用像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();
    


【讨论】:

谢谢,我认为可能是这样,但我不太确定用法(如何将服务从 ServiceTrackerCustomizer 传递给 MyClass)。我已经用可能的实现和一些新问题更新了我的问题;)你能看看吗?我想学习处理这种情况的最佳实践。谢谢! 我会添加一个更好的建议。特别是不需要实现注册表,因为 OSGi 已经有一个服务注册表! 但在您的示例中,您在 BundleActivatorstart 方法中实例化了一次 MyClass。我想要的是能够随时实例化MyClass,并拥有任意数量的MyClass 实例。在这种情况下,你会推荐什么?谢谢你的帮助! :) 是的,你也可以这样做,没问题。 谢谢尼尔。我已经发布了我的(改进的)解决方案作为我的问题的答案。感谢您为我指明正确的方向:)

以上是关于OSGi:没有生命周期管理的服务绑定的主要内容,如果未能解决你的问题,请参考以下文章

Android生命周期绑定

Angular 5 中服务的生命周期是啥

安卓服务service全解,生命周期,前台服务后台服务,启动注销绑定解绑,注册

Service 绑定方式启动,生命周期。绑定方式读取服务器数据

OSGI

什么是IT资产管理?有没有生命周期和自动化软件?