Dashboard > AppFuseCN > Appfuse中文化 > Using Struts 2
AppFuseCN Log In   View a printable version of the current page.
Using Struts 2
Added by woshumao, last edited by woshumao on Jul 13, 2007  (view change)
Labels: 

使用 Struts 2

关于本指南

本指南将向你展示如何利用Struts 2 创建控制界面和细节界面。列表(控制)界面有能力按列进行排序,一次可以处理25条记录。表单(细节)界面使用优雅的CSS 表单布局(蒙Wufoo授权),你也可以通过修改客户端和服务端的认证机制来增强用户体验。

本指南假设你已经利用appfuse-basic-struts原型创建了一个工程并且已经阅读了Persistence and Services 指南。如果你正在使用appfuse-modular-struts 原型,请将你的思路转换到使用web module作为根目录的方法上来。如果你使用了除Struts以外的其他web框架来创建你的工程,你可能会对本指南感到困惑,这些内容对你也就没有任何用处了。

目录
  1. Struts 2 简介
  2. 创建 PersonActionTest
  3. 创建可以获得people的PersonAction
  4. 创建用来显示结果的personList.jsp
  5. 修改PersonActionTest和PersonAction 为了edit(), save() and delete() 方法
  6. 创建personForm.jsp来修改person
  7. 修改Validation
  8. 创建Canoo WebTest来测试类似游览器的action
  9. 向菜单中添加链接
    源代码

    本指南的代码存放于Google Code上的appfuse-demos工程中的tutorial-struts2模块。使用以下指令从Subversion中将代码check out:
    svn checkout http://appfuse-demos.googlecode.com/svn/trunk/tutorial-struts2

    Struts 2简介

Struts 2(以前被叫做WebWork)是一个简单易行的web框架。它在一个通用的命令行框架XWork基础上构建。XWork也具有一个IoC容器,但不如Spring的特征那样完整,当然本节的内容并不包含这个。Struts 2的控制器被叫做Action,主要原因是它们必须实现Action接口。ActionSupport类就实现了这个接口,它是Struts 2中的大多数的action的父类。下图说明了Struts 2如何与一个web应用程序构架相结合。


着图片搞不定了啊!

典型的Struts 2 action包含访问模型属性的方法和返回字符串的方法。这些字符串将和struts.xml配置文件中的result names进行匹配。典型的action只包含一个execute() 方法,但要利用URL和按钮的名字来添加多个方法和control execution也是很容易的。

Struts 2使用拦截器来拦截request和response过程。这种行为和Servlet Filter极为相似,除非你可以和action直接对话。Struts 2在框架本身中使用拦截器。其中一部分对Action进行初始化,为它的population做准备,为其中的各个变量赋值并处理任何转换过程中的错误。

创建PersonActionTest

测试是任何一个应用中的重要组成部分,测试一个Struts应用是再简单不过的事情了。XWork所提供的通用命令行模式根本不依赖Servlet API。这就使得使用JUnit来测试Action变得极为容易。

在src/test/java/**/webapp/action中创建一个类PersonActionTest.java

package org.appfuse.tutorial.webapp.action;

import com.opensymphony.xwork2.ActionSupport;
import org.appfuse.service.GenericManager;
import org.appfuse.tutorial.model.Person;
import org.appfuse.webapp.action.BaseActionTestCase;

public class PersonActionTest extends BaseActionTestCase {
    private PersonAction action;

    @Override
    protected void onSetUpBeforeTransaction() throws Exception {
        super.onSetUpBeforeTransaction();
        action = new PersonAction();
        GenericManager personManager = (GenericManager) applicationContext.getBean("personManager");
        action.setPersonManager(personManager);

        // add a test person to the database
        Person person = new Person();
        person.setFirstName("Jack");
        person.setLastName("Raible");
        personManager.save(person);
    }

    public void testSearch() throws Exception {
        assertEquals(action.list(), ActionSupport.SUCCESS);
        assertTrue(action.getPersons().size() >= 1);
    }
}

这个类现在还不能编译,因为类PersonAction需要先创建出来。

创建可以获得people的PersonAction

在src/main/java/**/webapp/action中创建继承AppFuse的BaseAction的类PersonAction.java

package org.appfuse.tutorial.webapp.action;   
  
import org.appfuse.webapp.action.BaseAction;   
import org.appfuse.tutorial.model.Person;   
import org.appfuse.service.GenericManager;   
  
import java.util.List;   
  
public class PersonAction extends BaseAction {   
    private GenericManager<Person, Long> personManager;   
    private List persons;   
  
    public void setPersonManager(GenericManager<Person, Long> personManager) {   
        this.personManager = personManager;   
    }   
  
    public List getPersons() {   
        return persons;   
    }   
  
    public String list() {   
        persons = personManager.getAll();   
        return SUCCESS;   
    }   
}

典型的Struts 2 action既是控制器又是模型。在本例中,list()方法担当控制器的角色,getPersons()方法为模型处理数据。这种对MVC的简化处理使得这个web框架在编程过程中非常容易使用。

用你的IDE运行PersonActionTest或者运行命令mvn test -Dtest=PersonActionTest。

零配置

如果你不习惯使用XML来配置你的Action,你可以考虑使用Struts的Zero Configuration特性。对于AppFuse 2.0 M4而言,zero configuration是默认启用的。

创建用来显示结果的personList.jsp

创建src/main/webapp/WEB-INF/pages/personList.jsp页面来显示people列表。

<%@ include file="/common/taglibs.jsp"%>   
  
<head>   
    <title><fmt:message key="personList.title"/></title>   
    <meta name="heading" content="<fmt:message key='personList.heading'/>"/>   
</head>   
  
<c:set var="buttons">   
    <input type="button" style="margin-right: 5px"  
        onclick="location.href='<c:url value="/editPerson.html"/>'"  
        value="<fmt:message key="button.add"/>"/>   
       
    <input type="button" onclick="location.href='<c:url value="/mainMenu.html"/>'"  
        value="<fmt:message key="button.done"/>"/>   
</c:set>   
  
<c:out value="${buttons}" escapeXml="false" />   
  
<s:set name="persons" value="persons" scope="request"/>   
<display:table name="persons" class="table" requestURI="" id="personList" export="true" pagesize="25">   
    <display:column property="id" sortable="true" href="editPerson.html"    
        paramId="id" paramProperty="id" titleKey="person.id"/>   
    <display:column property="firstName" sortable="true" titleKey="person.firstName"/>   
    <display:column property="lastName" sortable="true" titleKey="person.lastName"/>   
  
    <display:setProperty name="paging.banner.item_name" value="person"/>   
    <display:setProperty name="paging.banner.items_name" value="people"/>   
  
    <display:setProperty name="export.excel.filename" value="Person List.xls"/>   
    <display:setProperty name="export.csv.filename" value="Person List.csv"/>   
    <display:setProperty name="export.pdf.filename" value="Person List.pdf"/>   
</display:table>   
  
<c:out value="${buttons}" escapeXml="false" />   
  
<script type="text/javascript">   
    highlightTableRows("personList");   
</script>

这个文件中最重要的一行是<display:table>标签上方的一行。也就是<s:set>标签。这个标签调用PersonAction.getPersons()并将resulting List中的值传送到request范围中,也就是<display:table>标签可以抓取的范围。这是必须的,因为用于显示的标签对于Struts 2中的数值栈ValueStack一无所知。

打开src/main/resources目录中的struts.xml文件。在文件末尾部分定义一个<action>并设定它的class属性和personAction中的bean相匹配。

<action name="persons" class="org.appfuse.tutorial.webapp.action.PersonAction" method="list">    
    <result>/WEB-INF/pages/personList.jsp</result>    
</action>

默认的结果类型为dispatcher,其名字为success。这种设定的结果类型只是当有success从PersonAction.list()中返回时,简单地跳转到personList.jsp文件。其他的结果类型包括redirect类型和chain类型。Redirect完成一个客户端的重定向,而chain则跳转到另一个action。若要了解所有的结果类型,请参看Struts 2的结果类型文档。

这个actioin的mothod属性有一个list属性。当URL "persons.html"被调用时list属性将调用list()方法。如果你不包含method属性,它将调用execute()方法。

打开src/main/resources/ApplicationResources.properties并对各个person属性添加i18n keys/values

# -- 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

在你的浏览器中运行mvn jetty:run-war 并访问http://localhost:8080/persons.html。用admin/admin登录,然后你将看到类似下图的界面。
又一张图

AppFuse的安全设定指定了所有*.html样式的url模式应当被保护(除了/signup.html 和 /passwordHint.html之外)。这就确保了客户端必须通过一个Action来获得一个JSP(或者至少是在WEB-INF/pages中的一个)。

CSS定制

如果你想要对某个特殊的页面定制CSS,你可以在文件的开头添加<body id="pageName"/>。它将被SiteMesh所处理,并放入最中生成的文件中。你可以用类似以下这种方式一页一页来定制你的CSS:
body#pageName element.class { background-color: blue }

修改PersonActionTest和PersonAction 的edit(), save() and delete() 方法

若要创建detail 界面,需对类PersonAction添加edit(), save(), and delete()方法。在你这样做之前,要记得对这些方法创建测试用例。
打开src/test/java/**/webapp/action/PersonActionTest.java并对修改,保存,删除操作进行测试。

public void testEdit() throws Exception {   
    log.debug("testing edit...");   
    action.setId(1L);   
    assertNull(action.getPerson());   
    assertEquals("success", action.edit());   
    assertNotNull(action.getPerson());   
    assertFalse(action.hasActionErrors());   
}   
  
