Objetivos:
Section 3: The Web Container Model
3.1- For the ServletContext initialization parameters: write servlet code to access initialization parameters; and create the deployment descriptor elements for declaring initialization parameters.
3.2- For the fundamental servlet attribute scopes (request, session, and context): write servlet code to add, retrieve, and remove attributes; given a usage scenario, identify the proper scope for an attribute; and identify multi-threading issues associated with each scope.
3.4- Describe the Web container life cycle event model for requests, sessions, and web applications;create and configure listener classes for each scope life cycle; create and configure scope attribute listener classes; and given a scenario, identify the proper attribute listener to use.
3.5- Describe the RequestDispatcher mechanism; write servlet code to create a request dispatcher; write servlet code to forward or include the target resource; and identify and describe the additional request-scoped attributes provided by the container to the target resource.
Inicializando el objeto ServletContext
Toda aplicación web tiene una instancia de javax.servlet.ServletContext. El contexto es inicializado en el momento que la aplicación es cargada. Existen parámetros de inicialización del contexto, los cuales se definen en el archivo web.xml dentro del elemento <context-param>. Cada parámetro de inicialización tiene los elementos <param-name> y <param-value>.
La interface ServletContext permite a los servlets obtener los parámetros de inicialización (del contexto) a través de dos métodos:
- String getInitParameter (String name): Devuelve un String que contiene el valor del parámetro, devuelve null si el parámetro no existe.
- java.util.Enumeration getInitParameterNames (): Devuelve un objeto Enumeration con los valores de los parámetros.
Para utilizar estos métodos, primero se debe obtener la instancia del objeto ServletContext. Este objeto esta contenido dentro de ServletConfig. Debido a esto, desde un servlet, existen dos formas de obtener el objeto ServletContext:
- getServletConfig().getServletContext()
- getServletContext(): Debido a que GenericServlet implementa la interface ServletConfig.
Utilizando Listeners
La especificación de servlets provee interfaces de listeners. Estas, reciben notificaciones cuando se producen eventos relacionados a la aplicación web. Para responder a estas notificaciones, se deben crear clases que implementen dichas interfaces, y especificar el nombre de la clase en el archivo web.xml. Entonces, el contenedor de servlets puede invocar los métodos apropiados sobre objetos de estas clases, cuando se producen los eventos.
Agregando y quitando atributos
Se pueden agregar atributos a los objetos de contexto (ServletContext), objetos de sesión (HttpSession) y objetos de petición (ServletRequest). Cada una de estas clases, contiene los mismos cuatro métodos para realizar las operaciones sobre los atributos:
- Object getAttribute (String name): Devuelve el objeto asociado con el nombre pasado como parámetro.
- Enumeration getAttributeNames (): Devuelve un objeto Enumeration con los nombres de los atributos.
- void setAttribute (String name, Object value): Asocia un objeto con el ámbito actual y lo identifica a través del nombre especificado.
- void removeAttribute (String name): Elimina el atributo del ámbito.
Se debe tener en cuenta tres puntos importantes:
- Los atributos del objeto ServletRequest se resetean luego de cada petición, pero los atributos de sesión están disponibles para cualquier servlet que reciba una petición como parte de la sesión.
- Cualquier atributo asociado con el contexto está disponible en todos los servlets de la aplicación.
- Solo se puede asociar un atributo con un nombre.
Escuchando los eventos sobre los atributos
Cada ámbito (contexto, sesión y petición) provee diferentes eventos y listeners para realizar un seguimiento de sus atributos:
Petición: Para responder a los eventos de atributos de petición, se debe crear una clase que implemente la interface ServletRequestAttributeListener. Dicha interface provee los métodos:
- void attributeAdded (ServletRequestAttributeEvent ser): Invocado cuando se agrega un atributo.
- void attributeRemoved (ServletRequestAttributeEvent ser): Invocado cuando se elimina un atributo.
- void attributeReplaced (ServletRequestAttributeEvent ser): Invocado cuando se reemplaza un atributo.
Sesión: Para responder a los eventos de atributos de sesión existen tres interfaces: HttpSessionAttributeListener, HttpSessionBindingListener y HttpSessionActivationListener.
Contexto: Para recibir las notificaciones sobre los cambios en los atributos del contexto de una aplicación, se debe crear una clase que implemente la interface ServletContextAttributeListener, la cual provee los siguientes métodos:
- void attributeAdded (ServletContextAttributeEvent scae): Invocado cuando se agrega un nuevo atributo.
- void attributeRemoved (ServletContextAttributeEvent scae): Invocado cuando se elimina un atributo.
- void attributeReplaced (ServletContextAttributeEvent scae): Invocado cuando se reemplaza un atributo.
Eventos y listeners del ciclo de vida de servlets
La Servlet Specification 2.4 define tres interfaces para hacer esto posible:
javax.servlet.ServletContextListener: Permite conocer cuando el servlet context se inicializa o se destruye:
- void contextDestroyed (ServletContextEvent sce): Invocado cuando se destuye el contexto.
- void contextInitialized (ServletContextEvent sce): Invocado cuando se inicializa el contexto.
javax.servlet.http.HttpSessionListener: Esta interface contiene los mismos métodos y capacidades que ServletContextListener, pero provee notificaciones cuando se crea o destruye una sesión.
javax.servlet.http.HttpServletRequestListener: Permite conocer cuando una petición se inicializa o se destruye:
- void requestDestroyed (ServletRequestEvent sre): Invocado cuando se destruye una petición.
- void requestInitialized (ServletRequestEvent sre): Invocado cuando se inicializa una petición.
Agregando listeners en el archivo web.xml
El archivo de configuración posee el elemento <listener>. Dicho elemento especifica las clases que escuchan a los eventos de la aplicación. Cada elemento <listener> posee uno y solo un elemento <listener-class>. El mismo contiene el nombre completo (incluido el paquete al que pertenece) de la clase que implementa alguna (o varias) interface listener.
<listener> <listener-class> com.abc.MyServletContextListener </listener-class> </listener>
En el archivo web.xml, no se especifica que clase tiene que ser utilizada para cada evento. El contenedor de servlets es el encargado de revisar cada una de las clases, viendo cada una de las interfaces que implementa, y agregando una instancia a una lista de listeners.
Se pueden implementar múltiples interfaces listeners en la misma clase. En este caso, se necesita solo un elemento <listener> en el archivo de configuración. El contenedor de servlets creará solo una instancia de la clase y enviará todas las notificaciones a esta instancia.
Se pueden implementar múltiples interfaces listeners en la misma clase. En este caso, se necesita solo un elemento <listener> en el archivo de configuración. El contenedor de servlets creará solo una instancia de la clase y enviará todas las notificaciones a esta instancia.
Los listeners de tipo HttpSessionBindingListener no se deben especificar en el archivo web.xml.
Archivo web.xml
<!ELEMENT web-app
(icon?, display-name?, description?, distributable?, context-param*, filter*, filter-mapping*, listener*, servlet*, servlet-mapping*, session-config?, mime-mapping*, welcome-file-list?, error-page*, taglib*, resource-env-ref*, resource-ref*, security-constraint*, login-config?, security-role*, env-entry*, ejb-ref*, ejb-local-ref*)>
Objetos contenedores thread-safe
Los contenedores ServletContext y HttpSession no son Thread-safe. Es decir, cuando se quiera manipular los atributos de estos contenedores es necesario sincronizar este código. De esta manera, antes de comenzar a realizar acciones como agregar, reemplazar o eliminar un atributo, se debe obtener la cerradura (lock) del contenedor para que ningún otro servlet pueda realizar estas acciones al mismo tiempo. Esta es la única manera de hacer que estos contenedores sean thread-safe. Otros modos como sincronizar el método service() (o algún otro método de servicio), o implementar la interface SingleThreadModel, no son utilizados porque solo permiten la ejecución de un solo thread sobre una instancia de un servlet. Lo cual, son técnicas que perjudican la performance de las respuestas.
Archivo web.xml
<!ELEMENT web-app
(icon?, display-name?, description?, distributable?, context-param*, filter*, filter-mapping*, listener*, servlet*, servlet-mapping*, session-config?, mime-mapping*, welcome-file-list?, error-page*, taglib*, resource-env-ref*, resource-ref*, security-constraint*, login-config?, security-role*, env-entry*, ejb-ref*, ejb-local-ref*)>
Objetos contenedores thread-safe
Los contenedores ServletContext y HttpSession no son Thread-safe. Es decir, cuando se quiera manipular los atributos de estos contenedores es necesario sincronizar este código. De esta manera, antes de comenzar a realizar acciones como agregar, reemplazar o eliminar un atributo, se debe obtener la cerradura (lock) del contenedor para que ningún otro servlet pueda realizar estas acciones al mismo tiempo. Esta es la única manera de hacer que estos contenedores sean thread-safe. Otros modos como sincronizar el método service() (o algún otro método de servicio), o implementar la interface SingleThreadModel, no son utilizados porque solo permiten la ejecución de un solo thread sobre una instancia de un servlet. Lo cual, son técnicas que perjudican la performance de las respuestas.