continue reading hover preload topbar hover preload widget hover preload

Appfuse开发实践(五)—— 增加校验和列表页面

Categories: Appfuse  |   No Comments

Appfuse开发实践(五)—— 增加校验和列表页面

      
      

       第四部分: 增加校验和列表页面 – 增加Person对象的校验逻辑保证firstName和lastName是非空字段并且加入列表页面显示数据库中所有的person纪录。


       看这个部分的指南参考上一部分的指南:创建Webwork 框架的 Actions和JSP

       第四部分: 增加校验和列表页面 – 增加Person对象的校验逻辑保证firstName和lastName是非空字段并且加入列表页面显示数据库中所有的person纪录。


       看这个部分的指南参考上一部分的指南:创建Webwork 框架的 Actions和JSP


内容提要

      本文将展示如何使用Webwork的校验框架加入校验逻辑。并且将使用Display标签库(Tag Library)创建列表页面显示数据库中的所有Person。


内容列表


[1] 使用校验规则创建Person-validation.xml文件
[2] 察看加入了校验的JSP页面并且进行测试
[3] 在DAO、ManagerTest类中加入testGetPeople方法声明年
[4] 在PersonDAO和Manager类中增加 getPeople 方法
[5] 在Action Test中加入testSearch方法
[6] 在Action中加入search方法
[7] 创建personList.jsp和Canoo test
[8] 在菜单中加入链接


一、使用校验规则创建Person-validation.xml文件


       为了利用WebWork校验框架实现数据校验有两件事情要做,第一是创建一个validation.xml文件,第二是在需要进行校验的action中加入一个校验interceptor引用。

       WebWork允许两种类型的校验 —— per-action和model-based。因为所有的Action对Person引用都要使用相同的校验规则,所以本文将使用model-based类型的校验。

       在src/dao/**/model目录下创建Person-validation.xml文件并加入下列内容:


<!DOCTYPE validators PUBLIC ”-//OpenSymphony Group//XWork Validator 1.0//EN” 
  ”http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd”
>
<validators>
    
<field name=”person.firstName”>
        
<field-validator type=”requiredstring”>
            
<message key=”errors.required”/>
        
</field-validator>
    
</field>
    
<field name=”person.lastName”>
        
<field-validator type=”requiredstring”>
            
<message key=”errors.required”/>
        
</field-validator>
    
</field>
</validators>


       在ApplicationResources_*.properties文件中的”errors.message” 键值使用字段的”name”属性以实现国际化。如果不需要提供对i18n的支持可以直接对<message>元素中指定显示内容。


errors.required=${getText(fieldName)} is a required field. 


 

       现在可以配置PersonAction使用visitor validation。为了实现这个目标,在PersonAction目录下创建一个PersonAction-validation.xml文件。加入下面的内容:


<!DOCTYPE validators PUBLIC ”-//OpenSymphony Group//XWork Validator 1.0//EN”     
    ”http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd”
>
<validators>
    
<field name=”person”>
        
<field-validator type=”visitor”>
            
<param name=”appendPrefix”>false</param>
            
<message/>
        
</field-validator>
    
</field>
</validators> 

 

        糟糕的是,WebWork没有提供一个透明机制读取Person-validation.xml文件并且标记在UI上标记哪个字段时必须的。AppFuse的Struts和Spring版本使用LabelTag实现了这个目标,不过他们也只是实现了一个普通的校验。我希望有人能够为WebWork提供相同的功能实现。同时JSP tags “required” 属性实际上没有对你所指定的校验规则作任何事情,仅仅是在加入对应的字段后面加入了一个星号而已。

        当然,也可以使用per-action校验。只需要拷贝Person-validation.xml文件到”webapp.action”包中并且把它重命名为PersonAction-validation.xml。

        为了使在”savePerson” 操作中我们新加入的校验规则发挥作用,我们要把原来在”validator”属性上的注释去掉。确定最后在web/WEB-INF/classes/xwork.xml文件的”savePerson” <action> 部分包含以下内容:


<interceptor-ref name=”validationStack”/>

  

    说明:在Appfuse中使用的validationStack和WebWork自带的有些不同,更多的信息可以在WebWork’s JIRA中查找。


二、查看加入了校验的JSP并进行测试


       现在保存所有的文件。为了测试加入了校验后的JSP,运行 ant db-load deploy,启动Tomcat并且在浏览器中输入 http://localhost:8080/myApp/editPerson.html?id=1

       如果擦掉了firstName或者lastName字段的值并点击save按钮,你将看到错误提示信息(图片略):

 


三、在DAO和Manager Test中加入testGetPeople方法


        为了创建一个List页面(或者说是master页面),我们需要穿件一个方法返回person表中的所有行。我们首先在PersonDAOTest和 PersonManagerTest类中创建测试方法。通常把这个方法命名为getEntities (例如getUsers),你也可以使用 getAll 或者 search —— 这其实是同一类问题。

        打开test/dao/**/dao/PersonDAOTest.java文件加入testGetPeople方法:


     

public void testGetPeople() {
        person 
= new Person();
        List results 
= dao.getPeople(person);
        assertTrue(results.size() 
> 0);
    }
  


       我在getPeople方法中传入一个person对象是想在以后方便加入过滤(filtering)处理 (基于person对象中的属性值)。在getPeople()方法中应该说明这个参数是可选的。

      
      现在打来test/service/**/service/PersonManagerTest.java文件加入testGetPeople方法。

     

public void testGetPeople() {
        List results 
= mgr.getPeople(new Person());
        assertTrue(results.size() 
> 0);
    }
  


    
     为了这两个类能够通过编译,需要在PersonDAO和PersonManager接口中加入getPeople()方法并且加入实现。


四、在DAO和Manager加入getPeople()方法

     打开src/dao/**/dao/PersonDAO.java文件并且加入getPeople()方法说明:


     

public List getPeople(Person person);  

    

       现在在src/service/**/service/PersonManager.java文件中加入同样的方法。保存所有的文件并且在tests类中调整imports类。接下来在实现类中实现getPeople()方法。打开src/dao/**/dao/hibernate/PersonDAOHibernate.java文件加入下面的代码:


     

public List getPeople(Person person) {
        
return getHibernateTemplate().find(from Person);
    }
  

    
      你可以注意到现在没有对person 参数作任何处理。仅仅是占了个位置 —— 在以后你可以依靠它的属性值使用Hibernate’s查询语言或者Criteria Queries加入filter。

      一个使用Criteria Query的示例:


    Example example = Example.create(person)
                             .excludeZeroes()    
// exclude zero valued properties
                             .ignoreCase();      // perform case insensitive string comparisons
    try {
        
return getSession().createCriteria(Person.class)
                           .add(example)
                           .list();
    }
 catch (Exception e) {
        
throw new DataAccessException(e.getMessage());
    }

    
return new ArrayList();


 

     在src/service/**/impl/PersonManagerImpl.java中实现getPeople() 方法:

    
   

public List getPeople(Person person) {
        
return dao.getPeople(person);
    }
  


    
     保存所有的变更,运行下面的测试方法:

    

ant test-dao -Dtestcase=PersonDAO 
ant test-service -Dtestcase=PersonManager 


      如果一切正常可以在web层加入读取所有人员信息的功能实现了。


五、在Action Test中加入testSearch()方法

      打开test/web/**/action/PersonActionTest.java文件加入下面的方法:


    

public void testSearch() throws Exception {
        assertNull(action.getPeople());
        assertEquals(action.list(), 
success);
        assertNotNull(action.getPeople());
        assertFalse(action.hasActionErrors());
    }
  

      
      只有在PersonAction中加入getPeople() 和 list() 方法这个类才能通过编译。


六、在Action中加入list()和getPeople()方法


      打开src/web/**/action/PersonAction.java 加入list() 方法。在此之前加入”people”变量和getPeople() 方法。

 

    

private List people;

    
public List getPeople() {
        
return people;
    }

    
    
public String list() {
        people 
= personManager.getPeople(new Person());

        
return SUCCESS;
    }

 

      运行ant test-web -Dtestcase=PersonAction进行测试。

       好!

BUILD SUCCESSFUL
Total time: 10 seconds


七、创建personList.jsp和Canoo测试


       现在在 web/pages目录下应该有了一个personList.jsp文件. If not, you can create it using viewgen. From the command-line, navigate to extras/viewgen and run ant -Dform.name=Person. This will generate a PersonList.jsp in extras/viewgen/build.

       打开web/pages目录下的personList.jsp文件进行编辑。

       在文件顶部有一个<ww:set>标签用来展示 “people” 信息。你需要把这个引用的值从”persons” 改成 “people”。


<ww:set name=”personList” value=”people” scope=”request”/> 

 

      用来创建JSP的模版针对id的属性列使用了硬编码,所以XDoclet加了两次。为了在personList.jsp移出它,在文件中删除下面的部分:


    

<display:column property=”id” sort=”true” headerClass=”sortable”
        titleKey
=”personForm.id”/>  


      如果有人知道办法修改extras/viewgen/src/List_jsp.xdt不再包括这个列的标签,请通知我。

      为了前后一致,可以把自动产生的”persons” 改变成”people”。在大约30行的位置,你可以找到下面这一行:


<display:setProperty name=”paging.banner.items_name” value=”persons”/>



修改成:


<display:setProperty name=”paging.banner.items_name” value=”people”/>



       最后在to web/WEB-INF/classes/ApplicationResources_en.properties文件中加入title和heading 键值 (personList.title和 personList.heading) :

# – person list page –
personList.title=Person List
personList.heading=All People


     需要注意的是,personList.title将会出现在浏览器的标题栏中,而personList.heading将会显示在页面中作为标题:

     在web/WEB-INF/classes/xwork.xml文件中加入一个新的”people” action:


    <action name=”people” class=”personAction” method=”list”> 
        
<result name=”success”>/WEB-INF/pages/personList.jsp</result> 
    
</action>  

      这时可以运行ant clean deploy, 启动Tomcat察看这个显示列表的页面了http://localhost:8080/myApp/people.html

      现在有了一个列表显示页面,让我们改变在新增和删除了一个Person后显示这个页面。在web/WEB-INF/classes/xwork.xml文件中,改变 savePersons的 “input” 和 “success” 结果指向”people.html”。

      同时需要改变 Canoo 测试中的 “AddPerson”和”DeletePerson”部分。打开test/web/web-tests.xml文件改变”AddPerson”目标中下面的行:

<verifytitle stepid=”Main Menu appears if save successful” 
    text
=”${webapp.prefix}${mainMenu.title}”/>

改变成:

<verifytitle stepid=”Person List appears if save successful” 
    text
=”${webapp.prefix}${personList.title}”/>



接下来改变”DeletePerson”目标中下面行的内容:

<verifytitle stepid=”display Main Menu” 
    text
=”${webapp.prefix}$(mainMenu.title}”/>

改变成:

<verifytitle stepid=”display Person List” text=”${webapp.prefix}${personList.title}”/>

     为了测试列表页面的工作,在test/web/web-tests.xml中创建一个新的JSP:


    

<!– Verify the people list screen displays without errors –>
    
<target name=”SearchPeople” 
        description
=”Tests search for and displaying all people”>
        
<canoo name=”searchPeople”>
            
&config;
            
<steps>
                
&login;
                
<invoke stepid=”click View People link” url=”/people.html”/>
                
<verifytitle stepid=”we should see the personList title” 
                    text
=”${webapp.prefix}${personList.title}”/>
            
</steps>
        
</canoo>
    
</target>

 

     你也许希望加入”SearchPeople”目标到 “PersonTests” 目标中去,以便能够和其他相关操作一起被测试。


    

<!– runs person-related tests –>
    
<target name=”PersonTests” 
        depends
=”SearchPeople,EditPerson,SavePerson,AddPerson,DeletePerson”
        description
=”Call and executes all person test cases (targets)”>
        
<echo>Successfully ran all Person JSP tests!</echo>
    
</target>  

      现在可以运行 ant test-canoo -Dtestcase=SearchPeople (或者运行 ant test-jsp 如果Tomcat没有运行)。如果结果是”BUILD SUCCESSFUL”就大功告成了!


八、在菜单上加入链接


     最后一步把list, add, edit和delete功能显示给用户访问最简单的办法是在web/pages/mainMenu.jsp文件中加入新的链接:


    

<li>
        
<href=”<c:url value=”/people.html”/>“><fmt:message key=”menu.viewPeople”/></a>
    
</li>  


      menu.viewPeople是定义在web/WEB-INF/classes/ApplicationResources_en.properties中的一个实体.。

    

menu.viewPeople=View People

另一个办法是改变web/WEB-INF/menu-config.xml加入下面的内容:


<Menu name=”PeopleMenu” title=”menu.viewPeople” forward=”viewPeople”/> 


      确定上面的XML在<Menus> tag内,但是没有另外一个<Menu>。然后在web/pages/menu.jsp中加入新菜单 —— 现在看起来是下面的样子:

<%@ include file=/common/taglibs.jsp%>

<div id=”menu”>
<menu:useMenuDisplayer name=”ListMenu” permissions=”rolesAdapter”>
    
<menu:displayMenu name=”AdminMenu”/>
    
<menu:displayMenu name=”UserMenu”/>
    
<menu:displayMenu name=”PeopleMenu”/>
    
<menu:displayMenu name=”FileUpload”/>
    
<menu:displayMenu name=”FlushCache”/>
    
<menu:displayMenu name=”Clickstream”/>
</menu:useMenuDisplayer>
</div>  



       现在运行ant clean deploy 启动Tomcat在浏览器中输入http://localhost:8080/myApp/mainMenu.html ,你可以看到下面的界面(图片略)。

 


现在已经完成了一个完整的master-detail的页面开发过程!现在可以运行所有的测试而看不到任何错误了!

有几个Gmail、Wallop邀请

Categories: 未分类  |   Comments(31)

   终于有了Gmail、Wallop邀请了!需要Gmail、Wallop、orkut的可以在此跟贴,说明firstname、lastname和E-Mail地址,MSN、hotmail的容易被收到垃圾箱中!

Appfuse开发实践(四)—— 创建Webwork 框架的 Actions和JSP

Categories: Appfuse  |   Comments(7)

Appfuse开发实践(四)—— 创建Webwork 框架的 Actions和JSP



 

内容提要

      这个部分将会展现给你如何创建Webwork框架的Action和JSP,同样会编写JUnit测试来测试PersonAction对象。 这个Action对象回合PersonManager交互。

     接下来我们开始在AppFuse的构架下创建一个新的Acetion和JSP。如果还没有安装WebWork模块,先运行ant install-webwork任务。


内容列表

内容提要

      这个部分将会展现给你如何创建Webwork框架的Action和JSP,同样会编写JUnit测试来测试PersonAction对象。 这个Action对象回合PersonManager交互。

     接下来我们开始在AppFuse的构架下创建一个新的Acetion和JSP。如果还没有安装WebWork模块,先运行ant install-webwork任务。


内容列表

内容提要

      这个部分将会展现给你如何创建Webwork框架的Action和JSP,同样会编写JUnit测试来测试PersonAction对象。 这个Action对象回合PersonManager交互。

     接下来我们开始在AppFuse的构架下创建一个新的Acetion和JSP。如果还没有安装WebWork模块,先运行ant install-webwork任务。


内容列表


[1] 使用XDoclet创建JSP
[2] 创建PersonActionTest以便测试PersonAction
[3] 创建PersonAction对象
[4] 运行PersonActionTest
[5]  清理JSP文件并进行发布
[6] 创建Canoo WebTests模拟测试浏览器行为


一、使用XDoclet创建JSP

      在这一步,我们要创建JSP文件显示Person对象的信息。这个JSP将使用Webwork的JSP Tag来把Person.java中的每个属性填充到表格的行中。我们使用的这个工具是由Erik Hatcher开发的。他由一个类(FormTagsHandler.java) 和一对XDoclet模版(FormKeys.xdt and Form_jsp.xdt组成)。这些文件都在extras/viewgen目录下。

      产生一个表单元素和对应的标签属性文件只需经过下面几个基本步骤:

      命令行下面切换到”extras/viewgen”目录下 
      执行ant -Dform.name=Person任务 在extras/viewgen/build 目录下产生下面三个文件:

Person.properties (表单元素的标签)
PersonForm.jsp (显示一个Person信息的JSP文件)
PersonList.jsp (显示People列表的JSP文件)


     把Person.properties文件中的内容拷贝到web/WEB-INF/classes/ApplicationResources_en.properties文件中。下面是一个具体的Person.properties文件例子:


 

# – person form –
person.firstName=First Name
person.id=Id
person.lastName=Last Name

 

      由于我们使用的是中文,需要把对应的信息填入web/WEB-INF/classes/ApplicationResources_cn.properties文件中,还要转化为Unicode编码。

      把PersonForm.jsp文件拷贝到web/pages/personForm.jsp目录下。把PersonList.jsp拷贝到web/pages/personList.jsp目录下。注意每个文件名的名字第一个字符是小写字符。


      “pages”目录下的文件在发布时将发布到”WEB-INF/pages”目录下。容器提供了WEB_INF目录下的所有文件的安全控制。这意味着来自客户端的请求,而不是由发过来的请求将不被相应。把所有的JSP文件放到WEB-INF目录下可以保证用户只能通过Actions来访问这些页面。这样就把系统安全性要求全部转移到Actions上,那里可以得到更高效的处理并且在基本的表示层外面。

AppFuse架构的web应用程序安全性保证了所有*.html文件得到了保护(除了/signup.html和/passwordHint.html文件)。这就保证了客户端必须通过Action才能访问JSP文件。


     说明: 如果自定义了CSS特殊页面,那么需要在文件的最上面加上<body id=”pageName”/>。这样就可以被 SiteMesh 识别并且放到最终的页面中去。你也可以使用下面的语句一个一个页面的用CSS来定义页面的格式:

body#pageName element.class { background-color: blue } 

 

body#pageName element.class { background-color: blue } 

 

body#pageName element.class { background-color: blue } 

 

      在ApplicationResources_en.properties文件中加入对应JSP文件的titles和headings键值。

      在自动产生的JSP文件中,有两个键表示title (浏览器的顶端)和header (页面的头上)。我们需要在ApplicationResources_en.properties文件中说明这两个键的值(personDetail.title and personDetail.heading),具体内容如下所示:


 

# – person detail page –
personDetail.title=Person Detail
personDetail.heading=Person Information

 


      在这上面我们要在文件中加入”personForm.*”键,为什么我们要使用personForm和personDetail这样的表示形式?最主要的原因是在页面的标签和文本信息有个明显的区别。另外一个原因是因为*Form.*这种形式可以很好的表示数据库中的字段信息。

      最近我有个客户希望所有数据库中的字段都可以查询。这个相当容易实现。我在ApplicationResources.properties 检查所有的包含”Form.”的键并且把它放到下拉列表框中。在用户界面上,用户可以输入查询项并且选择想要查询的列。


二、创建PersonActionTest类测试PersonAction类

       为PersonAction创建一个JUnit测试类, 首先在test/web/**/action目录下创建PersonActionTest.java文件。


package org.myApp.webapp.action;

import org.springframework.mock.web.MockHttpServletRequest;

import com.opensymphony.webwork.ServletActionContext;

public class PersonActionTest extends BaseActionTestCase {
    
private PersonAction action;

    
protected void setUp() throws Exception {    
        super.setUp();
        action 
= (PersonAction) ctx.getBean(personAction);
    }

    
    
protected void tearDown() throws Exception {
        super.tearDown();
        action 
= null;
    }

    
    
public void testEdit() throws Exception {
        log.debug(
testing edit);
        action.setId(
1);
        assertNull(action.getPerson());
        assertEquals(action.edit(), 
success);
        assertNotNull(action.getPerson());
        assertFalse(action.hasActionErrors());
    }


    
public void testSave() throws Exception {
        MockHttpServletRequest request 
= new MockHttpServletRequest();
        ServletActionContext.setRequest(request);
        action.setId(
1);
        assertEquals(action.edit(), 
success);
        assertNotNull(action.getPerson());
        
        
// update last name and save
        action.getPerson().setLastName(Updated Last Name);
        assertEquals(action.save(), 
input);
        assertEquals(action.getPerson().getLastName(), 
Updated Last Name);
        assertFalse(action.hasActionErrors());
        assertFalse(action.hasFieldErrors());
        assertNotNull(request.getSession().getAttribute(
messages));
    }


    
public void testRemove() throws Exception {
        MockHttpServletRequest request 
= new MockHttpServletRequest();
        ServletActionContext.setRequest(request);
        action.setDelete(
“”);
        Person person 
= new Person();
        person.setId(
new Long(2));
        action.setPerson(person);
        assertEquals(action.delete(), 
success);
        assertNotNull(request.getSession().getAttribute(
messages));
    }

}
  


       由于没有创建PersonAction类这个对象无法通过编译。


4、创建PersonAction类


      在src/web/**/action目录下,创建PersonAction.java文件:


 

package org.myApp.webapp.action;

import java.util.ArrayList;
import java.util.List;

import org.myApp.model.Person;
import org.myApp.service.PersonManager;

public class PersonAction extends BaseAction {
    
private Person person;
    
private String id;
    
private PersonManager personManager;

    
public void setId(String id) {
        
this.id = id;
    }


    
public void setPersonManager(PersonManager manager) {
        
this.personManager = manager;
    }


    
public Person getPerson() {
        
return person;
    }


    
public void setPerson(Person person) {
        
this.person = person;
    }


    
public String delete() {
        personManager.removePerson(String.valueOf(person.getId()));

        List args 
= new ArrayList();
        args.add(person.getFirstName() 
+   + person.getLastName());
        saveMessage(getText(
person.deleted, args));

        
return SUCCESS;
    }


    
public String edit() {
        
if (id != null{
            person 
= personManager.getPerson(id);
        }
 else {
            person 
= new Person();
        }


        
return SUCCESS;
    }


    
public String save() throws Exception {
        
if (cancel != null{
            
return cancel;
        }


        
if (delete != null{
            
return delete();
        }


        boolean isNew 
= (person.getId() == null);

        List args 
= new ArrayList();
        args.add(person.getFirstName() 
+   + person.getLastName());

        personManager.savePerson(person);

        String key 
= (isNew) ? person.added : person.updated;
        saveMessage(getText(key, args));

        
if (!isNew) {
            
return INPUT;
        }
 else {
            
return SUCCESS;
        }

    }

}
  


      有一些Key需要加到ApplicationResources_en.properties文件中用来显示给用户看的操作成功的提示信息打开web/WEB-INF/classes目录下的ApplicationResources_en.properties并且加入如下内容:


 

I usually add these under the # – success messages – comment.

person.added=Information for 
<strong>{0}</strong> has been added successfully.
person.deleted=Information for 
<strong>{0}</strong> has been deleted successfully.
person.updated=Information for 
<strong>{0}</strong> has been updated successfully.


 


       当然你可以加入一般的added, deleted和updated提示信息,这取决于你的需要。为每个实体使用独立的信息可以在特别情况下改变它。

      你可能注意到了我们以和PersonManager类类似的方式调用了TestPersonManager对象。PersonAction和PersonManagerTest都是PersonManagerImpl的客户,这是个很好的结构。

      现在你需要告诉Spring和WebWork这个action的存在。首先在web/WEB-INF/action-servlet.xml文件中加入如下的PersonAction定义信息:


    

<bean id=”personAction” class=”org.myApp.webapp.action.PersonAction” singleton=”false”>
        
<property name=”personManager”><ref bean=”personManager”/></property>
    
</bean>  

 


    然后在web/WEB-INF/classes/xwork.xml中加入引用信息说明:


    

<action name=”editPerson” class=”personAction” method=”edit”>
        
<result name=”success”>/WEB-INF/pages/personForm.jsp</result>
    
</action>
    
    
<action name=”savePerson” class=”personAction” method=”save”>
        
<!–interceptor-ref name=”validationStack”/–>
        
<result name=”cancel” type=”redirect”>mainMenu.html</result>
        
<result name=”input”>/WEB-INF/pages/personForm.jsp</result>
        
<result name=”success” type=”redirect”>mainMenu.html</result>
    
</action>  


       在上面的书名中”validationStack” interceptor-ref注释掉了因为你没有为Person对象定义任何的validation规则. 我们会在下一章中加入校验规则并且去掉注释。


四、运行PersonActionTest

      如果你看了我们的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都会加载这个文件,因此当你运行Action test时这些纪录肯定是有效的。

      确定你在project目录下并且所有文件都正确保存,启动MySql,运行ant test-web -Dtestcase=PersonAction任务

BUILD SUCCESSFUL
Total time: 21 seconds


5、清理JSP以便正常显示Person信息

    现在清理自动产生的personForm.jsp文件隐藏”id”属性。把下面的代码从 web/pages/personForm.jsp文件中移除掉:


 

    <ww:textfield label=”getText(‘person.id’)” name=”‘person.id’”
        value
=”person.id” required=”true”/>  

 

    在<table>标签后加入鲜面的代码:


 

<ww:hidden name=”‘person.id’” value=”person.id”/>  

 

如果希望提高界面的易用性,你可以把焦点设置在第一个字段的文本框上。这需要加入下面的JavaScript代码:


 

<script type=”text/javascript”>
    document.forms[
"person"].elements["firstName"].focus();
</script>


      现在执行ant db-load deploy任务, 启动Tomcat并且在浏览器中(我用firefox)输入http://localhost:8080/myApp/editPerson.html?id=1

抛出异常:

com.opensymphony.xwork.config.ConfigurationException: Error loading configuration file xwork.xml
    with nested exception 
com.opensymphony.xwork.config.ConfigurationException: Caught exception while loading file xwork.xml
    with nested exception 
org.xml.sax.SAXParseException: Element type ”result” must be followed by either attribute specifications, ”>” or ”/>”.
 at com.opensymphony.xwork.config.providers.XmlConfigurationProvider.init(XmlConfigurationProvider.java:126)
 at com.opensymphony.xwork.config.impl.DefaultConfiguration.reload(DefaultConfiguration.java:85)
 at com.opensymphony.xwork.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:56)
 at com.opensymphony.xwork.DefaultActionProxyFactory.setupConfigIfActionIsCommand(DefaultActionProxyFactory.java:58)
 at com.opensymphony.xwork.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:43)
 at com.opensymphony.webwork.dispatcher.ServletDispatcher.serviceAction(ServletDispatcher.java:292)
 at com.opensymphony.webwork.dispatcher.ServletDispatcher.service(ServletDispatcher.java:264)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:118)
 at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:52)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:86)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:73)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:73)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at com.opensymphony.clickstream.ClickstreamFilter.doFilter(ClickstreamFilter.java:42)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.appfuse.webapp.filter.ActionFilter.doFilter(ActionFilter.java:116)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.springframework.orm.hibernate.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:170)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:73)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.appfuse.webapp.filter.GZIPFilter.doFilter(GZIPFilter.java:51)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:198)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:118)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
 at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:705)
 at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
 at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
 at java.lang.Thread.run(Thread.java:534)
with nested exception com.opensymphony.xwork.config.ConfigurationException: Caught exception while loading file xwork.xml
    with nested exception 
org.xml.sax.SAXParseException: Element type ”result” must be followed by either attribute specifications, ”>” or ”/>”.
com.opensymphony.xwork.config.ConfigurationException: Caught exception while loading file xwork.xml
    with nested exception 
org.xml.sax.SAXParseException: Element type ”result” must be followed by either attribute specifications, ”>” or ”/>”.
 at com.opensymphony.xwork.config.providers.XmlConfigurationProvider.loadConfigurationFile(XmlConfigurationProvider.java:525)
 at com.opensymphony.xwork.config.providers.XmlConfigurationProvider.init(XmlConfigurationProvider.java:123)
 at com.opensymphony.xwork.config.impl.DefaultConfiguration.reload(DefaultConfiguration.java:85)
 at com.opensymphony.xwork.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:56)
 at com.opensymphony.xwork.DefaultActionProxyFactory.setupConfigIfActionIsCommand(DefaultActionProxyFactory.java:58)
 at com.opensymphony.xwork.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:43)
 at com.opensymphony.webwork.dispatcher.ServletDispatcher.serviceAction(ServletDispatcher.java:292)
 at com.opensymphony.webwork.dispatcher.ServletDispatcher.service(ServletDispatcher.java:264)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:118)
 at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:52)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:86)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:73)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:73)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at com.opensymphony.clickstream.ClickstreamFilter.doFilter(ClickstreamFilter.java:42)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.appfuse.webapp.filter.ActionFilter.doFilter(ActionFilter.java:116)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.springframework.orm.hibernate.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:170)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:73)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.appfuse.webapp.filter.GZIPFilter.doFilter(GZIPFilter.java:51)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:198)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:118)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
 at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:705)
 at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
 at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
 at java.lang.Thread.run(Thread.java:534)
with nested exception org.xml.sax.SAXParseException: Element type ”result” must be followed by either attribute specifications, ”>” or ”/>”.
org.xml.sax.SAXParseException: Element type ”result” must be followed by either attribute specifications, ”>” or ”/>”.
 at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source)
 at org.apache.xerces.util.ErrorHandlerWrapper.fatalError(Unknown Source)
 at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
 at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source)
 at org.apache.xerces.impl.XMLScanner.reportFatalError(Unknown Source)
 at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source)
 at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
 at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
 at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
 at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
 at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
 at javax.xml.parsers.DocumentBuilder.parse(Unknown Source)
 at com.opensymphony.xwork.config.providers.XmlConfigurationProvider.loadConfigurationFile(XmlConfigurationProvider.java:521)
 at com.opensymphony.xwork.config.providers.XmlConfigurationProvider.init(XmlConfigurationProvider.java:123)
 at com.opensymphony.xwork.config.impl.DefaultConfiguration.reload(DefaultConfiguration.java:85)
 at com.opensymphony.xwork.config.ConfigurationManager.getConfiguration(ConfigurationManager.java:56)
 at com.opensymphony.xwork.DefaultActionProxyFactory.setupConfigIfActionIsCommand(DefaultActionProxyFactory.java:58)
 at com.opensymphony.xwork.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:43)
 at com.opensymphony.webwork.dispatcher.ServletDispatcher.serviceAction(ServletDispatcher.java:292)
 at com.opensymphony.webwork.dispatcher.ServletDispatcher.service(ServletDispatcher.java:264)
 at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at com.opensymphony.module.sitemesh.filter.PageFilter.parsePage(PageFilter.java:118)
 at com.opensymphony.module.sitemesh.filter.PageFilter.doFilter(PageFilter.java:52)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:86)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:73)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:73)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at com.opensymphony.clickstream.ClickstreamFilter.doFilter(ClickstreamFilter.java:42)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.appfuse.webapp.filter.ActionFilter.doFilter(ActionFilter.java:116)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.springframework.orm.hibernate.support.OpenSessionInViewFilter.doFilterInternal(OpenSessionInViewFilter.java:170)
 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:73)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.appfuse.webapp.filter.GZIPFilter.doFilter(GZIPFilter.java:51)
 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:186)
 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:198)
 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:118)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
 at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
 at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
 at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
 at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:705)
 at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
 at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
 at java.lang.Thread.run(Thread.java:534)


     打开Dreamwaver检查xwork.xml,发现最后拷贝到xwork.xml文件中的几行字符颜色显示不正确,经过一番尝试发现去掉空格后重新输入颜色就正常了,干脆全部重新手工录入全部代码!

 

<action name=”editPerson” class=”personAction” method=”edit”>
  
<result name=”success”>/WEB-INF/pages/personForm.jsp</result>
 
</action>
 
 
<action name=”savePerson” class=”personAction” method=”save”>
        
<!–interceptor-ref name=”validationStack”/–>
        
<result name=”cancel” type=”redirect”>mainMenu.html</result>
        
<result name=”input”>/WEB-INF/pages/personForm.jsp</result>
          
<result name=”success” type=”redirect”>mainMenu.html</result>
 
</action>  

      删除Tomcat下webapp目录下发布好的所有文件和conf目录下的myApp.xml配置文件
现在执行ant setup任务, 启动Tomcat并且在浏览器中(我用firefox)输入http://localhost:8080/myApp/editPerson.html?id=1

登陆后,你将看到下面的界面:

 

      最后,为了增加页面的用户友好性,你可以在表单的最上面加入标题信息,这个很容易实现只需要在personForm.jsp页面的最上方加入<fmt:message>就可以了。


六、[可选部分] 创建Canoo WebTest模拟浏览器行为测试
      最后一步可以创建一个Canoo WebTest 测试Jsp页面。

      之所以说这一步是可选的,因为可以通过浏览器进行测试,可以通过下面的URLs测试用户adding,editing和saving的操作。

Add – http://localhost:8080/myApp/editPerson.html
Edit – http://localhost:8080/myApp/editPerson.html?id=1 (确定在执行测试以前运行ant db-load)。

Delete – 使用上面的Edit页面点击Delete按钮。
Save – 访问edit页面然后点击Save按钮。


       Canoo测试非常灵活,只需要简单在XML文件增加配置项就可以完成。为了测试add、edit、save和delete操作,打开test/web/web-tests.xml文件并且加入下面的XML代码。你可以注意到这个代码片断有一个PersonTests目标任务运行所有的相关测试。




<!– 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”>
    
<canoo name=”editPerson”>
        
&config;
        
<steps>
            
&login;
            
<invoke stepid=”click Edit Person link” url=”/editPerson.html?id=1″/>
            
<verifytitle stepid=”we should see the personDetail title”
                text
=”${webapp.prefix}${personDetail.title}”/>
        
</steps>
    
</canoo>
</target>

<!– Edit a person and then save –>
<target name=”SavePerson”
    description
=”Tests editing and saving a user”>
    
<canoo name=”savePerson”>
        
&config;
        
<steps>
            
&login;
            
<invoke stepid=”click Edit Person link” url=”/editPerson.html?id=1″/>
            
<verifytitle stepid=”we should see the personDetail title”
                text
=”${webapp.prefix}${personDetail.title}”/>
            
<setinputfield stepid=”set lastName” name=”person.lastName” value=”Canoo”/>
            
<clickbutton label=”Save” stepid=”Click Save”/>
            
<verifytitle stepid=”Page re-appears if save successful”
                text
=”${webapp.prefix}${personDetail.title}”/>
        
</steps>
    
</canoo>
</target>

<!– Add a new Person –>
<target name=”AddPerson”
    description
=”Adds a new Person”>
    
<canoo name=”addPerson”>
        
&config;
        
<steps>
            
&login;
            
<invoke stepid=”click Add Button” url=”/editPerson.html”/>
            
<verifytitle stepid=”we should see the personDetail title”
                text
=”${webapp.prefix}${personDetail.title}”/>
            
<setinputfield stepid=”set firstName” name=”person.firstName” value=”Abbie”/>
            
<setinputfield stepid=”set lastName” name=”person.lastName” value=”Raible”/>
            
<clickbutton label=”${button.save}” stepid=”Click button ’Save’”/>
            
<verifytitle stepid=”Main Menu appears if save successful”
                text
=”${webapp.prefix}${mainMenu.title}”/>
            
<verifytext stepid=”verify success message”
                text
=”Information for &lt;strong&gt;Abbie Raible&lt;/strong&gt; has been added successfully.”/>
        
</steps>
    
</canoo>
</target>

<!– Delete existing person –>
<target name=”DeletePerson”
    description
=”Deletes existing Person”>
    
<canoo name=”deletePerson”>
        
&config;
        
<steps>
            
&login;
            
<invoke stepid=”click Edit Person link” url=”/editPerson.html?id=1″/>
            
<clickbutton label=”${button.delete}” stepid=”Click button ’Delete’”/>
            
<verifytitle stepid=”display Main Menu” text=”${webapp.prefix}${mainMenu.title}”/>
            
<verifytext stepid=”verify success message”
                text
=”Information for &lt;strong&gt;Matt Canoo&lt;/strong&gt; has been deleted successfully.”/>
        
</steps>
    
</canoo>
</target>  


完成上面的工作后,可以在Tomcat启动的情况下运行ant test-canoo -Dtestcase=PersonTests或者在Tomcat没有启动的情况下运行ant test-jsp -Dtestcase=PersonTests。如果希望运行所有的Canoo测试(包括PersonTests)。

未通过,错误信息如下:

D:\myfile\myApp\build.xml:750: The following error occurred while executing this
 line:
Test step verifytitle named ”we should see the login title” failed with message
“Wrong document title found! Expected ”AppFuse ~ Login” but got ”AppFuse ~ 登录”


      因为我们使用环境的默认语言是中文,修改test/web/web-tests.xml文件12行:

<property file=”web/WEB-INF/classes/ApplicationResources_en.properties”/>


     修改为:

<property file=”web/WEB-INF/classes/ApplicationResources_zh_CN.properties”/>


    运行ant test-canoo -Dtestcase=PersonTests ,还是没有通过:

D:\myfile\myApp\build.xml:750: The following error occurred while executing this
 line:
Test step clickbutton named ”Click the submit button” failed with message ”Butto
n with name 
<<not specified>> and value <Login> not found!”


     查找问题发现关于Login的Target定义在test/login.xml中,修改下面的一行:

<clickbutton label=”Login” stepid=”Click the submit button”/>


 改成:

<clickbutton label=”${button.login}” stepid=”Click the submit button”/>


      OK,成功了!

      此时Cacoo在客户端没有纪录日至。如果你想加入日志你可以在</canoo>和</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 

Appfuse开发实践(三) —— 创建Manager类

Categories: Appfuse  |   No Comments


Appfuse开发实践(三) —— 创建Manager类

        第二部分:创建Managers – 创建一个Business Facades调用数据库管理层(DAOs)类并且处理事务(transaction)管理。


        阅读本文前最好阅读前一部分:Appfuse开发实践(二)—— 创建DAO对象。

       本文将展示如何创建Business Facade class (包括一个JUnit单元测试),它将调用在第一部分创建的DAO类。 再Appfuse的结构里,我们把这个对象叫做Manager类。这个层次的主要职责是作为持久化层(DAO)和web 层之间的桥梁,可以用来解除应用程序表示层(不仅包括web 层,也包括数据库层)和数据库层的耦合度。Manager类也是在应用程序总体结构中放置业务逻辑处理实现代码的地方。

      接下来就开始在Appfuse架构下创建一个新的ManagerTest类和一个Manager类。


内容提要:


[1] 创建一个ManagerTest类以运行JUnit测试以对Manager方法进行单元测试
[2] 创建一个调用DAO类的Manager类
[3] 在Spring中配置新创建的Manager指定这个Manager的事务(Transactions)管理对象
[4] 运行ManagerTest


1、创建一个ManagerTest对象以能够用JUnit来测试Manager对象

      在第一部分, 我们创建了一个Person对象和一个PersonDAO对象 – 接下来我们继续后面的开发。首先创建一个JUnit对象测试PersonManager. 在test/service/**/service目录下创建PersonManagerTest对象. 我们将要测试DAO对象的基本方法(get, save, remove)。


      这个看起来是个多余的步骤(所有的东西都要测试!), 但是这些测试在接下来的半年里都是有极大价值的。

      这个对象需要继承service包里面的BaseManagerTestCase 对象。父对象(BaseManagerTestCase)有一个和BaseDAOTestCase对象一样的方法 – 加载properties并对*Test.class对象中对应的属性赋值, 和ApplicationContext一起初始化。

 

      我通常复制(open → save as)也各已经写好的对象test (i.e. UserManagerTest.java) 用查找/替换来实现我需要的新对象。

下面的代码是一个基本的Manager的JUnit测试. 下面的代码来创建和销毁 PersonManager对象. “ctx” 对象是在BaseManagerTestCase 中初始化的.。


 

package org.myApp.service;

import org.myApp.model.Person;
import org.springframework.dao.DataAccessException;

public class PersonManagerTest extends BaseManagerTestCase {

    
private Person person = null;
    
private PersonManager mgr = null;

    
protected void setUp() throws Exception {
        super.setUp();
        mgr 
= (PersonManager) ctx.getBean(personManager);
    }


    
protected void tearDown() throws Exception {
        super.tearDown();
        mgr 
= null;
    }

}
  


 

       我们实现了JUnit框架要求的基本方法,接下来补充新的测试方法以保证Manger类各个方法都能正常工作。

       …我们建立的所有测试方法都以”test” (全部小写)开头。这些方法必须是public类型的,不带任何参数也没有任何返回值,这样就能被定义在Ant build.xml的junit任务自动调用。这里给出的是关于CRUD操作的简单测试方法。一件重要的注意事项时每个方法应该是独立的(跟其他测试方法没有关系)。

在PersonManagerTest.java文件中增加下面的方法:

    
    

public void testGetPerson() throws Exception {
        person 
= mgr.getPerson(1);

        assertNotNull(person.getFirstName());
    }


    
public void testSavePerson() throws Exception {
        person 
= mgr.getPerson(1);
        person.setFirstName(
test);

        mgr.savePerson(person);
        assertEquals(person.getFirstName(), 
test);
    }


    
public void testAddAndRemovePerson() throws Exception {
        person 
= new Person();
        person 
= (Person) populate(person);

        mgr.savePerson(person);
        assertEquals(person.getFirstName(), 
Bill);
        assertNotNull(person.getId());

        log.debug(
removing person, personId:  + person.getId());

        mgr.removePerson(person.getId().toString());

        
try {
            person 
= mgr.getPerson(person.getId().toString());
            fail(
Person found in database);
        }
 catch (DataAccessException dae) {
            log.debug(
Expected exception:  + dae.getMessage());
            assertNotNull(dae);
        }

    }
  


    
     不过这个类还不能通过编译,因为我们还没有创建PersonManager接口。

 

 


2、创建一个Manager对象访问DAO对象


      首先应该在src/service/**/service目录下创建PersonManager.java接口,并且为实现类声明基本的CRUD方法。I’ve eliminated the JavaDocs in the class below for display purposes.

 

 

package org.myApp.service;

import org.myApp.model.Person;

public interface PersonManager {
    
public Person getPerson(String id);
    
public void savePerson(Person person);
    
public void removePerson(String id);
}
  



      接下来创建PersonManagerImpl类实现PersonManager接口的基本方法。在src/service/**/service/impl目录下创建PersonManagerImpl.java文件。这个类要继承BaseManager类并且实现PersonManager接口。


 

package org.myApp.service.impl;

import org.myApp.model.Person;
import org.myApp.dao.PersonDAO;
import org.myApp.service.PersonManager;

public class PersonManagerImpl extends BaseManager implements PersonManager {
    
private PersonDAO dao;

    
public void setPersonDAO(PersonDAO dao) {
        
this.dao = dao;
    }


    
public Person getPerson(String id) {
        
return dao.getPerson(Long.valueOf(id));
    }


    
public Person savePerson(Person person) {
        dao.savePerson(person);
    }


    
public void removePerson(String id) {
        dao.removePerson(Long.valueOf(id));
    }

}
  



      需要说明的是setPersonDAO方法。这是方便Spring对Manager对象的PersonDAO属性赋值。这个可以在applicationContext-service.xml 文件中配置。我们接下来会在第三步中说明如何配置[3]。现在可以用”ant compile-service”编译所有创建的对象了。

Finally, we need to create the 最后需要在test/service/**/service目录下创建PersonManagerTest.properties文件以便 person = (Person) populate(person); 能够正常工作。


firstName=Bill
lastName=Joy


     我们现在可以为业务逻辑层改写Spring配置文件了,增加对新建对象的支持。


3、为这个Manager和事务配置Spring


    打开src/service/**/service/applicationContext-service.xml文件,在文件末尾追加下面的内容。


 

    <bean id=”personManager” parent=”txProxyTemplate”>
        
<property name=”target”>
            
<bean class=”org.myApp.service.impl.PersonManagerImpl” autowire=”byName”/>
        
</property>
    
</bean>  

 


4、运行ManagerTest

      保存所有编辑好的文件运行ant test-service -Dtestcase=PersonManager任务。
      出错了,配置文件中applicationContext-service.xml的类找不到。
     检查看到service错误的拼成了secvice。
     修改重新运行测试。
     测试未通过,getPerson方法取得了null对象,
     恩~~,应该重新创建数据库???否则编号就错了….
     运行ant setup-db 然后运行ant test-service -Dtestcase=PersonManager还是出错了
    仔细检查,
     恩,在applicationContext-service.xml文件中没有指定personManager对象personDAO的值~~,修改如下

 

<bean id=”personManager” parent=”txProxyTemplate”>
  
<property name=”target”>
   
<bean class=”com.vicfuture.service.impl.PersonManagerImpl” autowire=”byName”>
    
<property name=”personDAO”><ref bean=”personDAO”/></property>
   
</bean>
  
</property>
 
</bean> 


     
    看来配置文件中的大小写以及和类中属性名称是否一致应该仔细检查…..
    再次运行ant test-service -Dtestcase=PersonManager
    出错了,错误变了

    [junit] Testcase: testAddAndRemovePerson(com.myapp.service.PersonManager
Test):  FAILED
    [junit] expected:<Matt> but was:<Bill>
    [junit] junit.framework.ComparisonFailure: expected:<Matt> but was:<Bill>

    检查PersonManagerTest和PersonManagerTest.properties文件。
    想起来,把PersonDAOTest.propertise修改过来没有修改内容,赶紧修改。

    再次运行ant test-service -Dtestcase=PersonManager

    OK,成功了,

BUILD SUCCESSFUL
Total time: 23 seconds

 

Appfuse开发实践(二)——创建DAO对象

Categories: Appfuse  |   Comments(4)

Appfuse开发实践(二)——创建DAO对象


      第一部分: 在Appfuse中创建新的DAO对象 – 本部分描述如何创建一个POJO并且创建一个Java类把这个对象存储到数据库中实现持久化。 

      我们将使用Hibernate作为示例,如果需要使用其他的持久化方案,可以参考Appfuse的相关文档。
 
内容提要


      第一部分: 在Appfuse中创建新的DAO对象 – 本部分描述如何创建一个POJO并且创建一个Java类把这个对象存储到数据库中实现持久化。 

      我们将使用Hibernate作为示例,如果需要使用其他的持久化方案,可以参考Appfuse的相关文档。
 
内容提要


[1] 创建一个新的POJO对象并且加入xdoclet标签
[2] Create a new database table from the object using Ant
[3] Create a new DAOTest to run JUnit tests on the DAO
[4] Create a new DAO to perform CRUD on the object
[5] Configure Spring for the Person object and PersonDAO
[6] Run the DAOTest


1、创建一个新的POJO对象并且加入xdoclet标签


     首先要创建一个需要持久化的对象。我们将创建一个简单的Person对象(包括id,firstName,lastName三个属性)把这个对象放在src/dao/**/model目录中

 

package org.myApp.model;

public class Person extends BaseObject {
  
private Long id;
  
private String firstName;
  
private String lastName;
   

    
/*产生三个属性对应的setter/getter方法
     产   
*/

}
  

      这个类必须继承基类org.appfuse.model.BaseObject,必须实现三个抽象方法equals(), hashCode() and toString().前两个方法是Hibernate需要的.比较容易的方法是使用Commonclipse 插件自动产生着几个方法的实现.


      使用IntelliJ IDEA可以产生generate equals() 和 hashCode()方法, 不过没有toString()方法. 可以使用 ToStringPlugin 插件。

现在创建了POJO对象,接下来需要加入XDoclet标签以便产生Hibernate映射文件。这个映射文件描述了对象→表和属性 →字段的映射关系。

用@hibernate.class标签说明表和对象的对应关系:


 

/**
 * @hibernate.class table=”person”
 
*/

public class Person extends BaseObject {  

We also have to add a primary key mapping or XDoclet will puke when generating the mapping file. Note that all @hibernate.
* tags should be placed in the getters Javadocs of your POJOs. 


    
/**
     * @return Returns the id.
     * @hibernate.id column=”id”
     *  generator-class=”increment” unsaved-value=”null”
     
*/


    
public Long getId() {
        
return this.id;
    }
  

 

 

      这里使用generator-class=”increment” 代替 generate-class=”native” 因为作者在使用某些数据库时发现了一些问题。如果仅仅使用MySQL,建议使用”native” value。


2、使用Ant创建数据库表

      这是,可以运行”ant setup-db”任务来创建Person表,这个任务将创建Person.hbm.xml 文件并且在数据库中创建”person.”表。 在控制台上,你可以看见Hibernate创建的table schema:
[schemaexport] create table person (
[schemaexport]    id bigint not null,
[schemaexport]    primary key (id)
[schemaexport] );

    如果你Hibernate产生的Person.hbm.xml的文件,可以在build/dao/gen/**/hibernate 目录下找到它,这是这个文件的具体内容 :


 

<?xml version=”1.0″?>

<!DOCTYPE hibernate-mapping PUBLIC
    ”-//Hibernate/Hibernate Mapping DTD 2.0//EN” 
    ”http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd”
>

<hibernate-mapping>
    
<class
        
name=”org.myApp.model.Person”
        table
=”person”
        dynamic-update
=”false”
        dynamic-insert
=”false”
    
>

        
<id
            
name=”id”
            column
=”id”
            type
=”java.lang.Long”
            unsaved-value
=”null”
        
>
            
<generator class=”increment”>
            
</generator>
        
</id>

        
<!–
            To add non XDoclet property mappings, create a file named
                hibernate-properties-Person.xml
            containing the additional properties and place it in your merge dir.
        
–>

    
</class>

</hibernate-mapping>  

 

     现在加入@hibernate.property描述我们的其他字段信息:


    

/**
     * @hibernate.property column=”first_name” length=”50″
     
*/

    
public String getFirstName() {
        
return this.firstName;
    }


    
/**
     * @hibernate.property column=”last_name” length=”50″
     
*/

    
public String getLastName() {
        
return this.lastName;
    }
  

      在这个例子中,加入column属性是因为创建的数据表的字段名和对应的类中的属性名不一样,如果是一样的,不需要加入这个属性说明。

运行 “ant setup-db”创建数据表.

[schemaexport] create table person (
[schemaexport]    id bigint not null,
[schemaexport]    first_name varchar(50),
[schemaexport]    last_name varchar(50),
[schemaexport]    primary key (id)
[schemaexport] );

       如果需要改变字段的长度,修改@hibernate.property 标签对应的属性值。如果希望他是一个必须的字段(NOT NULL),,加上not-null=”true”这样的属性。


3、创建一个DAOTest对象并且用JUnit测试你的DAO对象


      现在我们创建一个DAOTest类来测试我们的DAO的工作状况。 “等一下”你说,“我们还没有创建一个DAO对象”你是对的。然而我们发现测试驱动开发(Test-Driven Development)是开发高质量软件的有效手段。在过去的几年里,我认为先编写测试再编写实现是无稽之谈。现在看起来我错了。现在我发现这是一个很伟大的方法。我现在推崇测试驱动的开发方式是因为我发现使用这种方法可以极大地加速软件开发过程。

      首先,在test/dao/**/dao目录下创建一个PersonDAOTest.java对象。这个对象必须继承 BaseDAOTestCase对象。这个父对象用来加载一个Spring的.properties文件 (资源邦定文件),此文件的文件名
和*Test.class一样。在这个例子里面如果你把PersonDAOTest.properties、PersonDAOTest.java放在一个目录下面, 这个文件的属性可以自动被赋值。

 

package org.myApp.dao;

import org.myApp.model.Person;
import org.springframework.dao.DataAccessException;

public class PersonDAOTest extends BaseDAOTestCase {
    
    
private Person person = null;
    
private PersonDAO dao = null;

    
protected void setUp() throws Exception {
        super.setUp();
        dao 
= (PersonDAO) ctx.getBean(personDAO);
    }


    
protected void tearDown() throws Exception {
        super.tearDown();
        dao 
= null;
    }

}
  

 

        现在我们需要编写代码测试DAO对象中的CRUD (create, retrieve, update, delete)方法。我们创建的这个方法必须以”test” (all 全部小写字母)开头。只要这些方法是public类型的,它必须是一个void类型返回值,并且不带任何参数,这些测试方法将会被定义在Ant build.xml的<junit>任务自动调用。这是一个对CRUD方法的简单测试方法。一个重要的需要记住的事情是每个方法应该是独立的。在这个例子里我们在PersonDAOTest.java文件中加入下面的代码:


    

public void testGetPerson() throws Exception {
        person 
= new Person();
        person.setFirstName(
Matt);
        person.setLastName(
Raible);

        dao.savePerson(person);
        assertNotNull(person.getId());

        person 
= dao.getPerson(person.getId());
        assertEquals(person.getFirstName(), 
Matt);
    }


    
public void testSavePerson() throws Exception {
        person 
= dao.getPerson(new Long(1));
        person.setFirstName(
Matt);

        person.setLastName(
Last Name Updated);

        dao.savePerson(person);

        
if (log.isDebugEnabled()) {
            log.debug(
updated Person:  + person);
        }


        assertEquals(person.getLastName(), 
Last Name Updated);
    }


    
public void testAddAndRemovePerson() throws Exception {
        person 
= new Person();
        person.setFirstName(
Bill);
        person.setLastName(
Joy);

        dao.savePerson(person);

        assertEquals(person.getFirstName(), 
Bill);
        assertNotNull(person.getId());

        
if (log.isDebugEnabled()) {
            log.debug(
removing person);
        }


        dao.removePerson(person.getId());

        
try {
            person 
= dao.getPerson(person.getId());
            fail(
Person found in database);
        }
 catch (DataAccessException dae) {
            log.debug(
Expected exception:  + dae.getMessage());
            assertNotNull(dae);
        }

    }
  

 

    在testGetPerson方法中,我们创建了一个Person对象并且调用了get方法。实际上我们这样做必须保证在数据库中有一条对应的纪录。因为采用了DBUni可以以我们需要的测试数据自动填充数据库,你只需要在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>
</table>


person = new Person();
person = (Person) populate(person);  

     
      此时,PersonDAOTest类无法通过编译,因为我们没有创建 PersonDAO.class,我们要创建它。 PersonDAO.java是一个接口,PersonDAOHibernate.java是一个基于这个接口的Hibernate持久化实现。下面我们将继续创建这些对象。
4、创建一个DAO对象执行CRUD操作

      首先在src/dao/**/dao目录中创建一个PersonDAO.java接口,并且说明实现类必须实现的CRUD方法。为了显示方便这里去掉了JavaDoc信息。


package org.myApp.dao;

import org.myApp.model.Person;

public interface PersonDAO extends DAO {
    public Person getPerson(Long personId);
    public void savePerson(Person person);
    public void removePerson(Long personId);
}  

      你可能注意到上面的类的方法说明上没有exception处理。这是因为Spring的强大例外处理功能能把Exception封装成为RuntimeExceptions。此时,您可以运行”ant compile-dao”来编译源文件。然而如果你运行”ant test-dao -Dtestcase=PersonDAO”,你将看到一个错误提示:No bean named ‘personDAO’ is defined。这是一个Spring产生的错误信息 – 我们需要给applicationContext-hibernate.xml指定一个具体的java Bean 到personDAO属性上。在此之前我们首先要创建一个PersonDAO的实现类。


      执行测试任务的ant命令是”test-dao”。如果传递特定的参数(用 -Dtestcase=name), 他将会搜索 **/*${testcase}*目录 – 允许我们执行PersonDAOTest测试。

接下来我们创建PersonDAOHibernate类实现PersonDAO接口使用Hibernate来实现Person对象的get/save/delete操作。为了实现这个目标在src/dao/**/dao/hibernate 目录下创建PersonDAOHibernate.java.
他需要继承 BaseDAOHibernate 对象并且实现PersonDAO接口。


 

package org.myApp.dao.hibernate;

import org.myApp.model.Person;
import org.myApp.dao.PersonDAO;
import org.springframework.orm.ObjectRetrievalFailureException;

public class PersonDAOHibernate extends BaseDAOHibernate implements PersonDAO {

    
public Person getPerson(Long id) {
        Person person 
= (Person) getHibernateTemplate().get(Person.class, id);

        
if (person == null{
            
throw new ObjectRetrievalFailureException(Person.class, id);   
        }


        
return person;
    }


    
public void savePerson(Person person) {
        getHibernateTemplate().saveOrUpdate(person);
    }


    
public void removePerson(Long id) {
        
// object must be loaded before it can be deleted
        getHibernateTemplate().delete(getPerson(id));
    }

}
  

      现在如果运行”ant test-dao -Dtestcase=PersonDAO”,会看到同样的错误。我们需要配置Spring让它知道PersonDAOHibernate是PersonDAO的实现,我们也需要告诉它Person对象的信息。


5、在Spring配置Person和PersonDAO对象


       首先要告诉Spring Hibernate mapping映射文件的位置。打开src/dao/**/dao/hibernate/applicationContext-hibernate.xml并且加入Person.hbm.xml信息。


 

<property name=”mappingResources”> 
    
<list> 
        
<value>org/myApp/model/Person.hbm.xml</value> 
        
<value>org/myApp/model/Role.hbm.xml</value> 
        
<value>org/myApp/model/User.hbm.xml</value> 
        
<value>org/myApp/model/UserCookie.hbm.xml</value>  
    
</list> 
</property>   

 

     现在我们需要增加一些XML标记绑定PersonDAOHibernate到PersonDAO。在文件的结尾处加入下面的内容:


 

<!– PersonDAO: Hibernate implementation –> 
<bean id=”personDAO” class=”org.myApp.dao.hibernate.PersonDAOHibernate”> 
    
<property name=”sessionFactory”><ref local=”sessionFactory”/></property> 
</bean>   

 


