如果您为某个网站编码和提供支持,或者在因特网上拥有一个页面,您就会知道使图像满足所有读者的需要是多么困难。对于偶尔上网的读者以及那些拥有较小显示器或低速拨号连接的读者而言,他们可能更喜欢小图像,大概是 320x240 像素或者更小。而对于其他拥有较大显示器或快速高带宽连接的读者而言,他们可能希望有更大的图像和更多的详细信息。
控制世界 ? 或者至少控制您的图像
作为一名站点开发人员或页面作者,迎合所有这些喜好是很困难的。要手工执行该工作,必须将站点上的每个图像转换成您的网站支持的图像大小。然后,需要调整站点上每个页面中的图像标记,使每个标记都正确地反映图像的大小。不能只更改 HTML img 标记的宽(width)和高(height),而不更改图像:这会导致低带宽用户下载大的图像,并在客户机端重新调整它的大小。如果提供了八个常用的图像大小,很容易可以看到每个图像将如何需要八个缩放大小,以及每个页面将如何需要八个版本的图像标记。这类图像管理很乏味而且容易出错,很简单地就能明白为什么大多数网站都不提供多个图像大小。
问题不在于技术:使用 Java 编程将图像转换成各种大小或格式是很容易的。问题也不在于服务:使用 Web 服务器定制页面以满足个别读者的需要是很常见的。相反,问题在于以易于部署和管理的形式组合技术和服务。
本文推荐了一种解决方案,使用 JavaServer Pages(JSP)标记管理您的图像。例如,不是象下面那样,在 HTML 中编码图像标记,并且为每个图像大小提供多个版本:
提供一个可根据读者首选项自动调整图像大小的标记更有意义,如下所示:
让读者从许多大小中选择并且让他们的首选项影响站点上的所有图像,这是可能的,如图 1 中的样本浏览器图像所示。插入宽和高属性,并且消除手工编辑这些标记的苦差事也是可能的。
图 1. 带有图像首选项的示例 JSP 页面(笑一个)
可能您以前从未看到过 JSP 定制标记,让我们简单地研究一下本示例中的语法。JSP 定制标记看上去非常象 HTML 标记,但有下列区别:
有一个由标记开发人员创建的标记名 imagesizer。
标记有一个前缀 util,它将标记集组合成库,这非常类似于 Java 编程中的包名。您可以创建新的前缀,或者使用与库一起提供的缺省名。
该标记拥有一个新的类似 XML 的结束标记“/>”。
与 HTML 标记一样,JSP 标记可以拥有任意数量的属性,如这里显示的 src 属性,它们可以包含主体,主体中可包含其它标记。由于我们正在模仿 HTML 的 img 标记,所以我们的 JSP 图像缩放标记将没有主体。
当 JSP 页面使用定制 image-sizer 标记时,标记的 Java 实现找到图像文件,将其转换成合适的大小(在这一过程中可能会添加版权或水印徽标),然后将图像提供给读者。该标记使站点管理器不必在发布前转换图像。它还简化了编写 Web 页面的工作,因为处理许多图像大小首选项只需要一个页面。最后,也是最重要的,为您的所有站点图像提供这类灵活性将赢得读者的青睐。
Web 服务器上在发生什么
本节提供了一个高级别视图,介绍了当客户机(读者使用的 Web 浏览器)访问提供 JSP 页面的站点时幕后在发生什么。有三种常见的交互,如图 2 所示:
图 2. Web 客户机和服务器之间的交互
在第一种情形中,假定浏览器只请求静态文档,如 HTML 文件或图像文件。服务器在它的文件空间中找到资源,然后将文件提供给浏览器。请求文档和响应请求是在 HTTP 中定义的,HTTP 构成了因特网上客户机/服务器交互的基础。Web 服务器完整地处理请求,无需与 servlet 容器或 Web 应用程序服务器进行交互。
在第二种情形中,假定浏览器请求包含 Java servlet 的 Web 资源。Java servlet 使 Web 服务器能够在服务器上使用 Java 编程语言执行任务。servlet 很有效,与旧有的技术(如公共网关接口(CGI)、服务器端 JavaScript)相比,使用的内存和处理能力更小。servlet 比其它技术更具有可移植性,因为许多 Web 服务器 ? 如 IBM WebSphere Application Server(Application Server)和 Apache Tomcat 都支持 servlet 容器,而 servlet 容器可以在许多不同的平台上运行相同的 servlet。最后,由于 Java 语言的内在安全性(如健壮的异常处理和细颗粒度安全性),错误的 servlet 很少会影响到 Web 服务器。如图 2 所示,Web 服务器搜索适当的 servlet,如有必要则编译 servlet 源代码,然后将 servlet 的处理结果返回给请求者。经常被请求的 servlet 会高速缓存在服务器的内存中。
在第三种情形中,假定浏览器请求包含 JSP 页面的 Web 页面。JSP 页面有助于简化显示信息的任务,并且有助于将动态内容(实时生成的)与静态页面分隔开。Web 页面设计人员象使用 HTML 库中的任何其它标记一样使用 JSP 标记。JSP 程序员遵循 JSP 编程规范,并且按照其约定实现标记。
下一节阐述了如何实现图像缩放 JSP 标记,以及如何编写 JSP 页面。从 Web 容器的观点来看,JSP 页面与 Java servlet 是密切相关的。Web 容器将基于文本的 JSP 页面转换成(每页转换一次)其 Java 实现。Web 容器寻找 Java 实现,将该实现看成 Java servlet,运行代码并将处理结果返回给客户机。这些看上去好象有很多层和重定向,但是对于用户而言,分派是快捷和透明的。象 servlet 一样,经常被请求的 JSP 页面也高速缓存在服务器的内存中。
编写定制 JSP 标记
既然明白了 Web 服务器是如何处理 JSP 页面请求的,那么让我们研究一下如何实现定制 JSP 标记。注:JSP 标记既来自标准库(如 Java Standard Template Library,JSTL),也来自您自己编写的库(也称为定制标记)。通常,定制标记处理特殊的问题领域。对本文而言,我们在处理如何管理图像。目前,Java 2 Extended Edition(J2EE)V1.2 和 V1.3 使用 JSP 规范 V1.2。在写作本文的时候,Sun 已经发布了 JSP 规范 V2.0。这个新规范并未对实现定制标记的方法进行重大更改。
通过 taglib 伪指令,可以将标准和定制标记库导入 JSP 页面,如下所示:
这个伪指令指定了标记库描述符文件的位置,这里指定的是 imagesizer.tld,还指定了在页面中使用它时的前缀,这里指定的是 util。如前面的标记示例所示,将标记与其前缀及其名称一起使用:
标记库描述符告诉 Web 容器哪些标记是可用的,以及它们如何发挥作用。清单 1 显示了这样一个示例。文件使用了 XML 格式,并且易于读取,而应用程序开发平台 ? 如 IBM WebSphere Studio Application Developer(Application Developer)可以帮助您填充字段,并且验证文件。最重要的信息是 tag 元素:它定义了定制 JSP 标记的名称和实现标记的 Java 类。它还显示了标记接受的任何属性和主体内容。
清单 1. 标记库描述符(Tag Library Descriptor,TLD)摘录
1.0
1.1
imagesizer
tags.ImageSizerTag
empty
src
required
alt
quality
在这个示例中,tag 有三个属性,其中只有 src 属性是必需的。可选的 alt 属性模拟 HTML 的 img alt 属性。作为练习,您可以扩展该 JSP 标记以包含其它可选的 img 属性。(大约有 12 种这些属性。)最后,实现提供了一个可选的 quality 属性,以使页面编写人员可以控制已重新调整大小的图像的颗粒度和大小。
编写定制 JSP 标记的下一步是实现标记的 Java 代码。在本文中,标记 imagesizer 是在 tags.ImageSizerTag Java 类中实现的。大多数 J2EE 定制标记支持都位于 javax.servlet.jsp.tagext 包中。imagesizer 类继承了标准的 TagSupport,后者实现不带主体的标记。TagSupport 的子代类是 BodyTagSupport,它也实现标记,并且带有主体。这两个类都实现了 Tag 接口,其中,当第一次读取标记以及当 Web 容器完全读取标记后再次读取标记时,会调用 doStartTag 和 doEndTag 方法。ImageSizer 标记只实现了 doEndTag,因为一旦所有属性信息都可用时,它需要起作用。
在 TagSupport 类中,PageContext 类提供了对有关 JSP 页面的重要信息的访问。例如,PageContext 提供了对 HttpRequest 和 HttpResponse 对象的访问。这些对象对于读取表单值和写响应是至关重要的。如果您希望跟踪用户的首选项,并且将表单值从一个页面传递到另一个页面,该请求还提供了对 HttpSession 的访问。PageContext 还提供了对 ServletContext 的访问,后者有助于您查找 servlet 的路径、名称和其它信息。在 ImageSizer 代码中(如清单 2 所示),有许多对 PageContext 对象及其提供信息的引用。图 3 显示了这些类的关系。就象任何标准类图一样,实心框代表类,而虚线框代表接口。用从派生的类或接口到其父类或接口的一条线来表示继承。
清单 2. ImageSizerTag doEndTag 实现 // Implement the tag once the complete tag has been read.
public int doEndTag() throws JspException {
// Move request data to session.
int outputSize = 0;
String sizeVal = request.getParameter( REQUESTSIZEKEY );
if ( sizeVal != null ) {
session.setAttribute( REQUESTSIZEKEY, sizeVal );
sizeVal = (String) session.getAttribute( REQUESTSIZEKEY );
if ( sizeVal != null ) {
outputSize = Integer.parseInt( sizeVal );
}
}
// Get specified image locally.
String contextPath = getContextPath( request );
Image image = Toolkit.getDefaultToolkit().getImage(contextPath + src );
ImageSizer.waitForImage( image );
int imageWidth = image.getWidth( null );
int imageHeight = image.getHeight( null );
if (( imageWidth > 0 ) && ( imageHeight > 0 )) {
if (( outputSize > 0 ) && ( outputSize != imageWidth )) {
// Convert image to new size.
Image outputImage = ImageSizer.setSize( image, outputSize, -1 );
ImageSizer.waitForImage( outputImage );
int outputWidth = outputImage.getWidth( null );
int outputHeight = outputImage.getHeight( null );
if ( outputWidth > 0 && outputHeight > 0 ) {
// Change image file name to xxxx.size.jpg
String originalSrc = src;
int lastDot = src.lastIndexOf( ´.´ );
if ( lastDot > -1 ) {
src = src.substring( 0, lastDot + 1 );
}
setSrc( src + outputSize + ".jpg" );
// Write new size image to JPEG file.
File file = new File( contextPath + src );
if ( !file.exists() ) {
out.println( "" );
FileOutputStream fos = new FileOutputStream( contextPath + src );
ImageSizer.encodeJPEG( fos, outputImage, quality );
fos.close( ) ;
}
imageWidth = outputWidth;
imageHeight = outputHeight;
}
} // if outputSize
} // if image found
// Produce output tag.
out.print( "
// Add alt text, if any
if ((alt != null ) && ( alt.length() > 0 )) {
out.print( " alt="" + alt + """ );
}
// Add proper width, height.
out.print( " width="" + imageWidth + "" height="" +
imageHeight + """ );
out.println( ">" );
return EVAL_PAGE;
} // doEndTag
图 3. 重要的 javax.servlet.jsp.tagext 类
清单 2 显示了 ImageSizerTag 类的 doEndTag 方法。这几乎是实现定制 JSP 标记所需的全部 Java 代码。它是一大段代码,但是它将有助于您的理解,以便全面地了解该方法。首先,任何 HTTP 请求参数都保存在 HTTP 会话中。该请求带有一个属性,如对于图像大小的用户首选项,并将其保存在会话中,以便它可以跟随用户从一个页面到另一个页面。要扩充该标记的功能,可以扩展它,使之可将用户首选项保存在 cookie 中,这样用户下一次访问站点时就可使用其首选项。
下一步是装入缺省图像,JSP 页面使用它来作为基图,所有其它图像都根据基图来调整大小。这里,java.awt.Toolkit 请求图像,图像是用 ImageSizer.waitForImage 装入的,并对其进行了检查,以了解其是否进行了正确的缩放。需要进行装入暂停,因为 Java 图像是异步装入的,当请求它们时并非始终完全可用。在本示例中,ImageSizer 辅助类执行整个图像处理操作,下一节将对此做进一步介绍。如果宽和高匹配,则无须重新调整图像大小,并将跳过 if 这一大块代码,而且使用图像名称和当前大小编写 HTML 图像标记。这就是 JSP 实现模拟 HTML 图像标记所需做的全部工作。
如果用户请求新的图像大小,那么 ImageSizer 辅助类会重新调整图像的大小。使用文件大小给图像文件指定新的名称,文件是 JPEG 编码的,并写到文件系统。接着,将在 HTML 图像标记输出中使用刚刚重新调整大小的文件。该标记的备用实现可能会将文件保存为 GIF 或 PNG 格式,或者甚至会通过内存来提供图像以节省磁盘空间。但是,清单 2 将重新调整大小的文件高速缓存到了磁盘,以备将来使用。因此,第一次重新调整大小时需要花费一些服务器处理时间,但是随后对图像大小进行请求时根本不需要处理。该示例的扩展可能会检查可用的磁盘空间,以便于帮助平衡有限的文件空间与您希望提供给客户机的即时信息之间的折衷。
调整图像大小
前一节研究了编写定制 JSP 标记的步骤。ImageSizerTag 类会自动重新调整图像的大小以匹配用户的首选项。这一节提供了有关您可以如何使用 ImageSizer 类来重新调整图像大小并将其保存为 JPEG 文件的更多详细信息。利用 java.awt.Image 类中的 getScaledInstance 方法,很容易在 Java 代码中重新调整图像的大小。利用新的宽和高调用这个方法,或者为某个参数提供一个值 -1 以保持纵横比,您可以得到新的重新调整过大小的图像。但是,就象任何 Java 图像一样,该图像并不是立即可用的,因此您必须使用 java.awt.MediaTracker 以等待图像完全装入。ImageSizer 的 waitForImage 方法封装了该代码。
在该示例中,最难的设计点在于决定如何保存图像。用 Java 编程编码和保存图像有很多选择,所有选择都有不同的权衡。
com.sun.image.codec。该包在 Java 2 SDK 1.2 和 1.3 实现中是可用的,但它在私有(private)包中,在将来的 Java 2 版本中这可能会发生变化。该包只限于 JPEG 编码。
Java Image I/O API。在 Java 2 SDK 1.4 中,该包是公共的和标准的。但是,在撰写本文时,还没有使用 SDK 1.4 的 J2EE 版本。该包提供了良好的图像操作功能和编码选项。
Java 高级映象 API(Java Advanced Imaging API)。这个 API 是一个标准扩展,但是使用它需要安装包 ? 您的 Web 管理员可能不支持该工作。
ACME GIF 编码器(ACME GIF Encoder)。该软件及许多其它第三方图像包很有用,您可以将它们合并在示例代码中,但是有个费用和可维护性的问题。同其它选择不同,该软件不是免费的,并且不完全支持 GIF 标准。
对于清单 3,我们使用 com.sun.image.codec 包,因为它在所有 J2EE 1.2 和 1.3 Web 服务器容器(如 IBM WebSphere 和 Apache Tomcat)中都可用。编码器很简单,并且是 100% 纯 Java 代码,但是它们是 com.sun 包中的。但是,从长远考虑,Java Image I/O 包可能是发展的方向。在图像转换特性以及保存为多种文件格式的能力方面,它比较强大。直到 Java 2 V1.4 时 Java Image I/O 包才会成为标准。
既然已经决定了使用哪个图像处理包,那么保存 JPEG 文件的代码就相当简单了。ImageSizer 的 encodeJPEG 方法封装了这个过程:
java.awt.image.BufferedImage 对象,这是一个增强的 Java Image 子代,是从重新调整过大小的输出图像创建的。注释在代码中标明了一个位置,可以在这里扩展示例以将徽标、水印、时间戳记或版权信息添加到图像。
在将 Image 转换为 BufferedImage 之后,在输出流上创建一个 JPEGImageEncoder 对象。输出编码质量的范围从 0.0(最差)到 1.0(最佳)。缺省值是 0.75,但 0.95 将生成较大的文件大小,它带有更详细的图像。作为该示例的扩展,您可以考虑由图像大小来决定质量 ? 较小的图像需要较高的质量设置,而较大的图像需要较低的设置。
将图像编码到输出流,并对该流进行刷新以确保所有信息都显示在图像文件中。
清单 3. ImageSizer encodeJPEG 实现 // Encodes the given image at the given
// quality to the output stream.
public static void encodeJPEG
( OutputStream outputStream,
Image outputImage, float outputQuality )
throws java.io.IOException {
// Get a buffered image from the image.
BufferedImage bi = new BufferedImage
( outputWidth, outputHeight,
BufferedImage.TYPE_INT_RGB );
Graphics2D biContext =
bi.createGraphics( );
biContext.drawImage
( outputImage, 0, 0, null );
// Additional drawing code, such as
// watermarks or logos can be placed here.
// com.sun.image.codec.jpeg package
// is included in Sun and IBM sdk 1.3.
JPEGImageEncoder encoder =
JPEGCodec.createJPEGEncoder
( outputStream );
// The default quality is 0.75.
JPEGEncodeParam jep =
JPEGCodec.getDefaultJPEGEncodeParam
( bi );
jep.setQuality( outputQuality, true );
encoder.encode( bi, jep );
outputStream.flush();
} // encodeImage
那就是重新调整图像大小及保存图像所需的全部。
打包并部署到 WebSphere 或 Tomcat 上
本节将阐述如何打包 ImageSizer JSP 标记并将其部署到 Application Server V4.0 或 Apache Tomcat V4.0 上。图 4 显示了 Application Developer 的抓屏。左上栏的 Navigator 窗格(windowpane)显示了 Web 应用程序的目录结构,以及必须如何根据 J2EE 规范打包定制 JSP 标记。由于 J2EE 规范的需要,因此该目录结构对于所有 Web 应用程序都是公共的。一旦归档了该结构,则它将成为 Web 归档(WAR)文件,并且可以方便地将其传送给 WebSphere、Tomcat 或任何其它兼容的 Web 容器。良好的开发环境(如 Application Developer)有助于开发人员遵循这些规范,并生成有效的应用程序。
图 4. 在 WebSphere Studio Application Developer 中打包 ImageSizer
在 ImageSizer 项目下,有一个源代码目录;开发人员可以选择是否在最终的 WAR 文件中包含该目录。webApplication 目录包含实际的程序代码。示例项目包含名为 PickASize.jsp 的测试 JSP 页面及称为 LazyDog.jpg 的巨大的测试图像。通常,ImageSizer 定制标记的库版本并不包含这些。标记的实现位于 WEB-INF 目录中。Java 类都位于 WEB-INF/classes 中,而标记库描述符(Tag Library Descriptor)文件位于 WEB-INF/tlds 中。这些是所有 Web 应用程序的标准目录布局。该树中的其它文件有助于设置服务器选项,但却并非 WAR 文件强制需要的。使用 Application Developer 或 Java SDK 以创建该应用程序的 WAR 文件。
要将 Web 应用程序部署在 Web Application Server(如 Tomcat)上,则将文件放在 ROOT/webapps 目录中,并且让服务器将 WAR 文件展开为目录结构。对于 Application Server,您可以使用 Administrators Console 中的 Web Application 向导来安装应用程序。部署完毕,可通过访问 http://yourhostname:port/ImageSizer/PickASize.jsp 运行 JSP 页面。
结束语
现在您已经创建了自动管理图像缩放的 JSP 定制标记。定制标记为您省去了重新调整图像大小的工作,还使用户在访问您的网站时可以指定自己的首选项。可以很方便地扩充该示例标记以执行各种图像操作:版权文本、时间戳记、徽标或水印。通过将代码部署到 Application Server 或 Apache Tomcat,并且编写一些基于图像的 JSP 页面或使用给定的示例,您可以试验代码。希望本文为您提供了“取出即可用”的 JSP 标记,还提供了能使您进一步扩展功能以满足您需要的代码。愿您看图愉快![@more@]
控制世界 ? 或者至少控制您的图像
作为一名站点开发人员或页面作者,迎合所有这些喜好是很困难的。要手工执行该工作,必须将站点上的每个图像转换成您的网站支持的图像大小。然后,需要调整站点上每个页面中的图像标记,使每个标记都正确地反映图像的大小。不能只更改 HTML img 标记的宽(width)和高(height),而不更改图像:这会导致低带宽用户下载大的图像,并在客户机端重新调整它的大小。如果提供了八个常用的图像大小,很容易可以看到每个图像将如何需要八个缩放大小,以及每个页面将如何需要八个版本的图像标记。这类图像管理很乏味而且容易出错,很简单地就能明白为什么大多数网站都不提供多个图像大小。
问题不在于技术:使用 Java 编程将图像转换成各种大小或格式是很容易的。问题也不在于服务:使用 Web 服务器定制页面以满足个别读者的需要是很常见的。相反,问题在于以易于部署和管理的形式组合技术和服务。
本文推荐了一种解决方案,使用 JavaServer Pages(JSP)标记管理您的图像。例如,不是象下面那样,在 HTML 中编码图像标记,并且为每个图像大小提供多个版本:
![LazyDog.jpg](images/LazyDog.jpg)
提供一个可根据读者首选项自动调整图像大小的标记更有意义,如下所示:
让读者从许多大小中选择并且让他们的首选项影响站点上的所有图像,这是可能的,如图 1 中的样本浏览器图像所示。插入宽和高属性,并且消除手工编辑这些标记的苦差事也是可能的。
图 1. 带有图像首选项的示例 JSP 页面(笑一个)
可能您以前从未看到过 JSP 定制标记,让我们简单地研究一下本示例中的语法。JSP 定制标记看上去非常象 HTML 标记,但有下列区别:
有一个由标记开发人员创建的标记名 imagesizer。
标记有一个前缀 util,它将标记集组合成库,这非常类似于 Java 编程中的包名。您可以创建新的前缀,或者使用与库一起提供的缺省名。
该标记拥有一个新的类似 XML 的结束标记“/>”。
与 HTML 标记一样,JSP 标记可以拥有任意数量的属性,如这里显示的 src 属性,它们可以包含主体,主体中可包含其它标记。由于我们正在模仿 HTML 的 img 标记,所以我们的 JSP 图像缩放标记将没有主体。
当 JSP 页面使用定制 image-sizer 标记时,标记的 Java 实现找到图像文件,将其转换成合适的大小(在这一过程中可能会添加版权或水印徽标),然后将图像提供给读者。该标记使站点管理器不必在发布前转换图像。它还简化了编写 Web 页面的工作,因为处理许多图像大小首选项只需要一个页面。最后,也是最重要的,为您的所有站点图像提供这类灵活性将赢得读者的青睐。
Web 服务器上在发生什么
本节提供了一个高级别视图,介绍了当客户机(读者使用的 Web 浏览器)访问提供 JSP 页面的站点时幕后在发生什么。有三种常见的交互,如图 2 所示:
图 2. Web 客户机和服务器之间的交互
在第一种情形中,假定浏览器只请求静态文档,如 HTML 文件或图像文件。服务器在它的文件空间中找到资源,然后将文件提供给浏览器。请求文档和响应请求是在 HTTP 中定义的,HTTP 构成了因特网上客户机/服务器交互的基础。Web 服务器完整地处理请求,无需与 servlet 容器或 Web 应用程序服务器进行交互。
在第二种情形中,假定浏览器请求包含 Java servlet 的 Web 资源。Java servlet 使 Web 服务器能够在服务器上使用 Java 编程语言执行任务。servlet 很有效,与旧有的技术(如公共网关接口(CGI)、服务器端 JavaScript)相比,使用的内存和处理能力更小。servlet 比其它技术更具有可移植性,因为许多 Web 服务器 ? 如 IBM WebSphere Application Server(Application Server)和 Apache Tomcat 都支持 servlet 容器,而 servlet 容器可以在许多不同的平台上运行相同的 servlet。最后,由于 Java 语言的内在安全性(如健壮的异常处理和细颗粒度安全性),错误的 servlet 很少会影响到 Web 服务器。如图 2 所示,Web 服务器搜索适当的 servlet,如有必要则编译 servlet 源代码,然后将 servlet 的处理结果返回给请求者。经常被请求的 servlet 会高速缓存在服务器的内存中。
在第三种情形中,假定浏览器请求包含 JSP 页面的 Web 页面。JSP 页面有助于简化显示信息的任务,并且有助于将动态内容(实时生成的)与静态页面分隔开。Web 页面设计人员象使用 HTML 库中的任何其它标记一样使用 JSP 标记。JSP 程序员遵循 JSP 编程规范,并且按照其约定实现标记。
下一节阐述了如何实现图像缩放 JSP 标记,以及如何编写 JSP 页面。从 Web 容器的观点来看,JSP 页面与 Java servlet 是密切相关的。Web 容器将基于文本的 JSP 页面转换成(每页转换一次)其 Java 实现。Web 容器寻找 Java 实现,将该实现看成 Java servlet,运行代码并将处理结果返回给客户机。这些看上去好象有很多层和重定向,但是对于用户而言,分派是快捷和透明的。象 servlet 一样,经常被请求的 JSP 页面也高速缓存在服务器的内存中。
编写定制 JSP 标记
既然明白了 Web 服务器是如何处理 JSP 页面请求的,那么让我们研究一下如何实现定制 JSP 标记。注:JSP 标记既来自标准库(如 Java Standard Template Library,JSTL),也来自您自己编写的库(也称为定制标记)。通常,定制标记处理特殊的问题领域。对本文而言,我们在处理如何管理图像。目前,Java 2 Extended Edition(J2EE)V1.2 和 V1.3 使用 JSP 规范 V1.2。在写作本文的时候,Sun 已经发布了 JSP 规范 V2.0。这个新规范并未对实现定制标记的方法进行重大更改。
通过 taglib 伪指令,可以将标准和定制标记库导入 JSP 页面,如下所示:
这个伪指令指定了标记库描述符文件的位置,这里指定的是 imagesizer.tld,还指定了在页面中使用它时的前缀,这里指定的是 util。如前面的标记示例所示,将标记与其前缀及其名称一起使用:
标记库描述符告诉 Web 容器哪些标记是可用的,以及它们如何发挥作用。清单 1 显示了这样一个示例。文件使用了 XML 格式,并且易于读取,而应用程序开发平台 ? 如 IBM WebSphere Studio Application Developer(Application Developer)可以帮助您填充字段,并且验证文件。最重要的信息是 tag 元素:它定义了定制 JSP 标记的名称和实现标记的 Java 类。它还显示了标记接受的任何属性和主体内容。
清单 1. 标记库描述符(Tag Library Descriptor,TLD)摘录
1.0
1.1
imagesizer
tags.ImageSizerTag
empty
src
required
alt
quality
在这个示例中,tag 有三个属性,其中只有 src 属性是必需的。可选的 alt 属性模拟 HTML 的 img alt 属性。作为练习,您可以扩展该 JSP 标记以包含其它可选的 img 属性。(大约有 12 种这些属性。)最后,实现提供了一个可选的 quality 属性,以使页面编写人员可以控制已重新调整大小的图像的颗粒度和大小。
编写定制 JSP 标记的下一步是实现标记的 Java 代码。在本文中,标记 imagesizer 是在 tags.ImageSizerTag Java 类中实现的。大多数 J2EE 定制标记支持都位于 javax.servlet.jsp.tagext 包中。imagesizer 类继承了标准的 TagSupport,后者实现不带主体的标记。TagSupport 的子代类是 BodyTagSupport,它也实现标记,并且带有主体。这两个类都实现了 Tag 接口,其中,当第一次读取标记以及当 Web 容器完全读取标记后再次读取标记时,会调用 doStartTag 和 doEndTag 方法。ImageSizer 标记只实现了 doEndTag,因为一旦所有属性信息都可用时,它需要起作用。
在 TagSupport 类中,PageContext 类提供了对有关 JSP 页面的重要信息的访问。例如,PageContext 提供了对 HttpRequest 和 HttpResponse 对象的访问。这些对象对于读取表单值和写响应是至关重要的。如果您希望跟踪用户的首选项,并且将表单值从一个页面传递到另一个页面,该请求还提供了对 HttpSession 的访问。PageContext 还提供了对 ServletContext 的访问,后者有助于您查找 servlet 的路径、名称和其它信息。在 ImageSizer 代码中(如清单 2 所示),有许多对 PageContext 对象及其提供信息的引用。图 3 显示了这些类的关系。就象任何标准类图一样,实心框代表类,而虚线框代表接口。用从派生的类或接口到其父类或接口的一条线来表示继承。
清单 2. ImageSizerTag doEndTag 实现 // Implement the tag once the complete tag has been read.
public int doEndTag() throws JspException {
// Move request data to session.
int outputSize = 0;
String sizeVal = request.getParameter( REQUESTSIZEKEY );
if ( sizeVal != null ) {
session.setAttribute( REQUESTSIZEKEY, sizeVal );
sizeVal = (String) session.getAttribute( REQUESTSIZEKEY );
if ( sizeVal != null ) {
outputSize = Integer.parseInt( sizeVal );
}
}
// Get specified image locally.
String contextPath = getContextPath( request );
Image image = Toolkit.getDefaultToolkit().getImage(contextPath + src );
ImageSizer.waitForImage( image );
int imageWidth = image.getWidth( null );
int imageHeight = image.getHeight( null );
if (( imageWidth > 0 ) && ( imageHeight > 0 )) {
if (( outputSize > 0 ) && ( outputSize != imageWidth )) {
// Convert image to new size.
Image outputImage = ImageSizer.setSize( image, outputSize, -1 );
ImageSizer.waitForImage( outputImage );
int outputWidth = outputImage.getWidth( null );
int outputHeight = outputImage.getHeight( null );
if ( outputWidth > 0 && outputHeight > 0 ) {
// Change image file name to xxxx.size.jpg
String originalSrc = src;
int lastDot = src.lastIndexOf( ´.´ );
if ( lastDot > -1 ) {
src = src.substring( 0, lastDot + 1 );
}
setSrc( src + outputSize + ".jpg" );
// Write new size image to JPEG file.
File file = new File( contextPath + src );
if ( !file.exists() ) {
out.println( "" );
FileOutputStream fos = new FileOutputStream( contextPath + src );
ImageSizer.encodeJPEG( fos, outputImage, quality );
fos.close( ) ;
}
imageWidth = outputWidth;
imageHeight = outputHeight;
}
} // if outputSize
} // if image found
// Produce output tag.
out.print( "
if ((alt != null ) && ( alt.length() > 0 )) {
out.print( " alt="" + alt + """ );
}
// Add proper width, height.
out.print( " width="" + imageWidth + "" height="" +
imageHeight + """ );
out.println( ">" );
return EVAL_PAGE;
} // doEndTag
图 3. 重要的 javax.servlet.jsp.tagext 类
清单 2 显示了 ImageSizerTag 类的 doEndTag 方法。这几乎是实现定制 JSP 标记所需的全部 Java 代码。它是一大段代码,但是它将有助于您的理解,以便全面地了解该方法。首先,任何 HTTP 请求参数都保存在 HTTP 会话中。该请求带有一个属性,如对于图像大小的用户首选项,并将其保存在会话中,以便它可以跟随用户从一个页面到另一个页面。要扩充该标记的功能,可以扩展它,使之可将用户首选项保存在 cookie 中,这样用户下一次访问站点时就可使用其首选项。
下一步是装入缺省图像,JSP 页面使用它来作为基图,所有其它图像都根据基图来调整大小。这里,java.awt.Toolkit 请求图像,图像是用 ImageSizer.waitForImage 装入的,并对其进行了检查,以了解其是否进行了正确的缩放。需要进行装入暂停,因为 Java 图像是异步装入的,当请求它们时并非始终完全可用。在本示例中,ImageSizer 辅助类执行整个图像处理操作,下一节将对此做进一步介绍。如果宽和高匹配,则无须重新调整图像大小,并将跳过 if 这一大块代码,而且使用图像名称和当前大小编写 HTML 图像标记。这就是 JSP 实现模拟 HTML 图像标记所需做的全部工作。
如果用户请求新的图像大小,那么 ImageSizer 辅助类会重新调整图像的大小。使用文件大小给图像文件指定新的名称,文件是 JPEG 编码的,并写到文件系统。接着,将在 HTML 图像标记输出中使用刚刚重新调整大小的文件。该标记的备用实现可能会将文件保存为 GIF 或 PNG 格式,或者甚至会通过内存来提供图像以节省磁盘空间。但是,清单 2 将重新调整大小的文件高速缓存到了磁盘,以备将来使用。因此,第一次重新调整大小时需要花费一些服务器处理时间,但是随后对图像大小进行请求时根本不需要处理。该示例的扩展可能会检查可用的磁盘空间,以便于帮助平衡有限的文件空间与您希望提供给客户机的即时信息之间的折衷。
调整图像大小
前一节研究了编写定制 JSP 标记的步骤。ImageSizerTag 类会自动重新调整图像的大小以匹配用户的首选项。这一节提供了有关您可以如何使用 ImageSizer 类来重新调整图像大小并将其保存为 JPEG 文件的更多详细信息。利用 java.awt.Image 类中的 getScaledInstance 方法,很容易在 Java 代码中重新调整图像的大小。利用新的宽和高调用这个方法,或者为某个参数提供一个值 -1 以保持纵横比,您可以得到新的重新调整过大小的图像。但是,就象任何 Java 图像一样,该图像并不是立即可用的,因此您必须使用 java.awt.MediaTracker 以等待图像完全装入。ImageSizer 的 waitForImage 方法封装了该代码。
在该示例中,最难的设计点在于决定如何保存图像。用 Java 编程编码和保存图像有很多选择,所有选择都有不同的权衡。
com.sun.image.codec。该包在 Java 2 SDK 1.2 和 1.3 实现中是可用的,但它在私有(private)包中,在将来的 Java 2 版本中这可能会发生变化。该包只限于 JPEG 编码。
Java Image I/O API。在 Java 2 SDK 1.4 中,该包是公共的和标准的。但是,在撰写本文时,还没有使用 SDK 1.4 的 J2EE 版本。该包提供了良好的图像操作功能和编码选项。
Java 高级映象 API(Java Advanced Imaging API)。这个 API 是一个标准扩展,但是使用它需要安装包 ? 您的 Web 管理员可能不支持该工作。
ACME GIF 编码器(ACME GIF Encoder)。该软件及许多其它第三方图像包很有用,您可以将它们合并在示例代码中,但是有个费用和可维护性的问题。同其它选择不同,该软件不是免费的,并且不完全支持 GIF 标准。
对于清单 3,我们使用 com.sun.image.codec 包,因为它在所有 J2EE 1.2 和 1.3 Web 服务器容器(如 IBM WebSphere 和 Apache Tomcat)中都可用。编码器很简单,并且是 100% 纯 Java 代码,但是它们是 com.sun 包中的。但是,从长远考虑,Java Image I/O 包可能是发展的方向。在图像转换特性以及保存为多种文件格式的能力方面,它比较强大。直到 Java 2 V1.4 时 Java Image I/O 包才会成为标准。
既然已经决定了使用哪个图像处理包,那么保存 JPEG 文件的代码就相当简单了。ImageSizer 的 encodeJPEG 方法封装了这个过程:
java.awt.image.BufferedImage 对象,这是一个增强的 Java Image 子代,是从重新调整过大小的输出图像创建的。注释在代码中标明了一个位置,可以在这里扩展示例以将徽标、水印、时间戳记或版权信息添加到图像。
在将 Image 转换为 BufferedImage 之后,在输出流上创建一个 JPEGImageEncoder 对象。输出编码质量的范围从 0.0(最差)到 1.0(最佳)。缺省值是 0.75,但 0.95 将生成较大的文件大小,它带有更详细的图像。作为该示例的扩展,您可以考虑由图像大小来决定质量 ? 较小的图像需要较高的质量设置,而较大的图像需要较低的设置。
将图像编码到输出流,并对该流进行刷新以确保所有信息都显示在图像文件中。
清单 3. ImageSizer encodeJPEG 实现 // Encodes the given image at the given
// quality to the output stream.
public static void encodeJPEG
( OutputStream outputStream,
Image outputImage, float outputQuality )
throws java.io.IOException {
// Get a buffered image from the image.
BufferedImage bi = new BufferedImage
( outputWidth, outputHeight,
BufferedImage.TYPE_INT_RGB );
Graphics2D biContext =
bi.createGraphics( );
biContext.drawImage
( outputImage, 0, 0, null );
// Additional drawing code, such as
// watermarks or logos can be placed here.
// com.sun.image.codec.jpeg package
// is included in Sun and IBM sdk 1.3.
JPEGImageEncoder encoder =
JPEGCodec.createJPEGEncoder
( outputStream );
// The default quality is 0.75.
JPEGEncodeParam jep =
JPEGCodec.getDefaultJPEGEncodeParam
( bi );
jep.setQuality( outputQuality, true );
encoder.encode( bi, jep );
outputStream.flush();
} // encodeImage
那就是重新调整图像大小及保存图像所需的全部。
打包并部署到 WebSphere 或 Tomcat 上
本节将阐述如何打包 ImageSizer JSP 标记并将其部署到 Application Server V4.0 或 Apache Tomcat V4.0 上。图 4 显示了 Application Developer 的抓屏。左上栏的 Navigator 窗格(windowpane)显示了 Web 应用程序的目录结构,以及必须如何根据 J2EE 规范打包定制 JSP 标记。由于 J2EE 规范的需要,因此该目录结构对于所有 Web 应用程序都是公共的。一旦归档了该结构,则它将成为 Web 归档(WAR)文件,并且可以方便地将其传送给 WebSphere、Tomcat 或任何其它兼容的 Web 容器。良好的开发环境(如 Application Developer)有助于开发人员遵循这些规范,并生成有效的应用程序。
图 4. 在 WebSphere Studio Application Developer 中打包 ImageSizer
在 ImageSizer 项目下,有一个源代码目录;开发人员可以选择是否在最终的 WAR 文件中包含该目录。webApplication 目录包含实际的程序代码。示例项目包含名为 PickASize.jsp 的测试 JSP 页面及称为 LazyDog.jpg 的巨大的测试图像。通常,ImageSizer 定制标记的库版本并不包含这些。标记的实现位于 WEB-INF 目录中。Java 类都位于 WEB-INF/classes 中,而标记库描述符(Tag Library Descriptor)文件位于 WEB-INF/tlds 中。这些是所有 Web 应用程序的标准目录布局。该树中的其它文件有助于设置服务器选项,但却并非 WAR 文件强制需要的。使用 Application Developer 或 Java SDK 以创建该应用程序的 WAR 文件。
要将 Web 应用程序部署在 Web Application Server(如 Tomcat)上,则将文件放在 ROOT/webapps 目录中,并且让服务器将 WAR 文件展开为目录结构。对于 Application Server,您可以使用 Administrators Console 中的 Web Application 向导来安装应用程序。部署完毕,可通过访问 http://yourhostname:port/ImageSizer/PickASize.jsp 运行 JSP 页面。
结束语
现在您已经创建了自动管理图像缩放的 JSP 定制标记。定制标记为您省去了重新调整图像大小的工作,还使用户在访问您的网站时可以指定自己的首选项。可以很方便地扩充该示例标记以执行各种图像操作:版权文本、时间戳记、徽标或水印。通过将代码部署到 Application Server 或 Apache Tomcat,并且编写一些基于图像的 JSP 页面或使用给定的示例,您可以试验代码。希望本文为您提供了“取出即可用”的 JSP 标记,还提供了能使您进一步扩展功能以满足您需要的代码。愿您看图愉快![@more@]
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/9650775/viewspace-924242/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/9650775/viewspace-924242/