Hi @supportmember,
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:
- Create a constructor that takes all the parameters you need.
- Annotate the constructor with @Inject.
- 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:

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:
- Field-injected dependencies are hidden from the outside, they are not part of the interface.
- 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 🙂