Expresso控制器的深入使用研究

来源:互联网 发布:chrome下载 知乎 编辑:程序博客网 时间:2024/06/06 18:17

Expresso控制器的深入使用研究

 

 

修订历史记录

日期

版本

说明

作者

2005-08-15

1.0

初建

唐家平

 

 

 

 

 

 

 

 

 

 

 

1     前言... 1

2     控制器的输入... 2

3     控制器的输出对象... 3

3.1        Input 3

3.1.1    Input常用表达用标签... 4

3.2        Output 4

3.2.1    Output常用表达用标签... 5

3.3        Transition. 5

3.3.1    Transition常用表达用标签... 5

3.4        Block. 6

3.4.1    Block常用表达用标签... 7

4     描述控制器的有力武器……控制器活动图... 10

5     控制器的状态... 11

5.1        内部式... 11

5.2        外部式... 12

5.3        控制状态的跳转... 13

5.4        选择合适的视图... 13

6     控制器的错误处理... 14

7     总结... 14

8     参考资料... 14

 

 

1         前言

控制器是业务逻辑层的象征,是所有用户动作的入口点。控制器可以看作是有限状态的机器,它由许多的状态组成,其中肯定有一个初始化状态和一个最后状态,初始化状态通常是输入状态,最后状态则通常是成功操作状态。

控制层和表示层结合较紧密,在描述控制层时,不得不带入表示层的一些常用知识。这种对应描述目的是反映一种通常用法,基本能应付处理程序设计中的一般问题。更具体的表示层使用说明可直接参考Expresso Developer's Guide,或参考本人将编写的“表示层使用研究”的文章。

文章里的例子代码均可以拷贝到你的项目中使用,只是具体的使用环境需要你稍作修改而已。

 

2         控制器的输入

控制器的数据输入通常是URL参数或HtmlForm对象,在Expresso控制器里,URL参数读取如下:

req.getParameter("dbobj")

req是控制器对象ControllerRequest的实例。

 

Form对象的读取则通常可采用Expresso DBMaint提供的方法,将数据读入DBObject对象里,这里把DBObject对象当作Model来使用。调用方法通常如下:

    DataObject myDBObj = this.getDataObject();

    ErrorCollection ee = new ErrorCollection();

 

    try {

      setFormCache();

      myDBObj.clear();

myDBObj =

DefaultAutoElement.getAutoControllerElement().parseDBObject(req, myDBObj, ee);

      clearFormCache();

     

      //...

 

    } catch (DBException de) {

}

(引自com.jcorporate.expresso.services.controller.dbmaint. AddUpdate.java

 

parseDBObject()方法实现核心思想如下:

for (Iterator i =

  myDBObj.getMetaData().getFieldListArray().iterator();

  i.hasNext();

  ) {

  String oneFieldName = (String) i.next();

  String oneParameterValue = request.getParameter(oneFieldName);

  myDBObj.set(oneFieldName, oneParameterValue);

}

具体细节在com.jcorporate.expresso.services.controller.ui. DefaultAutoElement

 

可以看出,数据的传入本质上都是使用request.getParameter()方法。

 

3         控制器的输出对象

我们处理的结果最终是要传递给UI表示层的,通常情况下,正如一些“框架”一样,我们采用Hashmap。在Expresso里,采用组织好一个ControllerResponse对象,所用到的容器对象主要有InputOutputTransitionBlock

3.1       Input

一个Input对象通常产生一个Form的一项内容,我们很容易将之对应于表里的一个字段。通常一个带有Input对象的状态输出是下一个状态的输入参数。于此同时,Input对象还提供“Hints”和下拉列表方式的值校验。典型使用如下:

  Input favcolour = new Input();

  favcolour.setName("FavColour");

  favcolour.setLabel("Favourite Colour?");

  favcolour.setType(Input.ATTRIBUTE_LISTBOX);

  favcolour.setDefaultValue("Red");

  favcolour. setDescription ("select Colour");

  Vector vec = new Vector();

  vec.addElement( new ValidValue( "Red", "Red as the Devil") );

  vec.addElement( new ValidValue( "Green", "Green as an olive") );

  vec.addElement( new ValidValue( "Blue", "Blue as the azure") );

  vec.addElement( new ValidValue( "Yellow", "Yellow as a lemon") );

  vec.addElement( new ValidValue( "Brown", "Brown like milk chocolate") );

  vec.addElement( new ValidValue( "White", "White as snow") );

  vec.addElement( new ValidValue( "Black", "Black as the farside of the moon") );

  favcolour.setValidValues( vec );

  myResponse.addInput(favcolour);

以上是创建了一个下拉列表输入框;一般情况下,我们若要创建一个输入文本框时,代码如下:

  Input firstname = new Input();

  firstname.setName("FirstName");

  firstname.setLabel("First Name");

  firstname.setDisplayLength(30);

  firstname.setMaxLength(30);

  firstname.setType("text");

  firstname.setDescription("");

  myResponse.addInput(firstname);

(引自com.xenonsoft.orangetrader.ot2.controller. InteractiveController.java

 

Expresso DBMaint里, Input对象是根据数据库表字段属性来创建的。可以看到形如下语句(增加记录):

Input i =

DefaultAutoElement.getAutoControllerElement().renderDBObjectField(

getControllerResponse(), myDBObj, oneFieldName, cachedValue, readOnly);

(引自com.jcorporate.expresso.services.controller.dbmaint.Add.java

对于下拉列表字段的处理,由于在数据对象已经建立关联,这里仅需要简单的调用即可,DBMaint里可以看到如下代码:

  oneField.setAttribute(Input.ATTRIBUTE_MULTIVALUED, "Y");

  oneField.setAttribute(Input.ATTRIBUTE_DROPDOWN, "Y");

  oneField.setType(Input.ATTRIBUTE_DROPDOWN);

  java.util.List values = dbobj.getValidValuesList(fieldName);

  oneField.setValidValues(new Vector(values));

若我们编码,由于数据对象已经非常明确,甚至可以更简单地调用:

  oneField.setType(Input.ATTRIBUTE_DROPDOWN);

  oneField.setValidValues(myDBObj.getValidValues(fieldName));

 

3.1.1    Input常用表达用标签

Expresso对应的tag<expresso:InputTag name="" />,通常情况下,我们就在表示层界面简单地这样使用Input对象的标签,标签库会自动地根据控制器传来的具体内容而进行相对的显示。

 

Expresso DBMaint里,使用的是Expresso扩展Struts的标签库,形式如下,以遍历所有Input对象:

<logic:iterate id="oneInput" property="inputs">

  <bean:define id="inputName" type="java.lang.String"

     name="oneInput" property="name"/>

  <label for="<%=inputName%>"><bean:write

     name="oneInput" property="label"/>:</label>

 

  <logic:present name="oneInput" property="@multiValued">

     <html:select styleClass="jc-formfield" name="oneInput"/>

  </logic:present>

  <logic:notPresent name="oneInput" property="@multiValued">

     <html:text styleClass="jc-formfield" name="oneInput"/>

  </logic:notPresent>

</logic:iterate>

(引自expresso/jsp/dbmaint/add.jsp

 

3.2       Output

Output对象用来传递一个或一组简单的字符串(String)信息,也可以传递一个由多个Output对象嵌套的树。通常使用如下:

  Output outJimi = new Output();

  outJimi.setName("Hendrix");

  outJimi.setAttribute("fullname", "James Marshall Hendricks");

  outJimi.setAttribute("guitar", "White Fender 1967");

  outJimi.setStyle("Electric Blues Rock");

  outJimi.setContent("The most influential guitarist in the twentieth century.");

  outJimi.setAlignment("apolitical");

  myResponse.addOutput(outJimi);

以上的例子是传递了一组信息,即一个人的基本信息,通过重复调用setAttribute()方法可以加入更多的信息。这样看起来,一个Output对象很容易作为表里的一个字段的载体:名字为字段名,内容为字段值,属性为字段的多个属性。不要将之对应为记录,这样将丢失字段属性信息。更一般情况,我们仅使用NameContent两个属性来传递字段内容。

InputOutput都可以用来传递字段信息,只是它们一个代表入一个代表出,所表达的具体逻辑意义不同而已。有人会问,那我是否可以将它们倒过来用?答案是肯定的,只是使用的感觉就象开车逆道行使而已。

 

可以使用addNested()方法来嵌套Output对象,多重嵌套便产生了一颗树,树的操作较为复杂,此处就不展开描述了。

 

3.2.1    Output常用表达用标签

Expresso标签库里对应的tag为如下形式:

<expresso:OutputTag name="Hendrix" >

  <expresso:ContentTag/>

  <expresso:AttributeTag name="fullname" />

</expresso:OutputTag>

可以看出以上三类标签的嵌套结构,这种结构是对应Output对象的,也就是说嵌套关系是不可变的。只是嵌套内部的的元素顺序是可变的,对于一个Output对象包含一组信息的情况,我们很容易使用Html里的<Table>标签和上述标签来展现。

 

Expresso DBMaint里,则通常简单地采用

<bean:write property="untable/pageHeader"/>标签表达。

 

3.3       Transition

Transition对象提供用户转入下一状态的一个路径,当然这个路径是当前状态允许的,且当前用户拥有所要转入状态的权限。值得说明的是,它不提供跳到其它网站URL的功能(没有setUrl()方法),即仅限于本网站控制器间、状态间的跳转。通常使用如下:

    Transition fireTransition = new Transition();

    fireTransition.setLabel("Fire Button");

    fireTransition.setName("fireButton");

    fireTransition.setControllerObject(getClass().getName());

    fireTransition.addParam("state", "showResults");

    if (myRequest.getParameter("next") != null)

      fireTransition.addParam("next", myRequest.getParameter("next"));

    myResponse.addTransition(fireTransition);

 

3.3.1    Transition常用表达用标签

Expresso标签库里对应的tag为如下形式:

<expresso:TransitionTag name="fireButton" />

值得注意的是,该标签将一个Transition输出为4<Input>Html标签,这些标签需要和<Form>连用,且这些标签将使<Form>Action属性失效。输出结果如下:

<input type="HIDDEN" name="fireButton_params" value=

  "controller=com.xenonsoft.orangetrader.ot2.controller.InteractiveController

    ?state=showResults">

<input type="HIDDEN" name="fireButton_encoding" value="u">

<INPUT TYPE="submit" NAME="button_fireButton" VALUE=

  "Fire Button" CLASS="null" >

<INPUT TYPE="hidden" NAME="cmd" VALUE="button" >

 

Expresso DBMaint里,则使用如下方式的标签,以遍历显示存在于容器里的所有Transition

<logic:iterate id="oneTransition" property="transitions">

  <html:submit name="oneTransition"/>

</logic:iterate>

<bean:define id="url" name="oneCol" property="/edit.url" type="java.lang.String"/>

<html:link page="<%= url %>"><bean:write name="oneCol"/></html:link>

 

以上3种对象位于Response的根时,这些标签可直接使用;若位于Block对象中,则需要按照Block组织规则来定位再使用。

 

3.4       Block

多个InputOutputTransition对象组成一个控制器的输出成分,如果我们对这些元素不加以分类组织,势必造成混乱。最终导致我们的表示层设计人员将不知道怎样去读取这些信息。Block对象正是这样一个组织它们的容器,它使得前3种对象按逻辑组织得井井有条。如果说将以上3种对象看作是一颗树(或森林)上的节点,Block描述的正是那颗树(或森林)的结构。

 

通常情况下,我们用Block加上前3种对象来传递一张数据库表的信息,当然这个表的记录条数是可知而有限的,否则这些数据将耗尽你的内存。下面就是一个例子,实现显示一个商品列表的代码。

  protected void runListState(ControllerRequest myRequest,

                              ControllerResponse myResponse

                              ) throws ControllerException {

    try {

      Goods lst = new Goods();

      Goods one = null;

      Block table = new Block("table");

      myResponse.addBlock(table);

      int q = 0;

      for (Iterator e = lst.searchAndRetrieveList()

           .iterator(); e.hasNext(); ) {

        one = (Goods) e.next();

        Block record = new Block("record" + (q + 1));

        table.add(record);

 

        Output out1 = new Output();

        out1.setName("GOODS_ID");

        out1.setAlignment("left");

        out1.setContent(one.getField("GOODS_ID"));

        record.add(out1);

 

        Output out2 = new Output();

        out2.setName("GOODS_NAME");

        out2.setAlignment("left");

        out2.setContent(one.getField("GOODS_NAME"));

        record.add(out2);

 

        // Add an transition

        Transition infoTransition =

            new Transition("Get detail", getClass().getName());

        infoTransition.setName("detail");

        infoTransition.addParam("state", "showDetail");

        infoTransition.addParam("goods_id", one.getField("GOODS_NAME"));

        record.add(infoTransition);

      }

 

      myResponse.setStyle("list");

 

    }

    catch (Exception ex) {

      throw new ControllerException(ex.getMessage());

    }

}

 

上例我们使用了2种意思的Block对象:表示表概念的名字为tableBlock对象和表示记录概念的名字为recordXBlock对象,这样InputOutputTransition对象便扮演了字段概念。

 

3.4.1    Block常用表达用标签

采用Expresso标签库进行表示层(View)的JSP代码如下:

<table border="1" cellspacing="0" cellpadding="1">

<expresso:Block name="table">

 

<!--

  <expresso:TableHead value="Block|Content|Action"/>

-->

  <tr bgcolor="#000099">

    <td width="15%"><font color="#FFFFFF"> Block </font></td>

    <td><font color="#FFFFFF"> Content </font></td>

    <td><font color="#FFFFFF"> Action </font></td>

  </tr>

 

  <expresso:ElementCollection type="block">

    <expresso:ElementIterator>

 

      <tr>

        <td bgcolor="#E0FFE0" width="15%">***</td>

 

        <expresso:ElementCollection type="output">

          <expresso:ElementIterator>

            <expresso:OutputTag name="xxx">

              <td><expresso:ContentTag /> </td>

            </expresso:OutputTag>

          </expresso:ElementIterator>

        </expresso:ElementCollection>

 

        <expresso:ElementCollection type="transition">

          <expresso:ElementIterator>

            <form

              action="<%= contextPath %>/Demo.do?cmd=button"

              method="GET">

            <td><expresso:TransitionTag name=" detail " /></td>

            </form>

          </expresso:ElementIterator>

        </expresso:ElementCollection>

      </tr>

 

    </expresso:ElementIterator>

  </expresso:ElementCollection>

 

</expresso:Block>

</table>

 

若采用Expresso扩展Struts的标签库,在Expresso DBMaint里可以看到如下JSP代码(摘自expresso/jsp/dbmaint/list.jsp,为只描述结构,删除了隔行颜色显示等功能):

<table border="1" cellspacing="0" cellpadding="1">

  <bean:define id="head" type="java.lang.String"

     property="recordList.@header-row"/>

  <expresso:TableHead value="<%= head %>"/>

  <logic:present property="recordList/1">

    <logic:iterate id="oneRow" property="recordList" indexId="rowCount">

       <tr>

       <struts_logic:iterate id="oneCol" name="oneRow" property="nested"

           type="com.jcorporate.expresso.core.controller.ControllerElement">

         <logic:present name="oneCol" property="/edit">

           <bean:define id="url" name="oneCol" property="/edit.url"

              type="java.lang.String"/>

           <td>

             <html:link page="<%= url %>">

               <bean:write name="oneCol"/>

             </html:link>

           </td>

         </logic:present>

         <logic:notPresent name="oneCol" property="/edit">

           <td>

             <bean:write name="oneCol"/>

           </td>

         </logic:notPresent>

       </struts_logic:iterate>

       </tr>

   </logic:iterate>

  </logic:present>

</table>

<logic:notPresent property="recordList/1">

  <h3 align="center" >

    No Records Found

  </h3>

</logic:notPresent>

应该注意到,以上两种标签库描述的表对象的差别,前者的Transition逻辑上是和Output对象平行的;而后者的Transition对象是嵌套在一系列Output对象里的其中一个Output对象里。

Expresso扩展Struts标签库的嵌套是根据idname来嵌套的,而property则是一个重要的条件判断控制点。

事实上,对于表字段对象类型不单一(同时有OutputTransition等)的情况,不管是Expresso标签库还是Expresso扩展Struts标签库,均只有通过组织特殊的字段对象结构(并列或嵌套)来处理。由于不能直接使用字段对象的名称来访问对象,感觉起来不是很方便,此功能大概需要我们编写自己的标签库得以实现吧(这成为我后面工作的一个研究点)。

对于上述问题,是否一定得开发标签库解决呢?那倒不一定,下面将描述一种方法解决此问题,只是该方法有点不符合MVC标准而已。

有些人会说:“那么多的标签库,学起来都需要时间,且都是应对某一类特殊情况的,我现在马上就要用,一下子又在标签库里找不到我想要的标签,那我怎么办?是否可以压根就不用标签库呢?”

现实就是这样,不可能规定所有人的思想方式成一个模型。针对上述情况,本人的建议是:事情紧迫,先就不用标签处理;日后有时间了,再研究是否可用已有标签表达,若不行,则将你的处理方法抽象成标签库。不用标签又怎么处理呢?以下的方法告诉你采用传统的方式(纯JSP)处理控制器传回来的那些对象。

首先,你应该知道控制器传回来的结果结构和内容。若控制器是你本人开发的,这便不是问题;若不是你本人开发的,则只有让项目运行,在你访问该控制器的URL后加上&style=xml&xsl=none参数,你就可以看到传回结果的XML格式的数据了(看XML的内容时,请把注意点放在blockinputoutputtransition等节点内容)。

接下来就是采用代码访问了,代码内容大致如下:

<%@ page import="com.jcorporate.expresso.core.controller.*" %>

<%

  ControllerResponse myResponse =

     (ControllerResponse)request.getAttribute( "myResponse" );

  Input oneInput= myResponse.getInput("FirstName");

  //...

  Block table= myResponse.getInput("Table");

  Block record= table.getBlock("Record");

  Output field= table.getInput("FieldName");

  //...

 %>

 

 

4         描述控制器的有力武器……控制器状态活动图

象设计类的人员使用类图一样,我们在设计控制器时,可以使用控制器状态活动图来描述控制器各状态间的关系。我们规定出基本图形符号如下:

状态:椭圆;

状态的输出:带名字的箭头;(更多情况指跳转Transition

 

下图就是一个简单的例子,描述的是学生网上选课的过程。开始状态是“Prompt”,该状态输出三个对象:一个Input(学生编号)、两个Transition(操作),也就是说,负责该状态表示层(View)的.jsp应该对应有这三个对象的呈现,只是图中并未描述View的形式而已。

还值得说明的是,开始状态产生的Input(学生编号)的值,它将随着动作的进行作为参数传往下一个状态。在“SELECT COURSE”状态的输出Input对象则是一个下拉列表方式的,选择的值同样作为参数传给了后续状态。

 

若使用Expresso框架进行设计,我们又多了一种设计的表达方法……状态活动图,大家学会它,我们又多了一种沟通的工具。

 

5         控制器的状态

控制器的每一种状态就是一个动作的入口,我们可以将之看作是一个类的一个方法,也可以将这个方法独立出来成一个单独的类。这样,控制器状态便存在2种开发形式:内部式和外部式。

5.1       内部式

一般情况下,这是我们常用的一种开发方式,这种方式的典型结构如下:

package com.zoomtech.controller;

 

import com.jcorporate.expresso.core.controller.*;

import com.zoomtech.obj.*;

import java.util.Iterator;

 

public class Demo extends Controller {

  public Demo() {

    super();

 

    State index = new State("index", "my index");

    addState(index);

 

    setInitialState("index");

  }

 

  public String getTitle() {

    return "Demo Expresso";

  }

 

  protected void runIndexState(ControllerRequest myRequest,

                               ControllerResponse myResponse

                               ) throws ControllerException {

    myResponse.setStyle("index");

 

  }

 

}

 

在这种开发方式里面,可以看到控制器用一个类来描述,而它的所有状态则对应为它的一个个受保护方法。这些受保护方法有着规定的名字:run+状态名(首字母大写)+State,且返回为void。当然,这些状态还是要通过控制器的addState()方法注册到控制器才能使用。

 

5.2       外部式

这种方式通常被使用在一个比较复杂的控制器中,控制器使用一个类描述,其余每个状态对应一个类描述。他们之间通过addState()addParameter()getController()等方法进行通讯。addParameter()方法相当于在请求URL后面增加了一个参数,只是这个参数是在控制器编码时使用而已。

下例为上传文件控制器的部分代码:

public Upload() {

   PromptBrowser browser = new PromptBrowser("browser",

     "Prompt for Upload from Browser");

   browser.addParameter("resource", false);

   addState(browser);

  

   DoBrowser dobrowser = new DoBrowser("dobrowser",

     "Process  Upload from Browser");

   dobrowser.addParameter("action");

   addState(dobrowser);

}

 

 

public class PromptBrowser extends State {

   public PromptBrowser(String stateName, String descrip) {

     super(stateName, descrip);

   }

 

   public void run() throws ControllerException {

     Controller myController = getController();

     String currentNumber =

       StringUtil.notNull(myController.getParameter("number"));

   }

}

 

建议:通常情况下,我们均使用“内部式”进行开发。当出现业务复杂的情况时,为避免所有代码在一个类里造成程序可读性降低,请另外创建一个没有继承的类来描述业务逻辑,而不是直接将方法“挂”在控制器里。

 

5.3       控制状态的跳转

有时候,我们的程序逻辑需要从一个状态跳到另外一个状态……这种跳转并不需要用户动作。这主要分为一下2种情况:

在一个控制器中的状态间跳转,不管状态是内部式和外部式,跳转的方法均一样,即调用控制器的transition()方法,具体形式如下:

this.transition("newstate", req, res);

return;

 

在不同控制器中的状态间跳转,这种情况就稍微复杂些,必须通过Transition来实现。例子如下:

Transition t = new Transition();

t.setName("goSomewhere");

t.addParameter("controller", "com.something.SomeOtherController");

t.addParameter("state", "someState");

t.addParameter("otherParameter", "etc");

t.transition(req, res);

return;

其中“goSomewhere”可以是任意的,但不可不调用setName()方法。

 

5.4       选择合适的视图

控制器的状态最终会将操作结构输出到一定的视图,这些视图均为在xxx-config.xml配置文件的forward别名。然而,某一状态将调用哪个视图呢?

默认情况下,状态调用的是和状态名称一样的forward别名。若我们想在写程序时动态调用forward别名时,则可以通过setStyle()方法得以实现,具体如下:

myResponse.setStyle("employeeForm");

建议所有的程序员这样显式调用该方法完成视图的选择,以增强代码的可读性。

 

6         控制器的错误处理

在控制器状态的方法里抛出ControllerException类型的错误均可由Expresso框架自动捕捉,错误信息由Struts-config.xml里配置的全局forward别名交由具体的JSP页面,设置通常如下:

<global-forwards>

  <forward name="error" path="/expresso/jsp/showerror.jsp"/>

</global-forwards>

 

在编写程序时,我们并不想这样简单地抛出错误,而是对于可预测性错误进行处理。Expresso提供一个ErrorCollection对象容器让我们收集错误信息,然后通过调用控制器的saveErrors()传出去。在页面通过如下标签显示即可:

<expresso:IfErrorExists>

 <expresso:ErrorTag/>

</expresso:IfErrorExists>

 

7         总结

本人在编写此文时,曾多次尝试将DBMaint作为例子来说,可是结果是花了本人不少时间寻找典型代码段落不说,找到某功能的实现点其实复杂而晦涩难懂,因而只得作罢。事实上,DBMaint为了增强功能的通用性、自动性,而不得不牺牲了代码的可读性。比如针对表字段的不同类型,往往是很长一段的If结构。条件的增多带来代码行急剧增多,又不得不采用一层层的函数调用。可以说,DBMaint为达到它作为通用数据库工具的目的,设计和编码都是非常成功的,若作为别的方面使用,是值得商榷的。

将控制器和视图分离,也就是将逻辑层和表示层分离,其核心思想就是分离,这正是大师的Observer设计模式。进一步利用这种思想,我们完全可以在开发控制器时,将更纯的业务逻辑分离出来,这时控制器就简化为一个动作的入口点和出口结果的组织点。

Expresso封装了Struts,提出了一些新观点,告诉我们不可以简单地把ExpressoStruts进行类比。可以肯定的是,它们的MVC思想是相通的,是一脉相承的。

 

8         参考资料

Orange Trader Example Web Application

Expresso 5.5 Developer's Guide

 

原创粉丝点击