Un'applicazione web un po 'decente consiste in un mix di modelli di design. Citerò solo quelli più importanti.
Il modello di progettazione (architettonico) di base che desideri utilizzare è il modello Model-View-Controller . Il controller deve essere rappresentato da un servlet che (in) crea / utilizza direttamente un modello e una vista specifici in base alla richiesta. Il modello deve essere rappresentato da classi giavabee. Ciò è spesso più divisibile nel modello di business che contiene le azioni (comportamento) e nel modello di dati che contiene i dati (informazioni). La vista è di essere rappresentato da file JSP che hanno accesso diretto al ( dati ) del modello da EL (Expression Language).
Quindi, ci sono variazioni basate su come vengono gestite le azioni e gli eventi. I più popolari sono:
MVC basato su richiesta (azione) : questo è il più semplice da implementare. L'( Affari ) Modello lavora direttamente con HttpServletRequest
e HttpServletResponse
oggetti. Devi raccogliere, convertire e convalidare (principalmente) i parametri della richiesta. La vista può essere rappresentata da HTML / CSS / JS semplicemente vaniglia e non mantiene lo stato tra le richieste. Ecco come funziona Spring MVC , Struts and Stripes .
MVC basato su componenti : è più difficile da implementare. Ma si finisce con un modello più semplice e si vede in cui tutta l'API Servlet "grezza" viene sottratta completamente. Non dovresti avere la necessità di raccogliere, convertire e validare tu stesso i parametri della richiesta. Il controller esegue questa attività e imposta i parametri di richiesta raccolti, convertiti e convalidati nel modello . Tutto quello che devi fare è definire i metodi di azione che funzionano direttamente con le proprietà del modello. La vista è rappresentata da "componenti" nel sapore di taglibs JSP o elementi XML che a loro volta generano HTML / CSS / JS. Lo stato della vista per le richieste successive viene mantenuta nella sessione. Ciò è particolarmente utile per eventi di conversione, convalida e modifica del valore sul lato server. Ecco come, tra gli altri , JSF , Wicket e Play! lavori.
Come nota a margine, andare in giro con un framework MVC nostrano è un esercizio di apprendimento molto bello, e lo raccomando finché lo tieni per scopi personali / privati. Ma una volta diventato professionale, si consiglia vivamente di scegliere un framework esistente piuttosto che reinventare il proprio. L'apprendimento di un framework esistente e ben sviluppato richiede a lungo termine meno tempo rispetto allo sviluppo e al mantenimento di un framework robusto da soli.
Nella spiegazione dettagliata di seguito mi limiterò a richiedere MVC basato su richiesta poiché è più facile da implementare.
Innanzitutto, la parte Controller dovrebbe implementare il modello Front Controller (che è un tipo specializzato di modello Mediatore ). Dovrebbe consistere in un solo servlet che fornisce un punto di accesso centralizzato per tutte le richieste. Dovrebbe creare il modello in base alle informazioni disponibili dalla richiesta, come pathinfo o servletpath, il metodo e / o parametri specifici. Il modello aziendale viene chiamato Action
nell'esempio seguente HttpServlet
.
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);
}
}
L'esecuzione dell'azione dovrebbe restituire un identificatore per individuare la vista. Il più semplice sarebbe usarlo come nome file del JSP. Visualizza la servlet su una specifica url-pattern
in web.xml
, per esempio /pages/*
, *.do
o anche solo *.html
.
In caso di schemi di prefissi, ad esempio, /pages/*
è possibile richiamare URL come http://example.com/pages/register , http://example.com/pages/login , ecc. E fornire /WEB-INF/register.jsp
, /WEB-INF/login.jsp
con le azioni GET e POST appropriate . Le parti register
, login
ecc. Sono quindi disponibili request.getPathInfo()
come nell'esempio sopra.
Quando si utilizzano schemi di suffissi come *.do
, *.html
ecc., È quindi possibile richiamare URL come http://esempio.com/register.do , http://esempio.com/login.do , ecc. E si dovrebbe modificare il esempi di codice in questa risposta (anche il ActionFactory
) per estrarre invece le parti register
e .login
request.getServletPath()
L' Action
dovrebbe seguire il modello di strategia . Deve essere definito come un tipo astratto / di interfaccia che dovrebbe fare il lavoro in base agli argomenti passati del metodo astratto (questa è la differenza con il modello di comando , in cui il tipo astratto / di interfaccia dovrebbe fare il lavoro in base al argomenti che sono stati passati durante la creazione dell'implementazione).
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
Potresti voler rendere il Exception
più specifico con un'eccezione personalizzata come ActionException
. È solo un esempio di kickoff di base, il resto dipende da te.
Ecco un esempio di un LoginAction
che (come dice il nome) accede all'utente. Lo User
stesso è a sua volta un modello di dati . The View è a conoscenza della presenza di 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.
}
}
}
L' ActionFactory
dovrebbe seguire il metodo di pattern Factory . Fondamentalmente, dovrebbe fornire un metodo creativo che restituisce un'implementazione concreta di un tipo astratto / interfaccia. In questo caso, dovrebbe restituire un'implementazione Action
dell'interfaccia basata sulle informazioni fornite dalla richiesta. Ad esempio, il metodo e pathinfo (pathinfo è la parte dopo il contesto e il percorso servlet nell'URL della richiesta, esclusa la stringa di query).
public static Action getAction(HttpServletRequest request) {
return actions.get(request.getMethod() + request.getPathInfo());
}
A actions
sua volta dovrebbe essere un po 'statico / a livello di applicazione Map<String, Action>
che contiene tutte le azioni conosciute. Sta a te come riempire questa mappa. brutalmente:
actions.put("POST/register", new RegisterAction());
actions.put("POST/login", new LoginAction());
actions.put("GET/logout", new LogoutAction());
// ...
O configurabile in base a un file di configurazione proprietà / XML nel percorso di classe: (pseudo)
for (Entry entry : configuration) {
actions.put(entry.getKey(), Class.forName(entry.getValue()).newInstance());
}
O basato dinamicamente su una scansione nel percorso di classe per le classi che implementano una certa interfaccia e / o annotazione: (pseudo)
for (ClassFile classFile : classpath) {
if (classFile.isInstanceOf(Action.class)) {
actions.put(classFile.getAnnotation("mapping"), classFile.newInstance());
}
}
Ricorda di creare un "non fare nulla" Action
per il caso in cui non ci sia mappatura. Lascia che ad esempio restituisca direttamente request.getPathInfo().substring(1)
allora.
Altri schemi
Questi erano gli schemi importanti finora.
Per fare un passo ulteriore, è possibile utilizzare il modello Facade per creare una Context
classe che a sua volta avvolge gli oggetti richiesta e risposta e offre diversi metodi di convenienza delegando gli oggetti richiesta e risposta e passandoli invece come argomento nel Action#execute()
metodo. Ciò aggiunge un ulteriore livello astratto per nascondere l'API Servlet non elaborata. Si dovrebbe quindi sostanzialmente finire con zero import javax.servlet.*
dichiarazioni in ogni Action
implementazione. In termini di JSF, questo è ciò FacesContext
che ExternalContext
stanno facendo le classi e . Puoi trovare un esempio concreto in questa risposta .
Quindi c'è il modello di stato nel caso in cui desideri aggiungere un ulteriore livello di astrazione per dividere le attività di raccolta dei parametri di richiesta, conversione, convalida, aggiornamento dei valori del modello ed esecuzione delle azioni. In termini di JSF, questo è ciò che LifeCycle
sta facendo.
Quindi c'è il modello composito per il caso in cui si desidera creare una vista basata su componenti che può essere collegata al modello e il cui comportamento dipende dallo stato del ciclo di vita basato sulla richiesta. In termini di JSF, questo è ciò che UIComponent
rappresentano.
In questo modo è possibile evolvere a poco a poco verso un framework basato su componenti.
Guarda anche: