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 .HttpServletRequestHttpServletResponse
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 Actionen el siguiente HttpServletejemplo.
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-patternen web.xml, por ejemplo /pages/*, *.doo 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.jspcon las acciones GET y POST apropiadas . Las partes register, loginetc. están disponibles request.getPathInfo()como en el ejemplo anterior.
Cuando utiliza patrones de sufijos como *.do, *.htmletc., 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 registery loginen su request.getServletPath()lugar.
El Actiondebe 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 Exceptionmá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 LoginActionque (como su nombre lo indica) inicia sesión en el usuario. El Usermismo 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 ActionFactorydebe 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 Actioninterfaz 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 actionssu 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" Actionpara 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 Contextclase 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 Actionimplementación. En términos de JSF, esto es lo que están haciendo las clases FacesContexty 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 LifeCycleestá 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 UIComponentrepresentan.
De esta manera, puede evolucionar poco a poco hacia un marco basado en componentes.
Ver también: