JSP (JavaServer Pages) es una tecnología que permite incluir código Java en páginas web. El denominado contenedor JSP (que sería un componente del servidor web) es el encargado de tomar la página, sustituir el código Java que contiene por el resultado de su ejecución, y enviarla al cliente. Así, se pueden diseñar fácilmente páginas con partes fijas y partes variables. El siguiente es un ejemplo muy sencillo de página JSP:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Mi primera página JSP</title> </head> <body> <h1> Hoy es: <%= new java.util.Date() %> </h1> </body> </html>
Para ejecutar la página primera.jsp basta con colocarla en una aplicación
web (por ejemplo, en Tomcat, dentro de webapps/ROOT
). No es necesario
que sea en un directorio específico como ocurre con los servlets, sino
que puede ir en cualquier directorio en el que se colocaría normalmente
un HTML.
La última versión de la especificación JSP es la 2.0, aunque es de reciente aparición (Tomcat 4.x implementa la versión anterior, la 1.2, misma que cubre este tema). Como se verá, es una especificación paralela al API de servlets, concretamente a la versión 2.3. Se puede encontrar más información sobre JSP en
Aunque JSP y servlets parecen a primera vista tecnologías distintas,
en realidad el servidor web traduce internamente el JSP a un servlet, lo compila
y finalmente lo ejecuta cada vez que el cliente solicita la página JSP.
Por ello, en principio, JSPs y servlets ofrecen la misma funcionalidad, aunque
sus características los hacen apropiados para distinto tipo de tareas.
Los JSP son mejores para generar páginas con gran parte de contenido
estático. Un servlet que realice la misma función debe incluir
gran cantidad de sentencias del tipo out.println()
para producir
el HTML. Por el contrario, los servlets son mejores en tareas que generen poca
salida, datos binarios o páginas con gran parte de contenido variable.
En proyectos más complejos, lo recomendable es combinar ambas tecnologías:
los servlets para el procesamiento de información y los JSP para presentar
los datos al cliente.
Como se ha comentado, la primera vez que se solicita una página JSP, el servidor genera el servlet equivalente, lo compila y lo ejecuta. Para las siguientes solicitudes, solo es necesario ejecutar el código compilado. El servlet generado de manera automática tiene un método _jspService
que es el equivalente al service
de los servlets "generados manualmente". En este método es donde se genera el código HTML, mediante instrucciones println
y donde se ejecuta el código Java insertado en la página. Por ejemplo, la página
primera.jsp anterior podría generar un servlet con estructura similar al siguiente:
public void _jspService(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, ServletException { JspWriter out = null; response.setContentType("text/html;ISO-8859-1"); out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">); out.println("<html>"); out.println("<head>"); out.println("<title>Mi primera pagina JSP</title>"); out.println("</head>"); out.println("<body>"); out.print("Hoy es "); out.println(new java.util.Date()); out.println("</body>"); out.println("</html>"); }
El directorio donde se coloca el servlet generado, así como su nombre, dependen del servidor web. Por ejemplo, Tomcat utiliza su directorio work/localhost/aplicacion_web
. En caso de que la página esté en ROOT
, el nombre de la aplicación se sustituye por un carácter de subrayado (_
).
Existen tres tipos de elementos JSP que podemos insertar en una página web:
Se pueden poner comentarios en una página JSP entre los símbolos <%--
y --%>
. El contenedor JSP ignorará todo lo contenido entre ambos. Dentro de los fragmentos de código Java también se pueden colocar comentarios siguiendo la sintaxis habitual del
lenguaje Java.
Hay tres formas de insertar código Java en una página JSP:
<%= expresión %>
: en este caso, la expresión se evalúa, su resultado se convierte a String
y se inserta en la salida. <% código %>
_jspService
del servlet generado.
<%! código %>
: se insertan en el cuerpo del servlet generado, fuera de sus métodos. 1. Expresiones
Como se ha visto, cualquier expresión se evalúa, su resultado se convierte a un String
y se
escribe en la salida (el objeto predefinido out
). La forma de traducir una expresión a código de servlet es imprimiéndola en out
(mediante una sentencia out.write(expresion)
) o similar.
2. Scriptlets
Permiten ejecutar código arbitrario, cuyo resultado no es necesario enviar a la salida. Si desde un scriptlet se desea escribir algo en ésta, bastará con utilizar el objeto predefinido out
. Un uso común de los scriptlets es hacer que ciertas partes de código HTML aparezcan o no en función de una condición. Por ejemplo:
<% java.util.Calendar ahora = java.util.Calendar.getInstance(); int hora = ahora.get(java.util.Calendar.HOUR_OF_DAY); %> <b> Hola mundo, <i> <% if ((hora>20)||(hora<6)) { %> buenas noches <% } else if ((hora>=6)&&(hora<=12)) { %> buenos días <% } else { %> buenas tardes <% } %> </i> </b>
3. Declaraciones
Permiten definir variables o métodos que se insertarán dentro del cuerpo del servlet generado. Esto da la posibilidad de sobreescribir los métodos jspInit
y jspDestroy
que son el equivalente en JSP del init
y destroy
de los servlets. Las variables declaradas conservarán su valor entre sucesivas llamadas a la página, ya que son variables miembro del servlet y no locales al método jspService
. Esto nos permite, por ejemplo, crear un contador de accesos a la página:
<%! private int accesos = 0; %> <h1> Visitas: <%= ++accesos %> </h1>
4. Objetos implícitos de JSP
En cualquiera de estas tres formas, se puede hacer referencia a una serie de objetos implícitos , que se corresponden con objetos útiles del API de servlets (petición, respuesta, ...) y que en realidad son variables instanciadas de manera automática en el servlet generado a partir del JSP. Los objetos predefinidos en JSP se referencian en la tabla 1.
Objeto | Significado |
---|---|
request |
el objeto HttpServletRequest asociado con la petición |
response |
el objeto HttpServletResponse asociado con la respuesta |
out |
el Writer empleado para enviar la salida al cliente. La salida de los JSP emplea un buffer que permite que se envíen cabeceras HTTP o códigos de estado aunque ya se haya empezado a escribir en la salida (out no es un PrintWriter sino un objeto de la clase especial JspWriter ). |
session |
el objeto HttpSession asociado con la petición actual. En JSP, las sesiones se crean automáticamente, de modo que este objeto está instanciado aunque no se cree explícitamente una sesión. |
application |
el objeto ServletContext , común a todos los servlets de la aplicación web. |
config |
el objeto ServletConfig , empleado para leer parámetros de inicialización. |
pageContext |
permite acceder desde un único objeto a todos los demás objetos implícitos |
page |
referencia al propio servlet generado (tiene el mismo valor
que this ).Como tal, en Java no tiene demasiado sentido utilizarla,
pero está pensada para el caso en que se utilizara un lenguaje de programación
distinto. |
exception |
Representa un error producido en la aplicación. Solo es accesible si la página se ha designado como página de error (mediante la directiva page isErrorPage ). |
Las directivas influyen en la estructura que tendrá el servlet generado a partir de la página JSP. Hay tres tipos de directivas:
page
: tiene varios usos: importar clases de Java, fijar el
tipo MIME de la respuesta, controlar el buffer de salida,...include
: sirve para incluir código en la página
antes de que se realice la compilación del JSP.taglib
: se emplea cuando el JSP hace uso de etiquetas definidas
por el usuario.El formato genérico de una directiva es:
<%@ directiva atributo="valor" %>
algunas directivas admiten más de un atributo.
1. La directiva page
La tabla 2 recoge los distintos atributos que admite la directiva page
y su significado.
Atributo | Significado | Ejemplo |
---|---|---|
import |
el equivalente a una sentencia import de Java |
<%@ page import="java.util.Date" %> |
contentType |
genera una cabecera HTTP Content-Type |
<%@ page contentType="text/plain" %> |
isThreadSafe |
si es false , el servlet generado implementará
el interface SingleThreadModel (ún único hilo
para todas las peticiones). Por defecto, el valor es true . |
|
session |
Si es false , no se creará un objeto
session de manera automática. Por defecto, es true . |
|
buffer |
Define el tamaño del buffer para la salida
(en kb), o none si no se desea buffer. Su existencia
permite generar cabeceras HTTP o códigos de estado aunque ya se
haya comenzado a escribir la salida. |
<%@ page buffer="64kb" %> |
autoflush |
Si es true (valor por defecto), el buffer se
envía automáticamente a la salida al llenarse. Si es false ,
al llenarse el buffer se genera una excepción. |
|
extends |
Permite especificar de qué clase debe descender el servlet generado a partir de la página JSP. No es habitual cambiarlo. | |
info |
define una cadena que puede obtenerse a través del método
getServletInfo |
<%@ page info="carro de la compra" %> |
errorPage |
especifica la página JSP que debe procesar los errores generados y no capturados en la actual. | <%@ page errorPage="error.jsp" %> |
isErrorPage |
Si es true , indica que la página actúa como
página de error para otro JSP. El valor por defecto es false . |
|
language |
Permite especificar el lenguaje de programación usado en el JSP. En la práctica, el lenguaje siempre es Java, por lo que esta directiva no se usa. | |
pageEncoding |
define el juego de caracteres que usa la página. El valor
por defecto es ISO-8859-1 . |
2. La directiva include
Es el equivalente al #include
del lenguaje C. su sintaxis es:
<%@ include file="fichero" %>
Como el código se incluye antes de la compilación, los fragmentos de código incluidos pueden tener efecto sobre la página actual. Así, se puede utilizar esta directiva para definir constantes, generar cabeceras HTTP...
El problema de esta directiva es que el estándar no exige que el contenedor JSP detecte de manera automática los cambios en los ficheros incluidos, de manera que si cambia uno de ellos puede ser necesario forzar la recompilación de las páginas JSP que los incluyan.
La especificación JSP recomienda que si la página incluida no es una página
JSP válida por sí sola (por ejemplo, porque utiliza variables que se confía
que se hayan declarado previamente) se utilice la extensión "estándar" .jspf
(JSP fragment) y se coloque en un directorio no público del contenedor
JSP (por ejemplo, WEB-INF, que no es accesible desde el cliente, pero sí desde
la directiva).
3. La directiva taglib
Como se ha comentado, la directiva taglib se emplea para emplear etiquetas JSP definidas por el usuario. Se verá una introducción a las taglibs más adelante.
En JSP existen varios mecanismos para incluir elementos externos en la página actual o redirigir la petición hacia otra página
include
permite insertar código en la
página antes de que ésta se transforme en un servlet. De este modo se pueden
reutilizar fragmentos de código JSP o HTML. <jsp:include>
<jsp:plugin>
permite incluir applets
que hagan uso de Java 2. <jsp:forward>
sirve para redirigir
la petición a otra página JSP 1. La acción <jsp:include>
Esta acción incluye en una página la salida generada por otra perteneciente a la misma aplicación web. La petición se redirige a la página incluida, y la respuesta que genera se incluye en la generada por la principal. Su sintaxis es:
<jsp:include page="URL relativa" flush="true|false"/>
El atributo flush
especifica si el flujo de salida de la página
principal debería ser enviado al cliente antes de enviar el de la página incluida.
En JSP 1.2 este atributo es optativo, y su valor por defecto es false
.
En JSP 1.1 es obligatorio y siempre debía valer true
(el forzar
el vaciado de buffer era problemático porque una vez que ha sucedido esto
no se pueden hacer redirecciones ni ir a páginas de error, ya que ya se han
terminado de escribir las cabeceras).
Esta acción presenta la ventaja sobre la directiva del mismo nombre de que
cambios en la página incluida no obligan a recompilar la "principal". No obstante,
la página incluida solo tiene acceso al JspWriter
de la "principal"
y no puede generar cabeceras (por ejemplo, no puede crear cookies).
Por defecto, la petición que se le pasa a la página incluida es la original,
pero se le pueden agregar parámetros adicionales, mediante la etiqueta jsp:param
.
Por ejemplo:
<jsp:include page="cabecera.jsp"> <jsp:param name="color" value="YELLOW" /> </jsp:include>
2. La acción <jsp:plugin>
Esta acción sirve para incluir, de manera portable e independiente del navegador, applets que utilicen alguna librería de Java 2 (Swing, colecciones, Java 2D, ...), ya que las máquinas virtuales Java distribuidas con algunos navegadores relativamente antiguos (Explorer 5.x, Netscape 4.x,...) son de una versión anterior a Java 2.
3. La acción <jsp:forward>
Esta acción se utiliza para redirigir la petición hacia otra página JSP que esté en la misma aplicación web que la actual. Un ejemplo de su sintaxis básica es:
<jsp:forward page="principal.jsp"/>
La salida generada hasta el momento por la página actual se descarta (se borra el buffer). En caso de que no se utilizara buffer de salida, se produciría una excepción.
Al igual que en el caso de <jsp:include>
, se pueden añadir
parámetros a la petición original para que los reciba la nueva página JSP:
<jsp:forward page="principal.jsp"> <jsp:param name="privilegios" value="root" /> </jsp:forward>
Un JavaBean (o, para abreviar, un bean) es un componente software reutilizable escrito en Java. En realidad un bean no es más que una clase Java escrita siguiendo unas ciertas convenciones. Estas convenciones hacen posible que herramientas automáticas puedan acceder a sus propiedades y manipularlas sin necesidad de modificar el código. Esto puede servir en el caso de un IDE, por ejemplo, para realizar "programación visual". En JSP el uso principal de los beans es manipular componentes Java sin necesidad de incluir código en la página, accediendo a sus propiedades mediante etiquetas.
El uso de beans en páginas JSP ofrece diversas ventajas con respecto al uso directo de código Java:
Como se ha comentado, un bean no es más que una clase Java en la que se observan ciertas convenciones. En lo que respecta a su uso con JSP, estas convenciones afectan al modo de definir constructores, métodos y variables miembro:
nombreVar
,
entonces el método de acceso debe llamarse getNombreVar
(obsérvese
el cambio a mayúsculas de la "N", siguiendo las convenciones habituales de
Java), y el método de cambio de valor (en caso de que exista) debe llamarse
setNombreVar
. En el caso especial de variables booleanas, el
método de acceso se debe denominar isNombreVar
.Por ejemplo, supongamos que se desea definir un bean para almacenar información relativa a un usuario (nombre, número de visitas al sitio y fecha de la última visita), y compartirla entre varias páginas, una vez que se ha autentificado en la aplicación y sabemos quién es. Para ello podríamos utilizar un código similar al siguiente:
package beans; import java.util.Date; public class UsuarioBean { //variables miembro, privadas private String nombre; private int visitas; private Date ultimaVisita; private boolean varon; //constructor sin argumentos public UsuarioBean() { nombre = null; visitas = 0; ultimaVisita = null; varon = false; } //métodos de acceso: getXXX, isXXX public String getNombre() { return nombre; } public int getVisitas() { return visitas; } public Date getUltimaVisita() { return ultimaVisita; } public boolean isVaron() { return varon; } //métodos de cambio de valor: setXXX public void setNombre(String nom) { nombre = nom; } public void setVisitas(int v) { visitas = v; } public void setUltimaVisita(Date fecha) { ultimaVisita = fecha; } public void setVaron(boolean valor) { varon = valor; } }
Para interactuar con un bean desde una página JSP es necesario primero asignarle un nombre y especificar qué clase Java lo define. Una vez hecho esto, se puede acceder a sus propiedades y darles nuevos valores.
1. Acceso al bean
Para hacer accesible un bean a una página JSP se emplea la etiqueta useBean
. En su forma más simple la sintaxis es:
<jsp:useBean id="nombreBean" class="paquete.Clase"/>
En caso de que el bean referenciado no existiera previamente, esta etiqueta se puede ver como la creación de una variable en Java de nombre nombreBean
y de tipo paquete.Clase
. Así, para crear un bean de tipo
UsuarioBean
sería equivalente utilizar las siguientes expresiones:
<jsp:useBean id="usuario" class="beans.UsuarioBean" />
<% beans.UsuarioBean usuario = new beans.UsuarioBean() %>
La clase a la que pertenece el bean debería colocarse donde están habitualmente las clases Java que pertenecen a una aplicación web, es decir, en WEB-INF/classes
. Para que el contenedor JSP pueda encontrar la clase del bean, es conveniente que éste pertenezca a un package (como en el ejemplo anterior). En caso contrario se asumiría que pertenece al mismo package que el servlet generado a partir del JSP, con el problema de que el nombre de este package es desconocido.
2. Acceso a las propiedades del bean
El acceso a una propiedad se realiza mediante la etiqueta jsp:getProperty
. Su sintaxis es:
<jsp:getProperty name="nombreBean" property="nombrePropiedad"/>
donde nombreBean
debe corresponderse con el atributo id
definido mediante alguna etiqueta anterior jsp:useBean
. El acceso
a la propiedad también se podría hacer llamando al método Java correspondiente.
De este modo, el acceso a la propiedad visitas
del bean
usuario
se puede hacer mediante las dos formas alternativas:
<jsp:getProperty name="usuario" property="visitas"/> <%= usuario.getVisitas() %>
aunque se considera preferible la primera forma, ya que la sintaxis es más accesible a diseñadores web que no programen en Java.
3. Asignación de valores a las propiedades del bean
La asignación de valores se realiza mediante la etiqueta jsp:setProperty
. Esta etiqueta requiere tres parámetros: name
(el id
del bean, definido anteriormente mediante alguna etiqueta jsp:useBean
), property
(el nombre de la propiedad) y value
(el valor que se desea dar a la propiedad). Por ejemplo, para darle a la propiedad visitas
del bean usuario
el valor 1 se haría:
<jsp:setProperty name="usuario" property="visitas" value="1"/>Una forma alternativa en código Java sería llamar directamente al método, aunque normalmente es preferible el uso de la sintaxis anterior:
<% usuario.setVisitas(1) %>
Además de poder asignar a una propiedad un valor fijo, se pueden usar expresiones JSP:
<jsp:setProperty name="usuario" property="ultimaVisita" value="<%= new java.util.Date() %>"/>
4. Inicialización de un bean
En algunos casos, puede ser necesario inicializar un bean antes de empezar a usarlo. Esto no se puede hacer directamente con la etiqueta jsp:useBean
, ya que no admite parámetros. Para solucionar el problema, en el cuerpo de la etiqueta jsp:useBean
(siguiendo sintaxis
XML) se pueden introducir etiquetas jsp:setProperty
que inicialicen las propiedades. Además se pueden colocar scriptlets y código
HTML.
<jsp:useBean id="usuario" class="beans.usuarioBean"> <b> inicializando datos de usuario </b> <jsp:setProperty name="usuario" property="ultimaVisita" value="<%= new java.util.Date() %>"/> </jsp:useBean>
Es importante destacar que el código de inicialización solo se ejecutará en caso de que el bean no existiera previamente (no sea un bean compartido con otras páginas o creado por ejemplo por un servlet).
5. Utilizar los parámetros de la petición HTTP
JSP incluye un mecanismo para asignar los valores de los parámetros de la
petición a las propiedades de un bean. Para ello hay que utilizar el
parámetro param
de la etiqueta jsp:setProperty
. Por
ejemplo, supongamos que se ha definido el siguiente formulario, que toma el
nombre del usuario y llama a la página main.jsp con los datos
introducidos por el usuario (nombre y sexo):
<html> <head> <title>Entrada a la aplicación</title> </head> <body> <form action="main.jsp" method="get"> Nombre <input type="text" name="nombre"> <br> Sexo: varon <input type="radio" name="varon" value="true"> mujer: <input type="radio" name="varon" value="false"> <br> <input type="submit" value="entrar"> </form> </body> </html>
En la página main.jsp se puede hacer uso de los parámetros para instanciar algunas propiedades del bean:
<html> <head> <title>Untitled</title> </head> <body> <jsp:useBean id="usuario" class="beans.UsuarioBean"/> <jsp:setProperty name="usuario" property="nombre" param="nombre"/> <jsp:setProperty name="usuario" property="varon" param="varon"/> Buenos días, <jsp:getProperty name="usuario" property="nombre"/> </body> </html>
Nótese que, aunque los parámetros HTTP son en realidad cadenas, el
contenedor JSP es capaz de efectuar la conversión al tipo correspondiente,
al menos para tipos primitivos (por ejemplo, se ha convertido de la cadena "true"
al valor true
que requiere el método setVaron
.
Esta conversión de tipos no funciona en caso de tipos no primitivos.
Por ejemplo, no se puede aplicar a la propiedad ultimaVisita
, de
tipo java.util.Date
, ya que no se puede convertir automáticamente
de cadena a Date
(al menos el estándar JSP no lo exige).
En caso de que las propiedades del bean tengan el mismo nombre que
los parámetros HTTP, (como en el caso anterior) la asignación
de todos los parámetros se puede hacer mediante una única etiqueta
setProperty
, con el parámetro property="*"
:
<html> <head> <title>Untitled</title> </head> <body> <jsp:useBean id="usuario" class="beans.UsuarioBean"/> <jsp:setProperty name="usuario" property="*" /> Buenos días, <jsp:getProperty name="usuario" property="nombre"/> </body> </html>
Hasta el momento, se han tratado los beans como si fueran objetos
propios de la página en la que se definen, y exclusivos de ella. Este
es el comportamiento por defecto, pero podemos cambiar el ámbito
de un bean para definir desde dónde será accesible, lo
que nos permite compartirlo entre varias páginas. El ámbito se
controla con el atributo scope
de la etiqueta jsp:useBean
,
y puede tomar cuatro valores distintos:
page
: es el valor por defecto. Indica que el bean
sólo es válido para la página actual.application
: el bean será accesible a todas las
páginas JSP de la aplicación web, y compartido entre todos los
usuarios. Los servlets pueden acceder a él a través del objeto
ServletContext
.session
: el bean será accesible a todas las páginas
JSP, pero cada usuario tendrá su propio objeto. Los servlets pueden
acceder a él a través del objeto HttpSession
, obteniendo
su valor con el método getAttribute
.request
: el bean será accesible durante la petición
actual, lo que significa que podrán acceder a él las páginas
a las que se desvíe la petición con un <jsp:include>
o un <jsp:forward>
. Los servlets pueden acceder a él
a través del objeto ServletRequest
, de donde se puede
obtener su valor con getAttribute
.La arquitectura MVC plantea un diseño de aplicaciones Web que separa la generación de contenido de su presentación. En la figura se muestra un esquema.
Figura 1: Modelo de arquitectura MVC
En este modelo, los servlets son los responsables de la generación de contenido, y las páginas JSP de la presentación. Esto elimina gran parte del código Java de las páginas JSP, lo que permite que se puedan dividir las tareas: los programadores Java implementan los servlets, mientras que los desarrolladores de páginas web se ocupan de los JSP, que tienen un mínimo de código Java o incluso ninguno si se utilizan beans y etiquetas propias.
Los componentes de esta arquitectura son: