端到端J2ME开发——介绍Smart Ticket

package com.sun.j2me.blueprints.smartticket.server.web.midp;
public class SmartTicketServlet extends HttpServlet {
  public static final String SESSION_ATTRIBUTE_SMART_TICKET_BD =
    "com.sun.j2me.blueprints.smartticket.server.web.midp.SmartTicketBD";
  protected void doPost(HttpServletRequest request,
                        HttpServletResponse response)
                        throws ServletException, IOException {
    HttpSession session = request.getSession(true);
    SmartTicketBD smartTicketBD = (SmartTicketBD)
      session.getAttribute(SESSION_ATTRIBUTE_SMART_TICKET_BD);
    // Calls handleCall() method and encodes the URL for
    // session tracking
  }
  public int handleCall(SmartTicketBD smartTicketBD, 
     InputStream in, OutputStream out)
         throws IOException, ApplicationException {
    // Identifies the requested action method
    // Executes the method, as selected in a switch statement
    switch (method) {
    // cases ...
    case MessageConstants.OPERATION_GET_MOVIE: {
      getMovie(smartTicketBD, call, successfulResult);
      break;
    }
    // more cases ...
    }
  }
}
package com.sun.j2me.blueprints.smartticket.server.web.midp;
public class SmartTicketBD implements RemoteModel {
  public static final String EJB_REF_FACADE = 
      "ejb/SmartTicketFacade";
  private SmartTicketFacadeLocal facade;
  private ServletContext servletContext = null;
  public SmartTicketBD(ServletContext servletContext) 
                       throws ApplicationException {
    this.servletContext = servletContext;
    try {
      Context context = (Context)
        new InitialContext().lookup("java:comp/env");
      facade = ((SmartTicketFacadeLocalHome)
        context.lookup(EJB_REF_FACADE)).create();
      return;
    } catch (Exception e) {
      throw new ApplicationException(e);
    } 
  }
  public Movie getMovie(String movieKey) 
      throws ModelException, ApplicationException {
    try {
      MovieLocal movieLocal = facade.getMovie(movieKey);
      Movie movie = new Movie(movieLocal.getId(), 
                              movieLocal.getTitle(), 
                              movieLocal.getSummary(), 
                              movieLocal.getRating());
      return movie;
    } catch (SmartTicketFacadeException stfe) {
      throw new 
          ModelException(ModelException.CAUSE_MOVIE_NOT_FOUND);
    } catch (Exception e) {
      throw new ApplicationException(e);
    } 
  }
  // Other action methods in RemoteModel interface ...
}
package com.sun.j2me.blueprints.smartticket.server.ejb;
public class SmartTicketFacadeBean implements SessionBean {
  // ...
  public MovieLocal getMovie(String movieId) 
                    throws SmartTicketFacadeException {
    try {
      return movieHome.findByPrimaryKey(movieId);
    } catch (FinderException fe) {
      throw new 
          SmartTicketFacadeException("No matching movie.");
    } 
  }
  // ...
}

下图展示了整体 MVC 和界面的体系结构:

Image

实现模式

MVC 和界面模式定义了应用程序的整体结构。另外,Smart Ticket 也列出了一些重要的行为模式,
这些模式能帮助开发人员提高效率。

处理程序链

The RemoteModelProxy 类把每一个请求动作都委托给 handler 类链,以便透明地解决 RMS 串行化
和 HTTP 连接的异常管道。 链接的处理程序体系结构基于实现它的 RequestHandler接口和
RemoteModelRequestHandler 抽象类:

public interface RequestHandler {
    RequestHandler getNextHandler();
    void init() throws ApplicationException;
    void destroy() throws ApplicationException;
}
abstract public class RemoteModelRequestHandler 
  implements RequestHandler, RemoteModel {
  private RemoteModelRequestHandler nextHandler;
  private Preferences preferences;
  protected static ProgressObserver progressObserver;
  public RemoteModelRequestHandler(
      RemoteModelRequestHandler nextHandler) {
    this.nextHandler = nextHandler;
  }
  public RequestHandler getNextHandler() {
    return nextHandler;
  }
  public void init() throws ApplicationException {
    if (nextHandler != null) {
      nextHandler.init();
    }
    return;
  }
  public void destroy() throws ApplicationException {
    if (nextHandler != null) {
      nextHandler.destroy();
    }
    return;
  }
  public void login(String userName, String password) 
      throws ModelException, ApplicationException {
    getRemoteModelRequestHandler().login(userName, password);
    return;
  } 
  public void createAccount(AccountInfo accountInfo) 
                            throws ModelException, 
                            ApplicationException {
    getRemoteModelRequestHandler().createAccount(accountInfo);
    return;
  }
  // Other action methods declared in RemoteModel
  // ...
} 

 

具体的 handler 类扩展了 RemoteModelRequestHandler 类。嵌套的构造
函数建立一个处理程序链。Smart Ticket 启用了两个处理程序类:RMSCacheHandler
HTTPCommunicationHandler。因此链接装配方法如下:

