When can I use @postconstructor
when I'm using in the SlingModel
?
@Model(adaptables=SlingHttpServletRequest.class)
public class MyModelTest {
@Inject
private PrintWriter out;
@Inject
@Named("log")
private Logger logger;
@PostConstruct
protected void sayHelloTest() {
logger.info("hello world");
}
}
The documentation states:
The @PostConstruct annotation can be used to add methods which are invoked upon completion of all injections:
In the example code you provided the "injections", the documentation talks about, are the fields you have annotated with the @Inject
annotation.
When the Sling Model is instantiated those fields will be "injected" by Sling. That means you do not have to set those fields yourself, but Sling will take care of it. This is also referred to as Dependency Injection.
To come back to your question: Once all those fields were injected by Sling the method with the @PostConstruct
annotation will be called. Usually, this method is used by developers to do further initialisation of a Sling Model.
The whole process looks like this:
new MyModelTest()
).@Inject
annotation).@PostConstruct
annotation.The @PostConstruct
annotation is basically a replacement for a constructor. If you would write a constructor for your model you would notice that when the constructor is called all the fields with the @Inject
annotation are not set yet. If you would try to do further initialisation with those fields you would get a NullPointerException
.
That is why the @PostConstruct
annotation was introduced. It allows you to do those further initialisation you would usually do in the constructor.
Injecting dependencies into fields is referred to as "field injection". There is another way to inject those dependencies through the constructor. This is called "constructor injection".
Personally, I like to use constructor injection with Sling Models when possible. Using constructor injection helps to increase immutability of your model, can help to reduce state and improve testability. That is something you usually should strive for in your code.
Your code example would like follows with constructor injection:
@Model(adaptables=SlingHttpServletRequest.class)
public class MyModelTest {
private final Logger logger;
private final PrintWriter out;
@Inject
public MyModelTest(
@ScriptVariable @Named("log") final Logger logger,
@ScriptVariable @Named("out") final PrintWriter out
) {
this.logger = logger;
this.out = out;
}
}
The class fields logger
and out
can now be set final
which increases immutability because they can not be changed anymore.
When you inject services etc. to do further initialisation you can store the result in a final
class field as well instead of injecting the service reference itself as a class field. Those references - in theory - could point to a non-existing service (services can come and go in OSGi). For example: If you have a service that stores configurations you need, you could just read the configuration value you are interested in and then store it in a class field.
Last but not least testability is improved because you can now create instances by simply calling new MyModelTest([...])
and passing a mock of logger
and out
(new MyModelTest(mockLogger, mockOut)
). If you would use field injection you would have to use reflection which is normally not what you want to do in your code. Although it has to be said that the Sling project contains support for testing models that use field injection.