     你也可以使用autowire=”byName” 这样不用对”sessionFactory”属性赋值。个人习惯,我喜欢在xml文档说明对应关系。


6、执行DAOTest

     保存文件再一次运行 “ant test-dao -Dtestcase=PersonDAO”。

OK,成功了^_^

BUILD SUCCESSFUL
Total time: 9 seconds

Appfuse里面带的Ant任务列表

Categories: Appfuse, Java/J2EE  |   No Comments

Appfuse里面带的Ant任务列表

    使用Appfuse开发应用程序经常需要使用开发包中定义好的Ant 任务,这里是Ant 任务的详细说明:
 

任务名

说明

bin 

创建war程序包,jdbc驱动,和XML文件

checkstyle  

检查代码是否符合代码规范要求

clean 清除制作好的各种产品
compile compile-web的别名
compile-dao 编译dao模块
compile-service  编译service模块
compile-web 编译web模块
copy-resources 从原程序目录中拷贝.properties和.xml文件
copy-web-files       拷贝静态文件
db-create  创建 ${database.type}类型的数据库
db-drop  删除数据库表
db-export 导出当前数据库中的数据
db-load  把示例数据加载到数据库中
db-prepare 创建数据库表
deploy 把war程序包解包部署到应用服务器的目录中去
deploy-test-reports 把JUnit测试报告发布到服务器上
deploy-war 发布’${webapp.name}.war’到本地Tomcat服务器上
deploy-web 仅仅发布web的classes文件到servlet容器中去
tory
dist 创建的一个压缩tar.gz文件,包含了全部路径和分发用的文件
docs 运行javadoc, todo, checkstyle和pmd tasks
ftp 通过FTPs上传分类文件到指定的服务器中
gen-tests-dao 为dao模块产生测试类
gen-tests-service 为模块产生测试程序
gen-tests-web  为web模块产生测试程序
hibernatedoclet  产生Hibernate映射文件
init  加入自定义任务
install  Tomcat安装应用
install-ibatis 安装iBATIS
install-springmvc 安装Spring’s MVC,删除Struts
install-webwork 安装WebWork,删除Struts
java2html   创建HTML页面以便在线察看代码
javadoc  产生JavaDoc API文档
jsp-2 转换JSP 1.2 Tags (i.e. JSTL) 到JSP 2.0标准
list 列出所有Tomcat应用
new 创建一个新的用户命名的project
package-dao DAO打成一个单独的JAR包
package-web 应用程序打WAR包
ping-tomcat  pings tomcat确定tomcat还在运行
pmd  定位没有无用的imports语句,无用的variables,等等。
prepare  创建目标目录
refresh 清除原来的程序并重新发布
reload  在Tomcat重新加载应用程序
remove 移除Tomcat中的应用程序
setup 创建数据库,设置tomcat环境,发布解包应用程序
setup-db 创建数据库并且填充数据,会被许多其他任务调用
setup-tomcat 拷贝jdbc driver和context.xml到tomcat对应的目录下
stage-web  调用所有的其他任务搜集静态资源
start 启动Tomcat应用
start.tomcat  在当前控制台窗口下启动tomcat
stop 停止以一个Tomcat应用
test-all 运行一个POJO所有的测试包括dao, service, web和jsp
test-all-running 运行所有的DAOs, Services, Controllers和JSPs测试
test-canoo  运行Canoo WebTests 以在 Tomcat中测试JSP程序
test-dao  测试dao模块
test-jsp  使用Cargo运行Canoo WebTests
test-reports 产生测试报告
test-service 测试service模块
test-web 测试web模块
todo  创建代码中的todo任务报告
undeploy 删除、反安装一个发布好的 war文件
war package-web的别名
webdoclet 产生web开发描述
wiki 从wiki下载文件产生文档

Appfuse实践(一)——配置安装

Categories: Appfuse  |   Comments(7)

使用的是webwork+spring+hibernate,其他的可以参考相关tutorial。

一、系统环境准备
      从http://java.sun.com下载,我使用1.4.2_05.
      从latest Tomcat release 下载tomcat,目前的版本是5.0.28。最好不要用LE版本否则你要添加DBCP和JavaMail的JAR包.
      下载最近的Appfuse版本,我使用的是 AppFuse 1.6。
      下载mysql。

      将会使用 Ant, XDoclet, Spring, Hibernate (or iBATIS), JUnit, Cactus, StrutsTestCase, Canoo’s WebTest, Struts Menu, Display Tag Library, OSCache, JSTL and Struts (or Spring MVC)这些组件。

二、开始安装配置Appfuse
       download appfuse 并且解压
       需要启动Mysql服务器
       建立自己的应用程序数据库默认字符集是UTF8,这个问题很关键,因为Appfuse支持多语言否则程序无法正常运行。所以Mysql服务器的默认的字符集也应该是UTF8,大家使用mysql怎么更改到UTF8字符集我不知道,我是通过命名行参数启动的:
       mysqld –default-character-set=utf8

      此时可以用ant建立自己的应用了:
ant new -Dapp.name=myApp -Ddb.name=mydb

如果需要改变数据库可以修改build.properties文件中这部分的参数:
#database.jar=${postgresql.jar}
#database.type=postgresql
#database.name=myApp
#database.host=localhost
#database URL for creating other databases (doesn’t work with pgsql)
#database.admin.url=jdbc:${database.type}://${database.host}/template1
#database.admin.username=postgres
#database.admin.password=postgres

#hibernate.dialect=net.sf.hibernate.dialect.PostgreSQLDialect
#database.driver_class=org.postgresql.Driver
#database.url=jdbc:${database.type}://${database.host}/${database.name}


建立数据库database, 在tomcat上发布应用

运行
ant setup

出错了,无法自动建立数据库mydb。出错信息如下:
BUILD FAILED: D:\myfile\myApp\build.xml:830: org.dbunit.dataset.NoSuchTableException: app_user

检查执行过程,把自动执行的创建app_user的sql语句放到mysql control center中执行
create table app_user (
username varchar(20) not null,
version integer not null,
password varchar(255),
first_name varchar(50),
last_name varchar(50),
address varchar(150),
city varchar(50),
province varchar(100),
country varchar(100),
postal_code varchar(15),
email varchar(255) not null unique,
phone_number varchar(255),
website varchar(255),
password_hint varchar(255),
primary key (username)
);
执行失败,错误提示:
[root@localhost:3306] 错误 1071: Specified key was too long. Max key length is 500
作如下修改,成功了,
email varchar(255) not null unique改成
email varchar(100) not null unique,

经过测试,长度超过166(包括)就出错了!
不知道是不是UTF8字符集造成的原因,一个UTF8字符被认为占用了三个字节(500/3<167)??
就是说声明为 not null unique的字段长度不能超过166。


由于sql语句根据POJO的tag自动创建:

找到org.appfuse.model.User的源程序

修改
@hibernate.column name="email" not-null="true" unique="true"

改成:
@hibernate.column name="email" not-null="true" length="166" unique="true"

运行
ant setup-db

成功了!ok!


启动tomcat 5.0.25
运行
ant setup

出错了

BUILD FAILED: D:\edu\edu\build.xml:33: Please copy junit.jar into C:\ant/lib

完成拷贝

成功了!excellent!
打开浏览器FireFox run一下:

输入:http://127.0.0.1:8080/myApp

没有反应??怎么回事??

命令行下面启动tomcat,看看有什么错误提示:

unregistering logger Catalina:type=Logger,path=/myApp,host=localhost

打开C:\Tomcat\conf\Catalina\localhost\myAqpp.xml

修改:注释掉  logger信息
<!– Logger className="org.apache.catalina.logger.FileLogger" prefix="myApp_log." suffix=".txt" timestamp="true"/ –>

重新启动tomcat

还是不对

更换版本tomcat 5.0.5.28
错误信息变了
Application 没有启动??

运行任务

ant install


没有成功!出错信息如下:

BUILD FAILED: D:\myfile\myApp\build.xml:1221: java.io.IOException: Server returned HTTP response code: 401 for URL: http://localhost:8080/manager/deploy?path=%2FmyApp


直接在浏览器中输入http://localhost:8080/manager/deploy?path=%2FmyApp

??出现窗口要求输入用户名和密码
查看myApp\tomcat.properties 管理员用户密码为admin,admin

修改 tomcat\conf\tomcat-users.xml 增加管理员admin

增加两个角色

<role rolename="admin"/>
  <role rolename="manager"/>

增加admin用户
<user username="admin" password="admin" roles="role1,tomcat,admin,manager"/>

删除tomcat下面myApp目录和conf\Catalina\localhost下的myApp.xml文件,重新启动tomcat

运行

ant install

成功了!!


但是tomcat报错:
2004-11-14 19:59:49 org.apache.catalina.core.StandardHostDeployer install
信息: Processing Context configuration file URL file:/C:/Tomcat/conf/Catalina/localhost/myApp.xml
2004-11-14 19:59:49 org.apache.catalina.core.StandardHostDeployer install
信息: Installing web application from URL jar:file:/C:/Tomcat/webapps/myApp.war!
/
2004-11-14 20:00:04 org.apache.catalina.core.StandardContext listenerStart
严重: Skipped installing application listeners due to previous error(s)
2004-11-14 20:00:04 org.apache.catalina.core.StandardContext start
严重: Error listenerStart
2004-11-14 20:00:04 org.apache.catalina.core.StandardContext start
严重: Context startup failed due to previous errors
2004-11-14 20:00:04 org.apache.catalina.logger.LoggerBase stop
信息: unregistering logger Catalina:type=Logger,path=/myApp,host=localhost

为什么呢??

tomcat版本不对??

运行struts安装程序,正常运行

重新运行ant install-webwork

BUILD FAILED: D:\myfile\myApp\build.xml:1391: Basedir D:\myfile\myApp\extras\webwork does not exist

嗯~~

更改extras\viewgen目录名称为webwork

重新运行ant install-webwork

再运行ant install

打开firefox,输入 http://127.0.0.1:8080/myApp。成功了!

 

输入用户名mraible
密码tomcat

OK!!进入主界面(界面没有上传,不好意思了^_^) 

Appfuse简介

Categories: Appfuse  |   Comments(6)

