[转!]嵌入Hamlet为运行OSGi的嵌入式设备编写基于Web的用户界面

来源:互联网 发布:软件项目研发计划 编辑:程序博客网 时间:2024/06/05 09:42

http://www.mcuol.com/Tech/175/15731.htm

开源的 Hamlet 框架可以用于协助 Web 开发和正确地分离内容与表示。OSGi 框架是在嵌入式设备上进行开发的一款优秀工具。这两个框架结合起来可以为最普通的小器具(比如咖啡机)提供基于浏览器的交互性。请继续跟随本文看看这是如何实现的。

      Hamlet 框架用于开发基于 Web 的应用程序,它易于使用和掌握。得益于其轻量化的设计和较少的资源需求,它们也非常适合于作为嵌入式组件使用。在本文中,您将学习如何使用 Hamlet 为运行 OSGi 的嵌入式设备编写基于 Web 的用户界面。

     何为 Hamlet?

      Servlet 之所以能成为 Web 开发的理想选择,其原因有很多,例如很好的移植性、高效性、安全性、可扩展性和灵活性。不过,还有一些其他可行的选择可以与 servlet 的强大和简洁性相媲美。

      尽管具有一些吸引人的特性,开箱即用的 servlet 还是缺少一个重要的属性:对内容和表示进行分离的支持。如果 servlet 专用于开发基于 Web 的应用程序,那么 HTML 和 Java? 代码将会不可避免地混于相同的源代码文件中。

      为解决这个问题,我提出了一种易于使用和掌握的框架,称为 Hamlet,用于开发基于 Web 的应用程序(有关 Hamlet 的更多内容,请参阅 参考资料)。该框架是 IBM Radical Simplification 运动的产物,因而,您可以很快地掌握它。

      Hamlet 是一个 Java servlet 扩展,它使用 Simple API for XML (SAX) 读取模板文件。在读取模板文件时,Hamlet 会使用一小组回调函数(由 HamletHandler 实现)在模板中的一些标有特殊标记和 ID 的地方添加动态内容(如图 1 所示)。

      图 1. Hamlet 使用 SAX 从模板文件中读取内容并调用 HamletHandler 以添加动态内容

Hamlet 使用 SAX 从模板文件中读取内容并调用 HamletHandler 以添加动态内容

      模板编译器可用于加速 Hamlet。模板编译器使用 SAX 读取模板文件并将内容转换为 Java 源代码。随后,它调用标准 JDK Java 编译器来生成 Java 字节码。在运行时,Hamlet 执行该代码并调用 HamletHandler 添加动态内容,如图 2 所示。有关模板编译运行机制的详细信息,请参阅 参考资料。

模板编译器将模板文件的内容转换为 Java 字节码,再由 Hamlet 执行并调用 HamletHandler 添加动态内容

      图 2. 模板编译器将模板文件的内容转换为 Java 字节码,再由 Hamlet 执行并调用 HamletHandler 添加动态内容
       本文中所使用的项目名称 Hamlet 指的是可用来开发基于 servlet 的框架的一个内部项目名,该框架可以分离基于 Web 的应用程序中的内容和表示。Hamlets 框架 1.4 版(正式名称为 IBM Servlet-Based Content Creation Framework)可从 IBM alphaWorks 上获得(有关链接,请参阅 参考资料)。

      何为 OSGi?

      OSGi 联盟(即之前所熟知的 Open Services Gateway 项目)是成立于 1999 年的非盈利性独立组织。它为一种可远程管理的、基于 Java 技术的服务平台定义了开放规范。此规范由两部分组成:OSGi 框架和一组标准的服务定义。

      OSGi 框架为 bundle 应用程序实现运行时环境,这些应用程序在单个 Java Virtual Machine (JVM) 中一同执行,如图 3 所示。Bundle 可安装、启动、停止、更新和在无需重启的情况下卸载。服务注册允许 在服务出现或消失时通知 bundle。之后,bundle 就可以做出相应的调整。此框架原本针对的是具有家庭自动化应用程序的 Internet 网关,但是它也已经成功应用于一些其他领域,比如自动化工业、家用电器及桌面应用程序空间。有关详细信息,请参阅标题为 “About the OSGi Service Platform” 的技术白皮书(请参阅 参考资料)。

      针对 OSGi 框架已开发出大量的服务和库。Hamlet 是增强该框架的理想选择,原因是它们允许创建表示(HTML 代码)和逻辑(Java 代码)严格分离的基于 Web 的应用程序。

      图 3. OSGi 架构

