站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2ME 专题端到端J2ME应用开发实例——介绍Smart Ticket

端到端J2ME应用开发实例——介绍Smart Ticket

摘要: 端到端J2ME应用开发实例——介绍Smart Ticket

作者:Norman Richards 和 Michael Yuan



2001 年发布的第一版 Java 技术蓝图 Java Pet Store 就充分展示了 Sun 公司 J2EE 技术的优势。 蓝图不仅为多层的、数据库驱动的电子商务应用程序提供了示例代码 , 而且提供了设计准则,示范了常用的模式。自第一版发布以来,对于想学习 J2EE 最新技术的开发人员来说,Java 技术蓝图已经成为最宝贵的资源和最佳实践。

Smart Ticket 蓝图增加了一个新的特性:移动性。它示范了如何创建一个实现电影订票功能的完整的端到端的移动商务系统 ,将 J2ME MIDP 用于无线前端,而将 J2EE 应用程序服务器和关系数据库用于后端。学习这个程序如何设计和构造将能极大地提高你对移动企业应用程序的难题和它们的解决方案的理解。

文章包含 2003 年 4 月发布的 Smart Ticket 的 Early Access 2.0 版的代码。 early-access 版中的屏幕截图和示例代码在最终版本中可能有细微的更改,但你从设计中学到的经验依然是有用的。Smart Ticket 1.2 仍然有效。它与我们现在讨论的版本有相同的模型和后台实现,因此,无论对过去还是未来的版本,很多详细解释都适用。除特别说明之外,Sun Microsystems 均对本文的所有源代码保留版权。

下载和安装

Smart Ticket 应用程序可从 Sun's Blueprints网站获得。Zip 压缩文件包含源代码、Ant 构建脚本和预构建可配置的应用程序。

Smart Ticket 应用程序包含一个 J2ME 组件和一个 J2EE 组件。运行它要求一个 J2EE 应用服务器(比如 Sun 的 J2EE 参考实现,1.3 版或更高),和任一个带有 Internet 连接的兼容 MIDP 2.0的设备或者合适的仿真程序。如 Sun 的 J2ME Wireless Toolkit 2.0。 Smart Ticket 发行版包括了特别的说明,帮助构建和部署这个应用程序。现在开始:

  1. 确保你已经安装以下资源:

    • JDK v1.4.1 或更高版本。
    • J2EE v1.3.1 或更高版本。
    • J2ME Wireless Toolkit 2.0 或更高版本。

  2. 设置以下环境变量:

    • JAVA_HOME:JDK 安装目录 。
    • J2EE_HOME:J2EE RI 安装目录。
    • J2MEWTK_HOME:J2ME Wireless Toolkit 安装目录。

  3. 启动 J2EE 服务器:

    <table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

    J2EE_HOME/bin/cloudscape -start
    
    J2EE_HOME/bin/j2ee -verbose
    
    
    </td></tr></table>

  4. 配置 J2EE 应用程序。 在 setup.xml 文件中,使用以下 setup 脚本调用 deploy Ant 任务:

    <table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>
    setup deploy
    
    
    </td></tr></table>
  5. 指定浏览器连接 http://localhost:8000/smartticket,单击 Populate Database 链接,将模拟影院和电影数据导入数据库。如果用的是老式计算机,这是非常慢的过程, 所以要有耐心!模拟数据包括位于 95054 和 95130 这两个邮政编码的影院。

  6. 启动 J2ME Wireless Toolkit 2.0,并运行在 smart_ticket-client.jad 中指定的 MIDlet。

运行中的 Smart Ticket

运行 MIDlet 后,采用简便途径就能实现用户需求。你会发现你需要完成四项任务。

Smart Ticket 的优势

较老的移动商务平台,比如基于 WAP/WML 的微型浏览器将所有的信息处理都放置在服务器端。 J2ME 的一个重要优势是它支持运行在客户机上智能客户端程序。Smart Ticket 充分体现了智能客户端应用程序范例的优势:

怎样实现这些特性?

重要的体系结构模式

总体 MVC 模式

Smart Ticket 应用程序的总体体系结构遵循模式-视图-控制器(Model-View-Controller)模式。这个应用程序被分为多个逻辑层,因此开发人员在修改一部分时不会影响其他部分。 Smart Ticket 符合 MVC 模型,如下所示:

客户端界面

对于大多数应用程序动作,指向模型层的控制器条目是 ModelFacade 类。为符合 MVC 模式,ModelFacade 类包含一个响应模型层中每个事件的方法。根据动作的本质,界面将它委托给以下的一个或多个模型类:

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

package com.sun.j2me.blueprints.smartticket.client.midp.model;

public class ModelFacade {

  private SynchronizationAgent syncAgent;

  private RemoteModelProxy remoteModel;

  private LocalModel localModel;

  // Action methods ...

 

  public Reservation reserveSeats(String theaterKey,

                         String movieKey,

                         int[] showTime, Seat[] seats)

                         throws ApplicationException {

    try {

      return remoteModel.reserveSeats(theaterKey,

          movieKey, showTime, seats);

    } catch (ModelException me) {

      // ...

    }

  }

  public void purchaseTickets(Reservation reservation)

                          throws ApplicationException {

    try {

      remoteModel.purchaseTickets(reservation.getId());

      localModel.addMovieRating(

        new MovieRating(remoteModel.getMovie(reservation.getMovieId()),

                        reservation.getShowTime()));

    } catch (ModelException me) {

      // ...

    }

    return;

  }

 

  public void synchronizeMovieRatings(int

     conflictResolutionStrategyId)

         throws ApplicationException {

    try {

      syncAgent.synchronizeMovieRatings(conflictResolutionStrategyId);

      return;

    } catch (ModelException me) {

      // ...

    }

  }

  // ...

}

</td></tr></table>

服务端界面

应用程序的服务端使用了很多 Enterprise JavaBeans 组件 (EJB) 来封装业务逻辑和管理与关系数据库的交互。当客户端的 RemoteModelProxy 向服务器端发出 RPC 调用时,HTTP servlet SmartTicketServlet 通过业务代理对象 SmartTicketBD 调用会话 EJB 中合适的动作方法 SmartTicketFacadeBean。根据请求性质,它进一步委托两个其他会话 bean 中的一个,TicketingBeanSynchronizingBean。一组实体 bean 在需要时使用 EJB 2.0 的容器托管的持久性来更新数据库。

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

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.");

    }

  }

 

  // ...

 

}

</td></tr></table>

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

<table cellSpacing=0 cellPadding=10 width=image_width align=center border=0> <tr> <td align=middle> </td></tr></table>

实现模式

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

处理程序链

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

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

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

  // ...

}

</td></tr></table>

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

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

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

 

}

</td></tr></table>

一个处理程序能够选择性地实现任何 RemoteModel 接口中的动作方法 ,采用两种方法之一:

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

public class RMSCacheHandler extends RemoteModelRequestHandler {

  // ...

  public Movie getMovie(String movieKey)

                        throws ModelException,

                        ApplicationException {

    IndexEntry indexEntry =

        rmsAdapter.getIndexEntry(movieKey,

            IndexEntry.TYPE_MOVIE, IndexEntry.MODE_ANY);

    if (indexEntry != null) {

      return rmsAdapter.loadMovie(indexEntry.getRecordId());

    }

    return super.getMovie(movieKey);

  }

 

  // ...

 

}

</td></tr></table>

HTTP 上的二进制远程过程调用

在模型层,RemoteModelProxy 类中的 HTTPCommunicationHandler 类通过 HTTP 连接上的二进制 RPC 协议调用服务器端的远程过程。该协议定义如下:

所有从客户端到服务器端的 RPC 请求都遵循同一基本模式。数据流的第一个字节指定服务器端的界面会话 bean 必须执行的动作方法,余下的字节编码为 UTF 数据串序列,它表示传递给远程方法的参数。HTTP 数据流包含 RPC 返回值。请求和响应的格式对每一种方法来说都是唯一的,所以,你必须查看每一种方法的源代码,以确定准确的格式。

进入请求数据流第一个字节的 RPC 编码在 MessageConstants 类中进行定义:

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

package com.sun.j2me.blueprints.smartticket.shared.midp;

public final class MessageConstants {

  public static final byte OPERATION_LOGIN_USER = 0;

  public static final byte OPERATION_CREATE_ACCOUNT = 1;

  public static final byte OPERATION_UPDATE_ACCOUNT = 2;

  public static final byte OPERATION_GET_THEATERS = 3;

  public static final byte OPERATION_GET_THEATER_SCHEDULE = 4;

  public static final byte OPERATION_GET_MOVIE = 5;

  public static final byte OPERATION_GET_MOVIE_POSTER = 6;

  public static final byte OPERATION_GET_MOVIE_SHOWTIMES = 7;

  public static final byte OPERATION_GET_SEATING_PLAN = 8;

  public static final byte OPERATION_RESERVE_SEATS = 9;

  public static final byte OPERATION_PURCHASE_TICKETS = 10;

  public static final byte OPERATION_CANCEL_SEAT_RESERVATION = 11;

  public static final byte OPERATION_GET_LOCALES = 12;

  public static final byte OPERATION_GET_RESOURCE_BUNDLE = 13;

  public static final byte OPERATION_INITIATE_SYNCHRONIZATION = 14;

  public static final byte OPERATION_SYNCHRONIZE_MOVIE_RATINGS = 15;

  public static final byte OPERATION_COMMIT_MOVIE_RATINGS = 16;

  public static final byte ERROR_NONE = 0;

  public static final byte ERROR_UNKNOWN_OPERATION = 1;

  public static final byte ERROR_SERVER_ERROR = 2;

  public static final byte ERROR_MODEL_EXCEPTION = 3;

  public static final byte ERROR_REQUEST_FORMAT = 4;

 

  private MessageConstants() {}

}

</td></tr></table>

下面的两个类解释了一个 RPC 往返过程,HTTPCommunicationHandler 类的动作方法请求指定电影的信息,并调用 Movie 类的一个方法来提取响应流中的返回值。

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

package com.sun.j2me.blueprints.smartticket.client.midp.model;

public class HTTPCommunicationHandler

  extends RemoteModelRequestHandler {

  // ...

 

  public Movie getMovie(String movieKey)

                        throws ModelException,

                        ApplicationException {

    HttpConnection connection = null;

    DataOutputStream outputStream = null;

    DataInputStream inputStream = null;

    try {

      connection = openConnection();

      updateProgress();

      outputStream = openConnectionOutputStream(connection);

      // The RPC request

      outputStream.writeByte(MessageConstants.OPERATION_GET_MOVIE);

      outputStream.writeUTF(movieKey);

      outputStream.close();

      updateProgress();

      // unmarshal the return values

      inputStream = openConnectionInputStream(connection);

      Movie movie = Movie.deserialize(inputStream);

      updateProgress();

      return movie;

    } catch (IOException ioe) {

      throw new

        ApplicationException(ErrorMessageCodes.ERROR_CANNOT_CONNECT);

    } finally {

      closeConnection(connection, outputStream, inputStream);

    }

  }

 

  // Other action methods ...

 

}

package com.sun.j2me.blueprints.smartticket.shared.midp.model;

public class Movie {

  private String primaryKey;

  private String title;

  private String summary;

  private String rating;

  private boolean alreadySeen = false;

  transient private byte[] poster = null;

   

  public static Movie deserialize(DataInputStream

      dataStream) throws ApplicationException {

    try {

      Movie movie = new Movie();

      // Read the RPC response stream

      movie.primaryKey = dataStream.readUTF();

      movie.title = dataStream.readUTF();

      movie.summary = dataStream.readUTF();

      movie.rating = dataStream.readUTF();

      try {

        movie.alreadySeen = dataStream.readBoolean();

      } catch (IOException ioe) {

        movie.alreadySeen = false;

      }

      try {

        return

            ModelObjectLoader.getInstance().getMovie(movie);

      } catch (ModelException me) {

        throw new ApplicationException();

      }

    } catch (IOException ioe) {

      throw new ApplicationException(ioe);

    }

  }

 

  // Other methods ...

 

}

</td></tr></table>

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

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

客户端线程模型

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

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

<table class=grey4 cellSpacing=0 cellPadding=10 width="100%" border=0> <tr> <td>

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;

    }

  }

}

</td></tr></table>

结束语

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

参考资料



↑返回目录
前一篇: End-to-End J2ME Application Development by Example - Introducing Smart Ticket
后一篇: J2ME-MIDP1.0游戏完整实现-双人扫雷1.0(二)