Exporter/Importer listener best practices

As mentioned above, Spring-DM exporter and importer allow listeners to be used for receiving notifications on when services are bound, unbound, registered or unregistered. Below you can find some guidance advices when working with listeners:

Listener and cyclic dependencies

There are cases where an exporter/importer listener needs a reference back to the bean it is defined on:

<bean id="listener" class="cycle.Listener">
    <property name="target" ref="importer" />
</bean>
	
<osgi:reference id="service" interface="SomeService">
    <osgi:listener bind-method="bind" ref="listener" />
</osgi:reference>

1

Listener bean

2

Dependency listener -> importer

3

Importer declaration

4

Dependency importer -> listener

The declaration above while valid, creates a dependecy between the listener and the importer it is defined upon. In order to create the importer, the listener has to be resolved and created but in order to do that, the importer called service needs to be retrieved (instantiated and configured). This cycle needs to be broken down so that at least one bean can be fully created and configured. This scenario is supported is supported by Spring-DM for both exporter and importers however, if the listener is defined as a nested bean, the cycle cannot be resolved:

<osgi:reference id="service" interface="SomeService">
    <osgi:listener bind-method="bind">
        <bean class="cycle.Listener">
            <property name="target" ref="importer" />
        </bean>
    </osgi:listener>
</osgi:reference>

1

OSGi service importer

2

Dependency between importer -> listener

3

Nested listener declaration

4

Dependency nested listener -> importer

The example above will fail since service bean cannot be initialized as it depends on the listener. The same cycle was seen before but in this case there is subtle yet big different from the container perspective - the listener is declared as a nested/inner-bean (hence the missing bean id). Inner beans have the same life cycle as their declaring parents and do not have any name. By definition, they are not tracked by the container and are simply created on demand. Since the importer cannot be partially created and the nested listener cannot be cached, the container cannot break the cycle and create the beans. While the two configurations shown above seem similar, one works while the other does not. Another reason to not use cycles unless you really, really have to.

To conclude, if you need inside the listener to hold a reference to the exporter/importer on which the listener is declared, either declare the listener as a top-level bean (as shown before) or consider doing dependency lookup. However, the latter approach requires extra contextual information such as the BeanFactory to use and the bean name and is more fragile then dependency injection.

Note

For those interested in the technical details, neither the exporter and importer cannot be partially initialized since they require the application context ClassLoader which is requested through the BeanClassLoaderAware which relies on a buit-in BeanPostProcessor which is applied only after the bean has been configured and is ready for initialization. If the ClassLoader was not required then the exporter/importer could have been partially initialized and the case above supported.