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?
@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
Solved! Go to Solution.
Views
Replies
Total Likes
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:
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:
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.
Personally, I use field injection for models.
Hope that all makes sense
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:
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:
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.
Personally, I use field injection for models.
Hope that all makes sense