Sling Models in AEM serve as a bridge between AEM’s content-centric architecture and Java-based development. These Java classes with specialized annotations, facilitate the mapping of AEM content to Java objects, providing an array of essential features and architectural advantages:
- Decoupling Logic: Sling Models separate business logic from presentation, enhancing code modularity.
- Simplified Data Access: Developers effortlessly access and manipulate AEM content as Java objects.
- Type Safety: Sling Models ensure reliability by providing type-safe content property binding.
- Code Reuse: Encouraging reusable Java classes, promoting maintainable code.
- HTL Integration: Seamlessly integrating with AEM’s templating language (HTL).
- Automatic Data Binding: AEM automates content adaptation to Sling Models.
It streamlines AEM development, improves code quality, and empowers developers to efficiently work with AEM components and content in an object-oriented manner.
If you are interested in learning abou best practices, please refer to Sling model annotations: Best Practices
Common Annotations
@Model
Apply this annotation at the class level to define a Sling Model. You can specify optional properties like:
adaptables
– resource types to which the model can be adapted. A Models can be adapted both Resource / SlingHttpServletRequest. This depends upon the fields required to be injected.- If only resource properties are required, prefer using Resource
- If Sling Objects (eg. currentStyle, currentPage, xssApi etc) are also required, prefer adapting to SlingHttpServletRequest.
- Avoid using both together to avoid injection differences. Explanation at the end in “Best practices” section
adapters
– adapters to which the model can be adaptedresourceType
– the resource type that triggers the model- defaultInjectionStrategy – In sling models, by default all the fields are required. If a majority of injected fields/methods are optional
- Use ‘defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL’ to mark all injected fields as optional
- Use ‘defaultInjectionStrategy = DefaultInjectionStrategy.REQUIRED’ to mark all injected fields as required. Its also the default configuration, if ‘defaultInjectionStrategy’ is not specified.
Using Resource adaptable
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;
@PostConstruct
protected void init() {
// Business logic to transform injected values and assign to bean variables
// Example: Resolve mailAddress/phoneNumber applicable for the user's region, based on officeCode
}
public String getMailAddress(){
return mailAddress;
}
}
- In the above example, ‘adaptables = Resource.class’ maps the sling model against a Sling Resource.
- The @ValueMapValue would inject ‘mailAddress’ property from the resource into ‘mailAddress’ class variable. The property value injection occurs after adapting the Resource into Value Map.
Using SlingHttpServletRequest adaptable
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;
@PostConstruct
protected void init() {
// Business logic to transform injected values and assign to bean variables
// Example: Resolve mailAddress/phoneNumber applicable for the user's region, based on officeCode
}
public String getMailAddress(){
return mailAddress;
}
}
Injection strategy on Field level:
- @Required / @Optional annotations can be used to selectively mark fields as required/optional.
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;
@ValueMapValue
@Required
private String phoneNumber;
...
@Default annotation
@Default allows to specify default value for fields (including arrays)
@ValueMapValue
@Default(values="Contact Us")
private String title;
@ValueMapValue @Default(intValues={1,2,3,4})
private int[] integers;
@ValueMapValue Annotation
The annotation retrieves a property from a ValueMap by name.
- If
@Via
is not set, it automatically takes the current resource (even if the adaptable is aSlingHttpServletRequest
) - If the
name
is not set, the name is derived from the method/field name.
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = SlingHttpServletRequest.class,
adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue(name = "jcr:title")
private String titleText;
@ValueMapValue
private String mailAddress;
...
In the example, @ValueMapValue
is used to fetch properties “jcr:title” and “mailAddress” from the ValueMap of the adaptable SlingHttpServletRequest
.
@PostConstruct Annotation
When a Sling Model is instantiated, Sling automatically injects values into specified fields, eliminating the need for manual assignment and facilitating Dependency Injection.
After Sling injects the necessary dependencies into these fields, it proceeds to invoke the method marked with the @PostConstruct annotation. Typically, developers utilize this method to perform additional setup tasks for the Sling Model.
The entire sequence unfolds as follows:
- Upon receiving a request, the Sling Framework selects a Sling Model corresponding to the resource.
- A new instance of the chosen model, such as
new MyModel()
, is generated by Sling through the adaptation of the current resource. - Sling then fulfills all declared dependencies utilizing annotations like
@Inject
,@ValueMapValue
,@OSGiService
, among others. - Subsequently, Sling executes the method annotated with
@PostConstruct
.
The @PostConstruct annotation essentially serves as an alternative to a constructor. If you were to employ a constructor in your model, you would observe that, upon its execution, the fields annotated with @Inject are still unassigned. Attempting further initialization with these fields at this point would result in a NullPointerException.
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ValueMapValue
private String mailAddress;
@PostConstruct
protected void init() {
// Business logic to transform injected values and assign to bean variables
// Example: Resolve mailAddress/phoneNumber applicable for the user's region, based on officeCode
}
public String getMailAddress(){
return mailAddress;
}
}
@Named annotation
When the @Named annotation is unspecified, name is automatically inferred from the variable name. If the field name differs from the property name, consider using the @Named annotation. This annotation is effective when paired with the @Inject annotation, although it is recommended to favor @ValueMapValue instead.
@Inject @Named("jcr:title")
private String title;
@Via annotation
The @Via
annotation proves useful in scenarios where:
- two injectors—one from the request and another from the resource—need to be employed.
- When a different object is required as the adaptable instead of the original one, this can be achieved using the
via
parameter.
In such situations, it is crucial to explicitly specify to the annotation that the injection is taking place via the resource.
For example, when obtaining a property from a child resource:
@Model(adaptables=Resource.class)
public interface MyModel {
// will return eesource.getChild("jcr:content").getValueMap().get("propertyName", String.class)
@Inject @Via(value = "jcr:content", type = ChildResource.class)
String getPropertyName();
Similarly, if the adaptable is sourced from Resource, then we can get Tagmanager like:
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
@Self
@Via("resourceResolver")
final TagManager tagManager,
}
@Self annotation
Injects the current resource into the object
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
//Accessing the resource that is adapted to ContactusModel
@Self
protected Resource resource;
}
@OSGiService
annotation
We can inject OSGi services as dependencies using the @OSGiService
annotation. This annotation allows you to look up services based on their class name, and it returns the service with the highest service ranking. Example:
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
// Inject an OSGi service of type SomeService
@OSGiService
private SomeService someService;
}
@ScriptVariable
annotation
The @ScriptVariable
annotation is utilized to inject objects via script variables that are defined in Sling Bindings. It enables developers to easily look up and inject objects present in the script bindings object by their specified names.
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ScriptVariable
private Component component;
@ScriptVariable
private ComponentContext componentContext;
@ScriptVariable
private Design currentDesign;
@ScriptVariable
private Node currentNode;
@ScriptVariable
private Page currentPage;
@ScriptVariable
private HttpSession currentSession;
@ScriptVariable
private Style currentStyle;
@ScriptVariable
private Designer designer;
@ScriptVariable
private EditContext editContext;
@ScriptVariable
private PageManager pageManager;
@ScriptVariable
private ValueMap pageProperties;
@ScriptVariable
private SlingHttpServletRequest request;
@ScriptVariable
private ResourceResolver resolver;
@ScriptVariable
private Resource resource;
@ScriptVariable
private Design resourceDesign;
@ScriptVariable
private Page resourcePage;
@ScriptVariable
private SlingHttpServletResponse response;
@ScriptVariable
private SlingScriptHelper sling;
@ScriptVariable
private WCMScriptHelper slyWcmHelper;
@ScriptVariable
private SightlyWCMMode wcmmode;
@ScriptVariable
private XSSAPI xssAPI;
}
The @ScriptVariable
annotation can take attributes like name
and injectionStrategy
:
name
: Specifies the name of the script variable to be injected. If thename
attribute is not set, the name is derived from the method or field name.injectionStrategy
: This attribute determines the injection strategy, which can be one of “Optional,” “Required,” or “Default.” It defines whether the injection is mandatory or optional for the Sling Model.
@ResourcePath
annotation
The @ResourcePath
annotation is used to inject one or multiple resources.
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ResourcePath(path = "/content/sourcedcode/en/home")
Resource techrevelHomeRes;
}
@ChildResource
annotation
The @ChildResource
annotation is used to fetch a child resource by its name.
import org.apache.sling.models.annotations.injectorspecific.ChildResource;
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@ChildResource (name="mailAddress")
private Resource child_node_of_current_resource; // Fetches a child resource named "mailAddress"
@ChildResource
private List<Resource> emailAddress; // Fetches multiple child resources under "emailAddress" child Node
@RequestAttribute
annotation
Used with SlingHttpServletRequest adaptable, the @RequestAttribute
annotation is used to inject a request attribute by its name. If the name
attribute is not set, the name is derived from the method or field name, making it convenient to access request attributes in a Sling Model.
import org.apache.sling.models.annotations.injectorspecific.RequestAttribute;
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@RequestAttribute(name = "area")
private String areaParam; // Injects a request attribute named "area"
...
@SlingObject
annotation
The @SlingObject
annotation is used to inject commonly used Sling objects based on the field’s type. It automatically injects the appropriate Sling object if there is a match with the field’s class. The supported Sling objects include SlingHttpServletRequest
, SlingHttpServletResponse
, Resource
, ResourceResolver
, and SlingScriptHelper
.
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel{
@SlingObject
private SlingHttpServletRequest slingHttpServletRequest; // Injects the request
@SlingObject
private SlingHttpServletResponse slingHttpServletResponse; // Injects the response
@SlingObject
private Resource currentResource; // Injects the current resource
@SlingObject
private ResourceResolver resourceResolver; // Injects the resource resolver
}
In this example, @SlingObject
is used to inject commonly used Sling objects into the Sling Model. The annotation automatically injects the appropriate object based on the field’s type, making it convenient to work with Sling-related functionality. Note that the availability of these objects depends on the adaptable, and all of them are available via SlingHttpServletRequest
, while ResourceResolver
can only resolve the ResourceResolver
object.
Additional Notes
Design Object
The Design object can be accessed on adapting model to Resource / SlingHttpServletRequest
Injecting currentStyle object
Used when the Sling Model is adapted from SlingHttpServletRequest. Example:
@Model(adaptables = SlingHttpServletRequest.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
@ScriptVariable
private Style currentStyle;
}
Fetch currentStyle object from Resource
Used when the Sling Model is adapted from Resource. Example:
@Model(adaptables = Resource.class, adapters=ContactUsModel, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContactUsModelImpl implements ContactUsModel {
private Style currentStyle;
private Designer designer;
@PostConstruct
protected void init() {
// Getting the currentStyle from the Designer.class
designer = this.resourceResolver.adaptTo(Designer.class);
if (designer != null) {
currentStyle = designer.getStyle(resource);
}
}
}
To learn best practices around Sling Models, refer to Sling model annotations: Best Practices
One thought on “Sling model: Basics”