The introduction in Servlet 3.0 of “web fragments” and both annotation-based and programmatic mechanisms for introducing components into a web-application are all very welcome.
However, combined with all the other new features, their configuration facilities, the relevant class/jar-finding mechanisms, and the interactions between everything, the overall complexity of the Servlet API seems to have increased horrendously.
To my mind, an awful lot of it is starting to look like a tangled mess of spaghetti – the API equivalent of spaghetti code.
Here’s just one relatively minor example (but please, please, please put me straight if I’ve missed the meaning of this and it’s all really simple and elegant).
The Javadoc of every “since 3.0” method in javax.servlet.ServletContext (for example, getEffectiveMajorVersion) includes a “throws” clause that says:
Throws: java.lang.UnsupportedOperationException – if this ServletContext was passed to the ServletContextListener#contextInitialized method of a ServletContextListener that was neither declared in web.xml or web-fragment.xml, nor annotated with WebListener
So the behaviour of a ServletContext, including things like whether or not you can determine which Servlet version it needs, thus depends on whether it “was passed to” a ServletContextListener to notify that listener of the context’s initialization – depending on which of various ways were used to create the listener.
For now let’s just gloss over the various minor questions and issues raised by this, such as:
- What does “was passed to” actually mean? Has been passed to, at any time previously? Is currently being processed within a call to? Both? Something else?
- Does or doesn’t this apply if the ServletContext “was passed to” multiple listeners of which some are of the specified type and some are not?
- What is the actual purpose of this rule (i.e. why should being passed to a particular type of listener prevent the ServletContext from processing any of its “since 3.0” methods)?
Quite apart from all that, and far more fundamentally, isn’t it rather perverse for an object’s methods to depend directly on what other objects it “was passed to”? Especially where there doesn’t seem to be any immediately obvious reason for such a dependency?
And doesn’t it seem even more wrong that an object’s behaviour should depend on which other objects are “listening” for events on it? Isn’t that the tail wagging the dog?
Even assuming there’s some reasonable reason for this, and that there’s some sense in which it makes some kind of sense, is this really the kind of thing we want to see in an API?
Just in case this still seems too simple for you, the ServletContext also now includes a createListener method for creating listeners, and a number of overloaded addListener methods for adding listeners to itself (but only provided it has not already been initialized). The method for creating listeners does allow the creation of ServletContextListeners, but the methods for adding listeners only supports the addition of a ServletContextListener “If this ServletContext was passed to ServletContainerInitializer#onStartup” (which I’ll come to later).
Now both of these methods are subject to various conditions, including the “throws” clause described above. Listeners created and added in this way are, presumably, precisely the sort of listeners that such “throws” clauses are referring to (that is, not defined in web.xml or web-fragment.xml and potentially not annotated with WebListener). But what does it mean for methods that create and add such listeners to also have this “throws” clause themselves? Especially when they also require the ServletContext to have not yet have been initialized, in which case it presumably can’t have been passed to any ServletContextListeners yet anyway?
Is anyone else getting confused yet?
If even that still seems simple enough, ServletContextListeners are also no longer the only things listening for the application and/or context’s initialization. There is also now a ServletContainerInitializer interface, for classes that want to handle the application’s start-up (or does it really mean the container’s start-up, as its name would seem to imply?). Clearly, this is another route through which ServletContextListeners can be programmatically created and introduced, in particular by having the ServletContainerInitializer use the ServletContext’s “createListener” and/or “addListener” methods – with the “addListener” methods making specific allowance for this as described above, and requiring the ServletContext to know whether or not it “was passed to” a ServletContainerInitializer.
Of course, this ServletContainerInitializer interface has its own complexities and quirks. I won’t go into full detail on these here, but just to give a flavour:
- It specifies naming conventions and mechanisms for how its implementing classes are found (and these mechanisms have their own quirks and ambiguities, for example the naming convention appears to require classes to be placed in the “javax.servlet” package, in violation of the usual rues and licence terms, and the class-level javadoc says that implementations must be within the application’s /WEB-INF/services directory but the relevant method’s javadoc talks about different behaviour depending on whether it is within /WEB-INF/lib or elsewhere);
- It uses an annotation to specify what types of classes are to be passed to its sole method as arguments, together with rules for how the relevant classes are to be found, with this in turn including a requirement for the container to provide “a configuration option” to control whether failure to find such classes should be logged;
- Its javadoc includes the quite wonderful statement “In either case, ServletContainerInitializer services from web fragment JAR files excluded from an absolute ordering must be ignored, and the order in which these services are discovered must follow the application’s classloading delegation model.”.
Am I alone in thinking this is all getting way out of hand? How many features like these (with their accompanying restrictions, exclusions and interactions) does it take before the API as a whole becomes incomprehensible?
At this point I was going to sarcastically sugguest some incredibly complex and convoluted fictional requirement for things I’d like to see added into the next version of the API. But I’m too afraid that someone might treat it as a serious feature request, and in any case it’s not easy to come up with anything that’s more convoluted than the existing features (at least, not without sounding completely silly).
So instead I’ll just say that, personally, I fear that the Servlet API may have already jumped the shark.