Una aplicación web un poco decente consiste en una mezcla de patrones de diseño. Mencionaré solo los más importantes.
El patrón de diseño central (arquitectónico) que le gustaría usar es el patrón Modelo-Vista-Controlador . El Controlador debe estar representado por un Servlet que (in) crea / usa directamente un Modelo y Vista específicos basados en la solicitud. El modelo se representará mediante clases Javabean. Esto a menudo es más divisible en el Modelo de Negocio que contiene las acciones (comportamiento) y el Modelo de Datos que contiene los datos (información). La vista se representará con archivos JSP que tengan acceso directo al modelo ( datos ) mediante EL (lenguaje de expresión).
Luego, hay variaciones basadas en cómo se manejan las acciones y los eventos. Los populares son:
MVC basado en solicitud (acción) : este es el más sencillo de implementar. El modelo ( comercial ) trabaja directamente con y objetos. Debe reunir, convertir y validar los parámetros de solicitud (principalmente) usted mismo. La Vista se puede representar mediante HTML / CSS / JS simple y no mantiene el estado en todas las solicitudes. Así es como funciona Spring MVC , Struts and Stripes , entre otros .HttpServletRequest
HttpServletResponse
MVC basado en componentes : esto es más difícil de implementar. Pero terminas con un modelo y una vista más simples en los que toda la API de Servlet "en bruto" se abstrae completamente. No debería tener la necesidad de reunir, convertir y validar los parámetros de solicitud usted mismo. El controlador realiza esta tarea y establece los parámetros de solicitud recopilados, convertidos y validados en el modelo . Todo lo que necesita hacer es definir métodos de acción que funcionen directamente con las propiedades del modelo. La Vista está representada por "componentes" en forma de taglibs JSP o elementos XML que a su vez generan HTML / CSS / JS. El estado de la vistapara las solicitudes posteriores se mantiene en la sesión. Esto es particularmente útil para la conversión del lado del servidor, la validación y los eventos de cambio de valor. ¡Así es como, entre otros , JSF , Wicket y Play! trabajos.
Como nota al margen, pasatiempo con un marco MVC de cosecha propia es un ejercicio de aprendizaje muy agradable, y lo recomiendo siempre que lo conserve para fines personales / privados. Pero una vez que sea profesional, se recomienda encarecidamente elegir un marco existente en lugar de reinventar el suyo. Aprender un marco existente y bien desarrollado requiere a largo plazo menos tiempo que desarrollar y mantener un marco robusto usted mismo.
En la explicación detallada a continuación, me limitaré a solicitar MVC basado, ya que es más fácil de implementar.
Primero, la parte Controlador debe implementar el patrón Controlador frontal (que es un tipo especializado de patrón Mediador ). Debe consistir en un solo servlet que proporcione un punto de entrada centralizado para todas las solicitudes. Debe crear el Modelo en función de la información disponible por la solicitud, como la información de ruta o la ruta de servlet, el método y / o parámetros específicos. El modelo de negocio se llama Action
en el siguiente HttpServlet
ejemplo.
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Action action = ActionFactory.getAction(request);
String view = action.execute(request, response);
if (view.equals(request.getPathInfo().substring(1)) {
request.getRequestDispatcher("/WEB-INF/" + view + ".jsp").forward(request, response);
}
else {
response.sendRedirect(view); // We'd like to fire redirect in case of a view change as result of the action (PRG pattern).
}
}
catch (Exception e) {
throw new ServletException("Executing action failed.", e);
}
}
La ejecución de la acción debería devolver algún identificador para ubicar la vista. Lo más sencillo sería usarlo como nombre de archivo del JSP. Asigne este servlet en un específico url-pattern
en web.xml
, por ejemplo /pages/*
, *.do
o incluso simplemente *.html
.
En el caso de patrones de prefijos como, por ejemplo /pages/*
, puede invocar URL como http://example.com/pages/register , http://example.com/pages/login , etc. y proporcionar /WEB-INF/register.jsp
, /WEB-INF/login.jsp
con las acciones GET y POST apropiadas . Las partes register
, login
etc. están disponibles request.getPathInfo()
como en el ejemplo anterior.
Cuando utiliza patrones de sufijos como *.do
, *.html
etc., puede invocar URL como http://example.com/register.do , http://example.com/login.do , etc. y debe cambiar el ejemplos de código en esta respuesta (también el ActionFactory
) para extraer las partes register
y login
en su request.getServletPath()
lugar.
El Action
debe seguir el patrón de estrategia . Debe definirse como un tipo abstracto / interfaz que debe hacer el trabajo basado en los argumentos pasados del método abstracto (esta es la diferencia con el patrón de Comando , en el que el tipo abstracto / interfaz debe hacer el trabajo basado en el argumentos que se han pasado durante la creación de la implementación).
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Es posible que desee hacer el Exception
más específico con una excepción personalizada como ActionException
. Es solo un ejemplo básico de inicio, el resto depende de usted.
Aquí hay un ejemplo de un LoginAction
que (como su nombre lo indica) inicia sesión en el usuario. El User
mismo es a su vez un modelo de datos . La Vista es consciente de la presencia de User
.
public class LoginAction implements Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = userDAO.find(username, password);
if (user != null) {
request.getSession().setAttribute("user", user); // Login user.
return "home"; // Redirect to home page.
}
else {
request.setAttribute("error", "Unknown username/password. Please retry."); // Store error message in request scope.
return "login"; // Go back to redisplay login form with error.
}
}
}
El ActionFactory
debe seguir el patrón del método de fábrica . Básicamente, debe proporcionar un método de creación que devuelva una implementación concreta de un tipo abstracto / interfaz. En este caso, debería devolver una implementación de la Action
interfaz basada en la información proporcionada por la solicitud. Por ejemplo, el método y la información de ruta (la información de ruta es la parte posterior al contexto y la ruta de servlet en la URL de solicitud, excluyendo la cadena de consulta).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
A actions
su vez, debe ser algo estático / en toda la aplicación Map<String, Action>
que contenga todas las acciones conocidas. Depende de usted cómo llenar este mapa. Código difícil:
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
O configurable en función de un archivo de configuración de propiedades / XML en el classpath: (pseudo)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
O basado dinámicamente en un escaneo en el classpath para clases que implementan una determinada interfaz y / o anotación: (pseudo)
for (ClassFile classFile : classpath) {
if (classFile.isInstanceOf(Action.class)) {
actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
}
}
Recuerde crear un "no hacer nada" Action
para el caso de que no haya mapeo. Deje que, por ejemplo, devuelva directamente el request.getPathInfo().substring(1)
entonces.
Otros patrones
Esos fueron los patrones importantes hasta ahora.
Para ir un paso más allá, puede usar el patrón Fachada para crear una Context
clase que, a su vez, envuelva los objetos de solicitud y respuesta y ofrezca varios métodos convenientes para delegar en los objetos de solicitud y respuesta y pasar eso como argumento al Action#execute()
método. Esto agrega una capa abstracta adicional para ocultar la API de Servlet sin procesar. Básicamente, debería terminar con cero import javax.servlet.*
declaraciones en cada Action
implementación. En términos de JSF, esto es lo que están haciendo las clases FacesContext
y ExternalContext
. Puedes encontrar un ejemplo concreto en esta respuesta .
Luego está el patrón de Estado para el caso en el que desea agregar una capa de abstracción adicional para dividir las tareas de recopilar los parámetros de solicitud, convertirlos, validarlos, actualizar los valores del modelo y ejecutar las acciones. En términos de JSF, esto es lo que LifeCycle
está haciendo.
Luego está el patrón compuesto para el caso en el que desea crear una vista basada en componentes que se puede adjuntar con el modelo y cuyo comportamiento depende del estado del ciclo de vida basado en la solicitud. En términos de JSF, esto es lo que UIComponent
representan.
De esta manera, puede evolucionar poco a poco hacia un marco basado en componentes.
Ver también: