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."); } } // ... }
具体的 handler 类扩展了
|
在服务器端, SmartTicketServlet
首先确定在请求数据流中的第一个字节编码所表达的动作,然后立即通过界面将请求分派合适的动作方法,并传递保留在数据流中的所有 RPC 参数。
在 Smart Ticket 程序中,客户机和服务器紧密联系。这种方式可提高网络性能,因为,每次 RPC 交换都可以经过特别的设计和优化。然而,要在开发速度和健壮性之间进行权衡。 即使服务器端的微小改变也很可能逼使客户端的协议和解析码进行改变,还有很多潜在的可能因素。开发人员需要对所有可能影响的代码保持跟踪,并在必要时更新它。 他们也需要经常重新编译和重新分发客户端程序,否则将可能导致错误。
客户端线程模型
Smart Ticket 应用程序在客户端采用一个复杂的线程模型,有两个重要方面:
- MIDP 规范请求
CommandListener.commandAction()
方法“立即返回”以避免阻塞 UI,因此任何长时间的操作都必须放入其他线程。 - 正运行的线程能够显示一个指示长时操作进度的动态进度条,特别是涉及远程网络操作的线程。进度条屏幕为缺乏耐心的用户提供了一个取消按钮,可终止太长的运作。
也许你早就注意到 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 向你展示了如何用几个我们刚才简要讲述的重要 设计模式来实现高级功能。我们希望我们本文讲述的内容能让你在端到端设计模式领域快速起步。
参考资料
- Sun Java Wireless Blueprints
- "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 年夏)一书的合著者。。