public void testSave() throws Exception {   
    MockHttpServletRequest request = new MockHttpServletRequest();   
    ServletActionContext.setRequest(request);   
    action.setId(1L);   
    assertEquals("success", action.edit());   
    assertNotNull(action.getPerson());   
       
    // update last name and save   
    action.getPerson().setLastName("Updated Last Name");   
    assertEquals("input", action.save());   
    assertEquals("Updated Last Name", action.getPerson().getLastName());   
    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(2L);   
    action.setPerson(person);   
    assertEquals("success", action.delete());   
    assertNotNull(request.getSession().getAttribute("messages"));   
}

这个类还不能够被编译直到你需要更新你的src/main/java/**/action/PersonAction.java类。cancel和delete方法将捕获cancel和delete按钮的点击事件。execute()方法将form中的各个action分发到相应的方法中。

private Person person;   
private Long id;   
  
public void setId(Long id) {   
    this.id = id;   
}   
  
public Person getPerson() {   
    return person;   
}   
  
public void setPerson(Person person) {   
    this.person = person;   
}   
  
public String delete() {   
    personManager.remove(person.getId());   
    saveMessage(getText("person.deleted"));   
  
    return SUCCESS;   
}   
  
public String edit() {   
    if (id != null) {   
        person = personManager.get(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);   
  
    person = personManager.save(person);   
  
    String key = (isNew) ? "person.added" : "person.updated";   
    saveMessage(getText(key));   
  
    if (!isNew) {   
        return INPUT;   
    } else {   
        return SUCCESS;   
    }   
}

仔细研究你的PersonActionTest,你会发现所有的test依赖于在数据库中拥有一个id=1的记录 (但对于testRemove,id=2),因此我们应当在样例sample数据文件中添加这些记录(src/test/resources/sample-data.xml)。从文件结尾处添加即可----记录的顺序并不重要因为当前它和其他表之间并不存在关系。如果你已经有这个表,要保证第二条记录存在,否则testRemove()将不能正常工作。

<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>Bob</value>   
    <value>Johnson</value>   
  </row>   
</table>

在任何test运行之前,DbUnit会将这个文件载入,这样你的Action test就可以访问这些记录。
保存所有的文件,然后利用命令 mvn test -Dtest=PersonActionTest 运行PersonActionTest中的test


BUILD SUCCESSFUL
Total time: 31 seconds

创建personForm.jsp来修改个人信息

创建页面src/main/webapp/WEB-INF/pages/personForm.jsp来显示表单:

<%@ include file="/common/taglibs.jsp"%>   
  
<head>   
    <title><fmt:message key="personDetail.title"/></title>   
    <meta name="heading" content="<fmt:message key='personDetail.heading'/>"/>   
</head>   
  
<s:form id="personForm" action="savePerson" method="post" validate="true">   
<s:hidden name="person.id" value="%{person.id}"/>   
  
    <s:textfield key="person.firstName" required="true" cssClass="text medium"/>   
    <s:textfield key="person.lastName" required="true" cssClass="text medium"/>   
  
    <li class="buttonBar bottom">            
        <s:submit cssClass="button" method="save" key="button.save" theme="simple"/>   
        <c:if test="${not empty person.id}">    
            <s:submit cssClass="button" method="delete" key="button.delete" onclick="return confirmDelete('person')" theme="simple"/>   
        </c:if>   
        <s:submit cssClass="button" method="cancel" key="button.cancel" theme="simple"/>   
    </li>   
</s:form>   
  
<script type="text/javascript">   
    Form.focusFirstElement($("personForm"));   
</script>

Struts减少了你原先不得不在form中写入的HTML代码的总量。<s:form>标签替你创建了 <form> 以及 结构标签。<s:textfield>标签创建整行,包括用来容纳input field 的<ul> 和 <li> 标签。

然后,更新文件src/main/resources/struts.xml,包含"editPerson" 和 "savePerson"这两个action。

<action name="editPerson" class="org.appfuse.tutorial.webapp.action.PersonAction" method="edit">    
    <result>/WEB-INF/pages/personForm.jsp</result>   
    <result name="error">/WEB-INF/pages/personList.jsp</result>   
</action>   
  
<action name="savePerson" class="org.appfuse.tutorial.webapp.action.PersonAction" method="save">   
    <result name="input">/WEB-INF/pages/personForm.jsp</result>   
    <result name="cancel" type="redirect">persons.html</result>   
    <result name="delete" type="redirect">persons.html</result>   
    <result name="success" type="redirect">persons.html</result>   
</action>

运行mvn jetty:run-war,打开你的浏览器,访问http://localhost:8080/persons.html,并点击Add按钮。

图片阿图片。。。

填写first name和last name 域并点击Save按钮。这将跳跳转道list界面,界面中会显示一条成功信息,新录入的人员信息则会显示在list中。

显示成功信息

Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.1.3 Build:#408 Jan 23, 2006) - Bug/feature request - Contact Administrators