您的位置:首页 > 编程语言 > Java开发

Expresso控制器的深入使用研究

2005-11-28 09:02 507 查看
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参数或Html的Form对象,在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对象,所用到的容器对象主要有Input、Output、Transition、Block。

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对象很容易作为表里的一个字段的载体:名字为字段名,内容为字段值,属性为字段的多个属性。不要将之对应为记录,这样将丢失字段属性信息。更一般情况,我们仅使用Name和Content两个属性来传递字段内容。Input和Output都可以用来传递字段信息,只是它们一个代表入一个代表出,所表达的具体逻辑意义不同而已。有人会问,那我是否可以将它们倒过来用?答案是肯定的,只是使用的感觉就象开车逆道行使而已。 可以使用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

多个Input、Output、Transition对象组成一个控制器的输出成分,如果我们对这些元素不加以分类组织,势必造成混乱。最终导致我们的表示层设计人员将不知道怎样去读取这些信息。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对象:表示表概念的名字为table的Block对象和表示记录概念的名字为recordX的Block对象,这样Input、Output、Transition对象便扮演了字段概念。 

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标签库的嵌套是根据id和name来嵌套的,而property则是一个重要的条件判断控制点。事实上,对于表字段对象类型不单一(同时有Output、Transition等)的情况,不管是Expresso标签库还是Expresso扩展Struts标签库,均只有通过组织特殊的字段对象结构(并列或嵌套)来处理。由于不能直接使用字段对象的名称来访问对象,感觉起来不是很方便,此功能大概需要我们编写自己的标签库得以实现吧(这成为我后面工作的一个研究点)。对于上述问题,是否一定得开发标签库解决呢?那倒不一定,下面将描述一种方法解决此问题,只是该方法有点不符合MVC标准而已。有些人会说:“那么多的标签库,学起来都需要时间,且都是应对某一类特殊情况的,我现在马上就要用,一下子又在标签库里找不到我想要的标签,那我怎么办?是否可以压根就不用标签库呢?”现实就是这样,不可能规定所有人的思想方式成一个模型。针对上述情况,本人的建议是:事情紧迫,先就不用标签处理;日后有时间了,再研究是否可用已有标签表达,若不行,则将你的处理方法抽象成标签库。不用标签又怎么处理呢?以下的方法告诉你采用传统的方式(纯JSP)处理控制器传回来的那些对象。首先,你应该知道控制器传回来的结果结构和内容。若控制器是你本人开发的,这便不是问题;若不是你本人开发的,则只有让项目运行,在你访问该控制器的URL后加上&style=xml&xsl=none参数,你就可以看到传回结果的XML格式的数据了(看XML的内容时,请把注意点放在block、input、output、transition等节点内容)。接下来就是采用代码访问了,代码内容大致如下:<%@ 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,提出了一些新观点,告诉我们不可以简单地把Expresso和Struts进行类比。可以肯定的是,它们的MVC思想是相通的,是一脉相承的。 

8         参考资料

Orange Trader Example Web ApplicationExpresso 5.5 Developer's Guide 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息