En los temas anteriores, se han visto los componentes básicos de una aplicación JSP:
En la fase de diseño de una aplicación JSP, el problema principal es cómo combinar estos componentes de modo que consigamos una aplicación modular y fácilmente mantenible y extensible. Además, a estos componentes habría que añadir los servlets, ya que, como se vio en el Tema 1, aunque JSP y servlets comparten el mismo API, cada uno es apropiado para distintos tipos de tareas: servlets para procesamiento de información y JSPs para presentación de datos, por lo que pueden combinarse.
Las especificaciones de JSP hablan de dos enfoques distintos a la hora de construir aplicaciones con esta tecnología: los denominados modelo 1 y modelo 2. Ambos enfoques difieren fundamentalmente en quién lleva a cabo el procesamiento de la información: en el modelo 1 lo realizan las páginas JSP, mientras que en el modelo 2 hacen esta tarea los servlets. En ambos modelos se emplean JavaBeans para almacenar temporalmente datos y aislar a la aplicación de los detalles de la fuente original de donde proceden (una base de datos, un sistema que no utilice tecnología J2EE,...).
En la figura 1 se muestra un esquema de un sistema que utiliza la arquitectura modelo 1.
Como se ha visto, los beans aíslan a las páginas JSP de cambios en las fuentes de datos, pero los JSP son responsables tanto de generación de contenido (búsquedas, cálculos, ...) como de presentación de datos. Esto hace que sea necesario introducir gran cantidad de código Java en las páginas, lo que reduce la modularidad del sistema.
El modelo 2 separa la generación de contenido de su presentación. En la figura 2 se muestra un esquema.
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.
El modelo 2 es un ejemplo de lo que se denomina genéricamente arquitectura MVC (Modelo-Vista-Controlador). Esta arquitectura es originaria del lenguaje Smalltalk en los años 80 y se ha usado en muchos sistemas (la librería Java Swing es un ejemplo de este enfoque). Los componentes de esta arquitectura son:
El servidor de FAQs sobre el que se ha venido trabajando emplea la arquitectura MVC:
El componente que implementa el estado ya lo hemos visto en temas anteriores: el bean FAQBean permite almacenar los datos de un FAQ de manera independiente de la fuente original de los mismos.
Cada una de las acciones se implementa mediante una clase de nombre FAQComandoXXX. Estas clases tienen dos características principales:
package faqs.comandos; import javax.servlet.*; import javax.servlet.http.*; public interface FAQComando { // Ejecuta el comando public String ejecutar(HttpServletRequest req) throws FAQComandoException; }
El método ejecutar() utiliza la petición HTTP (el objeto HttpServletRequest) para extraer los parámetros de la acción y para almacenar sus resultados.
A modo de ejemplo de comando se muestra la clase FAQComandoGet, que
obtiene los datos de un FAQ a partir de su código (que se toma del parámetro
HTTP id
). Esta clase busca el FAQ en la base de datos (utilizando
para ello la clase FAQBD) y lo guarda como un atributo de la petición
HTTP, para que las páginas JSP u otros servlets puedan acceder a él.
package faqs.comandos; import javax.servlet.*; import javax.servlet.http.*; import faqs.bd.*; import faqs.*; public class FAQComandoGet implements FAQComando { // Siguiente pagina a mostrar private String siguiente; // Constructor public FAQComandoGet(String siguiente) { this.siguiente = siguiente; } // Ejecuta el comando public String ejecutar(HttpServletRequest req) throws FAQComandoException { try { FAQBD faqs = FAQBD.getInstancia(); int id = Integer.parseInt(req.getParameter("id")); FAQBean faq = faqs.getFAQ(id); req.setAttribute("faq", faq); return siguiente; } catch (NumberFormatException e1) { throw new FAQComandoException("FAQComandoGet: " + e1.getMessage()); } catch (FAQDesconocidoException e2) { throw new FAQComandoException("FAQComandoGet: " + e2.getMessage()); } catch (FAQBDException e3) { throw new FAQComandoException("FAQComandoGet: " + e3.getMessage()); } } }
Como ya se ha comentado, se implementa mediante páginas JSP. Cada comando
tiene una vista asociada, que debe mostrarse tras su ejecución. La página
JSP puede acceder a los datos almacenados en la petición por el comando
ejecutado, tomándolos como beans con ámbito de petición
(scope = request
).
Es un servlet (en realidad hay dos, uno para acceso a las FAQs y otro para su administración) que recoge los parámetros de la petición, llama a la acción correspondiente (clase FAQComandoXXX) y redirige la petición hacia la página JSP apropiada. A modo de ejemplo, se muestra el código del controlador de la parte de administración (clase FAQCentralServlet):
package faqs.servlets; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.*; import faqs.comandos.*; public class FAQCentralServlet extends HttpServlet { // Comandos que se pueden solicitar private HashMap comandos; // Pagina de error private String error = "error.jsp"; // Directorio de las paginas JSP private String dirJSP = "/jsp/"; // ============== METODOS PRINCIPALES DEL SERVLET ============== // Metodo de inicializacion public void init(ServletConfig config) throws ServletException { super.init(config); comandos = new HashMap(); comandos.put("menu", new FAQComandoNull("menu.jsp")); comandos.put("abortar", new FAQComandoAbortar("menu.jsp")); comandos.put("insert", new FAQComandoNull("insert.jsp")); comandos.put("exe-insert", new FAQComandoInsert("menu.jsp")); comandos.put("menu-update", new FAQComandoGetAll("update_menu.jsp")); comandos.put("update", new FAQComandoGet("update.jsp")); comandos.put("exe-update", new FAQComandoUpdate("menu.jsp")); comandos.put("menu-delete", new FAQComandoGetAll("delete_menu.jsp")); comandos.put("delete", new FAQComandoGet("delete.jsp")); comandos.put("exe-delete", new FAQComandoDelete("menu.jsp")); } // Metodo de procesamiento de peticion public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String siguiente; try { FAQComando cmd = getComando(req.getParameter("cmd")); siguiente = cmd.ejecutar(req); FAQComandoToken.set(req); } catch (FAQComandoException e) { req.setAttribute("javax.servlet.jsp.jspException", e); siguiente = error; } RequestDispatcher rd = getServletContext().getRequestDispatcher(dirJSP + siguiente); rd.forward(req, res); } // ============== METODOS AUXILIARES ============== // Obtiene un comando indicado por un nombre private FAQComando getComando(String cmd) throws FAQComandoException { if (cmd == null) cmd = "menu"; if (comandos.containsKey(cmd.toLowerCase())) return (FAQComando)comandos.get(cmd.toLowerCase()); else throw new FAQComandoException("Comando invalido"); } }
El servlet "sabe" cuál es el parámetro que hay que
ejecutar porque lo toma del parámetro de la petición HTTP denominado
cmd
. Vemos cada método del servlet: