2006年01月05日

作者:jGuru

课程大纲

JavaMail API简介

JavaMail API是一种可选的、能用于读取、编写和发送电子消息的包(标准扩展)。您可使用这种包创建邮件用户代理(Mail User Agent MUA 类型的程序,它类似于EudoraPineMicrosoft Outlook这些邮件程序。其主要目的不是像发送邮件或其他邮件传输代理(Mail Transfer AgentMTA)类型的程序那样用于传输、发送和转发消息。换句话说,用户可以与MUA类型的程序交互,以阅读和撰写电子邮件。MUA依靠MTA处理实际的发送任务。

JavaMail API的设计是,为收发信息提供与协议无关的访问。方式是把该API划分成两个部分:

  • API的第一个部分是本课程的重点。基本上是如何发送和接收独立于提供程序/协议的消息。

  • 第二个部分则使用特定的协议语言,如:SMTPPOPIMAPNNTP。如果要让JavaMail API与服务器通信,就需要为之提供协议。由于Sun公司对特定协议提供程序有充分的介绍,用户可以免费获取,所以本课程没有介绍创建特定协议提供程序的内容。

复习相关协议

在学习JavaMail API的深层知识之前,让我们回过头来看一看在该API中使用的协议,本质上有4种人们常用的协议:

  • SMTP

  • POP

  • IMAP

  • MIME

您还需要了解NNTP及其他一些协议。理解这些协议的基本原理有助于您理解如何使用JavaMail API。而该API的设计要与协议无关,所以不能克服这些基础协议的限制。如果选用的协议不支持某种功能,那么JavaMail API也无法在其上添加这种功能。(正如您一会儿就会看到的,在操作POP协议时,常常会碰到这种问题)。

SMTP

简单邮件传输协议(SMTP)是用于传送电子邮件的机制。在JavaMail API环境中,您的基于JavaMail的程序将与您公司或Internet服务提供商(ISP)的SMTP服务器通信。该SMTP服务器将会把消息转发给用作接收消息的SMTP服务器,最后用户可通过POPIMAP协议获取该消息。由于支持身份验证,所以不需要SMTP服务器是一种开放的转发器,但需要确保SMTP服务器配置正确。JavaMail API中没有集成用于处理诸如配置服务器以转发消息或添加/删除电子邮件帐户这一类任务的功能。

POP

POP的含义是邮局协议,当前的版本为3,也称作POP3,该协议是在RFC 1939中定义的。POPInternet上的大多数人用来接收邮件的机制。它为每个用户的每个邮箱定义支持,这是它所做的全部工作,也是大多数问题的根源。在使用POP协议时,人们熟悉的很多功能,如查看收到了多少新邮件消息的功能,POP根本不支持。这些功能都内置到诸如EudoraMicrosoft Outlook之类的邮件程序中,能为您记住接收的上一封邮件,以及计算有多少新邮件这类信息。因此,使用JavaMail API时,如果想获取这类信息,将需要由自己进行计算。

IMAP

IMAP是用于接收消息的更加高级的协议,它是在RFC 2060中定义的。IMAP的含义是“Internet消息访问协议”,当前版本是第4版,也称作IMAP4。使用IMAP时,您的邮件服务器必须支持该协议。您不能只是简单地把程序转变为支持IMAP,而不是支持POP,就指望能支持IMAP中的一切。假定您的邮件服务器支持IMAP,那么基于JavaMail的程序就可利用在服务器上拥有多个文件夹的用户,并且这些文件夹可以被多个用户共享的功能。

由于IMAP协议具有更高级的功能,您也许会想IMAP应该被每一个人使用,但事实不是这样。因为IMAP会加重邮件服务器的负荷,它需要服务器接收新消息,发送消息给请求的用户,并在多个文件夹中为每个用户维护这些消息。而这要集中备份,因而长期下去用户的文件夹会变得越来越大,当磁盘空间用光了时,每个人都会遭受损失。而使用POP协议时,已保存消息可以解除服务器的重负。

MIME

MIME的含义是“多用途的网际邮件扩充协议”。它不是一种邮件传输协议,相反,它定义传输的内容:消息的格式、附件等。许多文档都定义了MIME协议,包含:RFC 822RFC 2045RFC 2046RFC 2047。作为JavaMail API的用户,一般不需要担心这些格式。但是,这些格式确实存在,并为您的程序所用。

NNP和其他协议

由于JavaMail API分开了提供程序和其他部分,所以您可以轻松地为附加协议添加支持。Sun公司提供第3方提供程序清单,这些提供程序要利用 Sun公司不支持的少见的协议。在这份清单中,您将会看到对NNTP(网络新闻传输协议)[新闻组]S/MIME(安全多用途的网际邮件扩充协议)及其他协议的提供支持的第3方提供程序。

安装

目前有两种版本的JavaMail API最常用:1.21.1.3。本课程中的所有例子都适用于这两种版本。其中JavaMail API 1.2是最新的,而JavaMail API 1.1.3中包含了Java 2企业版(J2EE)平台1.2.1版,所以它仍然很常用。使用JavaMail API的版本会对您的下载和安装产生一些影响。这两种版本的JavaMail API都能与JDK 1.1.6Java 2标准版(J2SE)平台1.2.x1.3.x协同工作。

注意:在安装了Sun公司的JavaMail工具后,会在演示目录下看到许多示例程序。

安装JavaMail 1.2

要使用JavaMail 1.2 API,可以下载JavaMail 1.2工具,然后解压缩javamail-1_2.zip文件,并把mail.jar文件添加到典型安装路径下。JavaMail 1.2工具带有SMTPIMAP4POP3提供程序以及核心类。

安装完JavaMail 1.2后,再安装JavaBeans Activation Framework

安装JavaMail 1.1.3

要使用JavaMail 1.1.3 API,可以下载JavaMail 1.1.3工具,然后解压缩javamail1_1_3.zip文件,并把mail.jar文件添加到典型安装路径下。JavaMail 1.1.3工具带有SMTPIMAP4提供程序以及核心类。

如果您想用JavaMail 1.1.3访问POP服务器,需要下载并安装POP3提供程序。Sun公司拥有一个独立于 JavaMail 工具的提供程序。在下载并解压缩pop31_1_1.zip文件后,也还需要把pop3.jar添加到典型安装路径下。

安装完JavaMail 1.1.3后,再安装JavaBeans Activation Framework

安装JavaBeans Activation Framework

JavaMail API的所有版本都需要JavaBeans Activation FrameworkJavaBeans激活框架),这种框架提供了对输入任意数据块的支持,并能相应地对其进行处理。看上去效果好像不太好,但该框架是在当今的许多浏览器和邮件工具中可以找到的基本MIME类型支持。下载该框架后,解压缩jaf1_0_1.zip文件,并将activation.jar文件添加到典型安装路径下。

对于JavaMail 1.2用户,现在应该把mail.jaractivation.jar文件添加到典型安装路径下。

对于JavaMail 1.1.3用户,现在应该把mail.jarpop3.jaractivation.jar添加到典型安装路径下。如果您不打算使用POP3,就不需要把pop3.jar文件添加到典型安装路径下。

如果您不想更改安装路径环境变量,可以把JAR文件复制到Java运行时环境(JRE)目录下的lib/ext目录下。例如,对于J2SE 1.3版本,Windows平台上的默认目录应该是C:\jdk1.3\jre\lib\ext

使用Java 2企业版

如果您使用的是J2EE,则在使用基本JavaMail API时,不需要做什么特殊的工作;JavaMail API带有J2EE类。只要确保j2ee.jar文件位于典型安装路径下,并完成了所有的设置工作。

对于J2EE 1.2.1POP3提供程序是单独提供的,因此需要下载该提供程序,并按安装JavaMail 1.1.3的步骤,在J2EE 1.2.1中包含POP3提供程序。J2EE 1.3的用户会获得J2EEPOP3提供程序,因而不需要对POP3提供程序执行独立安装。使用这两种版本的J2EE用户,都不需要安装JavaBeans Activation Framework

练习

设置您的 JavaMail 环境

复习核心类

在开始深入研究JavaMail类之前,首先让用户浏览一下构成API的核心类:会话、消息、地址、验证程序、传输,存储和文件夹。所有这些类都可以在JavaMail APIjavax.mail的顶层包中找到,尽管您将频繁地发现您自己使用的子类是在javax.mail.internet包中找到的。

Session

Session类定义了一个基本的邮件会话。通过该会话可让别的工作顺利执行。Session对象利用java.util.Properties对象获取诸如邮件服务器、用户名、密码等信息,以及其他可在整个应用程序中共享的信息。

Session类的构造器是私有的。您可以获得一个可被getDefaultInstance()方法共享的单一的默认会话:

Properties props = new Properties();

// fill props with any information

Session session = Session.getDefaultInstance(props, null);

或者,您可以用getInstance()方法创建一个独特的会话:

Properties props = new Properties();

// fill props with any information

Session session = Session.getInstance(props, null);

这两种情形下的null参数都是一种Authenticator对象,它不是在此时使用的。详细信息请参阅其后的“Autherticator”一节。

在大多数情况下,使用共享会话就足够了,即使为多个用户邮箱处理邮件会话也是如此。您可以在通信过程的后面一步添加上用户名和密码的组合,并保持所有的一切是独立的。

Message

一旦创建了自己的Session对象,就是该去创建要发送的消息的时候了。这时就要用到消息类型。作为一个抽象类,您必须操作一个子类,在大多数情况下,该子类是javax.mail.internet.MimeMessage。一个MimeMessage是一种理解MIME类型和报头(在不同的RFC文档中均有定义)的消息。消息的报头被严格限制成只能使用US-ASCII字符,尽管非ASCII字符可以被编码到某些报头字段中。

可以通过将Session对象传递给MimeMessage构造器的方法来创建消息:

MimeMessage message = new MimeMessage(session);

注意:还有其他的构造器,像用于创建消息的源于RFC822格式化的输入流的构造器。

一旦创建了消息,就可以设置其各个部分,如Message(消息)实现Part(部分)接口(以MimeMessage实现MimePart)。设置内容的基本机制是setContent()方法,它带有表示内容和MIME类型的参数:

message.setContent("Hello", "text/plain");

但是,如果正在使用 MimeMessage,并且您的消息是纯文本,那么您就可以使用setText()方法。该方法只需要一个表示实际内容的参数,默认的MIME类型为纯文本:

message.setText("Hello");

对于纯文本消息,setText()方法更常常被用来设置内容。要发送其他类型的消息,如HTML消息,就要使用setContent方法()。现在用的更多的是HTML消息。

要设置主题,可以使用setSubject()方法:

message.setSubject("First");

Address

一旦创建了会话和消息,并为消息填充了内容,就需要用Address类为您的信件标上地址了。同Message类一样,Address类也是一种抽象类。您可以使用javax.mail.internet.InternetAddress类。

要创建只带有电子邮件地址的地址,可以把电子邮件地址传递给Address类的构造器:

Address address = new InternetAddress("president@whitehouse.gov");

如果想让一个名字出现在电子邮件地址后,也可以将其传递给构造器:

Address address = new InternetAddress("president@whitehouse.gov", "George Bush");

您要为消息的from(发送者)字段和to(接收者)字段创建地址对象。除非您的邮件服务器阻止这样做,否则要在发送的消息中注明该消息的发送者。

一旦创建好了地址,有两种方法可让您将地址与消息连接起来。为了鉴别发送者,您可以使用setFrom()setReplyTo()方法。

message.setFrom(address)

如果您的消息需要显示多个地址来源,则可以使用addFrom()方法:

Address address[] = …;

message.addFrom(address);

为了鉴别消息接收者,您可以使用addRecipient()方法。该方法除了需要一个地址参数外,还需要一个Message.RecipientType属性(消息的接收类型)。

message.addRecipient(type, address)

地址的3种预定义类型如下:

  • Message.RecipientType.TO

  • Message.RecipientType.CC

  • Message.RecipientType.BCC

因此,如果一条消息将发送给副总统,同时还将发送该消息的副本给第一夫人,则采用下面的代码:

Address toAddress = new InternetAddress("vice.president@whitehouse.gov");

Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");

message.addRecipient(Message.RecipientType.TO, toAddress);

message.addRecipient(Message.RecipientType.CC, ccAddress);

JavaMail API没有提供检查电子邮件地址有效性的机制。您可以自己编写支持扫描有效字符(在RFC 822文档中所定义的)的程序或检验MX(邮件交换)记录,这些都超越了JavaMail API的范围。

Authenticator

java.net类一样,JavaMail API可以利用Authenticator(验证程序)类通过用户名和密码来访问受保护的资源。对于JavaMail API来说,这种受保护的资源是指邮件服务器。JavaMailAuthenticator类可以在javax.mail包中找到,并有别于同名的java.net类。当JavaMail APIJava 1.1下工作时,JavaMailjava.net不会共享同一个Authenticator类名称,这是因为Java 1.1中不含有java.net

要使用Authenticator类,您可以使用该抽象类的子类,并通过getPasswordAuthentication()方法返回一个PasswordAuthentication实例。在创建时,您必须用会话记录Authentication类。其后,当需要进行身份验证时,会通知您的Authenticator。会弹出一个窗口,或从一个配置文件(尽管不加密就不安全)中读取用户名和密码,并把它们作为一个PasswordAuthentication对象返回给调用程序。

Properties props = new Properties();

// fill props with any information

Authenticator auth = new MyAuthenticator();

Session session = Session.getDefaultInstance(props, auth);

Transport

发送消息的最后一步操作是使用Transport类。该类使用特定于协议(通常是SMTP)的语言来发送消息。它是一个抽象类,其操作与Session类有些相似。您可以通过只调用静态的send()方法来使用该类的默认版本:

Transport.send(message);

或者,您可以从用于您的协议的会话中获取一个特定的实例,然后传递用户名和密码(不必要时可以为空)并发送消息,最后关闭连接:

message.saveChanges(); // implicit with send()

Transport transport = session.getTransport("smtp");

transport.connect(host, username, password);

transport.sendMessage(message, message.getAllRecipients());

transport.close();

当您需要发送多个消息时,建议采用后一种方法,因为它将保持消息间活动服务器的连接。而基本的send()机制会为每一个方法调用都建立一条独立的连接。

注意:要查看经过邮件服务器邮件命令,可以用session.setDebug(true)方法设置调试标志。

StoreFolder

使用Session类来获取消息,开始时与发送消息很相似。但是,在获取会话后,很有可能使用用户名和密码或Authenticator类来连接Store类。与Transport类一样,您要告诉Store类将使用什么协议:

// Store store = session.getStore("imap");

Store store = session.getStore("pop3");

store.connect(host, username, password);

在连接Store类后,就可以获取一个Folder类,在读取其中的消息前必须先打开该类。

Folder folder = store.getFolder("INBOX");

folder.open(Folder.READ_ONLY);

Message message[] = folder.getMessages();

对于POP3协议,惟一可用的文件夹是INBOX。如果使用的是IMAP协议,则可以使用其他的文件夹。

注意:Sun公司的提供程序本来想提供方便。而Message message[]=folder.getMessages();这条语句却是一种从服务器逐条读取消息的缓慢操作,所以仅当您确实需要获取消息部分(该内容是所检索消息的内容)时可以使用这条语句。

一旦读取消息,就可以使用getContent()方法获取其内容,或使用writeTo()方法将其内容写到一个流中。getContent()方法只获取消息内容,而writeTo()方法则还会输出报头。

System.out.println(((MimeMessage)message).getContent());

一旦您阅读完邮件,就可以关闭对文件夹和存储的连接。

folder.close(aBoolean);

store.close();

传递给文件夹的close()方法的布尔变量指定了是否通过清除已删除的消息来更新文件夹。

继续前进

实际上,理解使用这7个类的方式,是使用JavaMail API处理几乎所有事情所需要的全部内容。用这7个类以外的方式构建的JavaMail API,其大多数功能都是以几乎完全相同或特定的方式来执行任务的,就好像内容是附件。特定的任务,如:搜索、隔离等将在后面进行介绍。

使用JavaMail API

您已经看到了如何操作JavaMail API的核心部分。在下面几节中,您将学习如何连接几个部分以执行特定的任务。

发送消息

发送电子邮件消息涉及到获取会话、创建和填充消息并发送消息这些操作。您可以在获取Session时,通过为要传递的Properties对象设置mail.smtp.host属性来指定您的SMTP服务器。

String host = …;

String from = …;

String to = …;

// Get system properties

Properties props = System.getProperties();

// Setup mail server

props.put("mail.smtp.host", host);

// Get session

Session session = Session.getDefaultInstance(props, null);

// Define message

MimeMessage message = new MimeMessage(session);

message.setFrom(new InternetAddress(from));

message.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

message.setSubject("Hello JavaMail");

message.setText("Welcome to JavaMail");

// Send message

Transport.send(message);

您应该在try-catch块中编写代码,以在创建消息并发送它时可以抛出一个异常。

练习

发送您的第一个消息

获取消息

对于阅读邮件来说,首先您要获取一个会话,然后获取并连接到一个相应的用于您的收件箱的存储上,接着打开相应的文件夹,再获取消息。同时,不要忘记了操作完成后关闭连接。

String host = …;

String username = …;

String password = …;

// Create empty properties

Properties props = new Properties();

// Get session

Session session = Session.getDefaultInstance(props, null);

// Get the store

Store store = session.getStore("pop3");

store.connect(host, username, password);

// Get folder

Folder folder = store.getFolder("INBOX");

folder.open(Folder.READ_ONLY);

// Get directory

Message message[] = folder.getMessages();

for (int i=0, n=message.length; i<n; i++) {

System.out.println(i + ": " + message[i].getFrom()[0]

+ "\t" + message[i].getSubject());

}

// Close connection

folder.close(false);

store.close();

每一条消息执行何种操作取决于自己决定。上面的代码块只是显示了消息的发送者和主题。从技术上讲,发送者地址列表可以为空,此时getFrom()[0]调用会抛出一个异常。

为了显示整条消息,您可以提示用户在看完消息的发送者和主题字段后,如果想看到消息的内容,可以再调用消息的writeTo()方法。

BufferedReader reader = new BufferedReader (

new InputStreamReader(System.in));

// Get directory

Message message[] = folder.getMessages();

for (int i=0, n=message.length; i<n; i++) {

System.out.println(i + ": " + message[i].getFrom()[0]

+ "\t" + message[i].getSubject());

System.out.println("Do you want to read message? " +

"[YES to read/QUIT to end]");

String line = reader.readLine();

if ("YES".equals(line)) {

message[i].writeTo(System.out);

} else if ("QUIT".equals(line)) {

break;

}

}

练习

检查邮件

删除消息和标志

删除消息涉及到操作与消息关联的标志。对不同的状态有不同的标志,有些标志是系统定义的,有些则是由用户定义的。预定义的标志都是在内部类Flags.Flag中定义的,如下所示:

  • Flags.Flag.ANSWERED

  • Flags.Flag.DELETED

  • Flags.Flag.DRAFT

  • Flags.Flag.FLAGGED

  • Flags.Flag.RECENT

  • Flags.Flag.SEEN

  • Flags.Flag.USER

仅仅因为标志存在,并不表示标志为所有的邮件服务器/提供程序所支持。例如,除了删除消息外,POP协议对它们都不支持。检查新邮件不是POP的任务,但它已内置到邮件客户程序中。要搞清楚什么标志受到支持,可以使用getPermanentFlags()方法来询问文件夹。

要删除消息,需要为消息设置DELETE标志:

message.setFlag(Flags.Flag.DELETED, true);

第一次以READ_WRITE(读-写)模式打开文件夹:

folder.open(Folder.READ_WRITE);

然后,处理完了所有的消息,请关闭文件夹,并传递true值以擦去删除的消息。

folder.close(true);

用户可使用Folder类的expunge()方法来删除消息。但是,该方法对Sun公司的POP3提供程序不起作用。其他提供程序或许能也或许不能实现其功能。它更有可能适用于IMAP提供程序。由于POP只支持对收件箱的简单访问,使用Sun公司的提供程序时,您将不得不关闭文件夹以删除消息。

要移去标志,只需传递一个false值给setFlag()方法。要看看是否设置了某个标志,可以使用isSet()进行检查。

自我验证

先前学到的是使用Authenticator类,以在需要时提示输入用户名和密码,而不是以字符串的形式传入它们。这里,您将真正看到如何更加充分地使用验证。

不需使用主机、用户名和密码连接到Store,您可以配置Properties带有主机,并告诉Session关于您自定义的Authenticator实例,如下所示:

// Setup properties

Properties props = System.getProperties();

props.put("mail.pop3.host", host);

// Setup authentication, get session

Authenticator auth = new PopupAuthenticator();

Session session = Session.getDefaultInstance(props, auth);

// Get the store

Store store = session.getStore("pop3");

store.connect();

然后您可以使用Authenticator类的子类,并通过getPasswordAuthentication()方法返回一个PasswordAuthentication对象。下面是这种实现的一个例子,其中一个字段同时适用于两部分内容。它不是一个Project Swing指南,只是在一个字段中输入了两部分内容,它们是用逗号隔开的。

import javax.mail.*;

import javax.swing.*;

import java.util.*;

public class PopupAuthenticator extends Authenticator {

public PasswordAuthentication getPasswordAuthentication() {

String username, password;

String result = JOptionPane.showInputDialog(

"Enter ‘username,password’");

StringTokenizer st = new StringTokenizer(result, ",");

username = st.nextToken();

password = st.nextToken();

return new PasswordAuthentication(username, password);

}

}

由于PopupAuthenticator依赖于Swing,因而将会启动用于AWT的事件处理线程。这在本质上要求您在代码中添加一个对System.exit()的调用,以终止程序的执行。

回复消息

Message类包含一个reply()方法,以用正确的接收者和主题(添加“Re::”,如果没有的话)配置一条新消息。该方法不会为消息添加任何内容,只是为新的接收者复制发送者或回复到的报头。该方法使用一个布尔型参数,提示是否只回复给发送者(false)或回复给所有人(true)

MimeMessage reply = (MimeMessage)message.reply(false);

reply.setFrom(new InternetAddress("president@whitehouse.gov"));

reply.setText("Thanks");

Transport.send(reply);

在发送消息时要配置回复到地址,可使用setReplyTo()方法。

练习

回复邮件

转发消息

转发消息涉及的内容要稍微多一点,没有一个专门用于转发消息的方法,您可以通过处理组成消息的各个部分来创建要转发的消息。

一条邮件消息可由多个部分组成,每一部分是一个BodyPart(报文部分),或更特殊一点,在操作MIME消息时则是MimeBodyPart。不同的报文部分组合到一个称为Multipart的容器中,或者又更特殊一点,是一个MimeMultipart容器。要转发消息,您要创建一个用于消息文本的部分,和用于要转发的消息的第二个部分,并将这两个部分组合成一个multipart(多个部分)。然后您可以把这个multipart添加到一个合适的注明地址的消息中并发送它。

这就是转发消息的本质。要把一条消息的内容复制给另一条消息,只需通过它的DataHandler类复制即可,它是出自于JavaBeans Activation Framework的一个类。

// Create the message to forward

Message forward = new MimeMessage(session);

// Fill in header

forward.setSubject("Fwd: " + message.getSubject());

forward.setFrom(new InternetAddress(from));

forward.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

// Create your new message part

BodyPart messageBodyPart = new MimeBodyPart();

messageBodyPart.setText(

"Here you go with the original message:\n\n");

// Create a multi-part to combine the parts

Multipart multipart = new MimeMultipart();

multipart.addBodyPart(messageBodyPart);

// Create and fill part for the forwarded content

messageBodyPart = new MimeBodyPart();

messageBodyPart.setDataHandler(message.getDataHandler());

// Add part to multi part

multipart.addBodyPart(messageBodyPart);

// Associate multi-part with message

forward.setContent(multipart);

// Send message

Transport.send(forward);

操作附件

附件是与邮件消息关联的资源,通常保存在消息之外,如:一个文本文件,电子表格或图片。对于像EudoraPine之类的常用邮件程序,您可以通过JavaMail API把资源附加到邮件消息上,并在您接收消息时获取附件。

发送附件

发送附件与转发消息非常相似,您要创建组成完整消息的各个部分。在创建好第一个部分即消息文本之后,您添加的用DataHandler类处理的其他部分就是您的附件,而不是转发消息中的共享处理程序。当您从一个文件读取附件时,附件的数据资源是FileDataSource;从URL读取时,则是URLDataSource。一旦您有了自己的DataSource,在将其通过setDataHandler()方法最终附加到BodyPart上之前,只需将其传递给DataHandler类的构造器即可。假定您想保留附件的原始文件名,要做的最后一件事就是用BodyPart类的setFileName()方法设置与附件关联的文件名。所有这些操作如下所示:

// Define message

Message message = new MimeMessage(session);

message.setFrom(new InternetAddress(from));

message.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

message.setSubject("Hello JavaMail Attachment");

// Create the message part

BodyPart messageBodyPart = new MimeBodyPart();

// Fill the message

messageBodyPart.setText("Pardon Ideas");

Multipart multipart = new MimeMultipart();

multipart.addBodyPart(messageBodyPart);

// Part two is attachment

messageBodyPart = new MimeBodyPart();

DataSource source = new FileDataSource(filename);

messageBodyPart.setDataHandler(new DataHandler(source));

messageBodyPart.setFileName(filename);

multipart.addBodyPart(messageBodyPart);

// Put parts in message

message.setContent(multipart);

// Send the message

Transport.send(message);

在消息中包含附件时,如果您的程序是一个servlet,您的用户就必须上传附件,并告诉您要把消息发送到什么位置。上传的每一个文件都可以用一个表单来处理,该表单是以multipart/表单数据(form-data)来编码的。

<FORM ENCTYPE="multipart/form-data"

method=post action="/myservlet">

<INPUT TYPE="file" NAME="thefile">

<INPUT TYPE="submit" VALUE="Upload">

</FORM>

注意:消息的大小要受到您的SMTP服务器的限制,而不是由JavaMail API限制的。如果出现了问题,可以通过设置msmx参数来考虑增加Java堆区的空间尺寸。

练习

发送附件

获取附件

从消息中取出附件比发送附件涉及的操作要稍微多一点,而MIME没有简单的附件概念。当消息带有附件时,消息的内容就是一个Multipart对象。然后需要处理各个部分,以获取主要内容和附件。通过part.getDisposition()方法标记上Part.ATTACHMENT配置的部分显然就是附件。同时,附件也可以不带有配置(和非文本MIME类型)或Part.INLINE配置。当配置是Part.ATTACHMENTPart.INLINE时,您可以脱离该消息部分的内容将其保存起来。只需通过getFileName()方法获取原始文件名,并通过getInputStream()方法获取输入流即可。

Multipart mp = (Multipart)message.getContent();

for (int i=0, n=multipart.getCount(); i<n; i++) {

Part part = multipart.getBodyPart(i));

String disposition = part.getDisposition();

if ((disposition != null) &&

((disposition.equals(Part.ATTACHMENT) ||

(disposition.equals(Part.INLINE))) {

saveFile(part.getFileName(), part.getInputStream());

}

}

saveFile()方法只用于根据文件名创建一个文件,从输入流中读取字节,并将它们写入一个文件中去。如果文件已存在,将在文件名后添加一个编号,直到找到一个不存在的文件为止。

// from saveFile()

File file = new File(filename);

for (int i=0; file.exists(); i++) {

file = new File(filename+i);

}

上面的代码介绍了消息的各个部分被标上相应的标志的一个最简单的例子。要想包含所有的情况,还要对disposition值为null及消息部分为MIME类型的情况作相应处理。

if (disposition == null) {

// Check if plain

MimeBodyPart mbp = (MimeBodyPart)part;

if (mbp.isMimeType("text/plain")) {

// Handle plain

} else {

// Special non-attachment cases here of image/gif, text/html, …

}

}



处理HTML消息

发送基于HTML的消息比发送纯文本消息要稍微复杂一点,尽管它不需要做大量的工作。它全部取决于您特定的需求。

发送HTML消息

如果您所要做的全部工作是发送一个等价的HTML文件作为消息,并让邮件阅读者忧心于取出任何嵌入的图片或相关片段,那么就可以使用消息的setContent()方法,以字符串形式传递消息内容,并把内容类型设置为text/html

String htmlText = "<H1>Hello</H1>" +

"<img src=\"http://www.jguru.com/images/logo.gif\">";

message.setContent(htmlText, "text/html"));

在接收端,如果您用JavaMail API获取消息,在该API中没有内置任何用于以HTML格式显示消息的功能。JavaMail API只以字节流的形式来查看消息。要以HTML格式显示消息,您必须使用Swing JeditorPane或某些第3HTML阅读器组件。

if (message.getContentType().equals("text/html")) {

String content = (String)message.getContent();

JFrame frame = new JFrame();

JEditorPane text = new JEditorPane("text/html", content);

text.setEditable(false);

JScrollPane pane = new JScrollPane(text);

frame.getContentPane().add(pane);

frame.setSize(300, 300);

frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

frame.show();

}

在消息中包含图片

另一方面,如果您的HTML消息中嵌入了作为消息一部分的图片,并且您想保持消息内容的完整,就必须把图片看作附件,并用特殊的通信标识符URL引用该图片,该通信标识符引用的是图片附件的内容ID报文。

嵌入图片的处理与附加一个文件到消息上非常相似,惟一的不同之处在于:您必须区分MimeMultipart中,哪些部分是在构造器(或通过setSubType()方法)通过设置其子类型而使之相关的,以及将图片的内容ID报头设置成任意字符串,它将在img标记中用作图片的源路径。下面显示了一个完整的示例:

String file = …;

// Create the message

Message message = new MimeMessage(session);

// Fill its headers

message.setSubject("Embedded Image");

message.setFrom(new InternetAddress(from));

message.addRecipient(Message.RecipientType.TO,

new InternetAddress(to));

// Create your new message part

BodyPart messageBodyPart = new MimeBodyPart();

String htmlText = "<H1>Hello</H1>" +

"<img src=\"cid:memememe\">";

messageBodyPart.setContent(htmlText, "text/html");

// Create a related multi-part to combine the parts

MimeMultipart multipart = new MimeMultipart("related");

multipart.addBodyPart(messageBodyPart);

// Create part for the image

messageBodyPart = new MimeBodyPart();

// Fetch the image and associate to part

DataSource fds = new FileDataSource(file);

messageBodyPart.setDataHandler(new DataHandler(fds));

messageBodyPart.setHeader("Content-ID","memememe");

// Add part to multi-part

multipart.addBodyPart(messageBodyPart);

// Associate multi-part with message

message.setContent(multipart);

练习

发送带有图片的 HTML 消息

SearchTerm搜索

JavaMail API包含一种可用于创建SearchTerm(搜索条件)的筛选机制,它可以在javax.mail.search包中找到。一旦创建了SearchTerm,您就可以询问某个文件夹匹配的消息,并检索出消息对象数组:

SearchTerm st = …;

Message[] msgs = folder.search(st);

22种不同的类可用于帮助创建搜索条件。

  • AND条件(AndTerm)

  • OR条件(OrTerm)

  • NOT条件(NotTerm)

  • SENT DATE条件(SentDateTerm)

  • CONTENT条件(BodyTerm)

  • HEADER条件(FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)

本质上,您可以为匹配的消息创建一个逻辑表达式,然后进行搜索。例如,下面显示了一条消息的条件搜索示例,该消息带有(部分带有)一个ADV主题字符串,其发送者字段为friend@public.com。您可能考虑定期运行该查询,并自动删除任何返回的消息。

SearchTerm st =

new OrTerm(

new SubjectTerm("ADV:"),

new FromStringTerm("friend@public.com"));

Message[] msgs = folder.search(st);

资源

您可以使用JavaMail API执行比这里介绍的多得多的任务。这里看到的课程和练习可以由下面列出的资源提供:

  • JavaMail API之家

  • JavaBeans Activation Framework之家

  • 令人感兴趣的Java邮件列表

  • Sun公司的JavaMail常见问题解答

  • jGuruJavaMail常见问题解答

  • 3方产品列表

版权1996-2001 jGuru.com. 版权所有。

用于调用Javaean组件中的操作和执行请求分派的标准JSP标签简化了JSP页面的开发和维护。JSP技术还提供了在自定义标签中封装其他动态功能的机制,这种自定标签是JSP语言的扩展。自定义标签通常是以标签库的形式出现的,它定义了一组相关的自定义标签,并包含实现这些标签的对象。

可以由自定义标签执行的任务包括对隐式对象的操作、处理表单、访问数据库和其他企业级服务,如电子邮件和目录、以及执行流程控制。JSP标签库是由精通Java编程语言和对访问数据库和其他服务非常熟悉的开发人员创建的,使用这些标签,Web应用程序开发人员就可以把注意力放到内容的呈现上,而不用费心考虑如何访问企业级服务。就像鼓励将库开发人员和库使用人员的工作分开一样,自定义标签通过封装反复执行的任务使它们可以在多个应用程序中重复使用,从而提高了生产率。

JSP技术社区给予标签库非常高的重视。有关标签库的信息和一些免费的库的地址,参见

http://java.sun.com/products/jsp/taglibraries.html

什么是自定义标签?

自定义标签是用户定义的JSP语言元素。当包含自定义标签的JSP页面转换为servlet时,这个标签就转换为一个名为tag handler的对象上的操作。之后当JSP页面的servlet执行时,Web容器就调用这些操作。

自定义标签有丰富的功能。它们可以

·  通过从调用页面传递的属性进行定制。

·  访问JSP页面可以使用的所有对象。

·  修改由调用页面生成的响应。

·  彼此通信。可以创建并初始化JavaBean组件、在一个标签中创建引用该bean的变量、再在另一个标签中使用这个bean。

·  彼此嵌套,可以在JSP页面中实现复杂的交互交互。

JSP页面示例

本章描述使用和定义标签所涉及的任务。本章用改写了的、在JSP页面示例中讨论的JSP版本的Duke’s Bookstore应用程序的部分演示这些任务,所做的改写利用了两个标签库的优点:Struts和tutorial-template。本章的第三节示例详细描述了两个标签:Strutst中的iterate和tutorial-template标签库中的一组标签。

Struts标签库提供了构建实现模型-视图-控制设计模式的国际化Web应用程序的框架。Struts包括完整的一组自定义工具标签,用于处理:

·  HTML 表单

·  模板

·  JavaBeans组件

·  逻辑处理

Duke’s Bookstore应用程序使用Struts bean和logic子库中的标签。

Tutorial-template标签库定义了一组用于创建应用程序模板的标签。模板是带有占位符的JSP页面,这些占位符需要在每一屏幕中改变。每一个占位符称为模板的参数。例如,一个简单的模板可能包括在生成的屏幕上方的title参数,和一个JSP页面作为屏幕的定制内容的body参数。模板是用一组嵌入的标签创建的——definition、screen和parameter——它们用于构建Duke’s Bookstore的屏幕定义表,并用insert标签将参数从表中插入屏幕。

图16-1显示了通过Duke’s Bookstore Web组件的请求流程:

·  template.jsp, template.jsp确定每一屏幕的结构。它使用insert标签用子组件组成屏幕。

·  screendefinitions.jsp,它定义了每一屏幕使用的子组件。所有屏幕都有相同的横幅,但是标题和正文不同(由表15-1中的JSP页面列所指定)。

·  Dispatcher,这是一个servlet,它处理请求并转发给template.jsp。

Request Flow Through Duke's Bookstore Components

图16-1 通过Duke’s Bookstore组件的请求流程

Duke’s Bookstore应用程序的源代码位于在解压缩教程包(见运行示例)时生成的docs/tutorial/examples/web/bookstore3目录中。要编译、部署并运行这个例子,你需要:

1.     从以下地址下载Struts version 1.0.2

http://jakarta.apache.org/builds/jakarta-struts/release/v1.0.2/

2.     解压缩Struts并将struts-bean.tld、struts-logic.tld和struts.jar从jakarta-struts-1.0/lib拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore3。

3.     在终端窗口,进入<JWSDP_HOME>/docs/tutorial/examples/bookstore3.

4.     运行ant build。Build目标会进行所有必要的编译并将文件拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore3/build目标。

5.     确保已经启动了Tomcat。

6.     运行ant install。Install目标通知Tomcat 已经有了内容。

7.     如果还没有做的话,就启动PointBas数据库服务器并加入数据(见从Web应用程序中访问数据库)。

8.     打开书店URL http://localhost:8080/bookstore3/enter。

有关诊断常见问题的帮助见常见问题及其解决方案故障排除

使用标签

本节描述JSP页面如何使用标签,并介绍不同类型的标签。

要使用标签,页面编写者必须做以下两件事:

·  声明包含标签的标签库

·  让标签库实现对于Web应用程序可用

声明标签库

通过在使用任何自定义标签之前,将taglib指令加入页面中声明JSP页面将使用在标签库中定义的标签:

<%@ taglib uri="/WEB-INF/tutorial-template.tld" prefix="tt" %>

uri属性表示唯一标识标签库描述符(TLD)的URI,在标签库描述符中描述了uri。这个URI可以是直接或者非直接的。prefix属性定义了区分指定标签库所定义的标签与其他标签库提供的标签的前缀。

标签库描述符文件名必须有扩展名.tld。TLD文件储存在WAR的WEB-INF目录中,或者在WEB-INF的子目录中。可以直接或者间接引用TLD。

下面taglib指令直接引用一个TLD文件名:

<%@ taglib uri="/WEB-INF/tutorial-template.tld" prefix="tt" %>

这个taglib指令使用一个短的逻辑名间接引用TLD:

<%@ taglib uri="/tutorial-template" prefix="tt" %>

在Web应用程序部署描述符中将逻辑名映射到一个绝对位置。要将逻辑名/tutorial-template映射为绝对位置/WEB-INF/tutorial-template.tld,在web.xml中添加元素taglib:

<taglib>
  <taglib-uri>/tutorial-template</taglib-uri>
  <taglib-location>   
  /WEB-INF/tutorial-template.tld
  </taglib-location>
</taglib>

让标签库实现可用

可以以两种方式让标签库实现对Web应用程序可用。实现了标签handler的类可以以非打包的形式储存在Web应用程序的WEB-INF/classes子目录中。另一种方法是,如果以JAR的形式发布库,就将它储存在Web应用程序的WEB-INF/lib目录中。在多个应用程序中共享的标签库储存在Java WSDP的<JWSDP_HOME>/common/lib目录中。

标签类型

JSP自定义标签是用XML语法编写的。它们有一个开始标签和结束标签,可能还有正文:

<tt:tag>
  body
</tt:tag>

不带正文的自定义标签如下表示:

<tt:tag />

简单标签

一个简单标签没有正文,也没有属性:

<tt:simple />

带属性的标签

自定义标签可以带有属性。属性列在开始标签中,语法为attr="value"。像用参数定制方法的行为一样,属性值用于定制自定义标签的行为。在标签库描述符中指定标签属性的类型(见带属性的标签)。

可以用一个常量或者运行时表达式设置属性值。常量和运行时表达式与属性类型之间的转换过程遵循在设置JavaBean组件属性中描述的JavaBean组件属性规则。

Struts logic:present标签的属性决定是否对标签的正文进行判断。在下面的例子中,一个属性指定需要一个名为的参数Clear:

<logic:present parameter="Clear">

Duke’s Bookstore应用程序页面catalog.jsp使用了运行时表达式设置属性的值,它决定Struts logic:iterate标签要枚举哪几本书。

<logic:iterate collection="<%=bookDB.getBooks()%>"
  id="book" type="database.BookDetails">

带正文的标签

自定义标签可以包含自定义和核心标签、脚本元素、HTML文本和开始与结束标签之间的、依赖于标签的正文内容。

在下面的例子中,Duke’s Bookstore应用程序页面showcart.jsp使用Struts logic:present标签清除购物车,并且如果请求包含一个名为Clear的参数就打印一个消息。

<logic:present parameter="Clear">
  <% cart.clear(); %>
  <font color="#ff0000" size="+2"><strong>
  You just cleared your shopping cart!
  </strong><br>&nbsp;<br></font>
</logic:present>

选择用属性或者正文传递信息

正如最后两节中所展示的,可以将给定的数据作为标签的属性或者标签的正文传递。一般来说,任何简单字符串或者可以由对简单表达式判断而生成的数据最好作为属性传递。

定义脚本变量的标签

自定义标签可以定义可在页面中的脚本中使用的变量。下面的例子展示了如何定义并使用包含一个从JNDI查询中返回的对象的脚本变量。这种对象的例子包括企业bean、事务、数据库、环境项等等:

<tt:lookupegin(); %> id="tx" type="UserTransaction"
  name="java:comp/UserTransaction" />
<% tx.b

在Duke’s Bookstore应用程序中,有几个页面使用了Struts的面向bean的标签以定义脚本变量。例如,bookdetails.jsp使用了bean:parameter标签以创建脚本变量bookId并设置它并将它设置为请求参数bookId的值。jsp:setProperty语句还设置bookDB对象的bookId属性为请求参数bookId的值。bean:define标签提取书店数据库bookDetails属性bookDetails的值并将结果定义为脚本变量book:

<bean:parameter id="bookId" name="bookId" />
<jsp:setProperty name="bookDB" property="bookId"/>
<bean:define id="book" name="bookDB" property="bookDetails"
  type="database.BookDetails"/>
<h2><jsp:getProperty name="book" property="title"></h2>

操作标签

自定义标签可以通过共享对象彼此合作。

在下面的例子中,tag1创建了一个名为obj1的对象,再由tag2返回这个对象。

<tt:tag1 attr1="obj1" value1="value" />
<tt:tag2 attr1="obj1" />

在下面的例子中,由一组嵌套标签中的外围标签创建的对象对于所有内部标签都是可用的。因为没有为对象命名,所以可以减少潜在的命名冲突。这个例子展示在JSP页面中一组协作的嵌入标签会是什么样子的。

<tt:outerTag>
  <tt:innerTag />
</tt:outerTag>

Duke’s Bookstore页面template.jsp使用了一组协作标签定义应用程序的屏幕。在模板标签库中描述了这些标签。

定义标签

要定义标签,需要:

·  为该标签开发一个tag handler和helper类

·  在标签库描述符中声明这个标签

本节描述标签handler和TLD的属性,并解释如何为在前面几节中介绍的标签开发tag handler和库描述符元素。

标签handler

标签handler是由Web容器调用的一个对象,用于执行带有自定义标签的JSP页面时对这个标签进行判断。标签handler必须实现Tag或者BodyTag接口。接口可以用于接受现有Java对象并使它成为标签handler。对于新创建的处理器,可以用TagSupportBodyTagSupport类作为基类。这些类和接口包含在javax.servlet.jsp.tagext包中。

JSP页面的servlet在对标签处理的不同阶段调用由Tag和BodyTag接口定义的标签handler。遇到自定义标签的开始标签时,JSP页面的servlet调用方法以初始化相应的handler,然后调用handler的doStartTag方法。遇到自定义标签的结束标签时,调用处理器的doEndTag方法。在标签handler需要与标签的正文交互时调用其他方法,见带正文的标签。为了提供标签handler的实现,必须实现在处理标签的不同阶段调用的方法,在表16-1中汇总了这些方法。

16-1标签handler方法

标签handler类型

方法

简单

doStartTag, doEndTag, release

属性

doStartTag, doEndTag, set/getAttribute1…N, release

正文、判断且无交互

doStartTag, doEndTag, release

正文、迭代判断

doStartTag, doAfterBody, doEndTag, release

正文、交互

doStartTag, doEndTag, release, doInitBody, doAfterBody, release

标签handler可以使用一个能让它得以与JSP页面通信的API。到API的入口点是页面上下文对象(javax.servlet.jsp.PageContext),通过它标签handler可以获取JSP页面能够访问的所有其他隐式对象(请求、会话和应用程序)。

隐式对象可以有与其相关联的命名属性。可以用[set|get]Attribute方法访问这种属性。

如果标签是嵌入的,标签handler还可以访问与外围标签关联的handler称为parent)。

一组相关的标签handler类(标签库)一般是打包的且作为JAR文档部署。

标签库描述符

标签库描述符(TLD)是一个描述标签库的XML文档。TLD包含有关整个库以及库中包含的每一个标签的信息。Web容器用TLD验证标签,JSP页面开发工具也使用TLD。

TLD文件名必须有扩展名.tld。TLD文件也储存在WAR文件的WEB-INF目录中或者在WEB-INF的子目录中。

TLD必须以指定XML的版本和文档类型定义(DTD)的XML文档序言(prolog)开始。

<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">

Tomcat支持版本 1.1和1.2的DTD。不过,本章所讨论的是1.2版本,因为在开发的所有标签库中都应该使用最新的版本。模板库TLDtutorial-template.tld符合版本1.2。Struts库TLD符合版本1.1的DTD,它的元素要少,且其中一些元素使用了稍微不同的名字。

TLD的根是taglib元素。表16-2中列出了taglib的子元素:

16-2 taglib子子元素 

元素

说明

tlib-version

标签库的版本

jsp-version

这个标签库要求的JSP规范版本

short-name

JSP页面编写工具可以用来创建助记名的可选名字

uri

唯一标识该标签库的的URI

display-name

将由工具显示的可选名

small-icon

将由工具使用的可选小图标

large-icon

可被工具使用的可选大图标

description

可选的标签特定信息

listener

listener元素

tag

tag元素

 

listener元素

标签库可以指定一些事件监听器类(见处理Servlet生命周期事件)。这些监听器在TLD中作为listener元素列出,Web容器将初始化监听器类并以类似在WAR级定义的监听器的方式注册它们。与WAR级监听器不同,这里没有指定标签库监听器注册的顺序。listener元素的唯一子元素是listener-class元素,它必须包含监听类的完全限定名。

tag元素

库中的每一个标签都由给出其名字和其标签handler的类、在由标签创建的脚本变量上的信息以及标签属性上的信息描述。脚本变量信息可以在TLD中直接给出,也可以通过tag extra info类给出(见定义脚本变量的标签)。每一个属性声明包含指明属性是否是必需的、其值是否可以由请求时表达式确定以及属性类型的内容(见属性元素)。

在tag元素中的TLD中指定标签。在表16-3中出了tag的子元素:

16-3 标签子元素

元素

说明

name

唯一标签名

tag-class

标签handler类的完全限定名

tei-class

javax.servlet.jsp.tagext.TagExtraInfo的可选子类。见提供有关脚本变量的信息

body-content

正文内容类型。见body-conten元素body-content元素

display-name

由工具显示的可选名

small-icon

可以由工具使用的小图标

large-icon

可以由工具使用的大图标

description

可选的标签特定的信息

variable

可选的脚本变量信息。见提供有关脚本变量的信息

attribute

标签属性信息。见Attribute 元素

下面几节描述开发在标签类型中介绍的每一种类型的标签所需要的方法和TLD。

简单标签

标签handler

简单标签的handler必须实现Tag接口的doStartTag和doEndTag方法。在遇到开始标签时调用doStartTag方法。因为简单标签没有正文,所以这个方法返回SKIP_BODY。在遇到结束标签时调用doEndTag方法。如果要对页面的其他部分进行判断,则doEndTag方法需要返回EVAL_PAGE,否则,它就返回SKIP_PAGE。

在第一节讨论的简单标签

<tt:simple />

由下列标签handler实现:

public SimpleTag extends TagSupport {
  public int doStartTag() throws JspException {
    try {       pageContext.getOut().print("Hello.");
    } catch (Exception ex) {
      throw new JspTagException("SimpleTag: " +
        ex.getMessage());     }
    return SKIP_BODY;   }
  public int doEndTag() {
    return EVAL_PAGE;
  }
}

body-content元素

没有正文的标签必须用body-content元素声明它们的正文内容是空的:

<body-content>empty</body-content>

JavaServer Pages标准标签库(JSTL)封装了许多JSP应用程序共有的核心功能。例如,不需要用脚本或者众多不同厂商的不同迭代器标签对列表进行迭代,JSTL定义了在所有地方都可以使用的标准标签。这种标准化使您可以学习一种标签后,在多个JSP容器中使用它。而且,当标签标准化以后,容器可以优化对它们的实现。

JSTL支持通用的、结构化的任务,如迭代和条件、操作XML文档的标签、国际化标签以及以及用SQL访问数据库的标签。它还引入了表达式语言的概念以简化页面的开发。JSTL还提供了集成现有标签库与JSTL的框架。

本章通过在前面几章讨论的Duke’s Bookstore应用程序的JSP版本的内容展示JSTL。假定您已经熟悉了第16章中的“使用标签”中的内容。

JSP页面示例

本章通过如下重新编写JSP版本的、在第16章讨论过的Duke’s Bookstore应用程序的内容来展示JSTL:

·  用JSTL核心标签替换Struts逻辑标签。

·  用消息格式标签替换访问消息储存的scriptlet。

·  用通过JSTL SQL标签对数据库的直接调用替换JavaBean组件数据库helper。对于大多数应用程序来说,最好将对数据库的调用封装到bean中。JSTL包含SQL标签,在创建已有原型的应用程序并有可能减少创建bean的开销的情况下可以使用这个标签。

Duke’s Bookstore应用程序的源代码在解开教程压缩包时创建的<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4目录中(见运行示例)。

要编译、安装和运行这个例子:

1.     在终端窗口,进入 <JWSDP_HOME>/docs/tutorial/examples/web/bookstore4.

2.     运行ant build。Build目标会进行所有需要的编译并将文件拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4/build 目录中。

3.     确保已经启动Tomcat。

4.     运行ant install。install目标通知Tomcat新的内容已经可用。

5.     如果还没有做的话,启动PointBase数据库服务器并填入数据 (见Web应用程序访问数据库)。

6.     打开书店URL http://localhost:8080/bookstore4/enter.

有关诊断常问题的帮助见常见问题及其解决方法故障排除

使用JSTL

JSTL包括很多种不同的标签,可应用到不同功能的领域中。因此,通过多个标签库描述符(TLD)明确表明它所覆盖的功能领域,并给每一领域它自己的命名空间。17-1总结了这些功能领域,以及在本章和Duke’s Bookstore应用程序中使用的逻辑TLD名和前缀。

17-1 JSTL 标签 

领域

功能

标签

TLD

前缀

核心

支持表达式语言

catch

out

remove

set

/jstl-c

c

流程控制

choose

  when

  otherwise

forEach

forTokens

if

URL管理

import

  param
redirect

  param

url

  param

XML

核心

out

parse

set

/jstl-x

x

流程控制

choose
  when
  otherwise

forEach

if

转换

transform

  param

I18n

区域

setLocale

/jstl-fmt

fmt

编排消息格式

bundle

message

  param

setBundle

编排数字和日期格式

formatNumber

formatDate

parseDate

parseNumber

setTimeZone

timeZone

数据库

 

setDataSource

/jstl-sql

sql

SQL

query

  dateParam

  param

transaction

update
  dateParam

  param

例如,要在JSP页面中使用JSTL核心标签,用引用TLD的taglib指令声明库:

<%@ taglib uri="/jstl-core" prefix="c" %>

JSTL标签库有两种版本(见孪生库)。JSTL-EL库的TLD命名为prefix.tld。JSTL-RT库的TLD命名为prefix-rt.tld。由于在本章讨论的例子使用逻辑TLD名,所以我们用Web应用部署描述符中的taglib元素将逻辑名映射为实际TLD位置。下面是将核心库逻辑TLD名/jstl-c映射到其位置/WEB-INF/c.tld的项:

<taglib>
 <taglib-uri>/jstl-c</taglib-uri>
 <taglib-location>/WEB-INF/c.tld</taglib-location>
</taglib>

在Java WSDP中,JSTL TLD储存在<JWSDP_HOME>/jstl-1.0.3/tld中。在构建Duke’s Bookstore应用程序时,这些TLD自动拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4/build/WEB-INF中。

还可以在taglib指令中用绝对URI引用TLD:

·  核心: http://java.sun.com/jstl/core

·  XML: http://java.sun.com/jstl/xml

·  国际化: http://java.sun.com/jstl/fmt

·  SQL: http://java.sun.com/jstl/sql

在使用绝对URI时,不必在web.xml中添加taglib元素,JSP容器会自动定位在JSTL库实现中的TLD。

除了声明标签库,还需要使JSTL API及其实现对于Web应用程序是可用的。它们是以<JWSDP_HOME>/jstl-1.0.3中的jstl.jar和<JWSDP_HOME>/jstl-1.0.3/standard中的standard.jar文档形式发布的。在构建Duke’s Bookstore应用程序时,这些库会自动拷贝到<JWSDP_HOME>/docs/tutorial/examples/web/bookstore4/build/WEB-INF/lib中。

表达式语言支持

JSTL的一个重要功能就是对表达式语言(EL)的支持。表达式语言结合JSTL标签,使得访问应用程序数据和以简单方式操纵它变得容易了,不需要使用scriptlet或者请求时表达式。当前,页面编写者必须使用表达式<%= aName %>访问系统的值或者用户定义的JavaBean组件。如:

<x:aTag att="<%= pageContext.getAttribute("aName") %>">  

对于嵌入式bean属性就更复杂了:

<%= aName.getFoo().getBar() %>

这使得页面的编写比它需要的更复杂。

表达式语言使页面编写者可以用简单的语法访问对象,如对于简单的变量

<x:atag att="${aName}">  

或者对于嵌入的属性

<x:aTag att="${aName.foo.bar}">  

JSTL表达式语言将JSP scoped 属性提升为业务逻辑与JSP页面交换信息的标准方式。例如,为这个条件标签的test属性提供一个比较名为cart的会话作用域属性中的项目数是否为0的表达式:

<c:if test="${sessionScope.cart.numberOfItems > 0}">   … </c:if>

JSP规范的下一个版本将会对所有自定义标签库的表达式语言进行标准化。这一版本的JSTL包括这一表达式语言的一个快照。

孪生库

JSTL标签库有两个版本,它们只是在对属性值使用运行时表达式的方式上有所不同。

在JSTL-RT标签库,表达式是在页面的脚本语言中指定的。这正是当前标签库的工作方式。

在JSTL-EL标签库中,表达式是在JSTL表达式语言中指定的。表达式是以EL的语法所写的一个String文字。

在使用EL标签库时,不能为一个属性的值传递脚本语言表达式。这个规则使它可以在转换时验证表达式的语法。

JSTL 表达式语言

JSTL表达式语言负责处理表达式和文字。表达式由${ }字符所包围。例如:

<c:if test="${bean1.a < 3}" />

所有不以${开始的值都认为是文字,用所预期的类型的PropertyEditor将文字解析为预期类型:

<c:if test="true" />

包含字符的文字值必须进行如下转义:

<mytags:example attr1="an expression is ${‘${‘}true}" />

属性

可以通过名称访问属性,也可以加上作用域。属性的属性是用.操作符访问的,并可任意嵌套。

EL统一了对.和[ ]操作符的处理。因此,expr-a.expr-b等于expr-a[expr-b]。要判断expr-a[expr-b],需将expr-a按value-a判断,将expr-b按value-b判断。

·  如果value-a是一个Map,返回value-a.get(value-b)。

·  如果value-a是一个List或者数组,强制value-b为int并相应并返回value-a.get(value-b)或者Array.get(value-a, value-b)。

·  如果value-a是JavaBean对象,强制value-b为String。如果value-b是value-a的可读属性,则返回getter调用的结果。

EL通过将标识作为属性查询、根据PageContext.findAttribute(String)的行为对标识进行判断。例如,${product}会查询名为product的属性,搜索页面、请求、会话和应用程序作用域,并返回其值。如果没有找到属性,则返回null。注意与一个在下面一节中描述的隐式对象相匹配的标识会返回该隐式对象而不是属性值。

隐式对象

JSTL 表达式语言定义了一组隐式对象:

·  pageContext: PageContext对象

·  pageScope :一个Map,它将页面作用域属性名映射为它们的值

·  requestScope:一个Map,它将请求作用域的属性名映射为它们的值

·  sessionScope :一个Map,它将会话作用域的属性名映射为它们的值

·  applicationScope:一个Map,它将应用程序作用域的属性值映射为它们的值

·  param:一个Map,它将参数名映射为单个String参数值(通过调用ServletRequest.getParameter(String))

·  paramValues: 一个Map,它将参数名映射为该参数的所有值的String[ ] (通过调用获得ServletRequest.getParameterValues(String) 获得)

·  header :一个Map,它将头名映射到单个String头值(通过调用ServletRequest.getheader(String)获得)

·  headerValues: 一个Map,它将头名映射到该参数的所有值的String[ ] (通过调用ServletRequest.getHeaders(String) 获得)

·  cookie: 一个Map,它将cookie名映射为单个Cookie (通过调用HttpServletRequest.getCookie(String) 获得)

·  initParam : 一个Map,它将参数名映射到单个String值 (通过调用ServletRequest.getInitParameter(String) 获得)

当表达式通过名字引用其中一个对象时,返回的是相应的对象而不是相应的属性。例如:${pageContext}返回PageContext对象,即使有一个现有的包含一些其他值的pageContext属性。表17-2显示了使用这种隐式对象的一些例子。

17-2 JSTL表达式例子

表达式

结果

${pageContext.request.contextPath}

上下文路径(从HttpServletRequest获得)

${sessionScope.cart.numberOfItems}

名为cart的会话作用域的numberOfItems属性

${param["mycom.productId"]}

mycom.productId参数的String 值

Literals

·  Boolean: true和false

·  Long: 与在Java中的一样

·  Floating point: 与在Java中一样

·  String: 带单引号和双引号。"转义为\",’ 转义为\’,而\ 转义为\\。

·  Null: null

操作符

EL提供了下列操作符:

·  算术:+、-、*、/和div、%和mod、-

·  逻辑: and、&&、or、||、not、!

·  关系: ==、eq、!=、ne、<、lt、>、gt、<=、ge、>=、le。可以是针对其他值,也可能针对布尔、字符串、整数或者浮点文字进行比较。

·  空: empty 操作符是一个前缀操作符,可以用来决定一个值是否为null或者空。

有关这些操作符的优先级和效果参见JSTL 1.0规范

标签协作

标签通常与它们的环境以隐式或者显式的方式协作。隐式协作是通过让上级标签公开定义好的接口、使嵌套的标签无缝地与上级标签协作来实现的。JSTL迭代器标签支持这种模式的协作。

显示协作在标签向环境公开信息时出现。传统上,这是通过公开一个脚本变量实现的(由JSP作用域的属性提供实际对象)。因为JSTL有一个表达式语言,因此不太需要脚本变量。所以JSTL标签(包括EL和RT版本)只是以JSP作用域属性公开信息,不使用脚本变量。下面的JSTL规范对于所有导出有关标签信息的标签属性使用名字var。例如,forEach标签以下面的方式公开购物车中正在迭代的当前项目

<c:forEach var="item" items="${sessionScope.cart.items}">
  …
</c:forEach>

选用名字var以突出公开的作用域变量不是脚本变量(一般情况下对于属性应命名为id)。

对于标签公开多种情息的情况,名字var用于公开的主要信息,对于公开的其他二级信息选用其他合适的名字。例如,forEach标签通过status属性公开迭代状态信息。

核心标签

核心标签包括那些与表达式、流程控制和一种通用的访问那些基于URL的、其内容可以包含在JSP页面或者在JSP页面中被处理的资源的方式相关的标签。

17-3 核心标签 

领域

功能

标签

TLD

前缀

核心

表达式语言支持

catch

out

remove

set

/jstl-c

c

流程控制

choose

  when

  otherwise

forEach

forTokens

if

URL管理

import

  param

redirect

  param

url

  param

表达式标签

out标签判断一个表达式并将判断结果输出为当前JspWriter对象。它对应于JSP语法<%= expression %>。例如,showcart.jsp像下面这样显示在购物车上的项目:

<c:out value="${sessionScope.cart.numberOfItems}"/>

set标签设置在任何JSP作用域(页面、请求、会话、应用程序)中的属性的值。如果该属性不存在,则创建它。

可以用属性值设置JSP作用域属性:

<c:set var="foo" scope="session" value="…"/>  

也可以用标签正文设置JSP作用域属性:

<c:set var="foo">
  …
</c:set>

例如,下面用名为Remove的请求参数设置名为bookID的页面作用域属性:

<c:set var="bookId" value="${param.Remove}"/>

如果使用RT版本的库,那么语句将为:

<c_rt:set var="bookId"
  value="<%= request.getParameter("Remove") %>" />

要删除一个作用域属性,使用remove标签。调用书店JSP页面receipt.jsp时,购货会话就结束了,所以像下面这样删除会话属性cart:

<c:remove var="cart" scope="session"/>

JSTL表达式语言减少了对脚本的需要。不过,页面编写者仍然会遇到在页面的脚本语言中一些非JSTL标签属性必须用表达式指定的情况。用标准JSP元素jsp:useBean声明可以在脚本表达式或者scriptlet中使用的脚本变量。例如,showcart.jsp使用scriptlet从购物车上删除一本书。这本要删除的书的ID作为请求参数传递。请求参数的值首先设置为页面属性(以便在后被标签JSTL sql:query使用),然后声明为脚本变量并传递给cart.remove方法:

<c:set var="bookId" value="${param.Remove}"/>
<jsp:useBean id="bookId" type="java.lang.String" />
<% cart.remove(bookId); %>
<sql:query var="books"
  dataSource="${applicationScope.bookDS}">
  select * from PUBLIC.books where id = ?
  <sql:param value="${bookId}" />
</sql:query>

catch标签为JSP错误页面机制提供了一种补充。它让页面编写者能够从可以控制的错误条件中正常恢复。页面上最重要的行动应当封装在catch中,这样它们的异常就会传播到错误页面。页面中次要的行动应当包装到catch中,这样它们就不会调用错误页面机制。

抛出的异常储存在由var标识的作用域变量中,它总是有页面作用域。如果没有发生异常,那么如果有由var标识的作用域变量的话,就将它删除。如果没有var,那么异常就仅仅被捕捉而不保存。

作者:杜玄

1.引言


在以因特网为基础的企业应用中,穿越防火墙是企业应用系统必须面对的问题。而Http协议是唯一一个可以在因特网中自由来往的协议,防火墙只有对Http访问是透明的,其它协议都会被挡在防火墙之外。Web Service正是基于这个原因,采用Http完成异构企业应用在因特网上的整合。本文提出一个方案,可以使基于Java平台的企业应用不必等待Web Service的渐渐成熟,而利用Java隧道技术实现在因特网上的企业应用部署。

Java隧道技术不是一个新的技术,它是Java对象序列化和Web技术的结合。2001年笔者就曾经做过基于Java隧道的试验。关于Java Servlet的书籍也有对Java隧道技术的相关论述(参考文献[2])。本文首先回顾一下Java隧道技术的基本原理,再通过一个实例来实现它。以此为后面的Java隧道回调技术和基于Java隧道技术的因特网通讯打下基础。

本文的内容可以用于Java Web Start客户端在因特网上的部署,可以用于利用因特网互连的Java应用服务。

2.Java隧道通讯


Web Service技术中的SOAP协议(Simple Object Application Protocol),简单的说是把对象用XML来序列化和反序列化的协议。XML表述的好处就是平台的独立性,不管对象在不同的平台中表现的形式是什么样的,最终都可以用XML来实现对象的互换。并且,基于Http协议的Web Service可以穿越防火墙。 Web Service技术整合因特网上的应用是它的强项。Web Service技术是一个很好的创意,包括IBM,Microsoft,Sun等公司都极力推出自己的相关产品。我们可以说,在未来的异构平台的集成,B2B应用中,Web Service将发挥重要的作用。

然而,对于Java平台的企业应用,你可以用Web Service相同的原理,利用Java平台的对象序列化,再借助Web技术,完成因特网上的应用部署。这样的技术就是Java的隧道技术。因为这个技术都是利用现有的基础技术来实现的,所以可以非常容易的完成系统的构架。当然Java隧道与Web Service的定位是完全不同的,他们应该用在不同的场合。Web Service更加强大,更加通用化,是一套新兴的互相访问的标准。也正是由于Web Service的强大,所以它也更复杂。回答下面的问题,来看看你需要哪种技术来构架你的应用:

  • 1.互连的系统是否都是Java平台?
  • 2.是否要在Internet上部署?
  • 3.是否要穿越防火墙?

如果第一个问题的答案是"否",其它问题回答"是",那么你别无选择,必须用Web Server技术;如果以上的问题的回答都是"是"的话,你可以用Web Service来构架系统,当然,你也可以用更便捷,简单,高效的Java隧道技术。

Java隧道技术的基本构架如下图。Java隧道系统的通讯构架,由Web Server提供Http服务,Servlet容器处理Java序列化对象。对于这样的一个框架,我们可以用常见的Tomcat,Jetty等软件作为Java隧道技术的运行基础环境。

Java隧道技术的基本工作原理是这样的:首先,Java客户端把对象序列化,用Http协议包装序列化对象的字节流在Internet上传播,透过防火墙,提交到Web Server上。Servlet容器把Http协议剥离开,取出序列化的对象,再反序列化这个java对象,传递给后台的应用。

整个工作过程,就是把Java序列化对象,通过Http协议的包装完成在Internet上的通讯。由于Java序列化技术是Java 1.1版本后内置的功能,只要实现java.io.Serializable接口就可以实现。而集成Web Server的Servlet容器更是有好多实现,这里也不乏开源的优秀产品,如Tomcat,Jetty等。这些都体现了Java隧道技术构架的简便性。

下面我们通过一根例子来看看Java隧道技术是如何具体实现的。

3.实例


这里我们选择Jetty(Jetty 4.2.21)作为Web Server和Java Servlet的运行容器。编写普通的Java应用程序TunnelClient作为java隧道的客户端。编写Servlet程序TunnelServer作为java隧道的服务端。如下图,运行java隧道客户端程序,在程序内部调用隧道服务http://127.0.0.1:8080/servlets/servlet/TunnelServer。从服务器返回对象,对象内容打印在控制台上。

如下图,是Jetty服务器中TunnelServer的运行情况,我们可以看到TunnelServer从客户端获得的对象内容的屏幕打印。

从下面隧道服务器程序TunnelServer的代码中我们可以看到,隧道服务首先是一个Servlet程序,TunnelServer继承于java.servlet.HttpServlet。隧道中的对象传递是通过java.io.ObjectInputStream, java.io.ObjectOutStream来实现的。被传递的对象,首先必须是可以序列化的对象,就是说必须实现java.io.Serializable接口。例子代码中的Msg类就是一个实现了java.io.Serializable接口的类。Msg对象通过java.io.ObjectInputStream, java.io.ObjectOutStream实现了对象的远程传递。由于TunnelServer继承于java.servlet.HttpServlet,所以对象的传递是基于Http协议的。对象的远程传递可以透过防火墙正是因为通过Http协议传输的结果。

从下面隧道客户端程序TunnelClient的代码中我们可以看到,其工作原理与Java隧道服务端的完全相同。客户端的java.io.ObjectInputStream,,java.io.ObjectOutStream分别对应服务端的java.io.ObjectOutStream ,java.io.ObjectInputStream。客户端与服务端形成了一个对象传输的管道。

在Java隧道中我们可以增加加密技术来保证传递信息在Internet上的安全传递。在对象进入通道之前,可以用加密密钥对对象流先加密(我们假设用非对称加密算法,当然可以根据加密强度的要求用不同的加密算法和密钥长度),再送入通道中;在接收到加密对象流之后,先用解密密钥对对象流解密,再还原为对象实例。

这里要注意,无论是在服务端还是在客户端,都必须包含Msg类的Class文件,而且Msg在两端必须要一致。因为隧道的一端要实例化Msg对象,另一端要从IO中获得对象流,再用Msg类来造型(Cast)。

4.结束语


Java隧道技术如此简单,无论是代码的编写,还是运行环境的组建,都非常容易。正是因为Java隧道技术的简单性,才使我们可以方便的把这个技术集成到应用系统中。然而,简洁不代表功能的简单,基于这个技术,可以实现基于Http的对象消息传递和远程方法调用,并且穿越防火墙。Java隧道技术为Java平台的应用在因特网上的部署提供了一个简单,高效的方案。

参考文献


1.
  http://www.sourceforge.com


2.
  http://www.codeproject.com


3.
  http://www.c-sharpcorner.com/


4.
  http://martinfowler.com/


5.
  http://channel9.msdn.com/default.aspx


6.
  http://www.ddj.com/


7.
  http://blogs.msdn.com/danielfe/


8.
  http://www.topcoder.com


9.
  http://www.spdevelop.com/default.aspx  (.NET开源项目)


10.
  http://java.sun.com/


11.
  http://asp.net  


12.
  http://www.jjhou.com/


13.
  http://lcspace.nease.net/dvp-mda/mda20.xml


14.
  http://www.dhxy.com/school/uml/List1.htm 


15.
  http://www.msuniversity.edu.cn


16.
  http://www.microsoft.com/china/technet/default.mspx


17.
  http://www.microsoft.com/china/msdn/


18.
  http://www.eastasp.com


19.
  http://www.chinaaspx.com/


20.
  http://www.developerfusion.com/utilities/convertcsharptovb.aspx


21.
  http://www.developerfusion.co.uk/utilities/convertvbtocsharp.aspx


22.
  http://bbs.mscommunity.com/forums/  (微软中文技术社区:)


23.
  http://blog.joycode.com


24.
  http://www.gotdotnet.com/


25.
  http://www.theserverside.net/


26.
  http://www.zhangyongji.name


27.
  http://www.csdn.net/


28.
  http://www.cnblogs.com


29.
  http://blog.joycode.com


30.
  http://dotnet.mblogger.cn/


31.
  www.ibook8.net


32.
  http://www.microsoft.com/china/msdn/default.mspx


33.
  http://www.asp.net/


34. 
  http://www.kbalertz.com/


35.
  http://www.15seconds.com/


36.
  http://www.microsoft.com/china/msdn/default.mspx


37.
  www.51js.com


38.
  http://dotnet.aspx.cc/  (孟子E章)


39.
  http://www.uml.org.cn/mxdx/mxdx.asp  (UML OO系列文章:)


40.
  http://singlepine.cnblogs.com/articles/263473.html   (小山网址集锦)


41.
  ASP.Net BBS www.52forum.com/bbs


42.
  http://communityserver.org/forums/85/showforum.aspx


43.
  http://sourceforge.net/projects/weblucene/


44.
  http://www.dotnetcharting.com/


45.
  http://www.sawin.cn/  (uml和系分的网站!)


46.
  http://www.chinaitpower.com/Dev/Softproject/UML/


47.
  http://www.madeinwuxi.com


48.
  http://www.host01.com/Get/Net/00020001/0561614361031675.htm  (教程在线)


49.
  www.experts-exchange.com


50.
  www.cnnie.net


51.
  http://discuss.develop.com


52.
  www.vckbase.com


53.
  http://bbs.firstdev.net


54.
  www.vckbase.com (VC网站)


55.
  www.codeguru.com


56.
  http://www.vckbase.com/


57.
  http://www.czvc.com


58.
  www.cnblogs.com


59.
  www.donews.com


60.
  www.chinaitpower.com 


61.
  http://www.w3schools.com/  (学习xml及相关技术可以去)


62.
  http://dret.net/glossary/


63.
  http://www.uml.org.cn/rational/list.asp?boardid=7  (Rose)


64.
  http://lcspace.nease.net  (松藕和空间)


65.
  http://support.apress.com  (网上书籍中代码下载)


66.
  http://www.codechina.net   (代码中国网,各类开发资源下载)


67.
  www.graphics.net.cn


68.
  www.21tx.com


69.
  www.codepub.com


70.
  http://www.dynamicdrive.com/


71.
  http://script.aculo.us/ (javascript)


72.
  http://singlepine.cnblogs.com/category/37633.html


73.
  http://www.ajaxgoals.com/index.html  (ajax)


74.
  http://www.regexplib.com/DisplayPatterns.aspx  (正则表达式)


75.
  http://weblogs.asp.net/


76.
  http://www.download.com/  (下载)


77.
  http://community.csdn.net/ChainStory/


78.
  http://search.csdn.net/Expert/topic/2439/2439969.xml?temp=.6801111


79.
  http://www.useit.com


80.
  http://www.webpagesthatsuck.com


81.
  www.iarchitect.com/mahame.htm   (接口错误集锦)


82.
  http://www.aspxcn.org/


83.
  http://www.matrix.org.cn/


84.
  http://www.seeitco.com


85.
  http://forums.microsoft.com/MSDN/


86.
  www.eclipse.org


87.
  http://www.uml.org.cn/oobject/OObject.asp


88.
  www.sf.net

2006年01月03日

有时候想为我们的网页提供多语言支持,如果一种语言用一张网页来做实在太麻烦了,幸好Google提供了语言工具功能,下面介绍如何利用它来实现网页多种语言之间的转换。   lan.htm <form><select name="lan"><option value="en|de">英语 翻译成 德语</option><option value="en|es">英语 翻译成 西班牙语</option><option value="en|fr">英语 翻译成 法语</option><option value="en|it">英语 翻译成 意大利语</option><option value="en|pt">英语 翻译成 葡萄牙语</option><option value="en|ja">英语 翻译成 日语 BETA</option><option value="en|ko">英语 翻译成 朝鲜语 BETA</option><option value="en|zh-CN" >英语 翻译成 中文(简体) BETA</option><option value="de|en">德语 翻译成 英语</option><option value="de|fr">德语 翻译成 法语</option><option value="es|en">西班牙语 翻译成 英语</option><option value="fr|en">法语 翻译成 英语</option><option value="fr|de">法语 翻译成 德语</option><option value="it|en">意大利语 翻译成 英语</option><option value="pt|en">葡萄牙语 翻译成 英语</option><option value="ja|en">日语 翻译成 英语 BETA</option><option value="ko|en">朝鲜语 翻译成 英语 BETA</option><option value="zh-CN|en">中文(简体) 翻译成 英语 BETA</option><input style="FONT-SIZE: 12px" type="button" value="Go->" name="Button1" onClick="javascript:window.open(’translate.asp?urls=’+document.location+’&lan=’+lan.value,’_self’,’’)"></select></form>   lan.htm中的内容用来选择要翻译的语言包括原来的语言和要翻译成的语言。我们只需要将lan.htm中的内容拷到提供多语言翻译的页面中就可以了。   translate.asp <html><head><title>在线翻译</title><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head> <body><% ’on error resume next ’ 如果网速很慢的话,可以调整以下时间。单位秒 Server.ScriptTimeout = 999999 ’======================================================== ’字符编码函数 ’======================================================== Function BytesToBstr(body,code) dim objstream set objstream = Server.CreateObject("adodb.stream") objstream.Type = 1 objstream.Mode =3 objstream.Open objstream.Write body objstream.Position = 0 objstream.Type = 2 objstream.Charset =code BytesToBstr = objstream.ReadText objstream.Close set objstream = nothing End Function ’取行字符串在另一字符串中的出现位置 Function Newstring(wstr,strng)  Newstring=Instr(lcase(wstr),lcase(strng))  if Newstring<=0 then Newstring=Len(wstr) End Function ’替换字符串函数 function ReplaceStr(ori,str1,str2)  ReplaceStr=replace(ori,str1,str2) end function ’===================================================== function ReadXml(url,code,start,ends)  set oSend=createobject("Microsoft.XMLHTTP")  SourceCode = oSend.open ("GET",url,false)  oSend.send()  ReadXml=BytesToBstr(oSend.responseBody,code )  if(start="" or ends="") then  else   start=Newstring(ReadXml,start)   ReadXml=mid(ReadXml,start)   ends=Newstring(ReadXml,ends)   ReadXml=left(ReadXml,ends-1)  end if end function dim urlpage,lan urlpage=request("urls") lan=request("lan") %><form method="post" action="translate.asp"><input type="text" name="urls" size="150" value="<%=urlpage%>"><input type="hidden" name="lan" value="<%=lan%>"><input type="submit" value="submit"></form><%  dim transURL  transURL="http://216.239.39.104/translate_c?hl=zh-CN&ie=UTF-8&oe=UTF-8&langpair="&server.URLEncode(lan)&"&u="&urlpage&"&prev=/language_tools"   if(len(urlpage)>3) then    getcont=ReadXml(transURL,"gb2312","","")    response.Write(getcont)   end if %></body></html>   translate.asp实现翻译功能,这是利用google的语言工具实现的。   注意,因为提供多语言支持,所以translate.asp文件所用的编码是支持所有字符的"utf-8"编码

一.大家试过没有,使用google,如果加用了超级代理,会有多十倍的东东可搜…

来个AD 高校交友联盟 http://7920.cn
二.下面是google的一些秘密入口

http://www.google.com/microsoft
  微软风格的入口
  
  http://www.google.com/mac
  MAC风格的入口
  
  http://www.google.com/linux
  Linux风格的入口
  
  http://www.google.com/bsd
  FreeBSD风格的入口
  
  Google有各种语言的版本,下面这些语言可能实在是稀罕了点儿
  
  http://www.google.com/intl/xx-klingon/
  克林语入口(没看过星际旅行吗?)
  
  http://www.google.com/intl/xx-bork/
  zz入口
  
  http://www.google.com/intl/xx-elmer/
  宗教入口
  
  http://www.google.com/intl/xx-piglatin/
  小猪入口
  
  http://www.google.com/intl/xx-hacker/
  黑客专用入口
  
  Google里还有一个小小的彩蛋游戏,大家自己去看吧!
  http://www.google.com/Easter/feature_easter.html
  
  
  下面是一此更有用的彩蛋
  
  http://froogle.google.com/
  作用:
通过特殊的搜索引擎,你可以在网上找到你想购物的网站位置。以及你可以很方便的搜索出同类产品的价格。
  
  看看世界各地的商品价格,自己再买的时候心底有数多了把
  
  http://labs.google.com/gviewer.html
  作用:一个小玩具,适合那些连鼠标都懒得动的懒人。当你在google上找到了查询结果后,你可以使用google
viewer让结果以自己定义的间隔时间来一条一条自动滚屏。
  
  http://labs.google.com/cgi-bin/webquotes
  作用:可以搜索出你要查询的内容在internet上被多少其他的网站引用过,可以让你知道internet上其他人对你要查询内容的观点,适合写论文和评论以及特殊用途人使用。
  
  http://labs.google.com/glossary
  作用:顾名思义了,就是一个查英语缩写语意的工具,google的词汇表
  
  http://labs1.google.com/gvs.html
  作用:给google打一个电话,说出你要查找的内容,然后google会把你要查询的结果显示出来。
给不识字的人用的