SpringControllers_it |
|
Your trail: |
This is version 8.
It is not the current version, and thus it cannot be edited.
[Back to current version]
[Restore this version]
Part III: Creazione Controller e JSP - Una guida per la creazione di Controller Spring e JSP in un progetto AppFuse.
- Questo tutorial dipende da Parte II: Creazione nuovi Manager.
Info su questo Tutorial
Questo tutorial mostra come creare Controller Spring e JSP. Dimostra anche come scrivere i test JUnit per i Controller. Il Controller che andremo a creare parlerà con il PersonManager creato nel tutoral Creazione Manager.
- Ti dirò come si fanno le cose nel Mondo Reale in un testo come questo.
Iniziano con il creare un nuovo Controller e JSP nell'architettura di AppFuse. Se non hai ancora installato il modulo Spring MVC, puoi farlo lanciando ant install-springmvc.
Indice
- Crea JSP scheletro usando XDoclet
- Crea il PersonFormControllerTest per il test del PersonFormController
- Crea il PersonFormController
- Esegui PersonFormControllerTest
- Ripulisci la JSP per renderla presentabile
- Crea i WebTest Canoo per il test delle azioni con simulazione del browser
Crea JSP scheletro usando XDoclet
In questo passo verrà generata un pagina JSP per visualizzare le informazioni contenute nell'oggetto Person. Conterrà il tag JSP di Spring per stampare righe di tabella per ogni proprietà in Person.java. Lo strumento AppGen utilizzato per farlo è basato su uno strumento StrutsGen - che è stato scritto in origine da Erik Hatcher. Si tratta banalmente di giusto un paio di classi ed un po' di template XDoclet. Tutti questi file si trovano in extras/appgen.
Ecco i semplici passi per generare la JSP ed un file di properties contenente le label per gli elementi nel form:
- Da linea di comando, naviga in "extras/appgen"
- Esegui ant -Dobject.name=Person -Dappgen.type=pojo -Dapp.module="" per generare un po' di file in extras/appgen/build/gen. Di fatto, saranno generati tutti i file di cui hai bisogno per completare questo tutorial. In ogni caso prendi solo quelli necessari.
- web/WEB-INF/classes/Person.properties (label per gli elementi del tuo form)
- web/pages/personForm.jsp (file JSP per la visualizzazione di una singola Person)
- web/pages/personList.jsp (file JSP per la visualizzazione di un elenco di Person)
- Copia il contenuto di Person.properties in web/WEB-INF/classes/ApplicationResources.properties. Queste sono tutte le chiavi necessarie per titoli/intestazioni e proprietà del form. Ecco un esempio di cosa dovresti aggiungere ad ApplicationResources.properties:
# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.
# -- person list page --
personList.title=Person List
personList.heading=Persons
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
- Copia personForm.jsp in web/pages/personForm.jsp. Copia personList.jsp in web/pages/personList.jsp.
- I files nella directory "pages" finiranno in "WEB-INF/pages" a deployment time. Il container fornisce protezione per tutti i file sotto WEB-INF. Ciò si applica a tutte le richieste da client, ma non ai forward da DispatchServlet. Mettere tutte le JSP sotto WEB-INF assicura che vengano accedute solo tramite Controller, e non direttamente dal client o l'una con l'altra. Ciò permette di spostare la security in alto sul Controller, dove può essere gestita in modo più efficiente, e fuori dallo strato di presentazione di base.
La security della web application per AppFuse specifica che tutti gli url-pattern *.html debbano essere protetti (eccetto /signup.html e /passwordHint.html). Ciò garantisce che i client passino da una Action per arrivare ad una JSP (o almeno a quelle sotto pages).
NOTA: Se vuoi personalizzare i CSS per una data pagina, puoi aggiungere <body id="pageName"/> in cima al file. Questo verrà assimilato da SiteMesh e messo nella pagina finale. Puoi poi personalizzare il tuo CSS pagina per pagina usando qualcosa del tipo:
body#pageName element.class { background-color: blue }
- Aggiungi chiavi in ApplicationResources.properties per titoli ed intestazioni nelle JSP
Nelle JSP generate ci sono due chiavi per il titolo (sulla finestra del browser in alto) e l'header (intestazione nella pagina). Ora dobbiamo aggiungere queste due chiavi (personDetail.title and personDetail.heading) ad ApplicationResources.properties.
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
- Just above, we added "personForm.*" keys to this file, so why do I use personForm and personDetail? The best reason is because it gives a nice separation between form labels and text on the page. Another reason is because all the *Form.* give you a nice representation of all the fields in your database.
I recently had a client who wanted all fields in the database searchable. This was fairly easy to do. I just looked up all the keys in ApplicationResources.properties which contained "Form." and then put them into a drop-down. On the UI, the user was able to enter a search term and select the column they wanted to search. I was glad I followed this Form vs. Detail distinction on that project!
Per creare un test JUnit per il PersonFormController, inizia a creare un file PersonFormControllerTest.java nella directory test/web/**/action.
package org.appfuse.webapp.action;
import org.appfuse.model.Person;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
public class PersonFormControllerTest extends BaseControllerTestCase {
private PersonFormController c;
private MockHttpServletRequest request;
private ModelAndView mv;
protected void setUp() throws Exception {
// needed to initialize a user
super.setUp();
c = (PersonFormController) ctx.getBean("personFormController");
}
protected void tearDown() {
c = null;
}
public void testEdit() throws Exception {
log.debug("testing edit...");
request = newGet("/editPerson.html");
request.addParameter("username", "tomcat");
mv = c.handleRequest(request, new MockHttpServletResponse());
assertEquals("personForm", mv.getViewName());
}
public void testSave() throws Exception {
request = newGet("/editPerson.html");
request.addParameter("id", "1");
mv = c.handleRequest(request, new MockHttpServletResponse());
Person person = (Person) mv.getModel().get(c.getCommandName());
assertNotNull(person);
request = newPost("/editPerson.html");
super.objectToRequestParameters(person, request);
request.addParameter("lastName", "Updated Last Name");
mv = c.handleRequest(request, new MockHttpServletResponse());
Errors errors =
(Errors) mv.getModel().get(BindException.ERROR_KEY_PREFIX + "person");
assertNull(errors);
assertNotNull(request.getSession().getAttribute("successMessages"));
}
public void testRemove() throws Exception {
request = newPost("/editPerson.html");
request.addParameter("delete", "");
request.addParameter("id", "2");
mv = c.handleRequest(request, new MockHttpServletResponse());
assertNotNull(request.getSession().getAttribute("successMessages"));
}
}
|
A questo punto non compila nulla (con ant compile) perché devi creare il PersonFormController cui questo test si riferisce.
In src/web/**/action, crea un file PersonFormController.java file con il seguente contenuto:
package org.appfuse.webapp.action;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.appfuse.model.Person;
import org.appfuse.service.PersonManager;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
public class PersonFormController extends BaseFormController {
private PersonManager mgr = null;
public void setPersonManager(PersonManager mgr) {
this.mgr = mgr;
}
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
String id = request.getParameter("id");
Person person = null;
if (!StringUtils.isEmpty(id)) {
person = mgr.getPerson(id);
} else {
person = new Person();
}
return person;
}
public ModelAndView processFormSubmission(HttpServletRequest request,
HttpServletResponse response,
Object command,
BindException errors)
throws Exception {
if (request.getParameter("cancel") != null) {
return new ModelAndView(new RedirectView(getSuccessView()));
}
return super.processFormSubmission(request, response, command, errors);
}
public ModelAndView onSubmit(HttpServletRequest request,
HttpServletResponse response, Object command,
BindException errors)
throws Exception {
if (log.isDebugEnabled()) {
log.debug("entering 'onSubmit' method...");
}
Person person = (Person) command;
boolean isNew = (person.getId() == null);
String success = getSuccessView();
Locale locale = request.getLocale();
if (request.getParameter("delete") != null) {
mgr.removePerson(person.getId().toString());
saveMessage(request, getText("person.deleted", locale));
} else {
mgr.savePerson(person);
String key = (isNew) ? "person.added" : "person.updated";
saveMessage(request, getText(key, locale));
if (!isNew) {
success = "editPerson.html?id=" + person.getId();
}
}
return new ModelAndView(new RedirectView(success));
}
}
|
Nella classe sopra ci sono alcuni metodi con i quali potresti non essere familiare. Il metodo formBackingObject() viene usato per fronire l'oggetto sul quale opera questo Controller. Il metodo processFormSubmission() viene usato per rilevare il bottone cancel, ed onSubmit() viene chiamate sulle richieste POST e gestisce il delete/add/update di un utente.
Ci sono alcune chiavi che dovresti (eventualmente) aggiungere ad ApplicationResources.properties per visualizzare i messaggi di successo. Questo file si trova in web/WEB-INF/classes - aprilo ed aggiungi quanto segue:
- Di solito li aggiungo sotto il commento # -- success messages --.
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.
Potresti usare anche dei messaggi generici di added, deleted ed updated, qualsiasi cosa funzioni per te. Va bene avere messaggi separati nel caso sia necessario modificarli per entità.
Come puoi notare il codice per chiamare il PersonManager è lo stesso utilizzato nel PersonManagerTest. Sia PersonFormController che PersonManagerTest sono client di PersonManagerImpl, pertanto ciò ha perfettamente senso.
Ora devi aggiungere una url-mapping per questo controller nel file web/WEB-INF/action-servlet.xml. Nel blocco sotto la nuova riga è alla fine, con <prop key="/editPerson.html">:
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/editProfile.html">userFormController</prop>
<prop key="/mainMenu.html">filenameController</prop>
<prop key="/editUser.html">userFormController</prop>
<prop key="/selectFile.html">filenameController</prop>
<prop key="/uploadFile.html">fileUploadController</prop>
<prop key="/passwordHint.xml">passwordHintController</prop>
<prop key="/signup.xml">signupController</prop>
<prop key="/editPerson.html">personFormController</prop>
</props>
</property>
</bean>
|
Devi anche aggiungere la definizione <bean> per il personFormController nello stesso file:
<bean id="personFormController" class="org.appfuse.webapp.action.PersonFormController">
<property name="commandName"><value>person</value></property>
<property name="commandClass"><value>org.appfuse.model.Person</value></property>
<!--property name="validator"><ref bean="beanValidator"/></property-->
<property name="formView"><value>personForm</value></property>
<property name="successView"><value>mainMenu.html</value></property>
<property name="personManager"><ref bean="personManager"/></property>
</bean>
|
- La proprietà "validator" è commentate nel blocco XML sopra in quanto non abbiamo ancora definito alcuna regola di validazione per l'oggetto Person. Decommenteremo questo valora quando aggiungiamo la validazione.
Se osservi il PersonFormControllerTest, tutti i test dipendono dall'avere un record con id=1 nel database (e testRemove depende da uno con id=2), pertanto aggiungiamo tali record al nostro file di dati di esempio (metadata/sql/sample-data.xml). Lo aggiungerei giusto alla fine - l'ordine non è importante in quanto (al momento) non ci sono relazioni con altre tabelle.
<table name='person'>
<column>id</column>
<column>first_name</column>
<column>last_name</column>
<row>
<value>1</value>
<value>Matt</value>
<value>Raible</value>
</row>
<row>
<value>2</value>
<value>James</value>
<value>Davidson</value>
</row>
</table>
DBUnit carica questo file prima di eseguire un qualsiasi test, pertanto questo record sarà disponibile per il test del tuo Controller.
Controlla di essere nella directory base del progetto. Se esegui ant test-web -Dtestcase=PersonFormController - tutto dovrebbe funzionare come previsto.
BUILD SUCCESSFUL
Total time: 21 seconds
Ripulisci la JSP per renderla presentabile
Se vuoi aggiungere un miglioramento in usabilità del tuo form, puoi impostare il cursore in modo da avere il focus sul primo campo al caricamento della pagina. Basta che aggiungi il JavaScript seguente in fondo al tuo form:
<script type="text/javascript">
Form.focusFirstElement($('personForm'));
</script>
Ora se esegui ant db-load deploy, avvii Tomcat e punti il browser su http://localhost:8080/appfuse/editPerson.html?id=1, dovresti vedere qualcosa del genere:
Infine, per rendere questa pagina più user friendly, puoi aggiungere un messaggio per i tuoi utenti ad inizion form, ciò può essere fatto facilmente aggiungendo del testo (usando <fmt:message>) all'inizio della pagina personForm.jsp.
[Opzionale] Crea i WebTest Canoo per il test delle azioni con simulazione del browser
Il passo finale (opzionale) in questo tutorial è la creazione di un WebTest Canoo per il test delle JSP.
- Dico che questo passo è opzionale, in quanto puoi eseguire gli stessi test navigando direttamente dal tuo browser.
Puoi usare le seguenti URL per il test delle varie azioni di aggiunta, modifica e registrazione utente.
I test Canoo sono molto comodi perché si configurano semplicemente in un file XML. Per aggiungere test per le operazioni di aggiungi, modifica, registra ed elimina, apri test/web/web-tests.xml ed aggiungi il seguente XML. Noterai che questo frammento ha un target denominato PersonTests che esegue tutti i test correlati.
- Uso nomi di target in CamelCase (al posto del tradizionale lowercase, separato da trattini) perché quanto digiti -Dtestcase=Nome, ho scoperto che sono abituato ad usare il CamelCase come per i miei test JUnit.
<!-- esegui i test relativi a person -->
<target name="PersonTests"
depends="EditPerson,SavePerson,AddPerson,DeletePerson"
description="Call and executes all person test cases (targets)">
<echo>Successfully ran all Person JSP tests!</echo>
</target>
<!-- Verifica che la schermata di modifica persona sia visualizzata senza errori -->
<target name="EditPerson"
description="Tests editing an existing Person's information">
<webtest name="editPerson">
&config;
<steps>
&login;
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
</steps>
</webtest>
</target>
<!-- Modifica una persona e poi registra -->
<target name="SavePerson"
description="Tests editing and saving a user">
<webtest name="savePerson">
&config;
<steps>
&login;
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
<setinputfield description="set lastName" name="lastName" value="Canoo"/>
<clickbutton label="${button.save}" description="Click Save"/>
<verifytitle description="Page re-appears if save successful"
text=".*${personDetail.title}.*" regex="true"/>
<verifytext description="verify success message" text="${person.updated}"/>
</steps>
</webtest>
</target>
<!-- Aggiungi un nuovo oggetto Person -->
<target name="AddPerson"
description="Adds a new Person">
<webtest name="addPerson">
&config;
<steps>
&login;
<invoke description="click Add Button" url="/editPerson.html"/>
<verifytitle description="we should see the personDetail title"
text=".*${personDetail.title}.*" regex="true"/>
<setinputfield description="set firstName" name="firstName" value="Abbie"/>
<setinputfield description="set lastName" name="lastName" value="Raible"/>
<clickbutton label="${button.save}" description="Click button 'Save'"/>
<verifytitle description="Main Menu appears if save successful"
text=".*${mainMenu.title}.*" regex="true"/>
<verifytext description="verify success message" text="${person.added}"/>
</steps>
</webtest>
</target>
<!-- Elimina persona esistente -->
<target name="DeletePerson"
description="Deletes existing Person">
<webtest name="deletePerson">
&config;
<steps>
&login;
<invoke description="click Edit Person link" url="/editPerson.html?id=1"/>
<prepareDialogResponse description="Confirm delete" dialogType="confirm" response="true"/>
<clickbutton label="${button.delete}" description="Click button 'Delete'"/>
<verifyNoDialogResponses/>
<verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/>
<verifytext description="verify success message" text="${person.deleted}"/>
</steps>
</webtest>
</target>
|
Dopo aver aggiunto quanto sopra, dovresti poter eseguire ant test-canoo -Dtestcase=PersonTests con Tomcat attivo o ant test-jsp -Dtestcase=PersonTests se vuoi che Ant avvii e fermi Tomcat per te. Per includere PersonTests quando vengono eseguiti tutti i test Canoo, aggiungilo come dipendenza al target "run-all-tests".
Noterai che non c'è alcun log nella finestra lato client generato da Canoo. Se vuoi vedere cosa sta facendo, puoi aggiungere le righe seguenti fra </webtest> e </target> alla fine di ogni target.
<loadfile property="web-tests.result"
srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>
BUILD SUCCESSFUL
Total time: 10 seconds
Prossimo: Parte IV: Aggiungere Validazione e Schermata Elenco - Aggiungere logica di convalida al personForm in modo che firstName e lastName siano campi obbligatori ed aggiungere una schermata elenco per visualizzare tutti i record nel database.
|