Search This Blog

Sunday 21 February 2016

Servlet 3.x - HandlesTypes annotation

I came across the @HandlesTypes annotation while looking at Servlet 3.1 specs, and of course with lot of time to spare, I ended up trying it out.
As per Oracle documentation:
This annotation is used to declare the class types that a ServletContainerInitializer can handle.
So the first step to understanding this annotation is to understand ServletContainerInitializer.
Interface which allows a library/runtime to be notified of a web application's startup phase 
and perform any required programmatic registration of servlets, filters, and listeners in response to it.
As seen here, the ServletContainerInitializer is a way to hook into the application's startup phase.
So what does the annotation do ? 
 Again from the docs:
Implementations of this interface may be annotated with HandlesTypes, in order to receive 
(at their onStartup(java.util.Set>,javax.servlet.ServletContext) method) the Set of 
application classes that implement, extend, or have been annotated with the class types 
specified by the annotation.
Thus the value of the annotation decides what classes should be passed on to the ContextInitializer here.
I created an example class:
@HandlesTypes({ HttpServlet.class, Filter.class })
public class ClassMetaInformation implements ServletContainerInitializer {

  @Override
  public void onStartup(Set<Class<?>> classes, ServletContext context)
      throws ServletException {
    // perform any required programmatic registration of servlets, filters,
    // and listeners in response to it.
    Set<String> servlets = new HashSet<>();
    Set<String> filters = new HashSet<>();
    for (Class<?> classVal : classes) {
      if (HttpServlet.class.isAssignableFrom(classVal)) {
        servlets.add(classVal.getName());
      } else if (Filter.class.isAssignableFrom(classVal)) {
        filters.add(classVal.getName());
      }
    }
    System.out.println("Container detected the below Servlet Implementations");
    System.out.println("Extends from Filter : " + filters);
    System.out.println("Extends from Servlet : " + servlets);
  }

}
Points of Note:
  1. The onStartup method is called during the startup phase of the web application.
  2. As the HandlesTypes annotation mentions HttpServlet and Filter class, all loaded classes that  implement/extend either of these classes will be passed in the set parameter to the onStartup method.
  3. If the annotation is missing, a null Set reference will be passed to the method - leading to an Exception here. 
  4. Same is the case if no matching classes were found.
  5. Both cases will result in application startup failure
If I were to run the code there will be no impact. For the initializer to work, it needs to be placed as a jar in th WEb_INF directory.
 The text file has the same name as the interface implemented - ServletContainerInitializer - only its fully qualified version. The contents of the file is the name of the class implementing this interface
com.web.ClassMetaInformation
This is how the class is located loaded and plugged in during initialization time.
The output on running the code is as below
INFO: At least one JAR was scanned for TLDs yet contained no TLDs. 
Enable debug logging for this logger for a complete list of JARs that 
were scanned but no TLDs were found in them. Skipping unneeded JARs 
during scanning can improve startup time and JSP compilation time.

Container detected the below Servlet Implementations
Extends from Filter : [com.web.TestFilter]
Extends from Servlet : [com.web.TestServlet]

ServletContext null has been initialized  at Fri Jan 15 00:05:18 CST 2016
TestFilter initialized as SampleFilter with init parameter value val1
TestServlet initialized as com.web.TestServlet with init parameter values val1 and val2
As seen here, the Initializer ran before loading of my Servlet/Filter or even Listeners. Here I do not do any productive work really. But there could be uses to this class. For instance I could programmatically initialize a servlet
    try {
      @SuppressWarnings("unchecked")
      Class<TestServlet> clazz = (Class<TestServlet>) Class
          .forName("com.web.TestServlet");
      Servlet s = context.createServlet(clazz);
      ServletRegistration.Dynamic dynamic = context.addServlet("Sample", s);
      dynamic.addMapping("/baz/*");
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
But could we not also do the same using ContextListeners ? Actually both work. Then why use the ServletContainerInitializer ?
To be honest, the above registration code would be better of with a ContextListener.
The point is that the ServletContainerInitializer is to be placed in a jar. So the code that goes here should be something that is generic and could be used in different applications. For example a code to initialize a database logger, or a ping servlet for internal use.
For something that is very application specific I would go with the ContextListener.

3 comments:

  1. The text file has the same name as the interface implemented - ServletContainerInitializer - only its fully qualified version. The contents of the file is the name of the class implementing this interface.thanks for your valuable information.java training in chennai | java training in velachery

    ReplyDelete



  2. very useful info, and please keep updating........

    ReplyDelete