Eine halbwegs anständige Webanwendung besteht aus einer Mischung von Entwurfsmustern. Ich werde nur die wichtigsten davon erwähnen.
Das zentrale (architektonische) Entwurfsmuster, das Sie verwenden möchten, ist das Model-View-Controller-Muster . En Controller soll durch ein Servlet repräsentiert werden, das (un)direkt eine bestimmte Datei erzeugt/verwendet. Modell y Siehe auf der Grundlage der Anfrage. Die Website Modell durch Javabean-Klassen dargestellt werden soll. Dies ist oft weiter unterteilbar in Geschäftsmodell der die Aktionen (Verhalten) enthält und Datenmodell der die Daten (Informationen) enthält. Die Siehe soll durch JSP-Dateien dargestellt werden, die direkten Zugriff auf die ( Daten ) Modell durch EL (Expression Language).
Außerdem gibt es Variationen, die darauf basieren, wie Aktionen und Ereignisse gehandhabt werden. Die beliebtesten sind:
-
Anfrage (Aktion) basierte MVC : Dies ist am einfachsten zu realisieren. Die ( Business ) Modell arbeitet direkt mit HttpServletRequest
y HttpServletResponse
Objekte. Sie müssen die Anforderungsparameter (meistens) selbst erfassen, konvertieren und validieren. Die Siehe kann durch einfaches HTML/CSS/JS dargestellt werden und behält den Zustand über Anfragen hinweg nicht bei. Dies ist unter anderem der Grund dafür Spring MVC , Streben y Streifen funktioniert.
-
Komponentenbasiertes MVC : Dies ist schwieriger zu realisieren. Aber Sie erhalten ein einfacheres Modell und eine einfachere Ansicht, in der die "rohe" Servlet-API vollständig abstrahiert ist. Sie sollten nicht die Notwendigkeit haben, die Anfrageparameter selbst zu sammeln, zu konvertieren und zu validieren. Die Controller übernimmt diese Aufgabe und setzt die gesammelten, konvertierten und validierten Anfrageparameter in die Modell . Alles, was Sie tun müssen, ist, Aktionsmethoden zu definieren, die direkt mit den Modelleigenschaften arbeiten. Die Siehe wird durch "Komponenten" in Form von JSP-Taglibs oder XML-Elementen dargestellt, die ihrerseits HTML/CSS/JS erzeugen. Der Zustand der Siehe für die nachfolgenden Anfragen wird in der Sitzung beibehalten. Dies ist besonders hilfreich bei serverseitigen Konvertierungs-, Validierungs- und Wertänderungsereignissen. Auf diese Weise wird unter anderem JSF , Wicket y Spielen! funktioniert.
Nebenbei bemerkt ist das Herumhantieren mit einem selbstentwickelten MVC-Framework eine sehr schöne Lernübung, und ich empfehle es, solange Sie es für persönliche/private Zwecke nutzen. Sobald Sie aber professionell arbeiten, sollten Sie lieber ein bestehendes Framework verwenden, als Ihr eigenes neu zu erfinden. Das Erlernen eines bestehenden und gut entwickelten Frameworks nimmt langfristig weniger Zeit in Anspruch als die Entwicklung und Pflege eines eigenen robusten Frameworks.
In der folgenden ausführlichen Erklärung beschränke ich mich auf anforderungsbasiertes MVC, da dies einfacher zu implementieren ist.
Erstens, die Controller Teil sollte die Front Controller Muster (das ist eine spezielle Art von Vermittler-Muster ). Es sollte nur aus einem einzigen Servlet bestehen, das als zentraler Einstiegspunkt für alle Anfragen dient. Es sollte die Modell auf der Grundlage von Informationen, die durch die Anfrage verfügbar sind, wie z. B. Pathinfo oder Servletpath, die Methode und/oder spezifische Parameter. Die Geschäftsmodell heißt Action
im Folgenden HttpServlet
Beispiel.
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);
}
}
Die Ausführung der Aktion sollte einen Bezeichner zum Auffinden der Ansicht zurückgeben. Am einfachsten wäre es, ihn als Dateinamen der JSP zu verwenden. Ordnen Sie dieses Servlet auf eine bestimmte url-pattern
en web.xml
z.B. /pages/*
, *.do
oder auch nur *.html
.
Im Falle von Präfix-Mustern wie zum Beispiel /pages/*
können Sie dann URLs aufrufen wie http://example.com/pages/register , http://example.com/pages/login usw. und bieten /WEB-INF/register.jsp
, /WEB-INF/login.jsp
mit den entsprechenden GET- und POST-Aktionen. Die Teile register
, login
usw. sind dann verfügbar über request.getPathInfo()
wie im obigen Beispiel.
Bei der Verwendung von Suffix-Mustern wie *.do
, *.html
usw., dann können Sie URLs wie http://example.com/register.do , http://example.com/login.do usw., und Sie sollten die Codebeispiele in dieser Antwort ändern (auch die ActionFactory
) zum Extrahieren der register
y login
Teile von request.getServletPath()
stattdessen.
En Action
sollte der Strategie-Muster . Er muss als abstrakter Typ/Schnittstellentyp definiert werden, der die Arbeit auf der Grundlage der Eingereicht Argumente der abstrakten Methode (dies ist der Unterschied zur Befehlsmuster wobei der Abstrakt-/Schnittstellentyp die Arbeit auf der Grundlage der Argumente erledigen sollte, die während der Erstellung der Umsetzung).
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Vielleicht möchten Sie die Exception
spezifischer mit einer benutzerdefinierten Ausnahme wie ActionException
. Das ist nur ein einfaches Beispiel für einen Anstoß, der Rest liegt ganz bei Ihnen.
Hier ist ein Beispiel für eine LoginAction
die (wie der Name schon sagt) den Benutzer anmeldet. Die User
selbst ist wiederum ein Datenmodell . En Siehe ist sich der Anwesenheit des 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.
}
}
}
En ActionFactory
sollte der Muster der Fabrikmethode . Grundsätzlich sollte sie eine Erstellungsmethode bereitstellen, die eine konkrete Implementierung eines abstrakten Typs/einer Schnittstelle zurückgibt. In diesem Fall sollte sie eine Implementierung des Typs Action
Schnittstelle auf der Grundlage der von der Anfrage gelieferten Informationen. Zum Beispiel kann die Methode y Pfadinfo (die Pfadangabe ist der Teil nach dem Kontext und dem Servlet-Pfad in der Anfrage-URL, mit Ausnahme des Query-Strings).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
En actions
wiederum sollten einige statische/anwendungsweite Map<String, Action>
die alle bekannten Aktionen enthält. Es liegt an dir, wie du diese Karte füllst. Hardcoding:
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
Oder konfigurierbar auf der Grundlage einer properties/XML-Konfigurationsdatei im Klassenpfad: (pseudo)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
Oder dynamisch auf der Grundlage einer Suche 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 "Nichtstun" zu schaffen Action
für den Fall, dass es kein Mapping gibt. Lassen Sie es zum Beispiel direkt die request.getPathInfo().substring(1)
dann.
Andere Muster
Das waren bisher die wichtigsten Muster.
Um einen Schritt weiter zu gehen, könnten Sie die Muster der Fassade zur Erstellung einer Context
Klasse, die wiederum die Anfrage- und Antwortobjekte umhüllt und mehrere bequeme Methoden anbietet, die an die Anfrage- und Antwortobjekte delegiert werden und diese als Argument an die Action#execute()
Methode. Dadurch wird eine zusätzliche abstrakte Schicht hinzugefügt, um die rohe Servlet-API zu verbergen. Sie sollten dann grundsätzlich mit Null import javax.servlet.*
Deklarationen in jedem Action
Umsetzung. In Bezug auf JSF ist dies das, was die FacesContext
y ExternalContext
Klassen tun. Ein konkretes Beispiel finden Sie in diese Antwort .
Dann gibt es noch die Staatliches Muster für den Fall, dass Sie eine zusätzliche Abstraktionsschicht hinzufügen möchten, um die Aufgaben des Sammelns der Anforderungsparameter, ihrer Konvertierung, ihrer Validierung, der Aktualisierung der Modellwerte und der Ausführung der Aktionen zu teilen. In JSF-Begriffen ist dies das, was die LifeCycle
tut.
Dann gibt es noch die Zusammengesetztes Muster für den Fall, dass Sie eine komponentenbasierte Ansicht erstellen möchten, die mit dem Modell verbunden werden kann und deren Verhalten vom Zustand des anforderungsbasierten Lebenszyklus abhängt. In JSF-Begriffen ist dies das, was die UIComponent
vertreten.
Auf diese Weise können Sie sich Stück für Stück zu einem komponentenbasierten Rahmen entwickeln.
Siehe auch: