WEB开发网
开发学院软件开发Java 使用 Geronimo 实现功能强大的浏览器检测和定制 J... 阅读

使用 Geronimo 实现功能强大的浏览器检测和定制 JSP 标记

 2010-04-16 00:00:00 来源:WEB开发网   
核心提示:开始之前本教程是为那些希望在应用程序中实现某些形式的浏览器检测 API 的开发人员编写的,您可以使用 Geronimo 作为 Java™ 2 Platform Enterprise Edition(J2EE)应用服务器(尤其是使用 Jetty 作为 servlet 容器)以及一些客户端的 JavaScrip

开始之前

本教程是为那些希望在应用程序中实现某些形式的浏览器检测 API 的开发人员编写的。您可以使用 Geronimo 作为 Java™ 2 Platform Enterprise Edition(J2EE)应用服务器(尤其是使用 Jetty 作为 servlet 容器)以及一些客户端的 JavaScript。您将开发几个单独的基于 Java 的组件,包括一个 servlet 过滤器和两个定制 JSP 标记。您将使用 Apache Ant 作为编译工具来构建 .war 文件,它可以部署到 Geronimo 中,因此如果熟悉 Ant 将会有所帮助。您应该熟悉过滤器和 JSP 标记库的概念,并且能够自如地阅读 Java 源代码。

在本教程最后,您应该了解如何开发自己的基本 servlet 过滤器和 JSP 标记库,并且能够使用 Apache Geronimo 应用服务器在 J2EE 程序中实现稳健的浏览器检测 API。

关于本教程

本教程的内容可能会回避一些实质问题,“为什么要利用另外一个浏览器检测工具?”,或者“为什么不使用 BrowserHawk 或市场上的其他产品?”有时在一个产品无法满足您的要求或不容易与应用程序进行集成时,最好 能够从头开发一个工具。另外,很多工具(包括 BrowserHawk)都是专用工具,这并不意味着是件不好的事情;然而,有些开发人员宁愿使用更加开放的工具,这样他们就可以很容易对这些工具进行调整来满足自己的特殊需求了。Geronimo 就是一个很好的例子。Apache 软件基金会希望提供一个完全开放的 J2EE 应用服务器来满足 Java 开发人员社区中尚未满足的一些要求。其原因并不是目前没有很好的 J2EE 应用服务器;而是因为 Apache 希望构建一个开放源码的、经过认证的 J2EE 服务器,它可以由 Java 开发人员社区进行支持。

本教程中使用的技术不但与浏览器和 OS 检测有关;相同的技术也可以用来检测客户机上安装的 Macromedia Flash 版本,或客户机位于哪个国家。有一些开放源码 JavaScript 程序提供了对这些信息的访问,目前用来检测浏览器和 OS 的相同技术也可以使用这些脚本。

本教程的结构如下:

定制 JSP 标记 —— 这是一个回顾 ,简要介绍了有关什么是定制 JSP 标记和标记库的知识,以及如何开发 JSP 标记和标记库。

用来进行浏览器检测的 JavaScript 概要介绍了选择 JavaScript 进行浏览器检测的原因,及其在应用程序中扮演的角色。

设计策略 让您可以大致了解如何设计这个示例应用程序和最终部署到 Geronimo 中的编译结构。

服务器端的组件 详细介绍了所有服务器端的 Java 组件和示例应用程序中的源代码,并将它们结合在一起相互进行交互。

JSP 组件和示例应用 详细介绍了在应用程序中使用的 JSP 组件,并提供了几个介绍如何使用这个应用程序中开发的定制标记的例子。

实验 让您可以从源代码开始编译并构建这个示例程序,并提供了将这个应用程序部署到 Geronimo 中和测试这个应用程序的功能所需要的命令。

前提条件

本教程假设您对 Java Servlet API 和 Java 编程语言有基本的了解。具有 JavaScript 方面的知识也可以。

系统需要

要运行本教程中的样例代码,您需要执行以下步骤:

下载并安装下面的应用程序:

Geronimo Milestone 4 或更新的版本。

Apache ANT 1.6.5。

J2SE 1.4.2_09。

确保表 1 中归纳的环境变量都已经在 shell 中定义了。

表 1. 设置环境变量
变量名需要的设置
GERONIMO_HOME设置为安装 Geronimo 的根目录
ANT_HOME设置为安装 Ant 的根目录
JAVA_HOME设置为安装 Java 的根目录
PROJECT_HOME设置为 BrowserDetection 应用程序的根目录
PATH确保 ANT_HOME/bin 已经在您的 PATH中

展开所提供的 .zip 文件到一个地方(这将是项目的根目录)。这个项目的布局如下:

清单 1. 项目布局

    /BrowserDetect/ 
       /conf/  ' contains taglib definition 
       /deploy/ ' created by ANT (will contain WAR file) 
       /src/  ' contains Java source code 
       /web/  ' contains JSP, JavaScript and web.xml 
       /build.xml ' ANT build file 

如果您急切地渴望编译并运行这个应用程序,请跳到 实验 一节。

定制 JSP 标记 —— 回顾

本节提供了介绍什么是定制 JSP 标记以及为什么希望开发自己的 JSP 标记的概述或回顾。

标记库:是什么和为什么?

标记库和定制 JSP 标记都是在 1.1 版本的 JSP 规范中引入的概念。从技术角度来说,定制标记的目的是创建并访问用来影响输出(将被发送回用户浏览器的信息)的编程对象,定制标记也可以帮助将 JSP 页面分解为一些小的组件,允许更容易地对表示层进行定制。定制标记可以执行的其他任务包括流程控制、格式处理、访问数据库和发送 e-mail。我们要介绍的标记比较接近于执行流程控制的类型,它在一个浏览器上显示一组数据,在另外一个不同的浏览器上显示另外一组数据。

Geronimo 在整个结构中充当运行 Jetty 的 J2EE 服务器。Jetty 是执行您所编写的定制标记的 servlet 容器。

标记库:如何做?

下面是在应用程序中创建和部署标记库的简要步骤。最基本的一件事情是,需要编写一个扩充 javax.servlet.jsp.tagext.TagSupport 或 javax.servlet.jsp.tagext.BodyTagSupport 的 Java 类。这些类是标准 servlet API 的一部分,可以与 Geronimo 绑定在一起使用。然后编写一个 Tag Library Definition(TLD)文件,它用来描述 servlet 容器中的定制标记名,这些标记具有什么属性,以及哪些属性是必须的。在创建必要的 Java 类和 TLD 文件之后,需要将这些标记与应用程序绑定在一起,并将其部署到应用服务器上。

在这个应用程序中,类文件和建立标记的 TLD 文件都被打包到一个 .jar(Java ARchive)文件中。这个 .jar 文件与应用程序一起进行部署,也可以包含在应用程序的 .war(Web ARchive)文件中。

虽然这只是对开发定制标记一个概要介绍,但是在本教程后面您将看到详细介绍构成定制 JSP 标记的 Java 类的具体例子。您还会看到相应的 TLD 文件和要部署到 Geronimo 中的 Web 应用程序的结构。

用来进行浏览器检测的 JavaScript

本节将介绍为什么选择使用 JavaScript 进行浏览器检测,并对其他基于 JavaScript 的检测机制进行了归纳。

为什么使用 JavaScript?

那么为什么要使用 JavaScript 进行浏览器检测,而不使用浏览器在每次请求时发送给服务器的用户代理字符串呢?在 JavaScript 中,您可以检测出很多在服务器端有用的东西,包括屏幕高度和宽度,所安装的浏览器插件(例如 QuickTime、Flash、Shockwave 和 Java Plug-in),以及这些插件的版本号。在为用户准备发送一些内容时,这些信息对于服务器端非常有益(有时是必须的)。

JavaScript 在应用程序中的角色

在浏览器检测应用程序中,JavaScript 扮演了一个非常简单但却非常重要的角色。应用程序包含了一个具有一些嵌入式 JavaScript 的 JSP,它们用来分析有关用户浏览器和操作系统的数据。让 JavaScript 将这些值分配给隐藏的 HTML 表单域,并将这个表单提交给服务器。

接下来的几节将介绍服务器端的一些组件,以及客户端和服务器端的组件是如何协同工作的。

设计策略

本节将介绍整个应用程序采用的整体设计策略。您可以从本节中学习到如何设计应用程序,如何构建应用程序的结构,从而能够更好地理解这些组件是如何一起工作的。

应用程序设计

这个样例浏览器检测应用程序为开发人员提供了浏览器检测的功能,允许使用各种基于客户机浏览器的动态内容。应用程序中基本的请求流程如下:

用户到达 Web 站点。

使用 javax.servlet.Filter 实现来检测该用户的 HttpSession,从而查看它是否已经检测到了浏览器的信息。

如果已经检测到,就将这个请求转发给视图。

如果没有检测到,就重定向到一个浏览器检测 JSP 页面。

在浏览器检测 JSP 页面中,执行必须的 JavaScript 来检测浏览器的信息。

将所检测到的浏览器信息保存在隐藏表单域中,并使用 JavaScript 来自动将表单数据发送回服务器。

对新提交的浏览器信息进行类似的过滤,将这些信息保存在用户会话中,并将用户重定向到他们最初请求的页面上。

在基于 Web 的应用程序中实现这种情景可能会有 500 种不同的方法,因此您可以自由选择适合自己要求的例子。

应用程序的部署

由于需要将一个 Web 应用程序部署到 Geronimo 中,而且这个应用程序并不包含任何 EJB 组件,因此应用程序可以作为一个 .war 文件进行部署。我们可以使用 清单 2 所示的结构利用 Ant 来构建 .war 文件。

清单 2. .war 文件使用的目录结构

   browserdetection.war/ 
   /detect.jsp 
   /index.jsp 
   /index2.jsp 
   /js/browser_detection.js 
   /WEB-INF/web.xml 
   /WEB-INF/lib/browserdetection.jar 

browserdetection.jar 文件包含了服务器端的组件,包括定制 JSP 标记和其他 Java 对象,以及用来描述定制标记的 TLD 文件。其结构如 清单 3 所示。

清单 3. browserdetection.jar 文件的目录结构

   browserdetection.jar/ 
   /META-INF/taglib.tld 
   net/humandoing/browserdetect/BrowserDetectFilter.class 
   net/humandoing/browserdetect/BrowserInfo.class 
   net/humandoing/browserdetect/BrowserInfoFactory.class 
   net/humandoing/browserdetect/BrowserParentTag.class 
   net/humandoing/browserdetect/BrowserTag.class 

通过在这个地方放上 taglib.tld 文件,Geronimo 就可以自动理解您希望可以在应用程序中使用这些标记库的定义,因此并不需要在 web.xml 文件中定义标记库。

下一节将重点介绍服务器端的组件和源代码,以及它们如何在应用程序中作为一个整体进行交互。

服务器端的组件

下面我们介绍在应用程序中使用的服务器端组件,以及它们如何相互集成来从客户机获得有用的信息,将它们传送给服务器,并将这些信息关联到某个特定的用户上。然后您就可以看到如何使用它们在 JSP 页面中捕获的有用信息来产生基于用户浏览器和操作系统的内容。从较高的层次上来说,下面是在应用程序中存在的服务器端组件的列表:

Bean 类(BrowserInfo),这是一个 Plain Old Java Object(POJO),为调用者提供了有关用户浏览器的信息。

工厂类(BrowserInfoFactory)创建并填充 BrowserInfo 对象,这是基于从当前执行的 HttpServletRequest 中提取出的参数进行的。

过滤器类(BrowserDetectFilter),它负责截取对应用程序 JSP 页面的请求,确保已经在用户会话中存储了一个表示有关用户浏览器信息的对象实例。

标记类,它包括一个父标记(BrowserParentTag),其功能是封装一个或多个子标记(BrowserTag)。

Bean 类

清单 4 中的代码(我们对这段代码进行了精简,因为在 下载 一节中已经提供了完整的代码)是一个 PLJO,用来存储 Boolean 中有关特定用户浏览器的值。

清单 4. BrowserInfo.java 类

   package net.humandoing.browserdetect; 
 
import javax.servlet.http.HttpSession; 
 
/** 
 * A class that represents a given users browser or user-agent 
 * for a given session. 
 */ 
public class BrowserInfo { 
 
 private boolean isIE = false; 
 private boolean isIE4 = false; 
 private boolean isIE5 = false; 
 private boolean isIE5x = false; 
 
... 
 
 public BrowserInfo( boolean IE, boolean IE4, boolean IE5, 
           boolean IE5x, boolean IE5Mac, boolean IE5xWin, 
           boolean IE6, boolean IE7, boolean netscape4, 
           boolean mozilla, boolean konqueror, 
           boolean safari, boolean opera, boolean opera4, 
           boolean opera5, boolean opera6, 
           boolean opera7, String browserName, 
           String browserFullVersion, boolean windows, 
           boolean mac, boolean linux ) { 
  isIE = IE; 
  isIE4 = IE4; 
  isIE5 = IE5; 
  isIE5x = IE5x; 
 
...  
 
 } 
 
 /** 
  * Utility method to grab the BrowserInfo object from a users 
  * session, if one exists. 
  */ 
 public static BrowserInfo getBrowserInfoFromSession( 
     HttpSession session ) { 
  if ( session != null ) { 
   BrowserInfo out = (BrowserInfo) session.getAttribute( 
       BrowserDetectFilter.BROWSER_INFO ); 
   return out; 
  } else { 
   return null; 
  } 
 } 
 
 public boolean isIE() { 
  return isIE; 
 } 
 
 public boolean isIE4() { 
  return isIE4; 
 } 
 
 public boolean isIE5() { 
  return isIE5; 
 } 
 
 public boolean isIE5x() { 
  return isIE5x; 
 } 
 
...  
 
 public String toString() { 
  StringBuffer buffer = new StringBuffer(); 
  buffer.append( "[Browser Name: " + getBrowserName() + "]\n" ); 
  buffer.append( 
      "[Browser Version: " + getBrowserFullVersion() + "]\n" ); 
  buffer.append( "[Is Linux? " + isLinux + "]\n" ); 
  buffer.append( "[Is Windows? " + isWindows + "]\n" ); 
  buffer.append( "[Is Mac? " + isMac + "]\n" ); 
 
  buffer.append( "[Is IE? " + isIE + "]\n" ); 
  buffer.append( "[Is Opera? " + isOpera + "]\n" ); 
  buffer.append( "[Is Mozilla? " + isMozilla + "]\n" ); 
  buffer.append( "[Is Netscape4? " + isNetscape4 + "]\n" ); 
  // ... etc. 
  return buffer.toString(); 
 } 
} 

在这个清单中,使用了一个简单的 bean 类来存储有关特定用户浏览器和操作系统的信息。在这个示例应用程序中,这个类的实例只能从 BrowserInfoFactory 类中进行构建,然后这个实例被保存到某个用户会话中供应用程序中的其他部分使用。

工厂类

工厂类可以对新对象进行实例化,并向调用者返回一个新对象实例。在这个示例应用程序中,使用了一个工厂类来创建 BrowserInfo 类的实例。BrowserInfoFactory,如 清单 5 所示,会从当前执行的 HttpServletRequest 中提取信息,并使用这些值来构建并返回一个封装好的 BrowserInfo 实例给调用者。

清单 5. BrowserInfoFactory.java 类

   package net.humandoing.browserdetect; 
 
import javax.servlet.http.HttpServletRequest; 
import java.util.Enumeration; 
 
/** 
 * This class is strictly a factory class that is responsible for 
 * parsing posted JavaScript variables from a ServletRequest, and 
 * constructing a BrowserInfo object from the resulting extracted 
 * variables. 
 */ 
public class BrowserInfoFactory { 
 
 
 private static final boolean DEBUG = false; 
 
 private static final String BROWSER_NAME_PARAM = "brow"; 
 private static final String MOZILLA_BROWSER_NAME_PARAM = "moz_brow"; 
 
 private static final String BROWSER_VERSION_PARAM = "nu"; 
 private static final String MOZILLA_BROWSER_VERSION_PARAM = 
     "moz_brow_nu_sub"; 
 
 private static final String IS_IE_PARAM = "ie"; 
 private static final String IS_IE4_PARAM = "ie4"; 
 private static final String IS_IE5_PARAM = "ie5"; 
 private static final String IS_IE5X_PARAM = "ie5x"; 
 
... 
 
 /** 
  * Private constructor - this is a factory for creating BrowserInfo 
  * instances only. 
  */ 
 private BrowserInfoFactory() { 
 } 
 
 
 /** 
  * Factory method to instantiate and populate a BrowserInfo 
  * instance for a given request. 
  */ 
 public static BrowserInfo createBrowserInfo( 
     HttpServletRequest request ) { 
  if ( DEBUG ) { 
   dumpRequest( request ); 
  } 
 
  // Check browser family... 
  boolean isIE = Boolean.valueOf( 
      request.getParameter( IS_IE_PARAM ) ) 
      .booleanValue(); 
  boolean isNS4 = Boolean.valueOf( 
      request.getParameter( IS_NS4_PARAM ) ) 
      .booleanValue(); 
  boolean isMozilla = Boolean.valueOf( 
      request.getParameter( IS_MOZILLA_PARAM ) ) 
      .booleanValue(); 
  boolean isOpera = Boolean.valueOf( 
      request.getParameter( IS_OPERA_PARAM ) ) 
      .booleanValue(); 
 
  //Check specific browser versions... 
  boolean isIE4 = Boolean.valueOf( 
      request.getParameter( IS_IE4_PARAM ) ) 
      .booleanValue(); 
  boolean isIE5 = Boolean.valueOf( 
      request.getParameter( IS_IE5_PARAM ) ) 
      .booleanValue(); 
  boolean isIE5x = Boolean.valueOf( 
      request.getParameter( IS_IE5X_PARAM ) ) 
      .booleanValue(); 
 
...   
 
  return new BrowserInfo( isIE, isIE4, isIE5, isIE5x, isIE5Mac, 
      isIE5Win, isIE6, isIE7, isNS4, isMozilla, isKonqueror, 
      isSafari, isOpera, isOpera4, isOpera5, isOpera6, isOpera7, 
      browserName, browserFullVersion, isWindows, isMac, 
      isLinux ); 
 } 
 
... 
 
} 

BrowserInfoFactory 类展示了工厂用来实现这些烦杂的工作而又重复的工作是多么有效。为了简单起见,我们在这个类中并没有添加错误处理机制(例如检查 NULL 条件)。但是在真实的情况中,这个类应该会包含更多的逻辑。现在让我们介绍一个应用程序中更为重要的组件。

过滤器类

清单 6 中的代码是 javax.servlet.Filter 的一个简单而又标准的实现(为了节省空间,我们删减了一些接口方法)。这个类用于截取对 Geronimo 所发出的对所有 JSP 页面的请求,并执行相关处理,从而了解您是否已经能够在会话中检测并存储当前用户浏览器的信息。

清单 6. BrowserDetectFilter.java 类

   import javax.servlet.Filter; 
import javax.servlet.FilterChain; 
import javax.servlet.FilterConfig; 
import javax.servlet.ServletException; 
import javax.servlet.ServletRequest; 
import javax.servlet.ServletResponse; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpSession; 
import java.io.IOException; 
 
/** 
 * This filter is configured to capture all .jsp requests and 
 * then checks to see if the session attached to the current 
 * request contains an instance of a BrowserInfo object. If 
 * not, it does some redirecting and captures some browser 
 * information from the client, stores it in their session, and 
 * subsequently redirects them to the page they initially 
 * requested. 
 */ 
public class BrowserDetectFilter implements Filter { 
 
 /** 
  * True/false depending on whether or not you want the debug 
  * logging messages to get printed to the console based on 
  * the UBER-simple logging method found below. 
  */ 
 private static final boolean DEBUG = false; 
 
 /** 
  * String used as a Key for the key-value pairing to store the 
  * BrowserInfo object in the users session. 
  */ 
 public static final String BROWSER_INFO = "__Browser__Info__"; 
 
 public void init( FilterConfig filterConfig ) 
     throws ServletException { 
  //initialize stuff here... 
 } 
 
 /** 
  * Perform the appropriate filter action. If there is no browser 
  * info, stop the filter-chain and redirect to a page that will do 
  * some browser detection. 
  */ 
 public void doFilter( ServletRequest servletRequest, 
            ServletResponse servletResponse, 
            FilterChain filterChain ) throws IOException, 
     ServletException { 
  HttpServletRequest request = (HttpServletRequest) servletRequest; 
  HttpServletResponse response = 
   (HttpServletResponse) servletResponse; 
  HttpSession session = request.getSession(); 
  String requestURI = request.getRequestURI(); 
  if ( session.getAttribute( BROWSER_INFO ) != null ) { 
   //carry on - nothing to see here... 
   filterChain.doFilter( servletRequest, servletResponse ); 
  } else if ( requestURI.equals("/browserdetection/BrowserDetect") ) { 
   //we just finished doing the detection and we're getting the 
   //results posted from the JavaScript, do something about it, 
   //and pass the user on to the previous page they requested. 
   BrowserInfo browserInfo = BrowserInfoFactory.createBrowserInfo( 
       request ); 
   session.setAttribute( BROWSER_INFO, browserInfo ); 
   request.getRequestDispatcher( 
       request.getParameter( "requested_page" ) ) 
       .forward( request, response ); 
  } else { 
   //no browser info was found - forward the client to our detection 
   //jsp page for some browser detection action. 
   request.setAttribute( "RequestURI", 
       requestURI.substring( 
           request.getContextPath().length() ) ); 
   request.getRequestDispatcher( "/detect.jsp" ).forward( 
       servletRequest, servletResponse ); 
  } 
 } 
 
 public void destroy() { 
  //destroy stuff here... 
 } 
} 

清单 6 稍微进行了删减,只显示了与浏览器检测应用程序有关的部分。在真实的情景中,不会在这个类中使用一些硬编码的字符串,而是会进行更多的错误检查(尤其是要检查 NULL 的情况)。这个清单的主要操作都发生在 doFilter 方法中。您已经得到了一个对用户会话对象的引用,用于查看在这个会话中是否有一个 BrowserInfo 对象。如果 BrowserInfo 对象存在,那么您就知道早已进行了浏览器检测,因此就只需通过过滤器链了。 if/else 语句中的另外两个条件是以下两种情况:

浏览器检测刚刚执行完,现在您需要从当前请求中提取出浏览器的信息。

根本就没有浏览器信息。

在第一种情况中,要检查请求 URI(统一资源描述符),从而确保它等于某个特定的字符串("/browserdetection/BrowserDetect")。对这个 URI 要进行检查是因为(转换后的) detect.jsp 页面在执行完用来检测浏览器的脚本之后,向这个 URI 发出了一个 HTML 表单。这样您就知道(除非有一个用户手工键入了这个 URI)是否得到了对这个特定 URI 的请求,对请求进行分析之后,就可以获得一些浏览器的信息。现在,您还可以看到 BrowserInfo 和 BrowserInfoFactory 类如何适应这部分应用程序。在这个清单中,BrowserInfoFactory 用来创建 BrowserInfo 对象,然后过滤器类则将新创建的实例插入到当前用户的 HttpSession 中。

在第二种情况中,需要获得一些浏览器信息,因此可以将这个用户重定向到 detect.jsp 页面上。这个页面包含了负责执行浏览器检测并将表单发回服务器的 HTML 和 JavaScript。

定制标记类

下面两个清单是在浏览器检测应用程序中创建和使用的定制 JSP 标记类。在 清单 7 中给出的类是简化的父标记类。其惟一职责是确保所有对 BrowserTag 类的使用都封装到了一个 BrowserParentTag 类中。

清单 7. BrowserParentTag.java 类

   package net.humandoing.browserdetect; 
 
import javax.servlet.jsp.tagext.TagSupport; 
 
/** 
 * A parent tag for the BrowserTag (Can have multiple 
 * nested BrowserTag instances). Nothing exciting to 
 * note here. 
 */ 
public class BrowserParentTag extends TagSupport { 
 
 public int doStartTag() { 
  return EVAL_BODY_INCLUDE; 
 } 
 
 public int doEndTag() { 
  return EVAL_PAGE; 
 } 

清单 8 是我们正在构建的浏览器检测应用程序中另外一个重要的部分。

清单 8. BrowserTag.java 类 —— 第一部分

   package net.humandoing.browserdetect; 
 
import javax.servlet.jsp.JspException; 
import javax.servlet.jsp.tagext.BodyTagSupport; 
import java.lang.reflect.Method; 
 
/** 
 * This tag is used to evaluate a body for a browser version or 
 * combination as required by set tag attributes. 
 * 
 * @author Daniel Wintschel 
 */ 
public class BrowserTag extends BodyTagSupport { 
 
 /** 
  * This is the value that the JSP programmer can use when they wish 
  * to have a "default" value to appear if none of the other values 
  * used in the set of BrowserDetect tags evaluates to true. 
  */ 
 private static final String DEFAULT = "default"; 
 
 /** 
  * The parent tag - nothing exciting here. 
  */ 
 private BrowserParentTag parent = null; 
 
 /** 
  * The attribute we need to check from the BrowserDetect JSP tag. 
  */ 
 private String check; 
 
 public String getCheck() { 
  return check; 
 } 
 
 public void setCheck( String check ) { 
  this.check = check; 
 } 

清单 8 显示了 BrowserDetectTag 前几行的内容。注意,这个标记的惟一属性是 check,其值在任何使用这个标记的 JSP 页面中都会被设置。check 属性的有效值包括任何 BrowserInfo 类中会返回 Boolean 值(即 isIE、isLinux 等等)的公共方法名。check 属性的另外一个有效值是 default。如果 BrowserInfo 实例在该用户的会话中并不存在,那么就可以使用默认值。在我们这个应用程序中,值 default 并不会作为 switch 语句中最终的 else 或最终的 default 值。然而,这种功能不费任何力气就可以添加到这个标记中,如 清单 9 所示。

清单 9. BrowserTag.java 类 —— 第二部分

   public int doStartTag() throws JspException { 
 doParentCheck(); 
 BrowserInfo browserInfo = (BrowserInfo) pageContext.getSession() 
     .getAttribute( BrowserDetectFilter.BROWSER_INFO ); 

这可以确保 BrowserTag 已经嵌入到 BrowserParentTag 中了。否则,就会触发一个异常。下面的代码会从用户会话中提取出 BrowserInfo 对象(如果存在的话)。

BrowserInfo 对象

清单 10 包含了根据是否包含一个值为真的表达式而将数据写回客户机浏览器的实现。现在您可以看到是否存在 BrowserInfo 对象;如果并不存在这种对象,而且用户已经指定 default 作为 check 值,那么就将嵌入在这个 JSP 标记中的数据写回输出流中。然而,如果您有 BrowserInfo 对象,事情就有些有趣了。在这种情况中,可以使用响应来获得对 BrowserInfo 类中对应 check 属性值的方法名的引用,并对 BrowserInfo 对象执行这个方法。如果所返回的值为 true,那么就可以将该标记中的内容写入到输出流中。如果该值为 false,那么内容就会被忽略。下面让我们来看一下这段代码。

清单 10. BrowserTag.java 类 —— 第三部分

   if ( browserInfo == null && check != null && check.equals( 
    DEFAULT ) ) { 
 return EVAL_BODY_INCLUDE; 
} else { 
 try { 
  Method method = browserInfo.getClass().getMethod( check, null ); 
  Object result = method.invoke( browserInfo, null ); 
  if ( result != null && result instanceof Boolean ) { 
   Boolean evaluate = (Boolean) result; 
   if ( evaluate.booleanValue() ) { 
    return EVAL_BODY_INCLUDE; 
   } else { 
    return SKIP_BODY; 
   } 
  } else { 
   throw new JspException( 
       "You have chosen a value for the 'check' " + 
       "attribute that does not correspond to an " + 
       "available attribute in the BrowserInfo class" ); 
  } 
 } catch ( NoSuchMethodException e ) { 
  throw new JspException( 
      "You have chosen a value for the 'check' attribute " + 
      "that does not correspond to an available " + 
      "attribute in the BrowserInfo class" ); 
 } catch ( Exception e ) { 
  e.printStackTrace(); 
  throw new JspException( 
      "Something bad definitely happened... Try some " + 
      "better exception handling next time." ); 
 } 
} 

清单 10 中的代码会决定在 JSP 页面的 BrowserTag 中所嵌入的数据是被写回给用户,还是被丢弃,直到使用对应浏览器的用户到来时才结束。您在这个标记中引用的最终变量是 EVAL_BODY_INCLUDE 和 SKIP_BODY。这些常量变量都是在超类 javax.servlet.jsp.tagext.BodyTagSupport 中声明的。EVAL_BODY_INCLUDE 意味着要将这个标记实体中包含的内容写回到输出流中,而 SKIP_BODY 则意味着要跳过这个标记的实体中的内容。

JSP 组件和示例应用

本节将对这个应用程序中所包含的 JSP 组件进行简要的归纳,并给出了两个使用所开发的定制标记的例子。

检测 JSP —— JavaScript

我们惟一要费心的实际 JSP 组件就是 detect.jsp 页面。其他的 JSP 组件都是一些非常简单的使用 BrowserTag 的例子。detect.jsp 页面是当前用户会话中没有相关的 BrowserInfo 对象时,BrowserDetectFilter 所转发到的页面。

清单 11. detect.jsp 页面

   <% 
  String originallyRequestedPage = 
      (String) request.getAttribute( "RequestURI"); 
%> 
<html> 
<head> 
<title>Detecting Browser Information...</title> 
</head> 
<body> 
  <form name="detect" method="post" action="BrowserDetect"> 
    <input type="hidden" name="brow" value="" /> 
    <input type="hidden" name="moz_brow" value="" /> 
    <input type="hidden" name="nu" value="" /> 
    ... // more hidden form field... 
    ... // omitted for the sake of brevity...     
  </form> 
 
  <script type="text/javascript" language="JavaScript"> 
 
<%-- 
Include our open source browser detection javascript 
 
Credit given to Harald Hope and Tapio Markula 
 
JavaScript from below is taken from the file browser_detection.js 
which is included in this project and is released under the GNU LGPL. 
 
http://techpatterns.com/ 
http://techpatterns.com/downloads/javascript_browser_detection.php 
 
Lesser GPL license text: 
http://www.gnu.org/licenses/lgpl.txt 
--%> 
    //initialization, browserInfo, os detection 
    var d, dom, nu='', brow='', ie, ie4, ie5, ie5x, ie6, ie7; 
    ... // lots more variable declarations... 
    ... // omitted for the sake of brevity... 
     
    d=document; 
    n=navigator; 
    nav=n.appVersion; 
    nan=n.appName; 
    nua=n.userAgent; 
    old=(nav.substring(0,1)<4); 
    mac=(nav.indexOf('Mac')!=-1); 
    win=( ( (nav.indexOf('Win')!=-1) || 
      (nav.indexOf('NT')!=-1) ) && !mac)?true:false; 
    lin=(nua.indexOf('Linux')!=-1); 
    ...// lots more variable assignments and browser testing... 
    ... // omitted for the sake of brevity... 
 
    document.detect.requested_page.value = 
      '<%=originallyRequestedPage%>'; 
    document.detect.brow.value = brow; 
    document.detect.moz_brow.value = moz_brow; 
    document.detect.nu.value = nu; 
    document.detect.moz_brow_nu_sub.value = moz_brow_nu_sub; 
 
    ... // lots more assignments that assign values to 
    ... // hidden form field elements... 
    ... // omitted for the sake of brevity... 
    document.detect.submit(); 
   //--> 
  </script> 
 
</body> 
</html> 

清单 11 中包含了在 detect.jsp 中使用的 HTML、JavaScript 以及 Java 代码段。这个页面会一行行地执行,并且会:

输出一个具有隐藏表单域的 HTML 表单。

声明 JavaScript 变量

使用 JavaScript 所提供的功能为所声明的变量赋值,它们用来标识发出用户请求的浏览器名、浏览器的版本号以及操作系统。

将所检测到的值赋予隐藏表单域元素。

将 HTML 表单自动提交回服务器。

HTML 表单的操作是 BrowserDetect,并且给定应用程序上下文,它就会转换为“/browserdetection/BrowserDetect”。这在示例应用程序中可以工作,因为上下文中没有子目录。

示例应用

清单 12 和 清单 13 给出了两个简单的 JSP 页面,它们用作应用程序的入口,显示了如何使用本教程中创建的定制 JSP 标记。

清单 12. index.jsp 页面

   
   <%@ taglib uri="/WEB-INF/lib/browserdetection.jar" prefix="bd" %> 
<html> 
<head> 
<title>Browser Detection Test</title> 
</head> 
<body> 
This is a basic server-side check of the browser using session-persisted values 
that were previously determined from an open-source JavaScript. 
 
<br><br> 
 
<bd:Browser> 
  <bd:BrowserDetect check="isIE"> 
  Look Ma!!! I'm Internet Explorer... Woof! 
  </bd:BrowserDetect> 
  <bd:BrowserDetect check="isMozilla"> 
  Look Ma!!! I'm some kinda Mozilla Browser... Yum! 
  </bd:BrowserDetect> 
</bd:Browser> 
 
<br><br> 
 
Let's see what else: 
 
<a href="index2.jsp">Check OS</a> 
 
</body> 
</html> 

清单 12 是应用程序的入口页面,或者称为索引页面。这是一个显示如何使用最终的 BrowserTag 的简单例子。如果用户的浏览器是 Microsoft® Internet Explorer,那么应用程序就会输出 Look Ma!!! I'm Internet Explorer... Woof!。如果浏览器是 Mozilla,那么就会输出 Look Ma!!! I'm some kinda Mozilla Browser... Yum!。这个示例应用程序并没有提供 else 功能,这并没有什么。例如,如果用户没有使用 Internet Explorer 或 Mozilla,那么就不会显示任何输出。要实现这种功能,您需要在 BrowserTag 中再添加一点儿功能。

清单 13 与 index.jsp 非常类似,但是它展示了如何实现操作系统特有的内容,而不是浏览器特有的内容。

清单 13. index2.jsp 页面

   
   <%@ taglib uri="/WEB-INF/lib/browserdetection.jar" prefix="bd" %> 
<html> 
<head> 
<title>Browser Detection Test</title> 
</head> 
<body> 
This is a server-side check of the operating system using session-persisted values 
that were previously determined from an open-source JavaScript. 
 
<br><br> 
 
<bd:Browser> 
  <bd:BrowserDetect check="isWindows"> 
  I'm running on Windows 
  </bd:BrowserDetect> 
  <bd:BrowserDetect check="isLinux"> 
  Linux is us, and we're you're friend 
  </bd:BrowserDetect> 
  <bd:BrowserDetect check="isMac"> 
  Mac - what else can we say? Yum! 
  </bd:BrowserDetect> 
</bd:Browser> 
 
</body> 
</html> 

Trying it out

现在您已经准备好编译并运行这个示例应用程序了。

编译并运行应用程序

要编译这个应用程序,请确保您已经下载并安装了适当的应用程序,并正确设置了环境变量,就像 前提条件 一节介绍的一样。打开一个 shell,执行下面的命令:

% cd %PROJECT_HOME% 
 
% ant build-deployment-war 

上面的命令使用了 Ant 编译文件(包含在附加资源中,例如 build.xml)并执行 build-deployment-war 目标。这会对本教程中的所有源代码都进行编译,并使用适当的格式将其打包到 .jar 和 .war 文件中,正如 应用程序部署 一节中介绍的一样。

下面的命令可以启动 Geronimo 这个应用服务器。

% java -jar "%GERONIMO_HOME%\bin\server.jar" 

下面的命令部署浏览器检测应用程序,使其可以使用。

% java -jar "%GERONIMO_HOME%\bin\deployer.jar" deploy   
"%PROJECT_HOME%\deploy\browserdetection.war" 

在输入最后一个命令之后,您会看到提示要求输入将应用程序部署到 Geronimo 中所需要的用户名和密码。输入默认的用户名 system 和默认的密码 manager。

在 Microsoft Windows 上引用环境变量 %ENV_VAR%,在 Linux®、UNIX 和 Mac OS X 上引用环境变量 $ENV_VAR。

假设您已经成功执行了以上步骤,现在就应该可以打开浏览器并使用下面的 URL 来测试这个应用程序了:

http://localhost:8080/browserdetection/index.jsp 

这个例子只能在使用 Internet Explorer 和基于 Mozilla 的浏览器时正常工作,但是它很容易进行扩展来处理其他的浏览器,包括 Safari 和 Opera。请参看 图 1 和 图 2 来了解基于所使用的不同浏览器的区别。图 2 显示了提供操作系统特有信息的一个例子。

图 1. Firefox 浏览器访问 index.jsp 的情况
使用 Geronimo 实现功能强大的浏览器检测和定制 JSP 标记

图 2. Firefox 浏览器访问 index2.jsp 的情况
使用 Geronimo 实现功能强大的浏览器检测和定制 JSP 标记

要测试这个应用程序的功能,请使用一个不同的浏览器来访问这个页面。请参看 图 3 中使用 Internet Explorer 来访问这个应用程序时的结果。

图 3. Internet Explorer 浏览器访问 index.jsp 的情况
使用 Geronimo 实现功能强大的浏览器检测和定制 JSP 标记

结束语

本教程充分利用了 Geronimo 对于 servlet 过滤器和定制 JSP 标记库的支持能力,展示了一种在基于 Web 的 Java/J2EE 应用程序中集成检测所使用的浏览器的方法。这种集成方法可以通过在服务器上执行一些 Java 代码从而简单地获得所检测的信息。另外,已经实现了一个定制 JSP 标记库,它让 JSP 开发人员可以为客户机和终端用户提供浏览器和操作系统特有的内容。我们无法非常全面地介绍这个主题的内容,但是这却是众多利用开放源码工具(例如 Apache Geronimo 应用服务器)在 J2EE 应用程序中实现健壮的浏览器检测 API 的方法中非常简单的一种。

Tags:使用 Geronimo 实现

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接