   AppfuseMatt Raible 开发的一个指导性的入门级J2EE框架,它继承了流行的SpringHibernateibatisstrutsXdcoletjunit等基础框架,最新的1.7版更是提供了对TaperstryJSF的支持。在持久层,AppFuse采用了Hibernate O/R映射工具(http://www.hibernate.org);在容器方面,它采用了Spring Frameworkhttp://www.springframework.org)。用户可以自由选择StrutsSpring/MVCWebworkTaperstryJSF这几个web框架。采用TDD的开发方式,使用JUnit测试各层,甚至测试 jsp 输出的 w/o 错误。为了简化开发,预定义好了一套目录结构、基类、用来创建数据库、配置Tomcat、测试部署应用的 Ant 任务,帮助快速自动生成源程序和自动维护部分配置文件。

参考资料:  
      在
https://appfuse.dev.java.net/可以下载Appfuse,目前的版本是1.7。 
      Appfuse
的参考资料和文档可以在http://raibledesigns.com/wiki/Wiki.jsp?page=AppFuse查看。

2004 Craig Larman敏捷中国行培训总结

Categories: 敏捷软件开发  |   Comments(3)

      10月20参加了Craig Larman 2004 敏捷中国行,敏捷与迭代式开发的培训讲座。短短的一天之内,讲的内容涉及到了迭代式软件开发过程、主要的敏捷过程Scrum和XP,最后介绍了敏捷建模的基本内容。
可能是由于主办方也缺乏主办经验吧,到会的人员不多,像我这样缴费参加的就更少了,所将的内容由于涉及面太广,翻译的水平又很一般,基本上说两句就停下来等翻译翻译,然后再说,连贯性无从谈起,翻译的很多内容也没有翻译出来,实际讲授时间大概只有半天吧(翻译占用的时间基本上占了一半以上),本来还以为有同声翻译的,最会讲到Scrum和XP以及敏捷建模就只能寥寥数语简单带过,实在是遗憾。
虽然如此,还是消除了原来认识上的一些误区。
        Craig以一个笑话开始了自己的演说,接着回顾了迭代软件开发的历史,也提到一个成熟的程序员应该有10年到15年的编码经验(这点是让国人难以想象的,我们工作两三年的程序员都以老程序员自居了)。以中世纪迷信放血可以治疗百病的例子(用对放血员进行等级认证的例子对现实进行了讽刺)对比如今软件工程的发展过程,指出了自50年代开始占统治地位的Waterfall(瀑布模型)这一错误的根本来源是源于人们基于臆想,脱离现实实践作理论研究的结果。追根溯源,当年首先提出瀑布模型概念的Dr. Winston W. Royce,被认为是瀑布模型的创始人,在其第一篇关于瀑布模型的论文中提到”I am going to describe my personal views about managing large software developments.”,就是这样一篇文章,其主要论点居然还是说瀑布模型是一种不好的开发模型,迭代式开发才是他认为合理的开发方式(这个我以前也不知道)。然而这样一个错误观念统治了学术界30多年。直到以1987年DOD否定了Waterfall软件工程模型为标志,近三十年的理论都提倡迭代开发。
        软件开发不像生产制造过程,更像新产品研发过程。遗憾的是,就算到了今天,我们大量客户方代表、项目经理、软件公司中高层管理人员有意无意的受瀑布模型支配,在实际过程中盲目追捧CMM、ISO9000,那些复杂冗长的过程管理模式。
        中间Craig介绍了迭代开发的基本过程,指出了运用迭代开发过程的常见错误:



  • 迭代周期过长。一般应该是2~6周。

  • 强加瀑布模型的做法。比如要求完整需求确定后再编码。把组织人员分为专么的分析、设计、编码人员(我倒是觉得适当的专业分工还是必要的,特别在目前的环境下,设计主体构架和核心构架实现的程序人员应该和普通的编码人员分离开来,这样容易管理,也能够降低开发组对全体人员的技能要求)。

  • 在项目开始就指望能做出详细的计划(这个项目经理、程序员容易接受,公司的中高层管理人员是最不能接受的了,不知道老美的软件公司实际情况怎么样)。迭代开发一般只制定一个迭代周期的详细计划,计划是在一个周期前两天的需求研讨会上确定的。
    把项目任务分解成若干个部分,然后分期实现并不是迭代开发
           

       需求研讨会是每个迭代过程中的第一个步骤,一般需要两天时间,用半天跟客户讨论,找出需要实现的关键需求,剩下一天半对需求进行分析,核定工作量,确定下一个迭代周期的目标。其中关键需求应该是具有关键商业价值的需求和影响系统构架的需求。做计划时计划的时间重点考虑的是IEH(一个人一日的理想工作时间),指出一个工程师的一日IEH不过是4~5个小时(XP强调每周工作40个小时也是有基于这个统计数据的考虑吧)。
       第一次迭代结束后开始第二次迭代,同样是两天的需求讨论会,需求提取是个持续的过程,第一个阶段能提取10%,选择其中20%作为第一次迭代的任务就可以了,项目最后阶段可能就没有这个讨论会了。每个迭代过程最后保留一天做系统集成,一天给客户演示Demo。
      接下来重点介绍了Scrum这个敏捷过程,其中很多方法还是值得借鉴的。
      Scrum过程中没有中心控制者,强调发挥个人的创造力和能动性,项目组成员自行选择任务。PM的职责不是检查和监督团队成员的日常工作
,而是主要消除团队开发的外部障碍,指导团队成员工作,对内部成员进行培训。通过Scrum meeting来保证项目组成员了解其他所有人的工作进度。Scrum meeting是每天早上进行的15分钟的会议,大家必须站立在白板前开会;项目经理确保每个人回答5个问题:



1、到今天做了什么
2、今天做什么
3、有什么障碍和困难
4、整个项目是否遗漏一些开发任务
5、你学到了什么经验可以分享和交流
       


        同时项目经理保证每个人只回答这5个问题,如果一个成员的问题涉及到其他成员,由他们会后自己安排临时小会讨论。
       有一个成员自愿担任Daily Tracker负责项目进度信息的收集,据此跟踪项目进度。Daily tracker负责问每个人各个任务还剩下多少天,因为迭代开发的每个迭代周期的任务截至日期是固定的,关键是调查剩余任务的完成时间。信息收集、成员之间交流最好不要采用E_mail和Web的方式,应该面对面的交流。 


      也介绍了XP,介绍XP强调了只有完全应用了XP的12个过程才是真正使用了XP,只是简单使用了其中一个方法并不代表使用了XP。
      1、现场客户(On-site Customer)
      2、计划博弈(Planning Game)
      3、系统隐喻(System Design)
      4、简化设计(Simple Design)
      5、集体拥有代码(Collective Code Ownership)
      6、结对编程(Pair Programming)
      7、测试驱动(Test-driver)
      8、小型发布(Small Release)
      9、重构(Refactoring)
      10、持续集成(Continous integration)
      11、每周40小时工作制(40-hour Weeks)
      12、代码规范(Coding Standards)


        事实上,很多项目因为第一个条件不满足根本就不能采用XP。XP本身没有发明任何新方法。


        不同的项目需要不同的方法论。
        最后提到了敏捷建模。
        模型是对现实的简约描述。
        模型的表现形式可以是图也可以使文字,关键是要直观。形式不重要,目的是在于交流。
        建模的主要工具其实还是白板和笔,为了方便记录可以采用数码相机。Case 工具没有必要。
        只需要对系统20%的复杂需求进行建模。
        建模最好也是两个人一起做。

把复杂的事情简单化

Categories: 敏捷软件开发  |   Comments(1)

       有个人要在客厅里钉一幅画,请邻居来帮忙,画已经在墙上扶好,正准备砸钉子,这个邻居却说:“这样不好,最好钉两块木板,把画挂上面。” 他找来锯子还没有锯两三下,又说:“不行,这锯子,得磨一磨。”于是,他丢下锯子去拿锉刀。锉刀拿来了,他又发现在使用锉刀之前,必须得给锉刀安个把柄。为此,他拿起斧头到屋外的一个灌木丛里去寻找小树。就在要砍树时,他又发现生满老锈的斧头实在是不能用,必须得磨一下……当这个邻居为磨斧头找不到磨石,又去买锯子而忙乎时,画早已钉在了墙上。


      有些事情原来并不复杂,可是由于人的思维惯性最终在寻求解决方案的过程中忘记了根本目标,最终里目标越来越远。

       当然不是说传统的软件过程是离软件开发的终极目标——创建满足客户需求的可运行的应用程序越来越远了,但是很多软件公司在实施软件过程管理的过程中过于强调文档、过程步骤的作用,以至于许多项目管理人员都把精力放在了监督程序员编写众多中看不中用的文档、报告上面了。这样是不是违反了引入软件工程的初衷呢?

      让我们看看敏捷软件开发的宣言吧:


  • 个体和交互                 胜过              过程和工具

  • 可以工作的软件         胜过              面面俱到的文档

  • 客户合作                    胜过               合同谈判

  • 响应变化                    胜过               遵循计划

      强调这些原则并不是说过程和工具、文档、合同、计划毫无用处,而仅仅是为了警醒世人软件开发的根本目标之所在。


      再强调一点,强调前者不是要忽略后者,仅仅说明前者的重要性胜于后者,在进行软件过程敏捷化改造的过程中应该保证前者目标的实现,后者应该是在前者能够被保证遵循的过程上补充的。