This week at my client’s offices, I discovered, with another IT consultant from a concurrent firm, a strange behaviour of ServletContextListeners.
The specification doesn’t state much about them. They are classes which implement the ServletContextListener interface, and they are instantiated with their default constructor during the webapp loading, in the same order as specified in the web.xml.
The specification doesn’t state if the listeners are first all instantiated then all executed or sequentially instantiated and executed one-by-one. That’s the point I’d like to discuss.
First of all, I’d like to mention the fact that I used the Jetty web container for development and test.
In the application, I had two listeners A and B, and listener B depended on the initialization of A. Thus, in web.xml, I first declared listener A then listener B.
Listener A actually was a Spring ContextLoaderListener, and listener B‘s constructor initialized its private variables with beans declared in the Spring application context. Listener B‘s constructor was expecting (as well as I did) that the Spring application context had already been initialized. But that was not the case. Actually, Spring’s ContextLoaderListener does initialize the application context in the contextInitialized() method. Thus listener B‘s constructor could not initialize B‘s variables, which stayed with their default null value. And the application did not work.
This was the method sequence I identified:
- Call
A‘s constructor. Does nothing special.
- Call
B‘s constructor. Fails because Spring’s context is not initialized.
- Call
A‘s contextInitialized() method. Initialized Spring’s application context.
- Call
B‘s contextInitialized() method. Tries to further initialize B, relying on the failure-less execution of B‘s constructor.
I expected another sequence:
- Call
A‘s constructor. Does nothing special.
- Call
A‘s contextInitialized() method. Initialized Spring’s application context.
- Call
B‘s constructor. Shouldn’t fail.
- Call
B‘s contextInitialized() method. Tries to further initialize B, relying on the failure-less execution of B‘s constructor. Should work.
The workaround to this issue is easy: simply initialize B‘s variables in B‘s contextInitialized() method. That may not be really important, as finally the application works, but I think there is in fact an important issue: B‘s constructor should initialize B. As I designed B to be immutable, this was the only place where I could initialize B‘s variables. Now, using the workaround, B isn’t immutable any more. I’m really not happy with this.
What I’d like to point out is that the specification is not quite precise about the sequence of method calls in context listeners, and this doesn’t help make good design. I usually find Jetty to be a well-designed web container, but here I believe Jetty’s developers made a mistake. Which I think is easy to correct.
I can’t tell if Tomcat has the same issue.
Sometimes, specifications should give more details about their implementation. This is even more important if the specification states about code that should be executed in a given sequence or if the implementation has a impact on the ability left to the developer to make good design.