<><A><B>清单 1. Test01.jsp</B></A> <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1><TR><TD><RE><CODE><%@ taglib uri="/WEB-INF/tlds/SlowMathTaglib.tld" prefix="SlowMathTag" %><% String mathText = request.getParameter( "MathText" ); String rawMathText = request.getParameter( "RawMathText" ); String height = request.getParameter( "height" ); String width = request.getParameter( "width" );%><html><head> <title>JSPMath test without image cache</title></head><body bgcolor=white><p><font color=green> Math formula in desired dimensions: </font><p><SlowMathTagatexMath height="<%=height%>" width="<%=width%>"> <%=mathText%></SlowMathTagatexMath><p><font color=green> Math formula in default dimensions: </font><p><SlowMathTagatexMath> <%=mathText%></SlowMathTag:LatexMath><p><font color=green> Latex segment in default dimensions: </font><p><SlowMathTag:LatexRaw> <%=rawMathText%></SlowMathTag:LatexRaw></body></html></CODE></PRE></TD></TR></TABLE></P><>清单 2 显示了在一次执行中 JSP 容器发送到 Web 浏览器的实际 HTML 代码。</P><P><A><B>清单 2. 从 Test01.jsp 发送到浏览器的 HTML 代码</B></A> <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1><TR><TD><PRE><CODE><html><head> <title>JSPMath test without image cache</title></head><body bgcolor=white><p><font color=green> Math formula in desired dimensions: </font><p><IMG SRC="http://localhost:8080/JSPMath/images/image1009709773390.gif" height=20 width=150 /><p><font color=green> Math formula in default dimensions: </font><p><IMG SRC="http://localhost:8080/JSPMath/images/image1009709776535.gif" /><p><font color=green> Latex segment in default dimensions: </font><p><IMG SRC="http://localhost:8080/JSPMath/images/image1009709777612.gif" /></body></html></CODE></PRE></TD></TR></TABLE></P><P>这里的 GIF 图像是由标记处理器生成的。如图 7 所示,使用用户指定的尺寸放大的图像看起来不是很平滑。有关出现这一情况的原因以及如何在 UNIX/Linux 系统上处理它,请参考标题为“<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#sidebar2" target="_blank" >矢量图与位图的对比</A>”的边注。</P><P>我们如何实现这些定制标记操作呢?请继续阅读,找出答案。</P><P><A>一种简单(但缓慢)的方法</A>
可以用很简单和直接的方式实现上面的示例。对于本文,您应该已经知道如何实现 JSP 定制标记处理器了。如果您想更新一下您的知识,请参考下面的<a href="http://www-900.ibm.com/developerWorks/cn/java/j-jspmath/index.shtml#resources" target="_blank" >参考资料</A>一节。</P><P><CODE><SlowMathTag:*></CODE> 标记处理器调用静态方法 <CODE>support.MathUtil.latex2gif()</CODE> 以生成 GIF 图像。让我们研究一下这种方法的源代码以及它是如何工作的。</P><P>首先,该方法确定当前标记生成的 GIF 图像以及中间文件的唯一名称,如清单 3 所示。那些文件的根名称是当前时间的毫秒值。</P><P><A><B>清单 3. 生成文件名</B></A> <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1><TR><TD><PRE><CODE>Date now = new Date ();String filename = "image" + Long.toString(now.getTime());// ...File f = new File(path + filename + ".tex");</CODE></PRE></TD></TR></TABLE></P><P>接下来,该方法从标记主体中采集内容,然后组装成一个完整的 LaTeX 输入文件,如清单 4 所示。JSP 容器用字符串变量 <CODE>latexStr</CODE> 将标记主体传送到方法 <CODE>latex2gif()</CODE>。使用 <CODE>\pagestyle{empty}</CODE> 命令以消除在结果页面底部显示的页号。使用 <CODE>\batchmode</CODE> 命令使 LaTeX 处理器在非交互方式下执行。</P><P><A><B>清单 4. 汇编 LaTeX 输入文件</B></A> <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1><TR><TD><PRE><CODE>private static String latexHead = "\\batchmode\n" + "\\documentclass[12pt]{article}\n" + "\\pagestyle{empty}\n" + "\\begin{document}\n";private static String latexEnd = "\\end{document}\n";// ...// String variable "latexStr" contains the body of the current tag.// Process modes.if ( mode.equals("displaymath") ) { latexStr = "\\Large\n\\begin{displaymath}\n" + latexStr.trim() + "\\end{displaymath}\n";} else { // Unrecognized modes are raw latex strings.}// Write content into a latex file.FileWriter fw = new FileWriter(f);fw.write( latexHead, 0, latexHead.length() );fw.write( latexStr, 0, latexStr.length() );fw.write( latexEnd, 0, latexEnd.length() );</CODE></PRE></TD></TR></TABLE></P><P>现在,该方法执行命令 <CODE>latex</CODE>、<CODE>dvips</CODE> 和 <CODE>convert</CODE>,以在服务器方的预先配置目录 <CODE>path</CODE> 下生成最终的 GIF 图像,如清单 5 所示。</P><P><A><B>清单 5. 生成 GIF 文件</B></A> <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1><TR><TD><PRE><CODE>private static String latexCommand = "/usr/bin/latex";private static String dvipsCommand = "/usr/bin/dvips";private static String convertCommand = "/usr/X11R6/bin/convert -antialias -crop 0x0";// ...public static synchronized String latex2gif (String path, String mode, String latexStr) throws Exception { // Generate root file name and create ".tex" file // ... // Get Runtime. Runtime r = Runtime.getRuntime(); Process p; File imagePath = new File( path ); // ... // Run latex in directory "imagePath". p = r.exec(latexCommand + " " + filename + ".tex", null, imagePath); if ( p.waitFor() != 0 ) throw new Exception("Error in Latex\n"); // Run dvips in directory "imagePath". p = r.exec(dvipsCommand + " -o " + filename + ".ps " + filename + ".dvi", null, imagePath); if ( p.waitFor() != 0 ) throw new Exception("Error in dvips\n"); // Run convert in directory "imagePath". p = r.exec(convertCommand + " " + filename + ".ps " + filename + ".gif", null, imagePath); if ( p.waitFor() != 0 ) throw new Exception("Error in convert\n"); // ...}</CODE></PRE></TD></TR></TABLE></P><P>最后,该方法删除所有中间文件,如清单 6 所示。</P><P><A><B>清单 6. 删除中间文件</B></A> <TABLE cellSpacing=0 cellPadding=5 width="100%" bgColor=#cccccc border=1><TR><TD><PRE><CODE>f = new File(path + filename + ".tex");f.delete();f = new File(path + filename + ".log");f.delete();f = new File(path + filename + ".aux");f.delete();f = new File(path + filename + ".dvi");f.delete();f = new File(path + filename + ".ps");f.delete();</CODE></PRE></TD></TR></TABLE></P><P>现在,标记处理器可以将生成的 GIF 图像的 <CODE><IMG></CODE> HTML 标记写入服务器输出流。</P><P>刚刚描绘的方法很简单,但是正如我们马上就会看到的它很慢而且容易出错。在接下来的几节中,我们将讨论改进这一解决方案的性能和健壮性的方法。</P><P><A>通过高速缓存改进性能</A>
从 LaTeX 输入文本生成图像是很慢的,因为它涉及多个外部程序。在上一节的简单实现中,每次调用 <CODE><SlowMathTag:*></CODE> 定制标记处理器时,它都必须生成一个新图像。这样效率是非常低的。在示例应用程序中,Test01.jsp 中的两个 <CODE><SlowMathTag:LatexMath></CODE> 标记需要为每个访问者两次生成相同的公式图像,只是为了用不同的尺寸来显示它。现实世界中,应用程序很可能比示例应用程序复杂得多,并且生成不必要的图像带来的性能开销可能很严重。想象一下,我们的站点将提供动态数学考试,并且许多访问者会遇到相同的考试题目。为每个访问者一次次地重新生成同一公式的图像太昂贵了。</P><P>对于一个规模可伸缩的解决方案,我们需要对生成的图像进行高速缓存,然后在将来需要的时候重用它们。可以将 LaTeX 文本用作图像名称的<I>键</I>。如果标记处理器第二次遇到同一个 LaTeX 文本,则它可以找出高速缓存图像的文件名,然后直接返回它。可以将一对对的 LaTeX 文本和图像名称存储在 SQL 数据库以便长久保存和快速搜索。可以使用具有不同作用域的数据库访问 JavaBean 来对那些高速缓存的图像的访问作进一步的调节。虽然对于读者来说,这样的调节可能是一个有趣的实践,但是却超出了本文的范围。</P><P>这里,我们将把高速缓存的图像作为静态数据成员存储在标记处理器助手类中。那些静态数据成员具有与 JSP 服务器容器相同的生命期。如果更改网站,然后重新启动服务器,则会丢弃旧的高速缓存。将高速缓存的 LaTeX 文本和图像名称存储在两个静态的 <CODE>Vector</CODE> 中。当标记处理器遇到 LaTeX 文本字符串时,它遍历高速缓存的 LaTeX 文本 <CODE>Vector</CODE>。如果它发现一个匹配项,则返回相应的图像名称。如果没发现,则继续生成一个新的公式图像并将新的 LaTeX 文本/图像对存储在高速缓存中。清单 7 说明了这个方法。</P> |