Javid Jamae(javidjamae@yahoo.com),独立软件顾问,Jamae Consulting
Kulvir Singh Bhogal(kbhogal@us.ibm.com),IBM WebSphere 服务顾问,IBM
2002 年 11 月
象 doubleclick.net 这样的公司依靠在 Web 上提供横幅广告服务已经赚了很多钱。他们提供的服务很棒,但为什么要为自己本来可以做的事情而付钱呢?在本文中,企业 Java 顾问 Javid Jamae 和 Kulvir Bhogal 将演示如何使用全开放源码环境:Apache Tomcat、MySQL 和 MM MySQL JDBC 驱动程序,来创建滚动横幅广告。首先,他们将向您逐步介绍必需的 Tomcat 和 MySQL 安装,然后向您演示如何安装 MM MySQL JDBC 驱动程序以允许运行在 Tomcat 中的 Java servlet 与 MySQL 进行通信。
当因特网开始从教育和政府信息存储器转变成国际商业中心起,横幅广告就已经出现了。滚动横幅是 Web 页面上一块已分配的空间,每次装入或重新装入 Web 页面时,就用该空间内来显示广告 — 随机显示或基于某些业务逻辑来显示。驱动滚动横幅广告的程序虽然相当简单,但却是重要的广告工具。正如它们的同类,如半分钟广播和电视广告一样,这些动态广告工具允许单个 Web 页面显示不同来源的广告,并使不同公司针对相同的观众建立他们产品和服务的品牌效应。
无论您怎么看待 Web 横幅广告(是的,我们都发现它们有时令人讨厌),它们已成为因特网上的一种生活方式。存在这样的事实:Web 的观众由庞大的消费者群体所组成,他们的金钱使电子商务的车轮得以转动。在因特网市场营销的短暂历史中,电子商务所有者已表现出他们愿意花大把的钱在热门站点上做横幅广告。
有些公司(如 doubleclick.net)已经通过担当应用程序服务供应商(ASP),提供诸如跟踪对特定横幅广告的点击之类的服务,从 Web 横幅现象中获利。随后这些 ASP 告诉广告客户他们的 Web 广告活动的有效程度。
当然,象 doubleclick.net 这样的 ASP 是要收费的。如果您和我们一样,那么您不会愿意在能够免费得到服务时还要花钱。稍等一会儿 — 您可能已经听过世上没有免费的午餐这句话。不过别担心。本文将花费的只是您的时间。事实上,我们将向您演示如何组成一个开放源码(即免费)环境来建立您自己的功能强大的 Web 横幅跟踪系统。为了完成这一任务,我们挑选的“武器”是 Tomcat、MySQL、一个 Java servlet 和几个助手类。兴奋吗?那么,让我们进行软件安装吧。
安装 Tomcat 和 MySQL
在这一节中,我们将逐步介绍 Tomcat 和 MySQL 的安装。然后,我们将向您介绍如何安装支持这两个应用程序相互通信所需的驱动程序。
安装 Tomcat
下载并安装 Tomcat。对于本文,我们使用了 Tomcat 4.1 Windows 版,它有一个很好的安装软件包,并且会为您创建图标和 Start 菜单文件夹。它还创建一个用于启动和停止 Tomcat 服务器的“Windows 服务(Windows Service)”。安装应该非常简单,但如果您遇到麻烦,请查阅 Tomcat 文档。因为 Tomcat 非常流行,所以在新闻组和 Web 上也可找到大量的帮助,参考资料中列出了其中的一部分。
安装了 Tomcat 之后,还要完成几个步骤以设置我们的滚动横幅 Web 应用程序。首先,我们将在 [installdir]\webapps 目录下创建一个名为 banner 的子目录。然后在 banner 子目录下,创建标准的 Web 应用程序目录结构:
[installdir]\webapps\banner
[installdir]\webapps\banner\WEB-INF
[installdir]\webapps\banner\WEB-INF\classes
[installdir]\webapps\banner\WEB-INF\lib
接下来,我们将添加指向我们 Web 应用程序的 context。context 只是一个别名,它告诉 Tomcat 在哪里可以访问 Web 应用程序。我们的 context 路径将是 /banner,它将指向我们刚刚创建的 banner 子目录。在用户输入 http://localhost:8080/banner 后,将转至 webapps 下的顶级 banner 目录。如果他想运行我们的 BannerServlet(将存在于 WEB-INF/classes 目录中),他可以使用 http://localhost:8080/banner/servlet/BannerServlet。
要添加 /banner context,首先,我们需要编辑 Tomcat conf 目录中的 server.xml 文件。在接近该文件底部的位置,您会看到几个 context 标记。那里应该有一个用于 /admin 的 context 和一个用于 /examples 的 context。请添加以下 context 标记:
<!-- BannerAd Context -->
<Context path="/banner" docBase="banner"
debug="0" reloadable="true" crossContext="true"/>
添加了 context 标记之后,重新启动 Tomcat 以使对 server.xml 文件所做的更改生效(在我们的示例中,我们只重新启动 Tomcat 安装的 Windows 服务)。
安装 MySQL
MySQL 因其价格(免费)而成为一种强有力的数据库,许多公司都使用 MySQL 来处理它们的数据。由于许多公司都想以较低的预算进入 Web 市场,所以使用 MySQL 的公司的数量每天都在增加。开放源码社区已张开双臂欢迎 MySQL。有关这个功能强大的数据库的文档十分丰富,而且同时有 Linux 和 Windows 版本。
下载并安装 MySQL,采用“Typical”安装选项进行安装(对于本文,我们假定您使用 MySQL 的 WinNT 版本)。完成安装后,您将注意到 MySQL 一个恼人的方面:它没有在 Start 菜单中放入任何东西。您需要到数据库的安装目录(缺省情况下是 c:\mysql\)下,然后转至 bin 目录,在那里您会找到运行 MySQL 的可执行文件。
首先,双击 winmysqladmin.exe 文件。首次打开该文件时,会要求您输入用户名和密码。接下来,在任务栏中出现的红绿灯图标上单击鼠标右键。转至 WinNT 并选择“Start the service”以使 MySQL 在后台运行。最后,双击“mysql.exe”图标以启动“MySQL Monitor”,您将在此使用 MySQL。
使 MySQL 和 Tomcat 共同工作
使 MySQL 和 Tomcat 相互通信可能有些困难。然而,通过使用 JDBC API,我们将能够相对容易地从 Java 类使用 SQL 与 MySQL 数据库通信。
我们将使用 MM MySQL JDBC 驱动程序(一个开放源码驱动程序),使 MySQL 和 Tomcat 之间的通信变得容易。(在撰写本文时,2.0.14 是其最新版本。)
遗憾的是,安装该驱动程序略微有些麻烦。首先,要从这里下载该驱动程序的合适的 JAR 文件。我们下载了名为 mm.mysql-2.0.14-you-must-unjar-me.jar 的文件。接下来,将文件解压缩(unjar 或 unzip)至一个临时目录。最后,将包含驱动程序的文件从解压缩的目录结构复制到 WEBAPPS/BANNER/WEB-INF/lib 目录中,然后重新启动 Tomcat。在我们下载的驱动程序版本中,文件的名称是 mm.mysql-2.0.14-bin.jar。
我们本可以使用 JDBC/ODBC 桥驱动程序与 MySQL 通信,但我们认为本机驱动程序在性能上有更大优势(尽管我们没有运行任何基准测试程序来证实我们的假设)。对于这个应用程序,在性能上它可能不会有很大区别,但我们决定演示如何使用本机 JDBC 驱动程序,以便您不必在设计较大的应用程序时才去了解它。
滚动横幅应用程序
既然我们已经安装了所有的软件,就让我们来看一下这个应用程序能够做什么以及我们是如何组织其架构和开发它的。
实质上,您可以使用我们的横幅 servlet 执行两个操作。首先,您可以用它查看 Web 页面上的随机横幅图像,每次装入包含横幅广告的 Web 页面时,该随机横幅图像就会出现。其次,您可以点击横幅图像,这将使您转至与装入的这个图像对应的链接。
按照 HTML,其代码类似于:
<a href="...Link For Random Image...">
<img src="...Random Image..."/>
</a>
如果希望装入随机图像,那么显然图像标记不能指向静态图像文件,因此我们将指示它运行 servlet,本例中将调用 BannerServlet。我们将使用 HTTP GET 方法参数来指示 servlet 为我们提供图像。因此,图像标记类似于:
<img src="http://localhost:8080/banner/servlet/BannerServlet?type=image"/>
该标记调用 servlet 并传入参数键值对 type=image。servlet 的 service() 方法会解释该请求,然后向浏览器返回随机图像。当然,servlet 必须以某种方式记住将哪个图像发送给了客户机,这样当客户机点击该图像时,它就知道应链接至何处。我们会把与发送的图像相关的元数据存储在客户机的会话上,这样,当用户点击图像时,将从他的会话装入元数据,并重新导向至适当的 URL。
链接标记看起来几乎与图像标记相同:
<a href="http://localhost:8080/banner/servlet/BannerServlet?type=link">
<img src="http://localhost:8080/banner/servlet/BannerServlet?type=image"/>
</a>
当用 type=link 键值对调用 servlet 时,servlet 抓取横幅的元数据并将用户重新路由至适当的 URL。
代码和 CLASSPATH 设置
为了使用与本文一起提供的代码,您必须首先解压缩这个 zip 文件(在参考资料中),然后用命令行 javac 编译器或您喜欢的 IDE 编译这个 .java 文件。要编译该代码,请设置 CLASSPATH 以包含这两个 JAR 文件。
mm.mySQL-2.0.14-bin.jar(或从 MM MySQL 网站获取的其它任何版本)
servlet.jar(如果它没有和您使用的 JDK 打包在一起)
将已编译的 .class 文件复制到先前创建的 [tomcat_installdir]\webapps\banner\WEB-INF\classes 目录中。必须将作为示例提供的图像文件和 .htm 文件放入 [tomcat_installdir]\webapps\banner 目录。
数据库设置
我们应用程序的数据库部分只是用来持久存储系统中每个横幅的元数据。换句话说,我们实际上并不在数据库中存储图像文件,而只是存储指向每个图像文件的引用。在我们的数据库中,我们将使用七个列来描述每个横幅广告。
表 1 中的描述说明了每条记录将包含的内容。我们实际上将在应用程序中只使用这七个数据库列中的五个。我们的版本中没有使用 CustomerName 和 NumberOfClicksPurchased,但我们把它们作为占位符放置在这里以用于扩展。您可以很容易地扩展这个应用程序,并把它用于现实的商业应用程序,其中客户为每个横幅的点击次数付款。
表 1. 数据库字段 字段名称 描述 示例
ImageFile 对横幅图像物理位置的引用 /images/sitea.gif
URL 站点用户点击横幅之后,应重新路由他们的目标 URL http://www.sitea.com
CustomerName 购买横幅的客户名称 John Doe
NumberOfClicksPurchased 用户购买的点击次数 140
NumberOfClicksRemaining 客户剩余的点击次数 139
NumberOfImpressions 横幅已被显示的次数 23
BannerWeight 正在显示的这一横幅的权重 10
当然,在现实环境中,您会有一个以上的站点横幅。根据您的横幅“赞助商”支付的金额与其他赞助商的比较,您可能希望较多地或较少地显示他的横幅。 BannerWeight 字段将被用来实现这一功能。我们已经实现了一个非常简单的加权系统,每个要显示的横幅所具有的百分比概率为:
(BannerWeight / Sum of all BannerWeights) * 100
将刚才所说的内容转换成 SQL,您可以使用 MySQL Monitor 输入以下语句:
mysql> create database BANNER;
要连接到数据库,您可以输入:
mysql> use BANNER;
接下来,我们创建表:
mysql> create table ADS
(IMAGEFILE VARCHAR(50) NOT NULL,
URL VARCHAR(50) NOT NULL,
CUSTOMERNAME VARCHAR(50),
NUMBEROFCLICKSPURCHASED INT(4),
NUMBEROFCLICKSREMAINING INT(4) NOT NULL,
NUMBEROFIMPRESSIONS INT(4) NOT NULL,
BANNERWEIGHT INT(4) NOT NULL);
ADS 表的“describe”操作类似于图 1 所示。
图 1. ADS 表
您需要用一些样本值来填充数据库以便确定所构建的数据库是否正确。该项目的 zip 文件中包括一些样本横幅(GIF 格式),可以使用它们以了解应用程序运行时的情况。当然,对于 Web 横幅 URL,需要声明您决定放置横幅文件的位置。可以按照下面的 SQL 语法将横幅“注册”到数据库中:
mysql> insert into ADS values('/sitea.gif','http://www.cnn.com',
'John Doe',100,100,0,10);
使用这一语法将表 2 中显示的记录插入到数据库中。
表 2. 数据库记录 IMAGEFILE sitea.gif siteb.gif sitec.gif sited.gif
URL http://
www.cnn.com http://
www.news.com http://
www.ibm.com http://
www.yahoo.com
CUSTOMERNAME John Doe Albert Einstein Jane Doe Madonna
NUMBEROFCLICKSPURCHASED 100 20 30 20
NUMBEROFCLICKSREMAINING 100 20 30 20
NUMBEROFIMPRESSIONS 0 0 0 0
BANNERWEIGHT 10 10 30 10
注:Web 横幅 URL 位于本地主机,仅供测试之用。在生产环境中,URL 会指向 GIF 文件的实际位置。该 URL 实际上可以是因特网上的任何位置。
既然我们有了数据库,就需要使用刚刚填入其中的数据。我们将用 Java servlet 做到这一点。下面描述 Java servlet 代码,它将“推动”我们的努力。您或许希望花一些时间来通读该项目 zip 文件中的 BannerServlet.java 代码。如果您觉得困难,别担心;我们将花一些时间解释代码是如何工作的。
横幅体系结构
这个横幅广告 servlet 的体系结构十分简单。我们将使用四个类:
通用的 Logger 类,一个将日志消息写到文本文件的类。
名为 BannerServlet 的 servlet,每次显示横幅图像(即每次装入页面)和每次点击横幅图像时,将调用它。该 servlet 是我们应用程序的核心。
通用的 DBHandler 类,BannerServlet 将用它与 MySQL 数据库进行通信。
Banner 类,我们用它来创建对象,这些对象包含数据库中每个横幅所拥有的所有元数据。
该 BannerServlet 类和 Banner 类特定于我们的应用程序。它们相当简单,您可以方便地扩展它们来添加更复杂的特性。
DBHandler 和 Logger 类的好处在于:您可以在任何您实际编写的、需要与数据库通信或写入日志文件的应用程序中重用这两个类。
我们将更详细地讨论所有这四个类,这样您就可以理解 servlet 是如何工作的,以及它如何使用 DBHandler 与 MySQL 数据库通信。
Logger 类
Logger 类非常简单。它有单个字段,代表我们正在写入日志的 File 对象。您可以将对单个 Logger 对象的引用传递给几个类,让这些类都写入同一个日志文件。Logger 类允许您做以下事情。您可以:
创建日志记录器(logger)对象
向日志文件添加分隔符(“------”字符串)
通过传入调用方法的名称和日志消息添加一条日志项
添加方法启动的缺省消息
添加方法结束的缺省消息
删除日志文件
返回由日志记录器对象使用的 File 对象
我们将同时在 DBHandler 类和 BannerServlet 中使用 Logger 对象。
DBHandler 类
DBHandler 是非常多用途的类,可用来通过 JDBC 与几乎任何数据库进行相互操作。它需要一个具有 JDBC/ODBC 驱动程序(我们正用此驱动程序来连接到数据库)名称的字符串、一个具有数据库(我们为其设置了 DSN)名称的字符串和一个 Logger 参数。Logger 参数在 DBHandler 完成其“神奇”任务时告诉它在哪里打印输出消息。DBHandler 的构造器打开到数据库的连接。在使用 DBHandler 完成任务后,必须用 close() 方法关闭它。
在创建了 DBHandler 对象之后,必须创建查询来执行。使用 setQueryString() 方法传入包含查询的字符串,查询可以是 PreparedStatement 类的形式。
PreparedStatement 是 JDBC 的一个很好的功能。它允许您定义一个查询字符串,使用问号字符替代查询中的变量标准。随后可以使用 PreparedStatement 类的 setter 方法来设置查询中未知元素的值。幸运的是,DBHandler 类为我们处理所有这些事情。我们只需设置想要执行的查询,然后调用 DBHandler 的某个方法,如下所示:
public Banner getBannerByName(String name) {
...
...
dbHandler.setQueryString("SELECT * FROM ADS WHERE NAME=?");
ResultSet rs = dbHandler.lookup(name);
dbHandler.close();
...
...
}
可以使用 lookup() 方法来执行 SELECT 查询,使用 executeUpdate() 方法来执行 UPDATE 查询,以及使用 insert() 方法来执行 INSERT 查询。还有一个 execute() 方法,它不需要参数,可以执行任何没有 PrepareStatement 参数的查询。
Banner 类
Banner 类只是一组直接对应于 ADS 数据库表中各列的值的 setter 和 getter 方法。
BannerServlet 类
BannerServlet 是我们这个应用程序的核心。我们将把该类分成几个不同部分来向您讲解。通过浏览代码,您将对如何用 DBHandler 类连接到数据库更加熟悉。
字段
BannerServlet 使用五个字段:
String _databaseUrl:要访问数据库的名称(jdbc:odbc:\\localhost\BANNER)。
String _driverName:用来与数据库通信的驱动程序的名称。正如上面所描述的那样,我们将使用 MM MySQL JDBC 驱动程序。该驱动程序的名称是 org.gjt.mm.mysql.Driver。
Logger _logger:Logger 类的名称,用此类来记录我们应用程序中发生的所有事件。
HashMap _banners:所有 Banner 对象的 HashMap。用 servlet 的 init() 方法填充该 HashMap。将数据库表中的每一行转换成存储在 HashMap 中的一个 Banner 对象。我们过一会儿将详细说明这一点。
int _totalWeight:所有 Banner 权重之和。该值也是在 init() 方法中设置的;我们将很快讨论这一点。
init()
任何 servlet 的 init(ServletConfig) 方法都在容器首次装入该 servlet 时被调用。在这里,容器就是 Tomcat。Tomcat 生成并传入 ServletConfig 对象,该对象包含容器设置的缺省配置信息以及开发人员(您)在 servlet 的配置文件中可以进行设置的定制配置信息。对于我们的用途,不需要传入任何配置信息,但您有时可能需要扩展 servlet,并使用这一功能。
我们调用 super.init() 之后在 init() 中所做的第一件事是初始化 HashMap 变量 _banners,并将 _totalWeight 设置为 0。然后,连接到数据库,以 ResultSet 的形式从 ADS 表获取所有行。我们使用 for 循环对 ResultSet 进行循环遍历,从每行构造一个 Banner 对象,接着,把 for 循环的索引作为散列值,将 Banner 对象添加到 HashMap。(我们也可以方便地使用 Vector 或其它一些 Collection 类来完成同样的任务。)
现在,内存中有了一个包含所有 Banner 的 HashMap。如果更新数据库,那么只需调用 init() 方法就可重新装入 HashMap。我们将在 increaseImpressions() 和 decreaseClicksRemaining() 方法中用到这一点。
service()
service() 方法是在我们的 BannerServlet 继承的 HttpServlet 类中定义的,可以处理任何请求,不管它是 GET 方法还是 POST 方法。对于 service() 方法的实现有两个核心部分。第一个部分处理 Web 页面发送图像请求时 servlet 的行为,第二部分处理链接请求。
最后,我们看一下从客户机发送来的 type 参数。如果 type 的值是 image,那么我们从数据库获取一个随机的 Banner 对象,将该 Banner 对象添加到用户的会话,同时增加这一给定横幅的已显示次数,并将用户路由至 Banner 对象的图像字段中所引用的图像。
如果 type 的值是 link,那么我们从会话中除去 Banner 对象,减少该横幅的剩余点击次数,并将用户重定向至 Banner 对象的 URL 字段中指定的链接。
其它方法
getRandomBanner()、increaseImpressions() 和 decreaseClicksRemaining() 都是助手方法,从 service 方法中调用它们。getRandomBanner() 使用一个简单的算法从 _banners HashMap 随机地选择一个横幅。increaseImpressions() 和 decreaseClicksRemaining() 使用 DBHandler 连接到数据库并更新给定 Banner 的信息。在这两个方法的结束部分,我们调用 init() 方法将已更新的 Banner 信息重新装入到 HashMap。
结束语
我们已经创建一个应用程序,该应用程序演示了 Apache Tomcat 和 MySQL 相互之间如何通信,并向您提供了一个能为您处理大多数数据库工作的、非常有用的可重用工具。您可以以几种方式来扩展这个应用程序,这些已超出本文的范围。正如我们前面描述的那样,您可以把这个应用程序扩展成一个系统,在这个系统中,您可以根据特定点击次数或者甚至是特定的显示次数向客户收费。可以很容易地用某个其它字段替代数据库中的 CustomerName 字段,这个字段可以是另一个包含所有客户信息的表中的主键。
您可以扩充 DBHandler 类以处理数据库连接合用。此外,可以将希望用于应用程序的查询具体化成文本、特性或 XML 文件,从而允许扩充或更改查询定义而无需编写更多代码。