OSGi 架构

   Hello bundle

      清单 1 所示的是最基本的 OSGi 应用程序。它包含一个 Activator 类,该类实现BundleActivator 接口的 start() 和 stop() 方法。当应用程序安装完成并启动后,OSGi 框架将调用 Activator 的 start() 方法并打印出 "Hello World!"。应用程序停止时将调用 Activator 的 stop() 方法。

      清单 1. Hello bundle 的 Activator 类
      package com.ibm.zurich.HelloBundle;

      import org.osgi.framework.*;

      public class Activator implements BundleActivator {

       public void start (BundleContext aContext) {
    System.out.println ("Hello World!");
  } // start

        public void stop (BundleContext aContext) {
  } // stop

      } // Activator

      bundle 是 OSGi 应用程序的交付和开发单元。bundle 是一个 jar 文件,其中包含应用程序的字节码和其资源。此外,它还包含一个 manifest 文件,用于描述这个 bundle。清单 2 就是对 Hello bundle 的描述。

      清单 2. Hello bundle 的 manifest 文件

Manifest-Version: 1.0
Bundle-Name: Hello Bundle
Bundle-SymbolicName: hellobundle
Bundle-Version: 1.0.0
Bundle-Description: This bundle prints 'Hello World!'.
Bundle-Vendor: Rene Pawlitzek
Bundle-Activator: com.ibm.zurich.HelloBundle.Activator
Bundle-Category: example
Import-Package: org.osgi.framework

      此 manifest 文件包含 bundle 的名称、版本、说明、供应商、类别和 activator,activator 是实现 ActivatorBundle 接口的类名。这个 manifest 文件还指定了导入包。有关 manifest 文件格式和语法的更多信息,请参阅 OSGi Service Platform 规范(请参阅 参考资料)。

      Bundle 可方便地使用 Ant 脚本构建。我是使用 IDE 和清单 3 中所示的脚本来构建 hellobundle.jar 的。

      清单 3. 使用 Ant 脚本构建 Hello bundle

 

 
   
 

 
          includes="**/*" manifest="./meta-inf/MANIFEST.MF" />
 

 
   
   
 

 
   
   
 

      在开发之前,可以使用 WinZip 或类似的工具来检查 hellobundle.jar。其中应该包含 MANIFEST.MF 和 Activator.class 文件。

      符合 OSGi 服务平台规范的实现有多个,其中包括 Knopflerfish、Eclipse Equinox 和 Apache Felix 这三种开源实现(请参阅 参考资料)。我选择使用 Kopflerfish 1.3.5 实现本文中的示例。从 Knopflerfish OSGi 桌面加载 hellobundle.jar 后,您应该可以在控制台上看到打印输出的 "Hello World!"(位于窗口的底部,如图 4 所示)。

      图 4. 运行 Hello bundle 的 Knopflerfish OSGi 桌面

运行 Hello bundle 的 Knopflerfish OSGi 桌面

      至此,我们已经了解了如何创建、部署和运行 OSGi 应用程序。

    Monitor bundle

      现在,我将创建一个更实用的程序用于监视 OSGi 框架内的 bundle 和服务的生命周期。如果应用程序注册了侦听程序,那么在 bundle 被安装、启动、停止或卸载时,框架都会通知此应用程序。同样地,在服务被注册、注销或修改时,应用程序也会接到通知。功能良好的软件绝不会假定某个服务永远可用。相反,它会注册一个侦听程序来接收通知事件并相应进行调整。

      Monitor 应用程序的 Activator 类实现 BundleActivator 和另外两个接口:BundleListener 和 ServiceListener。正如之前您在 Hello bundle 看到的,当 Monitor bundle 启动时,OSGi 框架会调用 start() 方法。这个应用程序并不会打印出 "Hello world!",而是使用 bundle 上下文通过 addBundleListener() 和 addServiceListener() 注册了一个 bundle 和一个服务侦听程序。当 Monitor bundle 停止后,stop() 方法将使用 removeBundleListener() 和 removeServiceListener() 移除这些侦听程序。

      当服务被注册、注销或修改,或者当 bundle 被安装、启动、停止或卸载时,serviceChanged() 和 bundleChanged() 方法(二者都由 Activator 类实现)都会从 OSGi 框架接收到相应的通知事件。ServiceEvent 和 BundleEvent 类中包含有关更改的详细信息,其中包括对服务或 bundle 的引用。serviceChanged() 和 bundleChanged() 用于打印这些信息。所有这些均可在清单 4 中看到。

      清单 4. Monitor bundle 的 Activator 类
package com.ibm.zurich.MonitorBundle;

import org.osgi.framework.*;

public class Activator implements BundleActivator, ServiceListener, BundleListener {

  public void start (BundleContext aContext) throws Exception {
    aContext.addBundleListener (this);
    aContext.addServiceListener (this);
  } // start

  public void stop (BundleContext aContext) {
    aContext.removeServiceListener (this);
    aContext.removeBundleListener (this);
  } // stop

  /* ----- implementation of ServiceListener ----- */

  public void serviceChanged (ServiceEvent aEvent) {
    String action = null;
    switch (aEvent.getType ()) {
      case ServiceEvent.MODIFIED:
        action = "modified";
        break;
      case ServiceEvent.REGISTERED:
        action = "registered";
        break;
      case ServiceEvent.UNREGISTERING:
        action = "unregistered";
        break;
    } // switch
    if (action != null) {
      ServiceReference ref = aEvent.getServiceReference ();
      String classes[] = (String[]) ref.getProperty ("objectClass");
      System.out.println ("Service '" + classes[0] + "' is " + action);
    } // if
  } // serviceChanged

  /* ----- implementation of BundleListener ----- */

  public void bundleChanged (BundleEvent aEvent) {
    String action = null;
    switch (aEvent.getType ()) {
      case BundleEvent.INSTALLED:
        action = "installed";
        break;
      case BundleEvent.STARTED:
        action = "started";
        break;
      case BundleEvent.STOPPED:
        action = "stopped";
        break;
      case BundleEvent.UNINSTALLED:
        action = "uninstalled";
        break;
    } // switch
    if (action != null) {
      Bundle bundle = aEvent.getBundle ();
      String name = (String) bundle.getHeaders().get (Constants.BUNDLE_NAME);
      System.out.println ("Bundle '" + name + "' is " + action);
    } // if
  } // bundleChanged

} // Activator

      Monitor bundle 的 manifest 文件和 Ant 构建脚本与 Hello bundle 几乎无异,所以这里未做解释。

    HelloServlet bundle

      HTTP 服务是 OSGi 框架的特性之一 ,该服务允许 servlet 的执行。下面介绍的 bundle 将提供和注册一个 Hello servlet,然后就可以通过 HTTP 获得该 servlet 了。servlet 的输出(问候语 "Hello world!")将会在显示在浏览器中。我将使用 ServiceTracker 对象(基于之前介绍的 ServiceListener 接口)在 HTTP 服务发生更改时接收通知事件。

      HelloServlet bundle 的 Activator 类实现 BundleActivator 接口的 start() 和 stop() 方法。在 start() 中,将创建和存储一个 HelloServlet 实例以便稍后在的实例变量(servlet)中使用。此外,还将实例化和公开一个 ServiceTracker 对象用于跟踪 HTTP 服务。ServiceTracker 构造函数要使用 bundle 上下文(context)、要跟踪的服务名(HttpService.class.getName())和当前的 Activator 实例(this)加以调用。Activator 类实现 ServiceTrackerCustomizer 接口中的三个方法(addingService()、modifiedService() 和 removedService()),服务跟踪器会在添加、修改和删除 HTTP 服务时调用这些方法,如清单 5 所示。

      清单 5. HelloServlet bundle 的 Activator 类
      package com.ibm.zurich.HelloServletBundle;

import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import org.osgi.service.http.*;

      public class Activator implements BundleActivator, ServiceTrackerCustomizer {

        public static BundleContext context = null;

        private  HttpService   httpService;
        private  HelloServlet  servlet;

       public void start (BundleContext aContext) {
      context = aContext;
      servlet = new HelloServlet ();
      ServiceTracker tracker =
      new ServiceTracker (context, HttpService.class.getName (), this);
     tracker.open ();
  } // start

     public void stop (BundleContext aContext) {
    unregisterServlet (httpService);
    servlet = null;
    context = null;
  } // stop

     private void registerServlet (HttpService aHttpService) {
    try {
      if (aHttpService != null)
        aHttpService.registerServlet ("/hello", servlet, null, null);
        System.out.println ("Registered /hello servlet");
    } catch (Exception e) {
      System.out.println ("Unable to register servlet");
    } // try
  } // registerServlet

     private void unregisterServlet (HttpService aHttpService) {
    try {
      if (aHttpService != null)
        aHttpService.unregister ("/hello");
        System.out.println ("Unregistered /hello servlet");
    } catch (Exception e) {
      System.out.println ("Unable to unregister servlet");
    } // try
  } // unregisterServlet

  /* ----- implementation of ServiceTrackerCustomizer ----- */

  public Object addingService (ServiceReference aRef) {
    httpService = (HttpService) context.getService (aRef);
    registerServlet (httpService);
    return httpService;
  } // addingService

  public void modifiedService (ServiceReference aRef, Object aObj) {
    httpService = (HttpService) context.getService (aRef);
    registerServlet (httpService);
  } // modifiedService

  public void removedService (ServiceReference aRef, Object aObj) {
  } // removedService

} // Activator

     跟踪器对象最终会调用 addingService() 方法来通知您 HTTP 服务可用。可以利用这个机会注册之前在 start() 中创建的 Hello servlet 。servlet 现在就可以使用并可在 URL http://localhost:8080/hello 本地访问到,如图 5 所示。

    图 5. HelloServlet bundle 向世界问好

HelloServlet bundle 向世界问好

       servlet 的输出由 HelloServlet 类的 doGet() 方法中的一系列 println() 语句实现,如清单 6 所示。在 Java 代码中嵌入HTML 对于小型程序来说可以接受。但对于较大型应用程序,这样会导致很严重的维护方面的问题。Hamlet 可以解决此类问题,本文下面将介绍的 Coffee Machine 就是一个例子。

清单 6. Hello servlet
package com.ibm.zurich.HelloServletBundle;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloServlet extends HttpServlet {

  public void doGet (HttpServletRequest req, HttpServletResponse res) throws
    ServletException {

    try {
      res.setContentType ("text/html");
      PrintWriter out = res.getWriter ();
      out.println ("");
      out.println ("Hello world!");
      out.println ("");
      out.println ("Hello world!");
      out.println ("");
      out.println ("");
      out.flush ();
    } catch (Exception e) {
      throw new ServletException (e);
    } // try

  } // doGet

} // HelloServlet

      现在,假设更加复杂的实现替代了当前的 HTTP 服务。很显然,跟踪器对象会先后调用 removedService() 方法和 addingService() 方法。并不需要在 removedService() 中注销 Hello servlet,但是必须使用新改进的 HTTP 服务重注册 servlet 以便让它再次可用。同样地,只要 HTTP 服务被修改,就必须要在 modifiedService() 中重注册 Hello servlet。Hello servlet 在 stop() 方法中注销,bundle 停止时会调用该方法。

      HelloServlet bundle 的 Ant 构建脚本和 manifest 文件都与上述的其他 bundle 几乎无异。注意,清单 7 中所示的 manifest 文件指定了一些额外的导入包。

      清单 7. Hello servlet
Manifest-Version: 1.0
Bundle-Name: Hello Servlet Bundle
Bundle-SymbolicName: helloservletbundle
Bundle-Version: 1.0.0
Bundle-Description: This bundles provides a servlet to print 'Hello World!'.
Bundle-Vendor: Rene Pawlitzek
Bundle-Activator: com.ibm.zurich.HelloServletBundle.Activator
Bundle-Category: example
Import-Package: org.osgi.framework, org.osgi.util.tracker, org.osgi.service.http,
javax.servlet, javax.servlet.http

      Hamlet bundle

      接下来,我们将创建 Hamlet bundle 以使 OSGi 基础设施可以使用 Hamlet 框架(其格式为 jar 库,名为 hamlet.jar)。这样,您就可以分离 bundle 中的 HTML 和 Java 代码了。这个 bundle 不需要 activator 类。Ant 构建脚本(如清单 8 中所示),只简单地将 hamlet.jar 和 MANIFEST.MF 打包进了一个 jar 文件,名为 hamletbundle.jar。

      清单 8.构建 Hamlet bundle 的 Ant 脚本

 

 
          includes="hamlet.jar" manifest="./meta-inf/MANIFEST.MF" />
 

 
   
 

 
   
 

      hamletbundle.jar 的 manifest 文件(如清单 9 中所示)不仅指定了导入包,而且还指定了导出包。注意 bundle 的类别现在是 lib。

      清单 9. Hamlet bundle 的 Manifest
Manifest-Version: 1.0
Bundle-Name: Hamlet Bundle
Bundle-SymbolicName: hamletbundle
Bundle-Version: 1.0.0
Bundle-Description: This bundle provides the Hamlets framework.
Bundle-Vendor: Rene Pawlitzek
Bundle-ClassPath: hamlet.jar
Bundle-Category: lib
Export-Package: com.ibm.hamlet, com.ibm.hamlet.helpers
Import-Package: org.osgi.framework, javax.servlet, javax.servlet.http, org.apache.log4j,
org.xml.sax, org.xml.sax.helpers

      当我们从 Knopflerfish OSGi 桌面加载 Hamlet bundle 时,它会显示在 bundle 视图中,如图 6 所示。

      图 6. Hamlet bundle 显示在 Knopflerfish OSGi 桌面中

Hamlet bundle 显示在 Knopflerfish OSGi 桌面中

       目前,Hamlet 框架依赖于 Log4j 实现日志服务。因此,我们需要构建和安装一个 Log4j bundle(包含 log4j.jar);其方法与构建 Hamlet bundle 相同。(请参阅 参考资料 获得到 Log4j 的链接。)

    Coffee Machine bundle

      在最后这个示例中,我将开发一个 bundle,它可以为支持网络的咖啡机提供一个基于 Web 的用户界面。您可以在世界上的任何地方使用浏览器设置咖啡机的定时器。我将使用 Hamlet 来分离表示和逻辑,所以您不会在 Java 代码中看到任何 HTML,反之亦然。此外,还将使用 model-view-controller (MVC) 设计模式用于实现。

      清单 10 中所示的 Activator 类还是使用 start() 和 stop() 两个方法实现 BundleActivator 接口。OSGi 框架首先调用 start(),其中 BasicConfigurator.configure() 用于对日志记录进行配置,CoffeeMachineTimer.getTimer().start() 用于对咖啡机模型 (M) 进行初始化。接下来,创建咖啡机的视图 (V) 和控制器 (C)。视图是 CoffeeMachine 的一个实例,是一个 Hamlet,而控制器则是 CoffeeMachineController 的一个实例,是一个 servlet(如图 7 所示)。最后,与在 HelloServlet bundle 中一样,实例化并公开一个 ServiceTracker 对象用于跟踪 HTTP 服务的生命周期。只要 HTTP 服务有所更改,跟踪器就会调用 addingService()、modifiedService() 和 removedService(),这些方法是 ServiceTrackerCustomizer 接口的一部分,由 Activator 实现。

      图 7. CoffeeMachine bundle 的 MVC 设计模式

CoffeeMachine bundle 的 MVC 设计模式

      清单 10. CoffeeMachine bundle 的 Activator 类
      package com.ibm.zurich.CoffeeMachineBundle;

import javax.servlet.http.*;
import org.apache.log4j.*;
import org.osgi.framework.*;
import org.osgi.util.tracker.*;
import org.osgi.service.http.*;

      public class Activator implements BundleActivator, ServiceTrackerCustomizer {

        public static BundleContext context = null;

  // log4j
  private static Category category = Category.getInstance (Activator.class.getName ());

  private  HttpService    httpService;
  private  HttpServlet    controller;
  private  CoffeeMachine  view;

  public void start (BundleContext aContext) {
    context = aContext;
    BasicConfigurator.resetConfiguration ();
    BasicConfigurator.configure ();
    CoffeeMachineTimer.getTimer().start ();
    view = new CoffeeMachine ();
    controller = new CoffeeMachineController ();
    ServiceTracker tracker =
      new ServiceTracker (context, HttpService.class.getName (), this);
    tracker.open ();
  } // start

  public void stop (BundleContext aContext) {
    unregister (httpService);
    CoffeeMachineTimer.getTimer().stop ();
    controller = null;
    view = null;
    context = null;
  } // stop

  private void register (HttpService aHttpService) {
    try {
      if (aHttpService != null) {
        aHttpService.registerServlet ("/CoffeeMachine", view, null, null);
        category.debug ("Registered '/CoffeeMachine' hamlet");
        aHttpService.registerServlet ("/CoffeeMachineController",
          controller, null, null);
        category.debug ("Registered '/CoffeeMachineController' servlet");
        HttpContext httpContext = new CoffeeMachineContext (context);
        aHttpService.registerResources ("/Include", "/Resources", httpContext);
        category.debug ("Registered '/Include' resources");
      } // if
    } catch (Exception e) {
      category.error ("Unable to register", e);
    } // try
  } // register

  private void unregister (HttpService aHttpService) {
    try {
      if (aHttpService != null) {
        aHttpService.unregister ("/CoffeeMachine");
        category.debug ("Unregistered '/CoffeeMachine' hamlet");
        aHttpService.unregister ("/CoffeeMachineController");
        category.debug ("Unregistered '/CoffeeMachineController' servlet");
        aHttpService.unregister ("/Include");
        category.debug ("Unregistered '/Include' resources");
      } // if
    } catch (Exception e) {
      category.error ("Unable to unregister", e);
    } // try
  } // unregister

  /* ----- implementation of ServiceTrackerCustomizer ----- */

  public Object addingService (ServiceReference aRef) {
    category.debug ("adding service");
    httpService = (HttpService) context.getService (aRef);
    register (httpService);
    return httpService;
  } // addingService

  public void modifiedService (ServiceReference aRef, Object aObj) {
    category.debug ("modified service");
    httpService = (HttpService) context.getService (aRef);
    register (httpService);
  } // modifiedService

  public void removedService (ServiceReference aRef, Object aObj) {
    category.debug ("removed service");
  } // removedService

} // Activator

      有时,跟踪器对象会调用 addingService() 通知您 HTTP 服务的可用性。随后,会调用 register() 向 HTTP 服务注册 Hamlet 提供的视图、servlet 提供的控制器以及一些资源(用来格式化输出的咖啡机位图和级联样式表)。

      使用 registerResources() 注册资源需要一个 HTTP 上下文(httpContext)。CoffeeMachineContext 类(如清单 11 所示)用于为咖啡机资源提供 HTTP 上下文。它实现了 HttpContext 接口的三个方法:handleSecurity()、 getMimeType() 和 getResource()。当请求某个特定的资源时(例如,使用 请求咖啡机位图),CoffeeMachineContext 类的 getResource() 方法会返回一个指向该资源(位于 bundle 中)的 URL(例如,bundle://36/Resources/coffee_machine.jpg)。

      清单 11. CoffeeMachine 上下文
package com.ibm.zurich.CoffeeMachineBundle;

import java.net.*;
import javax.servlet.http.*;
import org.osgi.framework.*;
import org.osgi.service.http.*;

public class CoffeeMachineContext implements HttpContext {

  private  BundleContext  context;

  public CoffeeMachineContext (BundleContext context) {
    this.context = context;
  } // CoffeeMachineContext

  public boolean handleSecurity (HttpServletRequest req, HttpServletResponse res) {
    return true;
  } // handleSecurity

  public String getMimeType (String name) {
    return null;
  } // getMimeType

  public URL getResource (String path) {
    return context.getBundle().getResource (path);
  } // getResource

} // CoffeeMachineContext

      可以通过在浏览器中输入 URL http://localhost:8080/CoffeeMachine 访问咖啡机的基于 Web 的用户界面,如图 8 所示。

CoffeeMachine bundle 为咖啡机提供了基于 Web 的前端

图 8. CoffeeMachine bundle 为咖啡机提供了基于 Web 的前端

       HTTP 服务被替换后,跟踪器对象将会先后调用 removedService() 方法以及 addingService() 方法。与 HelloServlet bundle 的情况一样,我们需要重新注册控制器 servlet、资源和能提供视图的 Hamlet。在调用了 modifiedService() 后,换句话说就是 HTTP 被修改后,也需要进行重新注册。

       CoffeeMachine bundle 停止时,将调用 Activator 的 stop() 方法。在这里,我们使用 CoffeeMachineTimer.getTimer().stop() 来注销 Hamlet、servlet 和资源以及清除模型 (M)。

       到目前为止,与前面的 HelloServlet 示例的区别并不大,但是,CoffeeMachine bundle 没有在 servlet 的 doGet() 方法中使用 println() 语句来生成输出,而是使用了 Hamlet 来分离 HTML 和 Java 代码。

       通过网络访问咖啡机时,将调用 doGet() 方法。我们使用 model.getTiming() 从数据模型检索当前的定时器设置并创建了一个 CoffeeMachineHandler(它是 CoffeeMachine 的私有类,扩展了 HamletHandler)的实例(handler)。

       接下来,将调用 serveDoc (req, res, template, handler) 方法。该方法执行 template 所引用的编译模板生成咖啡机的用户界面(HTML 代码),并调用 handler 的 getElementAttributes() 方法使用 Helpers.getAttributes() 添加当前的定时器设置( CHECKED 和 SELECTED 属性)。此编译模板的 Java 字节码 (CoffeeMachineTemplate.class) 在设计时由模板编译器使用 Ant 根据 XHTML 模板 (CoffeeMachineTemplate.html) 生成。然后我们在使用 init() 方法中的 Class.forName("CoffeeMachineTemplate.class") 创建 CoffeeMachine hamlet 的过程中加载此代码。有关 getElementAttributes() 如何运作的详细信息,请参阅 参考资料。

       清单 12. CoffeeMachine hamlet 提供了咖啡机视图
package com.ibm.zurich.CoffeeMachineBundle;

import javax.servlet.*;
import javax.servlet.http.*;
import com.ibm.hamlet.*;
import com.ibm.hamlet.helpers.*;
import org.apache.log4j.*;
import org.xml.sax.*;

public class CoffeeMachine extends Hamlet {

    // log4j
    private static Category category =
      Category.getInstance (CoffeeMachine.class.getName ());

    private Class template;

    private static class CoffeeMachineHandler extends HamletHandler {

      private CoffeeMachineTiming timing;

      public CoffeeMachineHandler (Hamlet hamlet, CoffeeMachineTiming aTiming) {
        super (hamlet);
        timing = aTiming;
      } // CoffeeMachineHandler

      public Attributes getElementAttributes (String id, String name, Attributes atts)
        throws Exception {

        if (id.equals ("Checkbox")) {
          if (timing.isSet ())
            atts = Helpers.getAttributes (atts, "CHECKED", "Checked");
        } else if (id.equals ("StartHour")) {
          String hour = atts.getValue ("Hour");
          if (hour.equals ("" + timing.getHour ()))
            atts = Helpers.getAttributes (atts, "SELECTED", "Selected");
        } else if (id.equals ("StartMinute")) {
          String minute = atts.getValue ("Minute");
          if (minute.equals ("" + timing.getMinute ()))
            atts = Helpers.getAttributes (atts, "SELECTED", "Selected");
        } // if
        return atts;

      } // getElementAttributes

    } // CoffeeMachineHandler

    public void init () throws ServletException {
      try {
        category.debug ("init");
        template = Class.forName ("CoffeeMachineTemplate");
      } catch (Exception e) {
        category.error ("", e);
        throw new ServletException (e);
      } // try
    } // init

    public void doGet (HttpServletRequest req, HttpServletResponse res)
      throws ServletException {

      try {
        category.debug ("doGet");
        CoffeeMachineTimer model = CoffeeMachineTimer.getTimer ();
        CoffeeMachineTiming timing = model.getTiming();
        HamletHandler handler = new CoffeeMachineHandler (this, timing);
        serveDoc (req, res, template, handler);
      } catch (Exception e) {
        category.error ("", e);
        throw new ServletException (e);
      } // try

    } // doGet

    public void destroy () {
      category.debug ("destroy");
    } // destroy

} // CoffeeMachine

       清单 13 显示了咖啡机的 HTML 代码 (CoffeeMachineTemplate.html),模板编译器会在设计时将该代码转换成 Java 字节码 (CoffeeMachineTemplate.class)。有关模板编译运作原理的详细信息,请参阅 参考资料。

       清单 13. CoffeeMachineTemplate.html
]>

 
    Coffee Machine
   
 
 

   

   


   

     

     

   


   

     

     


       
     

     


       
       
     

     

Coffee Machine
          TYPE="Checkbox" NAME="Checkbox" onClick="submit();" />
       

         
         
       

     

       
     

   

   

   


 

现在,假设用户想要设置咖啡机的定时器。正如您在清单 14 中看到的,用户界面不需要提供提交按钮,其原因是:有了 标签和两个