Documento actualizado por última vez el día 13 de abril 2001. Estoy abierto a cualquier sugerencia para mejorar estos materiales. No dudes en contactar con el autor, Jonathan Revusky.
(This document in English) (Ce document en français)En este documento, se da por descantado que has conseguido hacer las etapas concretas que explican las instrucciones aquí, es decir cómo compilar y ejecutar el ejemplo.
Este servlet sencillo compone las siguientes partes:
HelloNiggleServletInteraction.java
hello.nhtml
En el API de servlets de base, defines una subclase concreta
de la clase abstracta javax.servlet.http.HttpServlet
e
implementas los métodos doGet() and doPost(), y también init() y destroy() si
es necesario inicializar/liberar algunos recursos que se usan durante
la vida del servlet. Empero, cuando escribes un servlet con Niggle,
normalmente, no hay ninguna necesidad de implementar
los métodos mencionados arriba. La librería ya tiene
una clase servlet com.revusky.niggle.servlet.NiggleServlet
que
tiene sus propias implementaciones de doGet(), doPost() e init()
que se usan directamente.
Por supuesto, queda la pregunta: ¿entonces, dónde defino
la funcionalidad de mi aplicación web? La respuesta es que
Niggle define otra clase abstracta base,
com.revusky.niggle.servlet.ServletInteraction
. Es esta
clase que es realmente el punto de subclase comparable en
Niggle: escribes tu propia subclase concreta de
ServletInteraction y pones la funcionalidad de tu aplicación allí dentro.
Ahora, debería señalar una diferencia de base en el diseño/arquitectura
de Niggle y el API de base de Sun. En el API de base, un servlet
es basicamente un objeto tipo singleton. Una instancia
se crea y esta instancia trata muchas peticiones durante su vida.
Pero en Niggle, una nueva instancia de tu subclase de ServletInteraction
se crea cada vez que un método doPost() o doGet() se invoca
en com.revusky.niggle.servlet.NiggleServlet
. Dicho de otra manera,
un objeto de tipo ServletInteraction
es un objeto
desechable. Se usa una vez y se tira. Encapsula el tratamiento
de una sola petición HTTP.
Ahora, si aun no lo has hecho, echa una ojeada al fichero HelloNiggleServlet.java file. ¡Quizá te sorprenda ver que la clase está vacía! Bueno, lo que pasa es que no hay ninguna necesidad de definir funcionalidad aquí, dado que, como señalamos antes, la superclase NiggleServlet ya define los métodos que implementamos normalmente cuando escribimos un servlet: doGet(), doPost() e init(). De hecho, el único papel que esta clase desempeña aquí es que Niggle usa el nombre del servlet para saber la subclase de ServletInteraction a usar. Ya que el nombre de la clase servlet es HelloNiggleServlet, se supone que la subclase de ServletInteraction correspondiente se llama HelloNiggleServletInteraction. (Lee este apunte técnico si quieres una explicación más completa de por qué tenemos que crear subclases de NiggleServlet yServletInteraction.)
Aquí bajo es el código completo de HelloNiggleServletInteraction.java:
import com.revusky.niggle.servlet.*; import javax.servlet.http.*; import java.io.IOException; public class HelloNiggleServletInteraction extends ServletInteraction { /** * El constructor obligatorio. Tu subclase de ServletInteraction debe tener * un constructor con este "signature". Es así incluso * si no tenemos que hacer nada dentro del constructor (aparte * de invocar el constructor de la superclase, mejor dicho) */ public HelloNiggleServletInteraction(HttpServletRequest request, HttpServletResponse response, NiggleConfig config) throws IOException { super(request, response, config); } /** * El "handler" por defecto. Este método es invocado * si no hay ningún parámetro tipo "action" en los * parámetros de la petición. De otra maner, si * la petición define un parámetro "action=foo" * Niggle supone que tiene que invocar el método * execFoo() method. Este ejemplo es sencillo y sólo * tiene una acción por defecto así que sólo definimos execDefault(). */ public void execDefault() throws IOException { this.page = getPage("hello.nhtml"); page.expose("title", "Hello, World"); page.expose("message", "Welcome to the brave new niggle world!"); } }
Echa una ojeada al método execDefault()
. En las primeras tres
líneas, la variable page
se fija y en las dos próximas líneas,
se expone un par de variables de plantilla
-- title
y message
.
Pues, ¿qué pasa aquí? Bueno, probablemente ya sabéis que la
filosofía de Niggle se basa en una separación muy rigurosa
entre la presentación y la lógica de aplicación.
Entonces, los detalles de presentación de cualquier salida al cliente
se encapsulan en una plantilla de página, la cual queda
completamente separada de la lógica de aplicación como tal.
Entonces, la clase ServletInteraction tiene una variable miembro
que se llama page
y esto se debe definir
en algun momento en un método tipo execXXXX
.
El código arriba es un ejemplo de esto.
En breve, la instancia de ServletInteraction instance tiene una variable miembro
que se llama page
que encapsula nuestra plantilla de página.
Fíjate que esta variable es una instancia del interfaz com.revusky.niggle.templates.Page
.
La explicación anterior dejó ciertas preguntas sin responder. El código java del ejemplo supone la existencia de una plantilla de página que corresponde a "hello.nhtml" y además, supone la existencia de un mecanismo para exponer las variables de plantilla a esta página.
Bueno, ahora es el momento de concretar esto. Abajo tienes la plantilla "hello.nhtml" template que usa las 2 variables de plantilla que son definidas en el código java.
<html> <head> <title>${title}</title> </head> <body> <P>Message: ${message}</P> </body> </html>
Fíjate que esta plantilla de página se parece a una página HTML page.
Pues, sí, básicamente es una página HTML. Lo único especial es la presencia
de 2 cadenas ${title}
y ${message}
que se tratan de modo especial. Supongo que ya has caído en la cuenta
de que corresponden a las 2 variables de plantilla que
definimos en el código java con las invocaciones de
page.expose()
.
Básicamente, la diferencia entre una plantilla de página
y una página HTML estática de toda la vida es que una parte del contenido
es definida de forma dinámica.
Fíjate que, por defecto, Niggle aprovecha el motor de plantillas
Freemarker.
Un aspecto clave de la sintaxis de Freemarker es que las
cadenas encerradas en ${...}
se determinan
dinamicamente.
Si has estudiado este ejemplo y estás cómodo con él, te animo a pasar al próximo ejemplo que es una simple aplicación tipo "guestbook".
Una pregunta interesante es porque es necsario hacer una subclase de com.revusky.niggle.servlet.NiggleServlet si es que ya contiene toda la funcionalidad que necesitas. Y después de todo, ¡la subclase que definimos queda vacía! Pues, yo sería el primero en reconocer que el hecho de tener 2 distintos puntos de subclase en paralelo suele indicar que hay algún problema de diseño en una librería de clases. Ciertamente va en contra de la regla básica de KISS (keep it simple, stupid).
Pues, aun y así, hay dos motivos que explican esto. El motivo más importante
es realmente de naturaleza muy técnica. Ves, un servidor de servlets
suele aislar los distintos contextos de aplicación los unos de los otros
haciendo que cada uno corra en su propio ClassLoader.
Entonces, si pones niggle.jar en el CLASSPATH global, entonces, la clase
com.revusky.niggle.servlet.NiggleServlet
sería cargada por el
ClassLoader global de sistema. Esto querría decir que no podría encontrar
las clases y recursos asociadas que ponemos en <app-base>/WEB-INF/classes
-- ya que sólo son visibles en el contexto de su propio ClassLoader.
Es que al obligar al programador de aplicación a definir su
propia subclase de NiggleServlet (aunque está vacía) y ponerla
en <app-base>/WEB-INF/classes, nos aseguramos que
las demás clases, como XXXServletInteraction se pueden encontrar.
Otra ventaja menor es que, al obligar al programador a crear una subclase de NiggleServlet, tenemos un nombre de clase por defecto para la subclase de ServletInteraction correspondiente. Si nombras tu clase servlet XXX, entonces, se supone por defecto que la subclase de ServletInteraction (la cual realmente contiene la funcionalidad de la aplicación) se llama XXXInteraction. Debería mencionar que, si no quieres contar con este esquema automático, puedes especificar un parámetro de inicialización INTERACTION_CLASS que especifica otro nombre para tu subclase de ServletInteraction. De hecho, anteriores versiones de Niggle te obligaban a especificar este parámetro. La gran ventaja de tener este esquema de nombre por defecto, no obstante, es que podemos tener un ejemplo mínimo que funciona sin la necesidad de definir ningún parámetro de inicialización, que es algo que suele causar problemas para ejecutar un servlet por primera vez.