Your achievements

Level 1

0% to

Level 2

Tip /
Sign in

Sign in to Community

to gain points, level up, and earn exciting badges like the new
BedrockMission!

Learn More

View all

Sign in to view all badges

Sling Models Constructor, What, Why, How?

Avatar

Avatar
Boost 1
Level 2
AEMWizard
Level 2

Like

1 like

Total Posts

30 posts

Correct Reply

1 solution
Top badges earned
Boost 1
Validate 10
Validate 1
Applaud 5
Affirm 1
View profile

Avatar
Boost 1
Level 2
AEMWizard
Level 2

Like

1 like

Total Posts

30 posts

Correct Reply

1 solution
Top badges earned
Boost 1
Validate 10
Validate 1
Applaud 5
Affirm 1
View profile
AEMWizard
Level 2

31-03-2020

Hi Community and Friends,

I'm taking a look at the Sling Models Injections using the Constructor, and I am wondering who has experience with this feature? 

  • Is this feature even being used by developers?
  • Why should we use the Sling Model's constructor method? 
  • What can the constructor expect in the method parameters?
  • How can we inject Sling Model Injector-Specific Annotations in the constructor?
  • Isn't it must easier to use the Sling Model Injector-Specific Annotations?
  • Example Code:

 

@Model(adaptables=Resource.class)
public class MyModel {    
    
    public MyModel(@Named("propertyName") String propertyName) {
      // constructor code
    }
}

 

Documentation: https://sling.apache.org/documentation/bundles/models.html#basic-usage

Accepted Solutions (1)

Accepted Solutions (1)

Avatar

Avatar
Validate 1
MVP
Theo_Pendle
MVP

Likes

236 likes

Total Posts

251 posts

Correct Reply

105 solutions
Top badges earned
Validate 1
Ignite 5
Ignite 3
Ignite 10
Ignite 1
View profile

Avatar
Validate 1
MVP
Theo_Pendle
MVP

Likes

236 likes

Total Posts

251 posts

Correct Reply

105 solutions
Top badges earned
Validate 1
Ignite 5
Ignite 3
Ignite 10
Ignite 1
View profile
Theo_Pendle
MVP

02-04-2020

Hi @AEMWizard,

Good question! I'll answer as best I can but I won't pretend to be a Sling expert 😉

So firstly, if you wanted to use constructor injection rather than field injection, you can do it quite easily using much of the same syntax. To illustrate my point, let's have a look at the HelloWorldModel generated by the latest AEM Archetype 23 (simplified a bit to be less verbose):

@Model(adaptables = Resource.class)
public class HelloWorldModel {

    @ValueMapValue(name = PROPERTY_RESOURCE_TYPE, injectionStrategy = InjectionStrategy.OPTIONAL)
    @Default(values = "No resourceType")
    protected String resourceType;

    @SlingObject
    private Resource currentResource;

    @SlingObject
    private ResourceResolver resourceResolver;

    @Getter
    private String message;

    @PostConstruct
    protected void init() {
        final PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
        final String currentPagePath = Optional.ofNullable(pageManager)
                .map(pm -> pm.getContainingPage(currentResource))
                .map(Page::getPath).orElse("");

        message = "\tHello World!\n"
                + "\tResource type is: " + resourceType + "\n"
                + "\tCurrent page is: " + currentPagePath + "\n";
    }
}

As we can see, it uses field injection. Now let's copy this component and create a version which uses constructor injection: 

@Model(adaptables = Resource.class)
public class HelloWorldModelConstructorInjection {

    protected String resourceType;
    private final Resource currentResource;
    private final ResourceResolver resourceResolver;

    @Getter
    private String message;

    @Inject
    public HelloWorldModelConstructorInjection(@ValueMapValue(name = PROPERTY_RESOURCE_TYPE, injectionStrategy = InjectionStrategy.OPTIONAL)
                                               @Default(values = "No resourceType") final String resourceType,
                                               @SlingObject final Resource currentResource,
                                               @SlingObject final ResourceResolver resourceResolver) {
        this.resourceType = resourceType;
        this.currentResource = currentResource;
        this.resourceResolver = resourceResolver;
    }

    @PostConstruct
    protected void init() {
        final PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
        final String currentPagePath = Optional.ofNullable(pageManager)
                .map(pm -> pm.getContainingPage(currentResource))
                .map(Page::getPath).orElse("");

        message = "\tHello World!\n"
                + "\tResource type is: " + resourceType + "\n"
                + "\tCurrent page is: " + currentPagePath + "\n";
    }
}

So basically you just:

  1. Create a constructor that takes all the parameters you need.
  2. Annotate the constructor with @Inject.
  3. Move all the injector-specific annotation from the fields to the arguments.

We can test this to make sure it works. Here is are the components side by side:

Selection_077.png

Works like a charm 🙂

Now regarding the actual usefulness of this feature: as a general rule, constructor is preferable to field injection for two main reasons:

  1. Field-injected dependencies are hidden from the outside, they are not part of the interface.
  2. You cannot instantiate or inject your own objects without using reflection. This is particularly bothersome in unit testing.

However, when using Sling models in an AEM context I think there is a trade-off to be made between observing best-practices and writing readable, maintanable code.

  • Point 1 is not particularly important because we only ever call models instatntiated by a ModelFactory so we don't need to instantiate the dependencies ourselves.
  • Point 2 isn't critical either if you're using AEM mocks for your unit tests as we can use that library to easily mock dependencies (no need for a constructor).

Personally, I use field injection for models.

Hope that all makes sense 🙂

Answers (0)