SpringControllers_it |
|
Your trail: |
This is version 4.
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 stile 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("messages"));
}
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("messages"));
}
}
|
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
If you want to add a usability enhancement to your form, you can set the cursor to focus on the first field when the page loads. Simply add the following JavaScript at the bottom of your form:
<script type="text/javascript">
document.forms["person"].elements["firstName"].focus();
</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.
[Optional] Create a Canoo WebTest to test browser-like actions
The final (optional) step in this tutorial is to create a Canoo WebTest to test the JSPs.
- I say this step is optional, because you can run the same tests through your browser.
You can use the following URLs to test the different actions for adding, editing and saving a user.
Canoo tests are pretty slick in that they're simply configured in an XML file. To add tests for add, edit, save and delete, open test/web/web-tests.xml and add the following XML. You'll notice that this fragment has a target named PersonTests that runs all the related tests.
- I use CamelCase target names (vs. the traditional lowercase, dash-separated) because when you're typing -Dtestcase=Name, I've found that I'm used to doing CamelCase for my JUnit Tests.
<!-- runs person-related tests -->
<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>
<!-- Verify the edit person screen displays without errors -->
<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>
<!-- Edit a person and then save -->
<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="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>
<!-- Add a new 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>
<!-- Delete existing person -->
<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>
|
After adding this, you should be able to run ant test-canoo -Dtestcase=PersonTests with Tomcat running or ant test-jsp -Dtestcase=PersonTests if you want Ant to start/stop Tomcat for you. To include the PersonTests when all Canoo tests are run, add it as a dependency to the "run-all-tests" target.
You'll notice that there's no logging in the client-side window by Canoo. If you'd like to see what it's doing, you can add the following between </webtest> and </target> at the end of each 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.
|