Introducing Declarative Services (DS)

Make Java POJOs into OSGi components and services using Java annotations. Features:

  • Declarative — no explicit code to publish or consume services
  • Lazy — service implementation not loaded until actually requested
  • Lifecycle — components have activation/deactivation, bounded by bundle lifecycle
  • Configuration — automatically receive config data from Configuration Admin

Two annotation sets exist: OSGi annotations (org.osgi.service.component.annotations) — use these for all new projects. Felix SCR annotations are legacy only.

Define an OSGi Component

// Component (not registered as service) — always active by default
@Component
public class MatrixWatcher {
    // component logic
}

// Service — registered in service registry, lazy by default
@Component(service = OracleService.class, immediate = true)
public class OracleServiceImpl implements OracleService {
    // component logic
}

The 'immediate' Flag

  • OSGi Component (only @Component) is always active by default
  • OSGi Service (@Component with service attribute) is lazy by default — started when first requested
  • Never use immediate=true for an OSGi Component
  • Don't use it for services requested by other OSGi services (they'll start on demand)
  • Do use immediate=true for services only used by request-bound code (e.g. Sling Models) — otherwise started/stopped per request

Lifecycle: Activate, Deactivate, Modified

@Component
public class SentinelComponent {

    @Activate
    private void activate(ComponentContext context) {
        // handle startup
    }

    @Deactivate
    private void deactivate(ComponentContext context) {
        // handle shutdown
    }

    @Modified
    private void modified(Map<String, Object> config) {
        // react on config change without restart
    }
}

// Lifecycle method arguments (any combination):
// - No arguments
// - ComponentContext
// - BundleContext
// - Map<String,Object>
// - Component property type (annotation class)

Reading OSGi Configuration (R6 Style)

@Component(service = GatekeeperService.class)
@Designate(ocd = GatekeeperServiceImpl.Config.class)
public class GatekeeperServiceImpl implements GatekeeperService {

    @ObjectClassDefinition(name = "Gatekeeper Configuration")
    @interface Config {
        @AttributeDefinition(description = "URL of gate service.")
        String entryPointUrl();

        @AttributeDefinition(description = "Timeout in seconds.")
        int timeout() default 5;
    }

    @Activate
    private void activate(Config config) {
        String url = config.entryPointUrl();
        int timeout = config.timeout();
    }
}

Factory Configurations

// 0..n instances with different configurations each
@Component(service = SentinelService.class)
@Designate(ocd = SentinelServiceImpl.Config.class, factory = true)
public class SentinelServiceImpl implements SentinelService {

    @ObjectClassDefinition(name = "Sentinel Service Factory")
    @interface Config {
        @AttributeDefinition(description = "Sentinel sector name")
        String sectorName();
    }

    @Activate
    private void activate(Config config) {
        // Each factory instance has its own config
    }
}

Component Properties

Additional OSGi component properties (readable by consumers, not part of OSGi config):

@Component(service = OracleService.class,
    property = {
        "property1=item1",
        "property1=item2",
        "property2:Integer=12345"
    })

Dependency Injection — Unary References

// Mandatory single reference (default)
@Reference
private GatekeeperService gatekeeperService;

// Optional single reference
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
private OracleService oracleService;

Dependency Injection — Multiple References

// Static multiple reference — component restarts if deps change
@Reference(cardinality = ReferenceCardinality.MULTIPLE,
    policy = ReferencePolicy.STATIC,
    policyOption = ReferencePolicyOption.GREEDY)
private List<SentinelService> sentinels;

// Dynamic multiple reference — services injected/removed at runtime
@Reference(cardinality = ReferenceCardinality.MULTIPLE,
    policy = ReferencePolicy.DYNAMIC,
    policyOption = ReferencePolicyOption.GREEDY)
private volatile List<SentinelService> dynamicSentinels;

// Always use volatile for dynamic references!

Filter Service References

// Filter based on service properties using LDAP-like syntax
@Reference(target = "(sectorName=mainframe)",
    cardinality = ReferenceCardinality.MULTIPLE,
    policy = ReferencePolicy.DYNAMIC)
private volatile List<SentinelService> mainframeSentinels;

// Filter syntax examples (RFC 1960):
// (cn=Neo)
// (!(cn=Smith))
// (&(objectClass=Agent)(|(sn=Smith)(cn=Agent*)))
// (sector=sector*)

Service and Reference Scope

Service scopes:

  • SINGLETON — single instance in OSGi container
  • BUNDLE — single instance per using bundle
  • PROTOTYPE — new instance for each reference

Reference scopes:

  • BUNDLE — single instance for all services in this bundle
  • PROTOTYPE — new instance if service supports it, otherwise shared
  • PROTOTYPE_REQUIRED — requires PROTOTYPE service scope, fails otherwise