AEM in 40 Days
CalendarPhase 5 · Advanced Development

Day 29 of 40

OSGi Services in Practice

Writing services, the component lifecycle, and references

~45 min read2 videos2 source pages

By the end of today you should be able to

  1. Write an OSGi service with Declarative Services annotations
  2. Explain the component lifecycle and when activate and deactivate run
  3. Choose correctly between mandatory, optional, static and dynamic references
  4. Use service ranking to override an existing service

Writing a service

Define an interface, implement it, annotate the implementation. That is the whole pattern:

public interface AdventureService {
    List<Adventure> findByDifficulty(String difficulty);
}

@Component(service = AdventureService.class, immediate = true)
public class AdventureServiceImpl implements AdventureService {

    private static final Logger LOG =
            LoggerFactory.getLogger(AdventureServiceImpl.class);

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Activate
    protected void activate() {
        LOG.info("AdventureService starting");
    }

    @Deactivate
    protected void deactivate() {
        LOG.info("AdventureService stopping");
    }

    @Override
    public List<Adventure> findByDifficulty(String difficulty) { ... }
}
  • service = AdventureService.class publishes it under that interface. Omitting service registers the component without publishing a service — fine for something that only reacts to events.
  • immediate = true activates it at startup rather than lazily on first use. Use it when the component must do something on its own, such as register a listener or start a scheduler.

The lifecycle

A DS component moves through a small set of states, and knowing them makes diagnosis quick:

  • Unsatisfied — a mandatory reference cannot be bound. It will not activate.
  • Satisfied — all mandatory references are available. For a lazy component, it waits here until first use.
  • Active@Activate has run and it is serving.

/system/console/components shows the state, and for unsatisfied components it names the reference that could not be bound. That single page answers most "why isn't my service running" questions.

Deactivate can be called at any time

Bundles stop when you redeploy, and on Cloud Service instances are replaced routinely. Anything you start in @Activate — a thread, a listener, a scheduled task — must be stopped in @Deactivate, or you leak it across redeploys.

Reference cardinality and policy

Two independent dimensions on @Reference:

  • CardinalityMANDATORY (default), OPTIONAL, MULTIPLE, AT_LEAST_ONE.
  • PolicySTATIC (default: the component is deactivated and reactivated when the reference changes) or DYNAMIC (the field is swapped without restarting the component).
// Collect every implementation, and cope with them coming and going
@Reference(cardinality = ReferenceCardinality.MULTIPLE,
           policy = ReferencePolicy.DYNAMIC)
private volatile List<AdventureProvider> providers;

A dynamic multiple reference must be volatile, because another thread can replace it while your code reads it. This is the standard pattern for a plug-in style extension point.

Service ranking

When several components implement the same interface, the one with the highest service.ranking wins for a single-reference injection. This is how you override an AEM service without touching Adobe's code:

@Component(
    service = SomeAemService.class,
    property = { Constants.SERVICE_RANKING + ":Integer=1000" }
)
public class MyOverridingImpl implements SomeAemService { ... }

Powerful and worth using carefully — you have now taken responsibility for behaviour Adobe may change or rely on elsewhere. Prefer composition where you can.

Getting a ResourceResolver in a service

A service has no request, so it has no user. It needs a service user — covered properly on day 31 — and the resolver must always be closed:

Map<String, Object> params = Collections.singletonMap(
        ResourceResolverFactory.SUBSERVICE, "wknd-content-reader");

try (ResourceResolver resolver =
         resolverFactory.getServiceResourceResolver(params)) {
    Resource r = resolver.getResource("/content/wknd");
    // ...
}   // closed automatically
Never use getAdministrativeResourceResolver

It is deprecated and unavailable on Cloud Service, and it bypasses the permission model entirely. Use a service user scoped to exactly the access it needs — and always close the resolver, or you leak repository sessions until the instance degrades.

Today's takeaway

@Component, @Reference, @Activate, @Deactivate. Clean up what you start, use dynamic multiple references for extension points, and always close service resolvers.

Watch

Adobe's own videos for this topic. They load only when you press play.

OSGi Services - Basics
OSGi Services - OSGi Component Lifecycle

Read on Experience League

The primary sources these notes are drawn from.

Your notes

Saved automatically to this browser.

Check yourself

10 questions on today's material. 80% to pass.

Take the quiz