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)

Diseccionar el servlet "Hello, Niggle"

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:

El código Java

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 plantilla de página

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".


Addendum: Un apunte sobre ciertos problemas de carga de clases/recursos

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.

Volver al texto principal