AEM in 40 Days
CalendarPhase 3 · Building with the Archetype

Day 16 of 40

Sling Models In Depth

Injectors, adaptables, the delegation pattern, and Model Exporter

~50 min read5 videos3 source pages

By the end of today you should be able to

  1. Write a Sling Model with the right adaptables and injection annotations
  2. Choose correctly between @ValueMapValue, @ChildResource, @SlingObject and @OSGiService
  3. Explain the delegation pattern for extending a Core Component's model
  4. Expose a model as JSON with Sling Model Exporter

What a Sling Model is

A Sling Model is a plain Java class, annotated so that Sling can adapt a Resource or SlingHttpServletRequest into it and inject values automatically. It is the "controller" of an AEM component: it reads the repository, does the thinking, and exposes clean getters that HTL renders.

@Model(
    adaptables = { SlingHttpServletRequest.class },
    adapters = { Byline.class },
    resourceType = "wknd/components/byline",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class BylineImpl implements Byline {

    @ValueMapValue
    private String name;

    @ChildResource
    private List<Resource> occupations;

    @SlingObject
    private ResourceResolver resourceResolver;

    @OSGiService
    private QueryBuilder queryBuilder;

    @PostConstruct
    private void init() { /* runs after injection */ }

    @Override
    public String getName() { return name; }
}

The annotations that matter

  • @ValueMapValue — inject a property from the resource. The field name is the property name; use @Named when they differ (as for jcr:title).
  • @ChildResource — inject a child resource or a list of them. This is how multifields are read.
  • @SlingObject — inject a Sling context object: ResourceResolver, Resource, SlingHttpServletRequest.
  • @OSGiService — inject an OSGi service.
  • @Self — inject the adaptable itself, commonly to adapt it to something else.
  • @ScriptVariable — inject an HTL binding such as currentPage or pageManager.
  • @PostConstruct — run initialisation after all injection completes.
Set defaultInjectionStrategy = OPTIONAL

The default is REQUIRED, which means a single missing property makes the whole adaptation return null — and your component silently renders nothing. OPTIONAL leaves absent fields null and lets the model still build. The archetype sets this for you; keep it.

Resource vs Request adaptable

  • Adapt from Resource when the model needs only repository data. Simpler, and usable outside a request.
  • Adapt from SlingHttpServletRequest when you need request context — the current page, selectors, the Style System's applied CSS classes, or anything from the request bindings. Core Components adapt from the request.

Declaring resourceType registers the model for that resource type, so HTL can use data-sly-use with the interface and Sling picks the right implementation.

The delegation pattern

Extending a Core Component's model is a specific problem: you want to add one method while keeping all of Adobe's behaviour. Reimplementing the interface would mean writing every method. The delegation pattern solves it:

@Model(adaptables = SlingHttpServletRequest.class,
       adapters = { Teaser.class, ComponentExporter.class },
       resourceType = "wknd/components/teaser")
public class TeaserImpl implements Teaser {

    @Self @Via(type = ResourceSuperType.class)
    private Teaser delegate;          // Adobe's implementation

    @ValueMapValue
    private String customField;       // your addition

    @Override
    public String getTitle() { return delegate.getTitle(); }   // pass through

    public String getCustomField() { return customField; }     // yours
}

@Via(type = ResourceSuperType.class) is the key: it adapts using the supertype's resource type, giving you Adobe's model to delegate to. Lombok's @Delegate can generate the pass-through methods so you only write what you change.

Sling Model Exporter

Adding two annotations turns a model into a JSON endpoint:

@Model(adaptables = SlingHttpServletRequest.class,
       adapters = { Byline.class, ComponentExporter.class },
       resourceType = "wknd/components/byline")
@Exporter(name = "jackson", extensions = "json")
public class BylineImpl implements Byline { ... }

The component's content is then available at /content/.../byline.model.json. This is what powers the SPA Editor and gives you a cheap API for a widget without writing a servlet. Control the output with Jackson annotations — @JsonProperty, @JsonIgnore.

Exported models are public

If a model is exported, its JSON is reachable at that URL by anyone who can reach the page. Do not expose internal fields, and make sure Dispatcher filters allow .model.json only where you intend.

Today's takeaway

Models hold the logic. Choose the adaptable deliberately, keep injection OPTIONAL, and use @Via(ResourceSuperType) to extend Core Components instead of reimplementing them.

Watch

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

Sling Models Exporter - Part 1
Sling Models Exporter - Part 2
Sling Models Exporter - Part 3
Sling Models Exporter - Part 4
Sling Models Exporter - Part 5

Read on Experience League

The primary sources these notes are drawn from.

Your notes

Saved automatically to this browser.

Check yourself

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

Take the quiz