...
A component has a lifecycle that controls when it is started or stopped. A bundle must be started before the DM Runtime can process its components. When the bundle is started, the DM Runtime will parse a specific DependencyManager-Component manifest headers, which points to a list of descriptors defining all annotated components. Such descriptors are actually generated at compilation time, and annotation are not reflectively parsed at runtime. Only the descriptor is used to process the components. For each component, the DM Runtime first ensures that all dependencies are satisfied before activating it. Likewise, the component is deactivated when some of the required dependencies are not available anymore or when the bundle is stopped. Unless the bundle is stopped, components may be deactivated and reactivated, dependending depending on the departure and arrival of required dependencies. The manager which is in charge of maintaining the state of components is implemented in the DM Runtime bundle (org.apache.felix.dm.runtime bundle).
...
Example
The following example show shows a basic servicecomponent, which uses the @Start, @Stop, annotation:
Code Block |
---|
/** * A Service Using lifecyce callbacks */ @Service class X implements Y { @ServiceDependency void bindOtherService(OtherService other) { // Will be injected before we are started (because it's a required dependency). } @Start void publishing() { // All required dependencies are injected: initialize our component. Once we return, theour Y service will be published // in the OSGi registry. } @Stop void unpublished() { // We are not registered anymore in the OSGi registry. } } |
...
Usage example:
This is an example of a service component X whose dependency "foo" filter is configured from ConfigAdmin. First, we defined a ConfigurationDependency in order to get injected with our configuration. Next, we define a dependency on the FooService, but this time, we declare the annotation like this: @ServiceDependency(name="foo"). As explained above, The ConfigurationDependency will be injected before the @Init method, but and the named dependency ("foo") will be calculated after the @Init method returns. So, from our Init method, we just return a map which contains the filter and required flag for the "foo" dependency, and we actually use the configuration which has already been injected:
Code Block |
---|
/** * A Servicecomponent whose FooService dependency filter is configured from ConfigAdmin */ @Service@Component class X { private Dictionary m_config; /** * Initialize our servicecomponent from config ... and store the config for later usage (from our init method) */ @ConfigurationDependency(pid="MyPid") void configure(Dictionary conf) { m_config = config; } /** * All unnamed dependencies are injected: we can now configure other named * dependencies, using the already injected configuration. * The returned Map will be used to configure our "foo" Dependency (see below) */ @Init Map init() { return new HashMap() {{ put("foo.filter", m_config.get("filter")); put("foo.required", m_config.get("required")); }}; } /** * This named dependency filter/required flag will be configured by our init method (see above). */ @ServiceDependency(name="foo") FooService fooService; /** * All dependencies are injected and our service is now ready to be published. */ @Start void start() { } } |
...
For instance, imagine that your component publishes an OSGi service, but before, it needs to register into a DHT (Distributed Hash Table), whose API is asynchronous: that is: the DHT API will callback you once you are inserted into a node in the DHT. In this case, what you would like to do is to publish your OSGi service, but only after you are inserted into the DHT (when the DHT callabacks callbacks you) ... Such a case is supported using the @LifecyceController annotation, which gives you full control of when your component is started/published and unpublished/stopped.
Let's illustrate this use case with a concrete example: First here is the DHT asynchronous API:
...
Next, here is our service, which uses the @LifecycleController in order to take control of when the service is published into the OSGi registry:
Code Block |
---|
@Service@Component(provides={MyService.class}) public class MyServiceImpl implements MyService, DHTElement { @ServiceDependency DHTService dht; @LifecycleController Runnable trigger; // will fire component startup, once invoked. @Init void init() { dht.insert(this); // asynchronous, will callback us in our inserted method once registered into the DHT } public void inserted() { // We are inserted into the DHT: we can now trigger our component startup. // We just invoke the runnable injected by our @LifecycleController annotation, which will trigger our // service publication (we'll be called in our @Start method before) trigger.run(); } @Start void start() { // method called only once we invoke our trigger Runnable (see inserted method). // Our Service will be published once this method returns. } } |
...
But when the component needs to specify some service properties dynamically (not statically from the annotation), then it may do so by just returning a Map from the @Start callback. For instance:
Code Block |
---|
@Service@Component(properties={@Property(name="foo", value="bar")}) public class MyServiceImpl implements MyService { @ConfigurationDependency(pid="MyPid", propagate=true) void updated(Dictionary conf) { // "conf" contains foo2=bar2, for example, and since we have set the "propagate" attribute to true, then // the property will be propagated to our published service ... } @Start Map start() { // Return some extra properties to be inserted along with our published properties. This map takes // precedence, and may override some properties specified in our @Service annotation, or some properties // propagated from our @ConfigurationDependency dependency ... return new HashMap() {{ put("foo3", "bar3"); }}; } } |
...