public class RemoteModelProxy extends ModelObjectLoader 
                              implements RemoteModel {
  private RemoteModelRequestHandler requestHandlerChain;
  private Preferences preferences = null;
  private Hashtable movies = new Hashtable();
  public RemoteModelProxy(String serviceURL) 
                          throws ApplicationException {
    requestHandlerChain =
      new RMSCacheHandler(
        new HTTPCommunicationHandler(null, serviceURL));
    return;
  }
  // ...
  public Movie getMovie(String movieKey) 
                        throws ModelException, 
                        ApplicationException {
    Movie movie = (Movie) movies.get(movieKey);
    if (movie == null) {
      movie = requestHandlerChain.getMovie(movieKey);
      movies.put(movieKey, movie);
    } 
    return movie;
  } 
  // Other methods ...
}

在服务器端, SmartTicketServlet 首先确定在请求数据流中的第一个字节编码所表达的动作,然后立即通过界面将请求分派合适的动作方法,并传递保留在数据流中的所有 RPC 参数。

在 Smart Ticket 程序中,客户机和服务器紧密联系。这种方式可提高网络性能,因为,每次 RPC 交换都可以经过特别的设计和优化。然而,要在开发速度和健壮性之间进行权衡。 即使服务器端的微小改变也很可能逼使客户端的协议和解析码进行改变,还有很多潜在的可能因素。开发人员需要对所有可能影响的代码保持跟踪,并在必要时更新它。 他们也需要经常重新编译和重新分发客户端程序,否则将可能导致错误。

客户端线程模型

Smart Ticket 应用程序在客户端采用一个复杂的线程模型,有两个重要方面:

  1. MIDP 规范请求 CommandListener.commandAction() 方法“立即返回”以避免阻塞 UI,因此任何长时间的操作都必须放入其他线程。
  2. 正运行的线程能够显示一个指示长时操作进度的动态进度条,特别是涉及远程网络操作的线程。进度条屏幕为缺乏耐心的用户提供了一个取消按钮,可终止太长的运作。

    Image 

也许你早就注意到 UIController 类中的动作方法只是 runWithProgress() 方法的简单包装,该方法设置屏幕为 ProgressObserverUI 并启动 EventDispatcher 线程。 ProgressObserverUI 屏幕显示一个进度条和一个 Stop 按钮,通过主 MIDlet 系统 UI 线程来监控它。如前所述, EventDispatcher 线程最终委托到模型层方法的请求动作。 这些方法中的每一个都在其执行的某个阶段调用 ProgressObserverUI.updateProgress() ,以告知用户的进度情况。

public class UIController {
  // Action methods ...
  public void chooseMovieRequested() {
    runWithProgress(
      new EventDispatcher(
        EventIds.EVENT_ID_CHOOSEMOVIEREQUESTED, 
            mainMenuUI), 
                getString(UIConstants.PROCESSING), false);
  }
  // Action methods ...
  public void runWithProgress(Thread thread, String title, 
                              boolean stoppable) {
    progressObserverUI.init(title, stoppable);
    getDisplay().setCurrent(progressObserverUI);
    thread.start();
  }
  class EventDispatcher extends Thread {
    // ...
    public void run() {
      // Switch -- case statements to delegate
      // actions to the model layer
    }
  }
}
public class ProgressObserverUI extends Form 
            implements ProgressObserver,
            CommandListener {
  private UIController uiController;
  private static final int GAUGE_MAX = 8;
  private static final int GAUGE_LEVELS = 4;
  int current = 0;
  Gauge gauge;
  Command stopCommand;
  boolean stoppable;
  boolean stopped;
  public ProgressObserverUI(UIController uiController) {
    super("");
    gauge = new Gauge("", false, GAUGE_MAX, 0);
    stopCommand = new 
        Command(uiController.getString(UIConstants.STOP), 
            Command.STOP, 10);
    append(gauge);
    setCommandListener(this);
  }
  public void init(String note, boolean stoppable) {
    gauge.setValue(0);
    setNote(note);
    setStoppable(stoppable);
    stopped = false;
  }
  public void setNote(String note) {
    setTitle(note);
  }
  public boolean isStoppable() {
    return stoppable;
  }
  public void setStoppable(boolean stoppable) {
    this.stoppable = stoppable;
    if (stoppable) {
      addCommand(stopCommand);
    } else {
      removeCommand(stopCommand);
    }
  }
  /**
   * Indicates whether the user has stopped the progress.
   * This message should be called before calling update.
   */
  public boolean isStopped() {
    return stopped;
  }
  public void updateProgress() {
    current = (current + 1) % GAUGE_LEVELS;
    gauge.setValue(current * GAUGE_MAX / GAUGE_LEVELS);
  }
  public void commandAction(Command c, Displayable d) {
    if (c == stopCommand) {
      stopped = true;
    }
  }
} 

 

结束语

本文介绍了全新的 Smart Ticket v2.0 蓝图。几个针对早期版本的重大改进都利用了智能客户端丰富的功能。 Smart Ticket 向你展示了如何用几个我们刚才简要讲述的重要 设计模式来实现高级功能。我们希望我们本文讲述的内容能让你在端到端设计模式领域快速起步。

参考资料

  1. Sun Java Wireless Blueprints
  2. "Developing an End to End Wireless Application Using Java Smart Ticket Demo",作者:Eric Larson (涵盖了 Smart Ticket v1.1)

Norman Richards 是美国得克萨斯州奥斯汀市 Zilliant 的一名工程师。他是 XDoclet in Action (Manning Publications,2003 年夏)一书的合著者。。