Sling Models field injectors are used to support injection of AEM Library-specific context objects. For example, @ScriptVariable SightlyWCMMode will inject the WCMMode object, @ScriptVariable Resource will inject the current resource object, and @ScriptVariable Style will inject the Style object. These objects are typically stored within the object, so it can be later used to construct the Sling Model’s properties to make available to the context who’s calling it.
With the Apache Sling Model’s injector specific annotations, we are able to inject Sling Objects, AEM Services, OSGI Components, etc… directly into the context of the Sling Model easily without much hassle.
The Sling Model constructor injection is also supported (as of Sling Models 1.1.0), and its documented that it does not store the reference to the adaptable, lets test this out.
In this article, we will test the Sling Model memory consumption with two scenarios:
Test 1: Sling Model Field Injectors
I have 3 injected AEM objects to be stored in my class variables into my Sling Model via field injection; another 3 variables used to expose data to the calling context. Each AEM objects will then be used in the @PostConstruct method, where data would be extracted from each object, and set in the class variables for exposure to the calling context.
I then ran a test for getting the bytes in size for a specific object using the Lucene’s until RamUsageEstimator.
The memory byte size of the Sling Model adaptable object is 40.
MyModel.class : RamUsageEstimator Results:
1 2 3 | import org.apache.lucene.util.RamUsageEstimator; ... System.out.println("MyModel.class + shallowSizeOf:" + org.apache.lucene.util.RamUsageEstimator.shallowSizeOf(req.adaptTo(MyModel.class))); |
MyModel.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package com.sourcedcode.core.models; import lombok.Getter; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; import org.apache.sling.models.annotations.injectorspecific.SlingObject; import javax.annotation.PostConstruct; @Model(adaptables = {SlingHttpServletRequest.class, Resource.class}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL) public class MyModel { @ScriptVariable private Resource resource; @ScriptVariable private com.day.cq.wcm.api.Page currentPage; @SlingObject private SlingHttpServletRequest slingHttpServletRequest; @Getter private String currentResourcePath; @Getter private String currentPagePagePath; @Getter private String requestParam; @PostConstruct protected void initModel() { currentResourcePath = resource.getPath(); currentPagePagePath = currentPage.getPath(); requestParam = slingHttpServletRequest.getParameter("myParam"); } } |
Test 2: Sling Model Constructor Injection
I have 3 injected AEM objects via Constructor Injection; another 3 variables used to expose data to the calling context. This time, I am not storing the AEM objects in as class variables. We can definitely see a change in byes size here.
The memory byte size of the Sling Model adaptable object is 24.
MyModelConstructor.class : RamUsageEstimator Results:
1 2 3 | import org.apache.lucene.util.RamUsageEstimator; ... System.out.println("MyModelConstructor.class + shallowSizeOf:" + org.apache.lucene.util.RamUsageEstimator.shallowSizeOf(req.adaptTo(MyModelConstructor.class))); |
MyModelConstructor.class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package com.sourcedcode.core.models; import lombok.Getter; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.injectorspecific.ScriptVariable; import org.apache.sling.models.annotations.injectorspecific.SlingObject; import javax.inject.Inject; import javax.inject.Named; @Model(adaptables = {SlingHttpServletRequest.class, Resource.class}, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL) public class MyModelConstructor { @Getter private String currentResourcePath; @Getter private String currentPagePagePath; @Getter private String requestParam; @Inject public MyModelConstructor( @ScriptVariable @Named("currentPage") final com.day.cq.wcm.api.Page currentPage, @ScriptVariable @Named("resource") final Resource resource, @SlingObject @Named("slingHttpServletRequest") final SlingHttpServletRequest slingHttpServletRequest ) { currentResourcePath = resource.getPath(); currentPagePagePath = currentPage.getPath(); requestParam = slingHttpServletRequest.getParameter("myParam"); } } |
Conclusion
An AEM object that has been field injected into a variable that is only being minimally used holds onto memory which will/can be be wasting resources. If AEM objects are not needed, then the Constructor Injection method can be appropriately used.
It’s not rocket science. We see the results! Test 2 has less instance variables than Test 1; of course Test 2 will have a smaller Object (in byte size) than Test 1.
My only recommendation is this. When you are using AEM Objects, ask yourself, how is this Object being used?
This article highlights why Sling Model Constructor Injection via constructor is useful in our AEM development.
Amazing!