December 28, 2020
Estimated Post Reading Time ~

AEM Sling Model Field Injection vs Constructor Injection Memory Consumption

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.

What is the catch? 
Whenever we are injecting objects into our Sling Models via field injection for later use and instantiated Sling Model adaptable stores a reference of those objects in memory; stored injected objects in the Sling Model instance will consume memory.

The Sling Model constructor injection is also supported (as of Sling Models 1.1.0), and it's 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:
import org.apache.lucene.util.RamUsageEstimator;
...
System.out.println("MyModel.class + shallowSizeOf:" + org.apache.lucene.util.RamUsageEstimator.shallowSizeOf(req.adaptTo(MyModel.class)));


MyModel.class:

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:
import org.apache.lucene.util.RamUsageEstimator;
...
System.out.println("MyModelConstructor.class + shallowSizeOf:" + org.apache.lucene.util.RamUsageEstimator.shallowSizeOf(req.adaptTo(MyModelConstructor.class)));


MyModelConstructor.class:
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 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 fewer 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.


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.