At line 1 changed 1 line. |
__Part III:__ [创建Actions和JSPs|CreateActions] - 在[AppFuse]架构下创建[Struts|http://jakarta.apache.org/struts] 的Actions和JSPs。 |
__Part III:__ [创建 Struts Actions和JSPs|CreateActions] - 在[AppFuse]架构下创建[Struts|http://jakarta.apache.org/struts] 的Actions和JSPs。 |
At line 3 changed 1 line. |
;:''本教程依赖于__Part II:__ [创建管理器Manager|CreateManager_zh].'' |
;:''本教程依赖于__Part II:__ [创建新的Manager|CreateManager_zh]。'' |
At line 6 changed 1 line. |
本教程将向你展示如何创建一个Struts Action,一个JUnit Test(使用 [StrutsTestCase|http://strutstestcase.sourceforge.net/]),和一个form的JSP,我们创建的这个Action将与教程[创建管理器Manager|CreateManager_zh]创建的PersonManager交互。 |
本教程将向你展示如何创建一个Struts Action,一个JUnit Test(使用 [StrutsTestCase|http://strutstestcase.sourceforge.net/]),和一个form的JSP,我们创建的这个Action将与教程[创建Managers|CreateManager_zh]创建的PersonManager交互。 |
At line 30 changed 1 line. |
我们首先在web层为Struts创建PersonForm对象,为此,我们需要对Person.java添加标签来产生我们的Struts ActionForm, 在Person.java的JavaDoc, 添加@struts.form标签(如果你需要一个实例,可以参考User.java): |
我们首先在web层为Struts创建PersonForm对象,为此,我们需要对Person.java添加标签来产生我们的Struts ActionForm,在Person.java的JavaDoc添加@struts.form标签(如果你需要一个实例,可以参考User.java): |
At line 39 changed 1 line. |
;:''如果你没有把"org.appfuse"包换成你的"com.company"没有把你的模型类放到默认的包,你必须确认在@struts.form中的标签对org.appfuse.webapp.form.BaseForm使用完全的引用。'' |
;:''如果你没有把"org.appfuse"包换成你的"com.company"或者没有把你的模型类放到默认的包,你必须确认在@struts.form中的标签对org.appfuse.webapp.form.BaseForm使用完全的引用。'' |
At line 42 changed 1 line. |
在这一步,你将会创建来显示Person对象信息的JSP页面,它会包括Strut的JSP标签用来表现Person.java每一个属性,[AppGen]工具建立在一个StrutsGen工具上,用来做这件事,这个StrutsGen工具起初由[Erik Hatcher|http://www.blogscene.org/erik/]编写,它只是一些XDoclet模版外加一些附加类,所有的这些文件在extras/appgen目录。 |
在这一步,你将会创建来显示Person对象信息的JSP页面,它会包括Strut的JSP标签用来表现Person.java每一个属性,[AppGen]工具建立在一个StrutsGen工具上,用来做这件事,这个StrutsGen工具起初由[Erik Hatcher|http://www.blogscene.org/erik/]编写,它只是一些XDoclet模版和一些附加类,所有的这些文件在extras/appgen目录里。 |
At line 44 changed 1 line. |
以下是用来生成JSP和包含标签和表单元素俄属性文件的简单步骤 |
以下是生成JSP和包含标签及表单元素属性文件的简单步骤 |
At line 46 removed 2 lines. |
|
|
At line 49 changed 1 line. |
* 执行__ant -Dobject.name=Person -Dappgen.type=pojo__ 会在extras/appgen/build/gen产生一些文件,事实上,它会产生本教程所需的所有文件,但是我们只获取我们所需要的那些。 |
* 执行__ant -Dobject.name=Person -Dappgen.type=pojo__会在extras/appgen/build/gen产生一些文件,事实上,它会产生本教程所需的所有文件,但是我们只获取我们所需要的那些。 |
At line 51 changed 3 lines. |
** web/personForm.jsp (察看单个文件的JSP文件) |
** web/personList.jsp (察看People列表的JSP文件) |
* 把Person.properties的内容拷贝到web/WEB-INF/classes/ApplicationResources_en.properties,这是所有form中需要的标题关键字 These are all the keys you will need for titles/headings and form properties. Here is an example of what y,以下是你将要添加到ApplicationResources_en.properties的内容的例子: |
** web/pages/personForm.jsp (察看单个文件的JSP文件) |
** web/pages/personList.jsp (察看People列表的JSP文件) |
* 把Person.properties的内容拷贝到web/WEB-INF/classes/ApplicationResources.properties,这是所有form中需要的标题关键字,以下是你将要添加到ApplicationResources.properties的内容的例子: |
At line 62 changed 3 lines. |
person.added=Information for <strong>{0}</strong> has been added successfully. |
person.updated=Information for <strong>{0}</strong> has been updated successfully. |
person.deleted=Information for <strong>{0}</strong> has been deleted successfully. |
person.added=Person has been added successfully. |
person.updated=Person has been updated successfully. |
person.deleted=Person has been deleted successfully. |
At line 78 changed 1 line. |
;: ''"pages"目录中的文件在部署环境下会放到"WEB-INF/pages"目录下,容器会对所有WEB-INF目录下的文件提供安全保护,这中保护针对客户端的请求,而通过Struts' ActionServlet的转发访问,把这些JSPs文件放到WEB-INF目下确保它们只会被Actions访问,而不会被客户或者彼此直接访问,这允许安区保护转移到Action,这样可以保证保护更有效,脱离表示层这个基础。'' |
;: ''"pages"目录中的文件在部署环境下会放到"WEB-INF/pages"目录下,容器会对所有WEB-INF目录下的文件提供安全保护,这种保护针对客户端的请求,而通过Struts ActionServlet的转发可以访问,把这些JSPs文件放到WEB-INF目录下确保它们只会被Actions访问,而不会被客户或者彼此直接访问,这允许安全保护转移到Action,这样可以保证保护更有效,脱离表示层这个基础。'' |
At line 80 removed 1 line. |
|
At line 83 changed 1 line. |
%%note __NOTE:__如果你想为某一页定制CSS,你必须在文件的开头添加<body id="pageName"/>,这会被SiteMesh看到并且应用到最终的页面,然后你可以一页一页的定制你的CSS,就像如下的方式: |
%%note __注意:__如果你想为某一页定制CSS,你必须在文件的开头添加<body id="pageName"/>,这会被SiteMesh看到并且应用到最终的页面,然后你可以一页一页的定制你的CSS,就像如下的方式: |
At line 86 changed 1 line. |
* 在ApplicationResources_en.properties中添加JSP文件出现的标题和题头,在生成的JSPs里有两处标题(浏览器顶端显示)的关键字,这些字段提供了personDetail.title和personDetail.heading关键字名。 |
* 在ApplicationResources.properties中添加JSP文件出现的标题和题头,在生成的JSPs里有两处标题(浏览器顶端显示)的关键字,这些字段提供了personDetail.title和personDetail.heading关键字名。 |
At line 152 added 314 lines. |
|
你需要为src/dao/**/Constants.java添加__PERSON_KEY__变量,这个"personForm"与struts-config.xml中的名称相对应。 |
|
[{Java2HtmlPlugin |
|
/** |
* The request scope attribute that holds the person form. |
*/ |
public static final String PERSON_KEY = "personForm"; |
}] |
|
如果你尝试运行测试,你会得到几个NoSuchMethodErrors,所以,让我们定义PersonAction的edit、save和delete方法。 |
|
!!创建PersonAction [#4] |
|
在src/web/**/action,创建PersonAction.java,有下面的内容: |
|
[{Java2HtmlPlugin |
|
package org.appfuse.webapp.action; |
|
import javax.servlet.http.HttpServletRequest; |
import javax.servlet.http.HttpServletResponse; |
|
import org.apache.struts.action.ActionForm; |
import org.apache.struts.action.ActionForward; |
import org.apache.struts.action.ActionMapping; |
import org.apache.struts.action.ActionMessage; |
import org.apache.struts.action.ActionMessages; |
|
import org.appfuse.model.Person; |
import org.appfuse.service.PersonManager; |
import org.appfuse.webapp.form.PersonForm; |
|
/** |
* @struts.action name="personForm" path="/editPerson" scope="request" |
* validate="false" parameter="method" input="mainMenu" |
*/ |
public final class PersonAction extends BaseAction { |
|
public ActionForward cancel(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
return mapping.findForward("mainMenu"); |
} |
|
public ActionForward delete(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'delete' method"); |
} |
|
ActionMessages messages = new ActionMessages(); |
PersonForm personForm = (PersonForm) form; |
|
// Exceptions are caught by ActionExceptionHandler |
PersonManager mgr = (PersonManager) getBean("personManager"); |
mgr.removePerson(personForm.getId()); |
|
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.deleted")); |
|
// save messages in session, so they'll survive the redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("mainMenu"); |
} |
|
public ActionForward edit(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'edit' method"); |
} |
|
PersonForm personForm = (PersonForm) form; |
|
// if an id is passed in, look up the user - otherwise |
// don't do anything - user is doing an add |
if (personForm.getId() != null) { |
PersonManager mgr = (PersonManager) getBean("personManager"); |
Person person = mgr.getPerson(personForm.getId()); |
personForm = (PersonForm) convert(person); |
updateFormBean(mapping, request, personForm); |
} |
|
return mapping.findForward("edit"); |
} |
|
public ActionForward save(ActionMapping mapping, ActionForm form, |
HttpServletRequest request, |
HttpServletResponse response) |
throws Exception { |
if (log.isDebugEnabled()) { |
log.debug("Entering 'save' method"); |
} |
|
// Extract attributes and parameters we will need |
ActionMessages messages = new ActionMessages(); |
PersonForm personForm = (PersonForm) form; |
boolean isNew = ("".equals(personForm.getId())); |
|
if (log.isDebugEnabled()) { |
log.debug("saving person: " + personForm); |
} |
|
PersonManager mgr = (PersonManager) getBean("personManager"); |
Person person = (Person) convert(personForm); |
mgr.savePerson(person); |
|
// add success messages |
if (isNew) { |
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.added")); |
|
// save messages in session to survive a redirect |
saveMessages(request.getSession(), messages); |
|
return mapping.findForward("mainMenu"); |
} else { |
messages.add(ActionMessages.GLOBAL_MESSAGE, |
new ActionMessage("person.updated")); |
saveMessages(request, messages); |
|
return mapping.findForward("edit"); |
} |
} |
} |
}] |
|
你会注意到以上的代码有许多对''convert''的调用来转化PersonForm到Person,这个转化''convert''方法在BaseAction.java(调用ConvertUtil.convert())中,并且使用[BeanUtils.copyProperties|http://jakarta.apache.org/commons/beanutils/api/org/apache/commons/beanutils/BeanUtils.html#copyProperties(java.lang.Object,%20java.lang.Object)] 来实现POJOs → ActionForms和ActionForms → POJOs的转化。 |
|
;:''如果你运行Eclipse,你需要对project运行"refresh"来显示PersonForm,它在build/web/gen目录中,这个目录必须是project的源目录之一,这是Eclipse看到和导入PersonForm的唯一方式,因为它是通过XDoclet产生并且不在通常的源目录中,你可以在build/web/gen/org/appfuse/webapp/form/PersonForm.java找到它。'' |
|
;:''在[BaseAction|http://raibledesigns.com/downloads/apptracker/api/org/appfuse/webapp/action/BaseAction.java.html]里你可以定义自己的转换器(例如 [DateConverter|http://raibledesigns.com/downloads/apptracker/api/org/apptracker/util/DateConverter.java.html]),因此BeanUtils.copyProperties能够知道Strings → Objects的转化。如果你的POJOs存在Lists(例如 父子关系),你需要使用{{convertLists(java.lang.Object)}}方法手工完成这个转化。'' |
|
现在你需要添加''edit'' forward和''savePerson'' action-mapping,这两个早就在PersonActionTest中提到,为此,可以在PersonAction.java添加一些额外的XDoclet标签,在类的声明前面加入这些注释,你一定已经有了''editPerson'' action-mapping的XDoclet标签,我在这里展示你会在类中看到的所有的XDoclet标签。 |
|
[{Java2HtmlPlugin |
|
/** |
* @struts.action name="personForm" path="/editPerson" scope="request" |
* validate="false" parameter="method" input="mainMenu" |
* |
* @struts.action name="personForm" path="/savePerson" scope="request" |
* validate="true" parameter="method" input="edit" |
* |
* @struts.action-forward name="edit" path="/WEB-INF/pages/personForm.jsp" |
*/ |
public final class PersonAction extends BaseAction { |
}] |
|
action-mappings中''editPerson''和''savePerson''主要区别是''savePerson''的XDoclet标签的validation标记是true(见validation="true"),注意"input"属性必须指向到一个forward,而不是个路径(例如/editPerson.html),如果你希望edit同save一样是是用用save的路径也是可以的,只要确保validate="false",然后在你的"save"方法中,你可以执行form.validate()来处理错误。 |
你一定注意到了这里是用PersonManager的方式与PersonManagerTest中的一样,PersonAction和PersonManagerTest都是PersonManagerImpl的''客户'',这很有意义。 |
|
本教程几乎所有的事情都搞定了,让我们来运行这些测试! |
|
!!运行PersonActionTest [#5] |
|
|
如果你查看PersonActionTest,你会发现所有的测试依赖于数据库中一条id=1的记录(testRemove依赖于id=2),所以要添加那些样本数据(metadata/sql/sample-data.xml),我会添加到底部,顺序并不重要(目前),因为它们互相并不存在依赖关系。 |
|
{{{ |
<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会在执行测试前加载这些数据,所以这些记录PersonActionTest可以得到。 |
|
如果现在你执行__ant test-web -Dtestcase=PersonAction__ - 所有事情会按计划执行,在作尝试前请确定Tomcat已经运行。 |
|
%%(color:green)BUILD SUCCESSFUL\\ |
Total time: 1 minute 21 seconds%% |
|
!!清理JSP来使它更好看 [#6] |
|
让我们开始清理personForm.jsp,修改<html:form>的''action''为"savePerson",这保证验证起作用,同时修改''focus''属性,从focus=""改为focus="firstName",这样在页面打开后光标会落到firstName字段(使用JavaScript)。 |
|
现在你如果执行__ant db-load deploy__,启动Tomcat,然后在你的浏览器输入[http://localhost:8080/appfuse/editPerson.html?id=1],你会看到如下信息: |
|
%%(border: 1px solid black; margin: 0 auto; height: 166px; width: 337px) |
[personForm-final.png] |
%% |
|
%%note __注意__:如果你修改了''web''中的任何文件,请使用__deploy-web__,否则,使用__deploy__来编译和部署。%% |
|
最后,为了使页面更加的用户友好,你也许希望为用户在form上面添加一段信息,只需在personForm.jsp页的顶端添加(使用 <fmt:message>)。 |
|
!![[可选的] 创建Canoo WebTests来测试模拟浏览器的actions [#7] |
本教程最后的步骤(可选的)是创建[Canoo WebTest|http://webtest.canoo.com]来测试JSPs。 |
|
;:''我说是可选的,是因为你可以通过浏览器运行同样的测试。'' |
|
你可以使用如下的URLs来测试不同actions的adding、editing和saving。 |
|
* Add - [http://localhost:8080/appfuse/editPerson.html]. |
* Edit - [http://localhost:8080/appfuse/editPerson.html?id=1] (make sure and run __ant db-load__ first). |
* Delete - [http://localhost:8080/appfuse/editPerson.html?method=Delete&id=1] (or edit and click on the Delete button). |
* Save - Click [edit|http://localhost:8080/appfuse/editPerson.html?id=1] and then click the Save button. |
|
Canoo测试非常的灵活,只需要配置XML文件,为了测试add、edit、save和delete,打开test/web/web-tests.xml添加如下的XML,你一定注意到这些片断的target名称是''PersonTests'',会运行所有相关的测试。 |
|
;:''我使用CamelCase的target命名方式(vs. 传统的小写,下划线分割的方式)是因为当拼写''-Dtestcase=Name''时,我会经常习惯于使用CamelCase方式来命名我的JUnit Tests。'' |
|
[{Java2HtmlPlugin |
|
<!-- 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"/> |
<clickbutton label="${button.delete}" description="Click button 'Delete'"/> |
<verifytitle description="display Main Menu" text=".*${mainMenu.title}.*" regex="true"/> |
<verifytext description="verify success message" text="${person.deleted}"/> |
</steps> |
</webtest> |
</target> |
}] |
|
完成添加后,你一定可以在Tomcat运行时运行__ant test-canoo -Dtestcase=PersonTests__,或者使用__ant test-jsp -Dtestcase=PersonTests__让Ant为你启动/关闭Tomcat,为了在所有的Canoo测试中包括PersonTests,把它作为"run-all-tests"的依赖target。 |
|
你必须注意到Canoo没有客户端的日志,如果你期望看到一些信息,你可以在每一个target的</canoo>和</target>之间添加如下代码。 |
|
{{{<loadfile property="web-tests.result" |
srcFile="${test.dir}/data/web-tests-result.xml"/> |
<echo>${web-tests.result}</echo>}}} |
|
%%(color:green)BUILD SUCCESSFUL\\ |
Total time: 11 seconds%% |
|
|
---- |
|
''Next Up:'' __Part IV:__ [增加校验功能和列表页面|ValidationAndList_zh] - 给personForm的firstName和lastName字段添加必添的验证逻辑和显示person所有记录的页面。 |