Eine etwas anständige Webanwendung besteht aus einer Mischung von Designmustern. Ich werde nur die wichtigsten erwähnen.
Das zentrale (architektonische) Entwurfsmuster, das Sie verwenden möchten, ist das Model-View-Controller-Muster . Der Controller soll durch ein Servlet dargestellt werden, das (in) direkt ein bestimmtes Modell und eine bestimmte Ansicht basierend auf der Anforderung erstellt / verwendet . Das Modell soll durch javabäische Klassen dargestellt werden. Dies ist im Geschäftsmodell, das die Aktionen (Verhalten) enthält, und im Datenmodell, das die Daten (Informationen) enthält, häufig weiter teilbar . Die Ansicht soll durch JSP-Dateien dargestellt werden, die über EL (Expression Language) direkten Zugriff auf das ( Daten- ) Modell haben .
Dann gibt es Variationen, die darauf basieren, wie Aktionen und Ereignisse behandelt werden. Die beliebtesten sind:
Anforderungs- (Aktions-) MVC : Dies ist am einfachsten zu implementieren. Das ( Geschäfts- ) Modell arbeitet direkt mit HttpServletRequest
und HttpServletResponse
Objekten. Sie müssen die Anforderungsparameter (meistens) selbst erfassen, konvertieren und validieren. Die Ansicht kann durch einfaches Vanille-HTML / CSS / JS dargestellt werden und behält den Status nicht über Anforderungen hinweg bei. So funktioniert unter anderem Spring MVC , Struts and Stripes .
Komponentenbasierte MVC : Dies ist schwieriger zu implementieren. Am Ende erhalten Sie jedoch ein einfacheres Modell und eine einfachere Ansicht, in der die gesamte "rohe" Servlet-API vollständig entfernt wird. Sie sollten die Anforderungsparameter nicht selbst erfassen, konvertieren und validieren müssen. Der Controller führt diese Aufgabe aus und legt die gesammelten, konvertierten und validierten Anforderungsparameter im Modell fest . Sie müssen lediglich Aktionsmethoden definieren, die direkt mit den Modelleigenschaften funktionieren. Die Ansicht wird durch "Komponenten" in der Variante von JSP-Taglibs oder XML-Elementen dargestellt, die wiederum HTML / CSS / JS generieren. Der Status der Ansichtfür die nachfolgenden Anfragen wird in der Sitzung gepflegt. Dies ist besonders hilfreich für serverseitige Konvertierungs-, Validierungs- und Wertänderungsereignisse. So geht unter anderem JSF , Wicket and Play! funktioniert.
Nebenbei bemerkt, das Hobby mit einem selbstgebauten MVC-Framework ist eine sehr schöne Lernübung, und ich empfehle es, solange Sie es für persönliche / private Zwecke aufbewahren. Wenn Sie jedoch erst einmal professionell sind, wird dringend empfohlen, ein vorhandenes Framework auszuwählen, anstatt Ihr eigenes neu zu erfinden. Das Erlernen eines vorhandenen und gut entwickelten Frameworks dauert langfristig weniger lange als das Entwickeln und Verwalten eines robusten Frameworks.
In der folgenden detaillierten Erklärung beschränke ich mich auf anforderungsbasierte MVC, da dies einfacher zu implementieren ist.
Zunächst sollte der Controller- Teil das Front-Controller-Muster implementieren (eine spezielle Art von Mediator-Muster ). Es sollte nur aus einem einzigen Servlet bestehen, das einen zentralen Einstiegspunkt für alle Anforderungen bietet. Das Modell sollte auf der Grundlage der von der Anforderung verfügbaren Informationen erstellt werden, z. B. der Pfadinfo oder des Servletpfads, der Methode und / oder bestimmter Parameter. Das Geschäftsmodell wird Action
im folgenden HttpServlet
Beispiel aufgerufen .
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);
}
}
Das Ausführen der Aktion sollte eine Kennung zurückgeben, um die Ansicht zu lokalisieren. Am einfachsten wäre es, es als Dateinamen der JSP zu verwenden. Karte Servlet auf einem bestimmten url-pattern
in web.xml
, zum Beispiel /pages/*
, *.do
oder auch nur *.html
.
Im Fall von Präfix-Muster , wie zum Beispiel /pages/*
könnten Sie dann invoke URL ist wie http://example.com/pages/register , http://example.com/pages/login , etc. und bietet /WEB-INF/register.jsp
, /WEB-INF/login.jsp
mit der entsprechenden GET und POST - Aktionen . Die Teile register
, login
sind etc dann zur Verfügung , request.getPathInfo()
wie in obigem Beispiel.
Wenn Sie mit Suffix-Muster wie *.do
, *.html
etc, dann könnte man dann invoke URL wie http://example.com/register.do , http://example.com/login.do , etc , und Sie sollten das ändern Codebeispiele in dieser Antwort (auch die ActionFactory
), um stattdessen die register
und login
Teile zu extrahieren request.getServletPath()
.
Die Action
sollten dem Strategiemuster folgen . Es muss als abstrakter / Schnittstellentyp definiert werden, der die Arbeit basierend auf den übergebenen Argumenten der abstrakten Methode ausführen soll (dies ist der Unterschied zum Befehlsmuster , wobei der abstrakte / Schnittstellentyp die Arbeit basierend auf dem ausführen soll Argumente, die bei der Erstellung der Implementierung übergeben wurden).
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Möglicherweise möchten Sie die Exception
mit einer benutzerdefinierten Ausnahme wie präzisieren ActionException
. Es ist nur ein einfaches Kickoff-Beispiel, der Rest liegt ganz bei Ihnen.
Hier ist ein Beispiel für ein, LoginAction
das (wie der Name schon sagt) den Benutzer anmeldet. Das User
selbst ist wiederum ein Datenmodell . Die Ansicht ist sich der Anwesenheit der bewusst 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.
}
}
}
Das ActionFactory
sollte dem Factory-Methodenmuster folgen . Grundsätzlich sollte eine Erstellungsmethode bereitgestellt werden, die eine konkrete Implementierung eines Abstract- / Interface-Typs zurückgibt. In diesem Fall sollte eine Implementierung der Action
Schnittstelle basierend auf den von der Anforderung bereitgestellten Informationen zurückgegeben werden. Beispiel: Methode und Pfadinfo (die Pfadinfo ist der Teil nach dem Kontext und dem Servlet-Pfad in der Anforderungs-URL, ausgenommen die Abfragezeichenfolge).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
Die actions
wiederum sollte statisch / anwendungsweit sein Map<String, Action>
und alle bekannten Aktionen enthalten. Es liegt an Ihnen, wie Sie diese Karte füllen. Hardcodierung:
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
Oder konfigurierbar basierend auf einer Eigenschaften- / XML-Konfigurationsdatei im Klassenpfad: (Pseudo)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
Oder dynamisch basierend auf einem Scan im Klassenpfad nach Klassen, die eine bestimmte Schnittstelle und / oder Anmerkung implementieren: (Pseudo)
for (ClassFile classFile : classpath) {
if (classFile.isInstanceOf(Action.class)) {
actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
}
}
Denken Sie daran, ein "Nichts tun" Action
für den Fall zu erstellen, dass keine Zuordnung vorliegt. Lassen Sie es zum Beispiel direkt das request.getPathInfo().substring(1)
Dann zurückgeben.
Andere Muster
Das waren bisher die wichtigen Muster.
Um noch einen Schritt weiter zu kommen, können Sie das Facade-Muster verwenden , um eine Context
Klasse zu erstellen , die wiederum die Anforderungs- und Antwortobjekte umschließt und verschiedene praktische Methoden bietet, die an die Anforderungs- und Antwortobjekte delegieren und diese Action#execute()
stattdessen als Argument an die Methode übergeben. Dadurch wird eine zusätzliche abstrakte Ebene hinzugefügt, um die unformatierte Servlet-API auszublenden. Sie sollten dann grundsätzlich in jeder Implementierung keine import javax.servlet.*
Deklarationen haben Action
. In JSF-Begriffen ist dies das, was die Klassen FacesContext
und ExternalContext
tun. In dieser Antwort finden Sie ein konkretes Beispiel .
Dann gibt es das Statusmuster für den Fall, dass Sie eine zusätzliche Abstraktionsschicht hinzufügen möchten, um die Aufgaben des Sammelns der Anforderungsparameter, Konvertieren, Validieren, Aktualisieren der Modellwerte und Ausführen der Aktionen aufzuteilen. In JSF-Begriffen ist dies das, was der LifeCycle
tut.
Dann gibt es das zusammengesetzte Muster für den Fall, dass Sie eine komponentenbasierte Ansicht erstellen möchten, die an das Modell angehängt werden kann und deren Verhalten vom Status des anforderungsbasierten Lebenszyklus abhängt. In JSF-Begriffen ist dies das, was die UIComponent
darstellen.
Auf diese Weise können Sie sich Stück für Stück zu einem komponentenbasierten Framework entwickeln.
Siehe auch: