En esta entrada crearemos nuestra propia librería de etiquetas que defina nueva funcionalidad reutilizable en distintas JSPs.
¿Cómo lo vamos a hacer? Lo primero será definir la etiqueta (nombre, atributos, cuerpo, etc) en un fichero denominado Tag Lib Description (TLD). Después crearemos la clase (o clases) Java que implementa la funcionalidad y configuraremos el fichero web.xml para que la aplicación «conozca» la nueva librería. Finalmente la utilizaremos en una página de prueba.
El ejemplo que seguiremos es el de una etiqueta que muestra el resultado de la operación matemática que se le indique como atributo.
Fichero TLD
- Creamos un directorio que contenga las descripciones de nuestras librerías: WEB-INF\tld\
- Creamos dentro del directorio el fichero que describe nuestra librería: operacion.tld (os dejo el enlace al fichero).
Aspectos interesantes que se pueden observar en el fichero:
- La librería de etiquetas se encuentra definida dentro de <taglib>…</taglib>.
- Mediante las etiquetas tlibversion, jspversion, shortname e info incluimos información sobre la librería.
- Cada etiqueta se define con <tag>…</tag>. Y contiene las siguientes subetiquetas:
- <name>Nombre de la etiqueta</name>
- <tagclass>Clase JAVA que la implementa</tagclass>
- <info>Información adicional</info>
- <attribute> Atributos de la etiqueta. Este campo es opcional y se pueden incluir varios.
- <name>Nombre del atributo</name>
- <required>True o False. Dependiendo de si es obligatorio o no</required>
En el ejemplo que se incluye en el fichero operacion.tld se ha declarado una etiqueta operador que está implementada en la clase src.tags.Operador y que tiene los siguientes atributos: op1, op2 y operator; todos ellos obligatorios. Por tanto, una etiqueta válida sería:
<op:mioperacion op1="3" op2="5" operator="+" />
La cual mostraría como resultado un 8. Lo único que aparece en la expresión anterior y de lo que no hemos dicho nada es «op:». Al final del articulo lo desvelaremos.
Clase JAVA
El siguiente punto es la creación de la clase que implementa la funcionalidad de la etiqueta. Como en el caso anterior dejo el fichero y hago unos comentarios sobre el código. (Operador.java).
Lo primero que cabe destacar es que la clase hereda de javax.servlet.jsp.tagext.TagSupport. Esto no es algo nuevo en Orientación a Objetos. Siempre que alguien desarrolla una aplicación o librería y desea que se pueda extender su funcionalidad, crea una interfaz (o clase) para que se implemente (o herede). El ejemplo típico es el de añadir funcionalidad a un botón. En este caso se creará una clase que implemente la interfaz ActionListener y, por tanto, el método actionPerformed. La clase JButton «sabe» que tiene que invocar dicho método pero desconoce el objeto concreto sobre el cual se ejecuta. Lo que si tiene claro es que será un hijo de ActionListener y que por polimorfismo podrá estar ligado al atributo ActionListener declarado en JButton.
También suele ser muy usual que las librerías ofrezcan la posibilidad de implementar una interfaz o de heredar de una clase. ¿Cuál es la diferencia? Pues que en la interfaz, por propia definición, no hay ningún método implementado mientras que en la clase sí que lo hay. De esta forma si heredamos en vez implementar ya disponemos de métodos desarrollados y por tanto no tenemos que implementarlos todos, sólo aquellos que sean necesarios.
Bueno, después de este breve inciso donde se ha explicado por qué se hereda de TagSupport, veamos las distintas opciones que existen:
- Implementar la interfaz Tag, IterationTag o BodyTag. Cuál de ellas utilicemos dependerá de lo que se quiera realizar.
- Tag define los métodos básicos: doStartTag y doEndTag.
- IterationTag: doAfterBody.
- BodyTag: doInitBody.
- Heredar de TagSupport o de BodyTagSupport.
- TagSupport implementa Tag e IterationTag.
- BodyTagSupport implementa las tres interfaces.
Aunque por el nombre del método podemos deducir cuando se ejecutará cada uno, en la siguiente imagen se muestra más claro
Cuando aparece una etiqueta propietaria en una JSP se crea el objeto de la clase que la implementa y se inicializan, haciendo uso de los métodos setX, los atributos del objeto con los valores que aparecen en la etiqueta. A continuación se invocan los métodos doStartTag y doInitBody. Si la etiqueta tuviera cuerpo, incluso otra etiqueta, se procede de la misma forma. Al terminar el cuerpo de la etiqueta se ejecuta doAfterBody y cuando se cierra se ejecuta doEndTag.
Para terminar de explicar las partes más importantes de una clase que implementa la funcionalidad de una etiqueta debemos volver al código. Si nos fijamos, vemos que todos los métodos implementados devuelven un entero. ¿Pero que significado tiene? Muy fácil, indica qué hacer una vez que se ha ejecutado: evaluar el cuerpo de la etiqueta, evaluar la página o continuar. Lo normal en estos casos es utilizar una constante definida para tal efecto. Toda la información la he sacado del API de JSP.
- Tag
Field Summary | |
static int |
EVAL_BODY_INCLUDE Evaluate body into existing out stream. |
static int |
EVAL_PAGE Continue evaluating the page. |
static int |
SKIP_BODY Skip body evaluation. |
static int |
SKIP_PAGE Skip the rest of the page. |
- IterationTag
Field Summary | |
static int |
EVAL_BODY_AGAIN Request the reevaluation of some body. |
- BodyTag
Field Summary | |
static int |
EVAL_BODY_BUFFERED Request the creation of new buffer, a BodyContent on which to evaluate the body of this tag. |
Por tanto, si hemos heredado de TagSupport, como implementa Tag e IterationTag, podemos utilizar las constantes EVAL_BODY_INCLUDE, EVAL_PAGE, SKIP_BODY, etc. Y así lo hemos hecho en la clase Operador.java, cuando al terminar de ejecutar doStartTag se invoca return EVAL_PAGE.
Un último apunte, en el API teneis el ciclo de vida de ejecución de una etiqueta. Que será diferente dependiendo de si se implementa una u otra interface. Os dejo los enlaces directos a las imágenes: Tag, IterationTag y BodyTag.
Configurar web.xml
Una vez que tenemos declarada nuestra librería de etiquetas e implementada la clase con la funcionalidad deseada sólo queda «decirle» a nuestra aplicación que esta existe. Y como siempre en Tomcat, debemos de modificar el fichero web.xml añadiendo el siguiente código:
<taglib> <taglib-uri>Operador</taglib-uri> <taglib-location>/WEB-INF/tld/operacion.tld</taglib-location> </taglib>
¿Qué estamos diciendo aquí? Pues que existe una librería definida en /WEB-INF/tld/operacion.tld y que la vamos a llamar Operador (no tiene nada que ver con el nombre de la clase, Operador.java; le podemos poner lo que queramos).
Usar la librería
Si ya tenemos definida la librería, implementada la clase y modificado el web.xml para que Tomcat sepa que existe… ya sólo queda utilizarla.
- Creamos el fichero jsp/suma.jsp
- En la cabecera definimos la etiqueta a utilizar: <%@ taglib prefix=»op» uri=»Operador» %>
- Con el atributo prefix establecemos el nombre de la etiqueta dentro de la página JSP.
- Con el atributo uri hacemos referencia a la librería de etiquetas. El nombre es el mismo que hay en el web.xml
- En cualquier parte del código JSP la utilizamos: <op:mioperacion op1=»3″ op2=»5″ operator=»+» />
- Para utilizar la librería utilizamos el prefijo definido en el punto anterior, op.
- El texto operador hace referencia a la etiqueta definida en el TLD. No tiene nada que ver con el web.xml. Si hubiera otra etiqueta, «mietiqueta», definida en el TLD la utilizaríamos así: <op:mietiqueta atr=»valor» />.
diciembre 18th, 2009 14:54
Mil Gracias amigo me fue de gran ayuda el contenido de tu blog.
diciembre 18th, 2009 15:19
Me alegra que te haya servido. Un saludo!!!
mayo 4th, 2010 20:27
Una pregunta. ¿Cual es el sentido de tener las funciones?
doStartTag(): invocada al encontrar la etiqueta
doInitBody(): invocada antes de leer el cuerpo
doAfterBody(): tras leer el cuerpo
doEndTag(): al final de le etiqueta
¿en lugar de una única función que fuese invocada al encontrar la etiqueta?. Por ejemplo, ¿que puede suceder entre el comienzo de la etiqueta (doStartTag) y el comienzo del cuerpo de la etiqueta (doInitBody) para que se separe en dos funciones independientes?
Gracias
mayo 4th, 2010 22:08
Hola Javi,
doStartTag se invoca siempre, mientras que doInitBody sólo lo hace en aquellas etiquetas que tienen cuerpo y siempre que doStartTag devuelva EVAL_BODY.
En algunos casos te puede interesar realizar ciertas acciones al iniciar la etiqueta y continuar (o no) con la evaluación del cuerpo (doInitBody), en función de si se cumple cierta condición.
En este enlace tienes más información (http://www.sicuma.uma.es/sicuma/Formacion/documentacion/JSP.pdf).
Y lo más importante, siempre es mejor tener varias posibilidades de implementación :).
mayo 5th, 2010 13:34
Gracias por la pronta respuesta. Entiendo lo que me dices, y el ciclo de vida de las funciones para cada etiqueta, pero no llego a comprender la utilidad.
Si quiero realizar la evaluación del cuerpo o cualquier otra acción en función de si se cumple cierta condición, bastaría tener una única función con if, por ejemplo. ¿Que se consigue con obligat a Tomcat (u otro contenedor) a que sea él quien invoque las distintas funciones en función de los valores devueltos (en lugar de que sólo invoque una función por etiqueta, que resultaría simple de comprender)?
Gracias
mayo 5th, 2010 13:47
¿Y como implementarías tú? Es decir, ¿como controlarías que se ejecute un código u otro dependiendo de si la etiqueta tiene cuerpo o no o de si se cumple una condición?
mayo 5th, 2010 17:34
Para comprobar si tiene cuerpo o no, bastaría ejecutar «getBodyContent()» o similar con un if.
Respecto a las «condiciones», una función se invoca o no según la función anterior del ciclo de vida devuelva una constante u otro. Eso no sería mas que un «switch (valor_devuelto) case SKIP_BODY: doAfterBody(); …»
Dentro de la clase que implemente a la etiqueta podriamos añadir todos los métodos que necesitásemos (incluso más a los que presente Tomcat) para invocarlos desde ese switch.
¿No?, o hay alguna utilidad adicional que yo no estoy viendo.
===
Una pregunta aparte. Una clase que hereda de «TagSupport», ¿puede obtener el cuerpo de la etiqueta con «getBodyContent();»?. Pensaba que TagSupport era sólo para etiquetas sin cuerpo; si no, ¿que aporta BodyTagSupport respecto a ella (aparte de la adición en el ciclo de vida de los métodos «setBodyContent()» y
«doInitBody()»)?