Apache
Home » Documentation » Apache Felix Dependency Manager

Dependency Manager - Components

Components are declared by the dependency manager and can be implemented by POJOs that contain no references to the OSGi framework whatsoever. Components are the main building blocks of your OSGi application. They have a life cycle, can register themselves as services and have zero or more dependencies.

You can either use the Java API or the Java Annotations and this reference section describes both.

Types of Components

There are different types of Dependency Manager components:

Life cycle

The dependency manager, as part of a bundle, shares the generic bundle life cycle explained in the OSGi specification. The life cycle of the dependency manager itself, and the components it manages, can be located inside the active state of the hosting bundle.

Each component you define gets its own life cycle, which is explained in the state diagram below.

State diagram

A component is associated with an instance. This instance can either be specified directly, or you can specify its class. If you do the latter, the actual instance will be created lazily.

Changes in the state of the component will trigger the following life cycle methods:

The dependency manager will look for methods with these names and one of the following signatures in this order:

If you don't specify anything, the methods with these names will be invoked on the instance. By using setCallbacks() you can however change this behavior: You can change the names of the methods to look for. Any methods that are set to null will not be invoked at all. Another thing you can do is to specify a different instance to invoke these methods on. If you do that, you will usually want to use the first signature, which gives you a reference to the Component whose life cycle method was invoked.

Here is a descrition of the component states:

What happens during component instantiation ?

  1. The service is instantiated.
  2. The following special objects are injected through reflection on class fields, if they exist (but you can deactivate this behavior from the API):
    • BundleContext
    • ServiceRegistration
    • DependencyManager
    • Component
  3. autoconfig dependencies are injected through reflection on class fields, if they exist. If an autoconfig optional dependency is unavailable, a NullObject is then injected.
  4. Required dependency callbacks defined from the Activator are called.
  5. The component init method is called, and from that method you can then dynamically add more dependencies by using the Component parameter passed to the init method, or using a class field of Component type (which in this case has been injected during step 2).
  6. When all required dependencies (including dependencies dynamically added from the init method) are available, they are injected (using callbacks, or autoconfig).
  7. The component start callback is invoked.
  8. Optional dependencies (with callbacks) are then tracked.
  9. The component service(s) is then registered in the OSGi service registry

When using Annotations, there are some specific behaviors:

Interfaces and properties

Components in the context of the dependency manager can be published as OSGi services under one or more interface names, plus optionally a set of properties. This is no different than a normal OSGi service. It's important to mention that you don't have to register a service. If you don't, you basically created a component that can do work and have dependencies and a managed life cycle.

Composition

When implementing more complex components, you often find yourself using more than one instance. However, several of these instances might want to have dependencies injected. In such cases you need to tell the dependency manager which instances to consider. This has to be a fixed set of instances however.

We now describe how to declare a service composition using the Api, and the Annotations:

Example:

When using the DependencyManager API, you can use the Component.setComposition method to declare a special callback in your component that returns the list of object that are part of the component, and all dependencies and lifecycle callbacks will be invoked on the objects returned by the method. Let's take an example, with a ProviderImpl top-level service implementation that is internally implemented using three Pojos: ProviderImpl, ProviderParticipant1, and ProviderParticipant2:

public class ProviderImpl implements Provider {
    private final ProviderParticipant1 m_participant1 = new ProviderParticipant1();
    private final ProviderParticipant2 m_participant2 = new ProviderParticipant2();
    private volatile LogService m_log; // injected

    Object[] getComposition() {
        return new Object[] { this, m_participant1, m_participant2 };
    }

    void start() {
        m_log.log(LogService.LOG_INFO, "ProviderImpl.start(): participants=" + m_participant1 + "," + m_participant2
            + ", conf=" + m_conf);
    }      
}

public class ProviderParticipant1 {
    private volatile LogService m_log; // also injected since we are part of the composition

    void start() {
        m_log.log(LogService.LOG_INFO, "ProviderParticipant1.start()");
    }
}

public class ProviderParticipant2 {
    private volatile LogService m_log; // also injected since we are part of the composition

    void start() {
        m_log.log(LogService.LOG_INFO, "ProviderParticipant2.start()");
    }
}

And here is the Activator, which uses the setComposition method:

public class Activator extends DependencyActivatorBase {
    public void init(BundleContext ctx, DependencyManager m) throws Exception {
        m.add(createComponent()
            .setImplementation(ProviderImpl.class)
            .setComposition("getComposition")
            .add(createServiceDependency().setService(LogService.class).setRequired(true)));
    }
}

Factories

Out of the box, there already is support for lazy instantiation, meaning that the dependency manager can create component instances for you when their required dependencies are resolved. However, sometimes creating a single instance using a default constructor is not enough. In those cases, you can tell the dependency manager to delegate the creation process to a factory.

Interestingly, you can also mix the usage of a Factory object and a Composition of objects returned by the Factory. The following is the same example as in the previous section (Composition), but using a Factory approach in order to instantiate a composition of objects: The "ProviderFactory" is first injected with a Configuration that can possibly be used to create and configure all the other objects that are part of the composition; each object will also be injected with the dependencies defined in the Activator.

public class ProviderFactory {
    private ProviderParticipant1 m_participant1;
    private ProviderParticipant2 m_participant2;
    private ProviderImpl m_providerImpl;
    private Dictionary<String, String> m_conf;

    public void updated(Dictionary<String, String> conf) throws Exception {
        // validate configuration and throw an exception if the properties are invalid
        m_conf = conf;
    }

    /**
     * Builds the composition of objects used to implement the "Provider" service.
     * The Configuration injected by Config Admin will be used to configure the components
     * @return The "main" object providing the "Provider" service.
     */
    Object create() {
        // Here, we can instantiate our object composition based on our injected configuration ...

        if ("true".equals(m_conf.get("some.parameter")) {
            m_participant1 = new ProviderParticipant1(); // depenencies and lifecycle callbacks will also be applied
            m_participant2 = new ProviderParticipant2(); // depenencies and lifecycle callbacks will also be applied
        } else {
            // Compose with some other objects ...
            m_participant1 = new ProviderParticipant3(); // depenencies and lifecycle callbacks will also be applied
            m_participant2 = new ProviderParticipant4(); // depenencies and lifecycle callbacks will also be applied
        }

        m_providerImpl = new ProviderImpl(m_participant1, m_participant2);
        return m_providerImpl; // Main object implementing the Provider service
    }

    /**
     * Returns the list of objects that are part of the composition for the Provider implementation.
     */
    Object[] getComposition() {
        return new Object[] { m_providerImpl, m_participant1, m_participant2 };
    }
}

And here is the Activator: notice the setFactory method that specifies the factory to use to create the implementation. Also pay attention to the setComposition method, which indicates the method to call in order to get all instances that are part of a composition and need dependencies injected:

public class Activator extends DependencyActivatorBase {
    public void init(BundleContext ctx, DependencyManager m) throws Exception {
        ProviderFactory factory = new ProviderFactory();
        m.add(createComponent()
            .setFactory(factory, "create") // factory.create() will return the implementation instance
            .setComposition(factory, "getComposition")
            .add(createConfigurationDependency()
                .setPid("some.pid")
                .setCallback(factory, "updated")) // will invoke "updated" on the factory instance
            .add(createServiceDependency().setService(LogService.class).setRequired(true)));
    }
}

You can refer to this sample code, which is part of the source distribution.

Rev. 1843269 by pderop on Tue, 9 Oct 2018 13:35:30 +0000
Apache Felix, Felix, Apache, the Apache feather logo, and the Apache Felix project logo are trademarks of The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their respective owners.