Tema 4: Arquitectura de aplicaciones JSP

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.

4.1. Modelos para una aplicación JSP

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,...).

4.1.1. Modelo 1

En la figura 1 se muestra un esquema de un sistema que utiliza la arquitectura modelo 1.


Figura 1: Modelo 1 de aplicación JSP

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.

4.1.2. Modelo 2: arquitectura MVC

El modelo 2 separa la generación de contenido de su presentación. En la figura 2 se muestra un esquema.


Figura 2: Modelo 2 de aplicación JSP

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:

4.2. La arquitectura MVC del servidor de FAQs

El servidor de FAQs sobre el que se ha venido trabajando emplea la arquitectura MVC:

4.2.1. El modelo

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:

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());
	}
   }
}

4.2.2. La vista

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

4.2.3. El controlador

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: