En este punto vamos a ver cómo compartir datos de forma global dentro de nuestra aplicación web. Para ello utilizaremos el objeto contexto de servlets (Servlet Context). Este objeto de contexto es propio de cada aplicación web, es decir, tendremos un objeto ServletContext por aplicación web, por lo que nos servirá para comunicar los servlets de dicha aplicación.
public void init(ServletConfig config)
En la inicialización del servlet (método init), se nos proporcionará un objeto ServletConfig como parámetro. Mediante este objeto podemos:
String nombre = config.getServletName();
String valor_param = config.getInitParameter(nombre_param);
Enumeration nombres_params = config.getInitParameterNames();
ServletContext context = config.getServletContext();
Esta última función es la más importante, ya que nos permite acceder al objeto de contexto global de la aplicación, con el que podremos realizar una serie de operaciones que veremos a continuación.
Figura 1. Contexto global de los servlets
Tanto el objeto ServletConfig como ServletContext pueden ser obtenidos directamente desde dentro de nuestro servlet llamando a los métodos getServletConfig y getServletContext respectivamente, definidos en GenericServlet, y por lo tanto disponibles en cualquier servlet.
Dentro del objeto de contexto de nuestra aplicación podremos establecer una serie de atributos, que serán globales dentro de ella. Estos atributos son un conjunto de pares <nombre, valor> que podemos establecer y consultar desde los distintos servlets de nuestra aplicación web. El nombre del atributo será una cadena de texto (String), mientras que el valor podrá ser cualquier objeto java (Object).
Para consultar el valor de un atributo utilizaremos:
Object valor = context.getAttribute(nombre);
Daremos valor a un atributo con:
context.setAttribute(nombre, valor);
Podemos también eliminar un atributo:
context.removeAttribute(nombre);
Lo cual dejará el atributo a null, igual que si nunca le hubiesemos asignado un valor. Por último, con
Enumeration enum = context.getAttributeNames();
Obtenemos la lista de nombres de atributos definidos en el contexto.
Hay que hacer notar en este punto, que el objeto de contexto a parte de ser propio de cada aplicación web, es propio de cada máquina virtual Java. Cuando trabajemos en un contexto distribuido, cada máquina ejecutará una VM distinta, por lo que tendrán también objetos de contexto diferentes. Esto hará que si los servlets de una aplicación se alojan en máquinas distintas, tendrán contextos distintos y este objeto ya no nos servirá para comunicarnos entre ellos. Veremos más adelante formas alternativas de comunicación para estos casos.
El objeto ServletConfig nos proporcionaba acceso a los parámetros de inicialización del servlet en el que nos encontramos. Con ServletContext, tendremos acceso a los parámetros de inicialización globales de nuestra aplicación web. Los métodos para obtener dichos parámetros son análogos a los que usabamos en ServletConfig:
String valor_param = context.getInitParameter(nombre_param);
Enumeration nombres_params = context.getInitParameterNames();
Este objeto nos permite además acceder a recursos estáticos alojados en nuestro sitio web. Utilizaremos los métodos:
URL url = context.getResource(nombre_recurso);
InputStream in = context.getResourceAsStream(nombre_recurso);
El nombre del recurso que proporcionamos será una cadena que comience por "/", lo cual indica el directorio raiz dentro del contexto de nuestra aplicación, por lo tanto serán direcciones relativas a la ruta de nuestra aplicación web.
El primer método nos devuelve la URL de dicho recurso, mientras que el segundo nos devuelve directamente un flujo de entrada para leer dicho recurso.
Hay que señalar que esto nos permitirá leer cualquier recurso de nuestra aplicación como estático. Es decir, si proporcionamos como recurso "/index.jsp", lo que hará será leer el código fuente del JSP, no se obtendrá la salida procesada que genera dicho JSP.
Podemos también obtener una lista de recursos de nuestra aplicación web, con:
Set recursos = context.getResourcePaths(String ruta);
Nos devolverá el conjunto de todos los recursos que haya en la ruta indicada (relativa al contexto de la aplicación), o en cualquier subdirectorio de ella.
Si lo que queremos es acceder a recursos dinámicos, el método anterior no nos sirve. Para ello utilizaremos estas redirecciones. Utilizaremos el objeto RequestDispatcher que nos proporciona ServletContext.
Hemos de distinguir estas redirecciones de la que se producen cuando ejecutamos
response.sendRedirect();
Con sendRedirect lo que estamos haciendo es devolver al cliente una respuesta de redirección. Es decir, será el cliente, quien tras recibir esta respuesta solicite la página a la que debe redirigirse.
Con RequestDispatcher es el servidor internamente quien solicita el recurso al que nos redirigimos, y devuelve la salida generada por éste al cliente, pero todo ello de forma transparente al cliente. En cliente no sabrá en ningún momento que se ha producido una redirección.
Para obtener un objeto RequestDispatcher podemos usar los siguientes métodos de ServletContext:
RequestDispatcher rd = context.getRequestDispatcher(ruta);
RequestDispatcher rd = context.getNamedDispatcher(nombre);
Como ruta proporcionaremos la ruta relativa al contexto de nuestra aplicación, comenzando por el carácter "/", del recurso al que nos queramos redirigir. También podemos obtener este objeto proporcionando una ruta relativa respecto al recurso actual, utilizando para ello el método getRequestDispatcher del objeto ServletRequest, en lugar de ServletContext:
RequestDispatcher rd = request.getRequestDispatcher(ruta);
Podemos utilizar el RequestDispatcher de dos formas distintas: llamando a su método include o a forward.
rd.include(request, response);
El método include incluirá el contenido generado por el recurso al que redireccionamos en la respuesta, permitiendo que se escriba este contenido en el objeto ServletResponse a continuación de lo que se haya escrito ya por parte de nuestro servlet. Se podrá llamar a este método en cualquier momento. Lo que no podrá hacer el recurso al que redireccionamos es cambiar las cabeceras de la respuesta, ya que lo único que estamos haciendo es incluir contenido en ella. Cualquier intento de cambiar cabeceras en la llamada a include será ignorado.
Si hemos realizado la redirección utilizando un método getRequestDispatcher (no mediante getNamedDispatcher), en la petición del servlet al que redireccionamos podremos acceder a los siguientes atributos:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string
Con los que podrá consultar la ruta desde donde fué invocado.
rd.forward(request, response);
El método forward sólo podrá ser invocado cuando todavía no se ha escrito nada en la respuesta del servlet. Esto es así porque esta llamada devolverá únicamente la salida del objeto al que nos redirigimos. Si esto no fuese así, se produciría una excepción IllegalStateException. Una vez el método forward haya devuelto el control, la salida ya habrá sido escrita completamente en la respuesta.
Si el recurso al que redireccionamos utiliza direcciones relativas, estás direcciones se considerarán relativas al servlet que ha hecho la redirección, por lo que si se encuentran en rutas distintas se producirá un error. Tenemos que hacer que las direcciones sean relativas a la raiz del servidor para que funcione correctamente (direcciones que comiencen por "/").
Paso de información entre servlets y JSPs
Los servlets y los JSPs son tecnologías complementarías. Cada una de ellas es más apropiada para realizar ciertas tareas. Por lo tanto, lo más adecuado será integrar ambas tecnologías, y realizar con cada una las tareas más apropiadas para ella.
Los servlets serán adecuados cuando se requiere mucha programación. Por el contrario, los JSPs serán más apropiados para generar HTML, ya que son mejores para separar el código y la presentación.
Podremos integrar ambas tecnologías, realizando la programación en el servlet, y redirigiendo al JSP adecuado para que produzca la presentación, utilizando el RequestDispatcher visto anteriormente, bien por el método forward o include.
Puede ser necesario que el servlet proporcione cierta información al JSP, ya que el servlet en el procesamiento puede haber establecido ciertos datos que el JSP deberá presentar. Podemos pasar estos datos en tres ámbitos distintos: petición, sesión y aplicación (contexto).
Podemos proporcionar los datos en el ámbito de la petición incluyéndolos como atributo en el objeto ServletRequest:
MiClase valor = generaDatos();
request.setAttribute("nombre", valor);
Este valor podrá ser cualquier objeto Java (Object). Después de esto haremos un forward al JSP que queramos que presente estos datos, indicando como parámetro este objeto request en el que hemos añadido el atributo. En el JSP podremos obtener dicho objeto de la petición:
<% MiClase valor = (MiClase)request.getAttribute("nombre"); %>
De esta forma este objeto con nuestros datos será accesible sólo dentro de esta petición del cliente.
Podemos también incluir estos datos en ámbito de la sesión, añadiéndolos como atributo de la sesión:
session.setAttribute("nombre", valor);
Y obtenerlos en el JSP de la siguiente forma:
<% MiClase valor = (MiClase)session.getAttribute("nombre"); %>
En este caso este objeto con nuestros datos estará disponible para todas las peticiones del mismo cliente.
Por último, también podremos establecer estos datos en el ámbito de la aplicación, añadiéndolos como atributo del contexto:
context.setAttribute("nombre", valor);
En el JSP se obtendrá este objeto de la siguiente forma:
<% MiClase valor = (MiClase)context.getAttribute("nombre"); %>
De esta forma estos datos estarán disponibles para todas las peticiones de todos los clientes.
La clase ServletContext nos proporciona otros métodos de utilidad, que podremos consultar accediendo a su documentación JavaDoc.
Un método de interés es log, que nos permite escribir texto en el fichero de log del servlet:
context.log(mensaje);
Esto será util para tener un registro de eventos que ocurren en nuestra web, o bien para depurar errores.
Existen objetos que permanecen a la escucha de los distintos eventos que pueden ocurrir en el objeto de contexto de servlets, ServletContext.
Un primer listener, es el ServletContextListener, que nos permitirá dar respuesta a los eventos de creación y destrucción del contexto del servlet. El código para este listener será como sigue a continuación:
import javax.servlet.*;
public class MiContextListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent sce) {
// Destruccion del contexto
}
public void contextInitialized(ServletContextEvent sce) {
// Inicialización del contexto
}
}
Esto nos será de gran utilidad si necesitamos inicializar ciertas estructuras de datos que van a utilizar varios servlets. De esta forma el contexto se habrá inicializado antes de que los servlets puedan ejecutarse.
Si lo que queremos es saber cuando se ha añadido, eliminado, o modificado alguno de los atributos del contexto global, podemos utilizar un listener ServletContextAttributeListener. Los métodos que deberemos definir en este caso son los siguientes:
import javax.servlet.*;
public class MiContextAttributeListener implements ServletContextAttributeListener {
public void attributeAdded(ServletContextAttributeEvent scae) {
// Se ha añadido un nuevo atributo
}
public void attributeRemoved(ServletContextAttributeEvent scae) {
// Se ha eliminado un atributo
}
public void attributeReplaced(ServletContextAttributeEvent scae) {
// Un atributo ha cambiado de valor
}
}
Para hacer que estos objetos se registren como listeners y permanezcan
a la escucha, deberemos registrarlos como tales en el descriptor de despliegue
de la aplicación. Deberemos añadir un elemento <listener>
para cada objeto listener que queramos registrar:
<listener>
<listener-class>MiContextListener</listener-class>
</listener>
<listener>
<listener-class>MiContextAttributeListener</listener-class>
</listener>