一般问题
问. 什么是 JavaMail API?
答:JavaMail API 是一组抽象的 API,它可用于构建邮件系统。API 提供了一个平台无关以及协议无关的框架,用于构建基于 Java 技术的电子邮件客户端应用程序。JavaMail API 提供了一些工具,用于阅读和发送电子邮件。服务提供程序实现了特定的一些协议。在 JavaMail API 软件包中,包括了几个服务提供程序;另外还有其他的一些服务提供程序可供使用。JavaMail API 是作为 Java 可选包实现的,它可用在 JDK 1.1.6 中,以后还可用在任何操作系统上。JavaMail API 也是 Java 2 Platform, Enterprise Edition (J2EE) 中的必需部分。
问. 我如何获得 JavaMail API 的实现?
答: Sun 提供了无版权的参考实现(二进制形式),开发人员可以使用和发行。参考实现包括核心的 JavaMail 包 和 IMAP、POP3 和 SMTP 服务提供程序。
问. 我如何发送反馈或评论?
答:发送电子邮件到 javamail@Sun.COM。也可以考虑加入我们的低容量通告邮件列表 (low-volume announce mailing list) javamail-announce@java.sun.com。在我们的 Web 页 http://java.sun.com/products/javamail 中有关于如何加入的说明。
问. javax.activation 在哪里?
答: javax.activation 是 JavaBeans Activation Framework (JAF) 的一部分。JAF 是 JavaBeans "Glasgow" 规范的一部分(参见 http://java.sun.com/beans/glasgow/jaf.html)。JavaMail API 将 JAF 用于数据内容处理。
问. JavaMail API 需要什么 JDK?
答:JavaMail API 要求 JDK/JRE 1.1.x 或更高版本。我们已经在 Sun Microsystems JDK 1.1.6 至 JDK 1.1.7 上测试过这个实现,而这些 JDK 运行于 Solaris SPARC、Microsoft Windows 95/NT 和 RedHat Linux 4.2。我们也已经在 Java 2 SDK, Standard Edition (J2SE SDK) 版本 1.2、1.3 和 1.4 上做过测试,而这些 JDK 运行于 Solaris SPARC。JavaMail API 是 Java 的可选包,它们不是核心 J2SE 的一部分,但在 J2EE 中却包括了它。参见 http://java.sun.com/j2se,获取关于 JDK 1.1 和 J2SE 的进一步信息。
问. 从哪里能找到我喜欢的操作系统的 JavaMail 版本?
答:JavaMail 是完全用 Java 编写的,并将运行在特定的操作系统上,该操作系统支持所需版本的 JDK。对于不同的操作系统,不需要任何特殊的版本。
问. JavaMail API 可以在浏览器中工作吗?
答:在 Netscape 和 Internet Explorer 中,我们已经成功运行过 JavaMail 1.3。
问. 我可以使用 JavaMail 来从 Web 邮件账户(比如 Yahoo 或 Hotmail)中阅读邮件吗?
答:不 一定。许多基于 Web 的电子邮件服务只提供在浏览器中用 HTTP 进行访问。这些服务不能使用 JavaMail 来访问。如果服务也提供 POP3 或 IMAP 访问,可能不可以使用 JavaMail 来访问它。 联系你的 Web 电子邮件服务提供商,获取更详细的信息。也可参阅 JHTTPMail 项目,获得 JavaMail 的开放源代码提供程序,它提供了对 Hotmail 的访问。另一个有用的工具是 MrPostman——提供到 Hotmail 和 Yahoo! Mail 的 POP3 访问的代理服务器,这个工具可从如下站点获得:http://mrbook.org/mrpostman/。
问. 什么是 IMAP?
答:IMAP 是 Internet 消息访问协议 (Internet Message Access Protocol)。它是访问电子邮件消息的一种方法,而这些消息存储在(可能是共享的)邮件服务器上。换句话说,它允许“客户端”电子邮件程序访问远程 的消息存储,并且在访问时,就像它们就在本地一样。IMAP 由 RFC2060 定义。有关进一步信息,请参阅 http://www.imap.org/。
问. 什么是 SMTP?
答:SMTP 就是简单邮件传输协议 (Simple Mail Transfer Protocol)。它可用于不同邮件主机间 RFC822 样式的消息的传输,以及用于将新消息提交给一台主机,然后由它来传送该消息。SMTP 在广泛使用中(起源于 1982 年),并由 RFC821 定义。
问. 什么是 MIME?
答:MIME 和 RFC822 是一些标准,它们用于描述电子邮件消息,而这些消息是跨 Internet 进行发送的。javax.mail.internet 子包(它是 JavaMail API 的一部分)提供了这两个包的完整实现。MIME 是由下述 RFC 指定的:RFC2045、RFC2046 和 RFC2047。
问. 什么是 POP3?
答:POP3 是邮局协议 (Post Office Protocol V3)。POP3 是一个非常有限的协议,用于访问单个邮箱。它在功能上比 IMAP 差了许多。POP3 应用非常广泛,并由 RFC1939 定义。
问. 对 MAPI、NNTP、Lotus Notes 和其他服务提供程序的支持怎样?
答:目前我们还没有计划实现这些提供程序中的任何一个。不过,JavaMail API 的体系结构提供一些功能,可以容易地插入第三方服务提供程序。我们的希望是第三方将通过编写针对其他标准和专有协议的提供程序来包括 JavaMail API。 参阅 第三方产品 页面,获取这些提供程序的最新列表。
问. 我如何在本地磁盘上存储邮件消息?
答:“本地存储提供程序”可用于将邮件消息存储在本地磁盘中。JavaMail API 不包括这一提供程序,但有几个提供程序可从第三方获得,它们针对不同的本地存储格式,比如 MH 和 Mbox。参阅我们的 第三方产品 页面,获取这些提供程序的最新列表。
问. 我可以从哪里找到有关 Sun 协议提供程序的文档?
答:针对 IMAP、POP3 和 SMTP 的 Sun 协议提供程序支持许多特性,它们不是 JavaMail API 规范的一部分。在 JavaMail 1.3 下载包中包括了这些协议提供程序的文档,这些文档是以 javadoc 格式包括的,并位于 docs/javadocs 目录。针对每个协议提供程序包的包级文档描述了一些属性,这些属性由协议提供程序支持。此外,协议提供程序包括了一些类和方法,应用程序可以使用它们来利用特定于提供程序的特性。注意,这些属性、类和方法的使用使得程序不可移植;它可能只与 Sun 的 JavaMail API 实现一起工作。
问. JavaMail API 实现是否完全免费?我可以把它与我的产品一起发行吗?
答:可以。当前版本的 JavaMail API 实现是完全免费的,你可以在产品中包括它。这一版本也包括 IMAP、POP3 和 SMTP 提供程序。请一定阅读 许可,并保证理解它。JavaBeans Activation Framework 也是在类似的许可下免费使用的。
问. 我可以获得 JavaMail API 实现的源代码吗?
答:在 SCSL 许可下,现在 JavaMail API 参考实现源代码可作为 J2EE 的一部分获得。可从 这里 下载它。
问. JavaMail 是否符合 Y2K?
答:是的。 Sun 的 JavaMail 实现是符合 Option-3 Y2K 的。请参看 JavaMail 包中的 NOTES.txt 文件,获取进一步信息。
问. 我的问题不在这里,我应该在哪里查找?
答:除 JavaMail API 规范和 javadocs 外(可从 我们的 Web 主页 获得并且是包括在下载包中的),不要忘记查阅 JavaMail API 包中包括的 README.txt 和 NOTES.txt 文件,获取其他的重要信息。
javamail-interest mailing list 是另一个信息来源。参阅 我们的 Web 主页,获取有关如何订阅的说明以及指向文档的链接。
Java Developer Connection 维护 JavaMail 论坛,请参阅 http://forum.java.sun.com。注意,JavaMail 团队管理 javamail-interest 邮件列表,但不管理 JavaMail 论坛。
jGuru 也维护 JavaMail FAQ,可在如下地址找到它:http://www.jguru.com/faq/JavaMail。
安装和配置
问. 我如何安装 JavaMail API 实现?
答:解压缩分发的 zip 文件,并编辑 CLASSPATH 环境变量以包含 mail.jar 文件,该文件包括在 JavaMail API 分发中。你也需要 JavaBeans Activation Framework(参见下面)的实现。参阅 README 文件(在分发中),获得其他细节和例子。
问. JavaMail 是否包括所有必要的邮件服务器?
答:不是,JavaMail API 包不包括任何邮件服务器。为了使用 JavaMail API 包,你将需要访问 IMAP 或 POP3 邮件服务器(用于阅读邮件)和/或 SMTP 邮件服务器(用于发送邮件)。这些邮件服务器通常由 Internet 服务提供商提供,或者作为组织网络基础结构的一部分。如果你不必访问这样的邮件服务器,请往下看。
问. 从哪里可以获得必要的邮件服务器?
答:Sun Java System Messaging Server 可用于 Solaris 和 Windows 平台。华盛顿大学的 IMAP 服务器支持多种平台 (UNIX、32 位 Windows 等)。可从如下地址获取源代码:ftp://ftp.cac.washington.edu/imap/imap.tar.Z。其他的许多供应商提供了支持 Internet 标准的邮件服务器。可以从 IMAP Connection 和 Internet Mail Consortium 获得更多信息。
问. 我应该使用什么主机名、用户名或密码?
答:我们不提供邮件服务器让你使用。你必须使用自己的邮件服务器,或者使用 Internet 服务提供商或你所工作的公司提供的邮件服务器。网络管理员可能给你一些必要的信息用于配置 JavaMail,以便同邮件服务器一起工作。
问. 我如何配置 JavaMail 通过代理服务器工作?
答:大多数代理服务器只支持 HTTP 协议。JavaMail 没有使用 HTTP 协议来阅读或发送邮件。使用代理服务器的一个主要原因是为了允许企业网络中的 HTTP 请求通过企业防火墙。防火墙通常会阻止对 Internet 的大多数访问,但允许来自代理服务器的请求通过。此外,企业网络内部的邮件服务器将为邮件执行类似的功能,通过 SMTP 接收消息,然后将它们转发到 Internet 上的最终目的地,以及接收传入的消息,然后将它们发送到合适的内部邮件服务器。
如果你的代理服务器支持 SOCKS V4 或 V5 协议 (http://www.socks.nec.com/aboutsocks.html, RFC1928),并允许匿名连接,可以告诉 Java 运行时把所有的 TCP socket 直接连接到 SOCKS 服务器。参阅 http://java.sun.com/j2se/1.4/docs/guide/net/properties.html,获取 socksProxyHost 和 socksProxyPort 属性的最新文档。这些是系统级属性,而不是 JavaMail 会话属性。当调用应用程序时,它们可以从命令行中设置,例如:java -DsocksProxyHost=myproxy ...。这个工具可用于指出从 JavaMail 到 SOCKS 代理服务器进行 SMTP、IMAP 和 POP3 通信。注意,设置这些属性将告诉 所有 TCP socket 连接到 SOCKS 代理,在应用程序的其他方面上,这可能会带来负面影响。
假如没有这样的 SOCKS 服务器,如果想使用 JavaMail 来直接访问防火墙外部的邮件服务器,那将需要配置防火墙来允许这一访问。一个简单的 HTTP 代理 Web 服务器是足够的。
问. 如何在 Windows NT 中设置 CLASSPATH?
答:详细说明可从 这里 获得。
问. 当试图在 Linux 中运行程序时,得到了非常奇怪的错误消息,而且程序运行失败了。错误在哪里?
答:通常,错误消息看起来像下面这样:
Exception in thread "main"java.lang.VerifyError:(Class:com/sun/mail/pop3/POP3Store,method: finalize Signature :()V)Illegal use of nonvirtual function call问题是由于在 Linux 上,使用的 unzip 命令是有 bug 的版本,这样解压缩 JavaMail 下载包时,unzip 命令破坏了 mail.jar 文件。获取更新版本的 unzip 命令或使用 JDK 的 jar 命令来解压缩下载包。
问. 在运行于 SecurityManager 下面的应用程序中,我如何使用 JavaMail;我必须授予应用程序和 JavaMail 什么权限?
答:在 具有 SecurityManager 的 JDK 1.2(或更新版本)中,当使用 JavaMail 时,JavaMail 读取 mail.jar 文件中的配置文件有时会失败。在从 activation.jar 文件中读取配置文件时,JavaBeans Activation Framework 可能也有相同的问题。这些默认配置文件是作为“资源”文件存储的,并且存储在 jar 文件的 META-INF 目录中。
有许多调试技术可用于决定这是否是个问题。设置 Session 属性“mail.debug”为 true(或调用 session.setDebug(true)), 将导致 JavaMail 在试图加载各个配置文件时打印调试消息。形如“DEBUG: can't load default providers file”(DEBUG: 不能加载默认提供程序文件) 的消息指出这个问题可能存在。同样,设置 System 属性“javax.activation.debug”为“true”(例如,通过使用 "java -Djavax.activation.debug=true ..." 来运行程序),将导致 JAF 在试图加载各个资源文件时打印调试消息。最后,通过设置 system 属性“java.security.debug” 为“access:failure”(例如,通过使用“java -Djava.security.debug=access:failure ...”来运行程序),JDK 可以产生有用的调试输出。
除了读取配置文件的必要权限外,应用程序(和 JavaMail)也将需要一定的权限才可以连接到它使用的邮件服务器。如果应用程序使用 System 属性来配置 JavaMail(例如,像许多 JavaMail 演示程序所做的那样,通过传递从 System.getProperties() 中返回的 Properties 对象到 Session 构造函数),它也将需要一定的权限才可以使用 System Properties 对象。另外,应用程序可以使用自己的 Properties 对象,以及确信设置 "mail.from" 属性 或 "mail.user" 和 "mail.host" 属性(参见 InternetAddress.getLocalAddress() 方法)。
在 JDK 1.2 SecurityManager 中,为了使应用程序能够使用 JavaMail,应用程序、JavaMail 和 JAF 将需要某些权限,比如下面的一些权限(一定要使用适当的值替换主机名和路径名);把这些权限添加到应用程序使用的安全策略文件中。
grant { // following two permissions allow // access to default config files permission java.io.FilePermission "/path/to/mail.jar", "read"; permission java.io.FilePermission "/path/to/activation.jar", "read"; // following to use SMTP permission java.net.SocketPermission "SMTPHOST:25", "connect,resolve"; // following to use IMAP permission java.net.SocketPermission "IMAPHOST:143", "connect,resolve"; // following to use POP3 permission java.net.SocketPermission "POP3HOST:110", "connect,resolve"; // following needed if System.getProperties() is used permission java.util.PropertyPermission "*", "read,write";};
问. 如何配置 Web 服务器来运行 JavaMail 演示 servlet?
答:针对以下 Web 服务器的指导说明可从这里获得:
Tomcat
Apache with JServ
iPlanet Web Server
Java Web Server
问. 当在 servlet 中使用 JavaMail 时,未找到任何的 JavaMail 类。我已经在服务器的 CLASSPATH 中添加了 mail.jar?
答:当改变 CLASSPATH 时,通常有必要完全重启 Web 服务器。
问. 我的 servlet 可以找到 JavaMail 类,但 JavaMail 抱怨它不能找到针对“smtp”或“imap”的服务提供程序或地址类型“rfc822”。
答:通常这是因为 JavaMail 无法访问 mail.jar 中的配置文件,而这可能是由于安全权限问题造成的;参见 本条目,获取更多的细节。也保证你没有提取 mail.jar 内容;在服务器的 CLASSPATH 中,应该包括未更改的 mail.jar 文件。
问. 在哪里可以找到 jws.jar?我已经安装了 Java Web Server 2.0,并试图运行 JavaMailServlet。README 文件指示我在 CLASSPATH 中添加 jws.jar。
答:jws.jar 不再与 Java Web Server 一起发行(在以前版本中,它们是一起发行的),因此不需要在 CLASSPATH 中添加它。只要在 CLASSPATH 中添加 mail.jar 和 activation.jar,然后重启 Java Web Server。
编程
问. 从哪里可以学习有关 Internet 邮件的基础知识,它是我高效编写 JavaMail 程序需要知道的?
答:参见我们的 Web 页中提及的 参考图书 中的一本,获得有关 Internet 电子邮件、MIME、SMTP、IMAP 和 POP3 等的背景信息。
问. 如何调试使用 JavaMail API 的应用程序?
答:可通过在代码中调用 Session 对象上的 setDebug(true) 方法来打开调试模式。这将造成在控制台中打印调试消息,包括协议跟踪信息。如果您认为在 JavaMail 中发现了 bug,就将这个跟踪及以下信息发送给我们:重生成问题的测试用例、使用的平台、使用的 JDK 的版本和使用的邮件服务器 (IMAP, SMTP) 的名称和版本。
问. 如何发送带有附件的消息?
答:带有附件的消息采用 MIME 多部分消息来表示,其中第一部分是消息的主体,其他的部分是附件。有大量的例子,它们展示了如何在演示程序中构建这种消息,JavaMail 下载包包含了这些演示程序。
问. 如何阅读带有附件的消息并保存附件?
答:如前面描述,在 MIME 中,带有附件的消息是作为多部分消息表示的。在简单的例子中,Message 对象的 getContent 方法的结果将是一个 MimeMultipart 对象。多部分对象的第一个主体部分将是消息的主文本。其他的主体部分将是附件。msgshow.java 演示程序展示了如何在消息中遍历所有的多部分对象,并提取各个主体部分数据。getDisposition 方法将给你一个提示,指出主体部分是否应该内嵌显示,或者应该将其考虑成附件(但请注意,不是所有的发件人都提供这一信息)。
为了将主体部分中的数据保存到文件(比如),请使用 getInputStream 方法来访问附件内容,并将数据复制到 FileOutputStream。
注意,也有一些更复杂的情形也要处理。例如,一些发件人把主体作为纯文本和 html 发送。通常,这将作为 multipart/alternative 内容(和 MimeMultipart 对象)出现在简单的文本主体部分的位置中。经过数字签名或加密的消息甚至会更复杂。处理所有的这些情形可能具有挑战性。请参考各种 MIME 规范和我们主页上列出的其他 参考资料。
问. 什么是“中断支持”(disconnected support)?
答:支持中断操作的邮件客户端将允 许用户访问远程消息存储(比如 IMAP)中消息,缓存这些消息中的一些消息的(部分)到本地,然后断开到服务器的连接。当处在断开连接状态中,邮件客户端可以访问已经缓存的消息,可能 也可以删除它们或将它们保存到其他文件夹。当邮件客户端下一次连接到远程消息存储时,本地所做变更会与远程存储同步。同样,中断支持可以允许客户端在断开 与服务器连接时“发送”消息,在到服务器的连接可用前,消息会进行排队。也请参阅 RFC1733。
问. 如何使用 JavaMail API 来支持中断操作?
答:JavaMail API 规范定义了一些接口,邮件客户端可以使用这些接口来支持中断操作。我们的 IMAP 提供程序实现了这些接口(UIDFolder 接口)。
问. 我如何使用 JavaMail API 来发送安全的电子邮件?
答:JavaMail API 目前不支持发送或接收安全电子邮件。JavaMail API 的体系结构使得可以在以后很容易地添加这一支持,我们可以添加支持,第三方也可以添加支持。有关当前电子邮件安全标准(S/MIME 和 PGP)的信息,可以从如下站点找到: http://www.imc.org/smime-pgpmime.html。 请浏览我们的 第三方产品 页,获取来自其他供应商的解决方案。
问. writeTo() 方法生成了消息文件,但消息中的一些行既不是数据的规范 MIME 表示(即使用 CRLF 来结束行),又没有使用我的平台的规范行分隔符(例如 UNIX 上的“\n”)。如果我需要这些表示时,如何获得它们当中的任何一种表示?
答:不管是哪种情形,你都将需要创建合适的 FilterOutputStream 对象来传递给 writeTo()。FilterOutputStream 将需要接受特定的一些行,它们具有任何常见终止符,然后写出另外的一些特定行,它们只具有期望的行终止符。下面是这种过滤器的一些例子。NewlineOutputStream 转换到本地平台的行终止符,当将消息写到文件时,它是有用的。CRLFOutputStream 转换到 MIME 规范 CRLF 终止符,当需要规范 MIME 格式时(比如计算数字签名),它是有用的。
问. 我可以使用 JavaMail API 来实现邮件服务器吗?
答:JavaMail API 设计目的不是帮你实现邮件服务器。但是,对你来说,一些实用工具类,比如 MIME 消息解析类,可能是有用的。通常您会发现,JavaMail API 是在“简单”而不是在“强有力”方面出错。对于邮件客户端,那是合适的,但对于邮件服务器,结果可能是不同的。
问. 我可以使用 JavaMail API 在我的邮件服务器上添加新的用户账户、删除用户账户或改变用户账户的密码吗?
答:JavaMail API 不包括任何工具,用于添加、删除或修改用户账户。在这一方面是没有标准的,每个邮件服务器对它的处理是不同的。
问. 为什么 MimeMessage 类没有实现 Serializable,这样我就可以将消息序列化到磁盘,并在以后读回它?
答:JavaMail API 是在现有电子邮件系统上面设计的,并使用了现有的消息格式。对于这样的一些实现,使用 Java 序列化能力既不是必要的,也不是有用的,因此,不能将它作为 JavaMail API 的目标来考虑。
有关序列化 Message 的困难部分是保留某些指针,它们指向 Folder(文件夹)、Store(存储)和 Session(会话)。如果只想保存消息的内容,而不是对象本身,消息的 writeTo 方法将为你完成这一切。如果想根据序列化消息创建整个电子邮件系统,您应该能够编写 Message 等的子类,并在子类中实现 Serializable。
如果想序列化自己的引用了 MimeMessages的其他对象,那么你的对象的 writeObject 方法可以使用 MimeMessage 的 writeTo 方法,对象的 readObject 方法可以使用 MimeMessage 构造函数,该构造函数会得到 InputStream。在构造 MimeMessage 时,你的类将需要提供一个 Session。
问. 我如何编写服务提供程序?
答:请阅读服务提供程序文档,获取一些细节信息。通常,如果想编写 Store 提供程序,那就编写 javax.mail.Store 和 javax.mail.Folder的子类,也可能要编写 javax.mail.Message 及其他一些类的子类。对于 Transport 提供程序,编写 javax.mail.Transport 的子类,也可能需要编写 javax.mail.Message 及其他的一些类的子类。然后在 javamail.providers 注册表中,添加描述提供程序的条目。如果对编写特别的服务提供程序感兴趣,并且它所针对的协议或邮件系统目前没有得到 JavaMail API 实现的支持,请通过 javamail@Sun.COM 联系我们。
问. 我在登录 Microsoft Exchange 服务器时碰到了麻烦,我确信正在使用的用户名和密码是正确的,我做错了什么?
答:当登录 Exchange 时,需要使用比简单登录名更多的用户名。例如,如果你的电子邮件地址是“J.User@server.com”,Windows NT 登录名是“juser”,NT 域名是“dom”,而且 Exahange 邮箱名是“Joe User”,那么在使用 JavaMail 登录时,你将需要使用用户名 “dom\juser\J.User”。
问. 在发送二进制文件前,我如何编码它?当收到它时,我又如何解码它?
答:你不必这样的做!JavaMail 将自动决定合适的编码用于消息部分,然后才发送消息。而且当读取它们时,将自动解码消息部分。getInputStream 方法将返回解码数据。
问. 如果我不需要自己编码和解码附件,我应该何时使用 MimeUtility 方法?
答:在 JavaMail 没有自动处理的情况下,MimeUtility 方法是有用的。经常发生的这样的一种情形是文件名的编码。基本的 MIME spec(规范)不允许按某种方式编码标题参数值(比如文件名参数),该方式与(比如)编码 Subject(主题)标题相同。这限制了参数值,从而限制了文件名到 ASCII。但一些发件人却实际使用 MIME 文本编码来做非 ASCII 文件名的编码工作。想与这种非标准发件人互操作的应用程序可以使用 encodeText 方法来编码文件名,然后调用 MimeBodyPartsetFileName 方法,而且可以使用 decodeText 方法来解码返回的文件名。
问. 尽管 JavaMail 完成了所有的编码和解码工作,但我仍需要手动控制一些主体部分的编码。
答:在少数的场合需要控制编码,这里有几个方法来重写 JavaMail 的默认行为。下面是一个简单的方法。在创建整个消息后,调用 msg.saveChanges(),然后使用像 mbp.setHeader("Content-Transfer-Encoding", "base64") 的语句来强制对指定主体部分做 base64 编码。
另一种办法是编写 MimeBodyPart 的子类,并重写 updateHeaders 方法,让它首先调用 super.updateHeaders(),然后像上面那样设置 Content-Transfer-Encoding 标题。
问. 为什么 JavaMail 没有在非 ASCII 字符集中正确编码和解码文件名?
答:文件名是作为参数存储在 MIME 标题中的。形如 =?ISO-8859-15?B?5OTkLUluZm8ucGRm?= 的编码文件名不是 MIME spec(规范)的一部分。形如 =?A?B?C?= 的文件名是一个完全有效的文件名,而不是一个不正确编码的文件名。JavaMail 没有编码和解码文件名,因为这样做会违反 MIME 规范。
基本的 MIME 规范不允许编码参数。RFC 2231 定义了一种新的方式,使得可以在 MIME 标题中包括编码参数,包括文件名。它与下面的事实方式不兼容:许多应用程序非法编码文件名。支持 RFC 2231 将不允许 JavaMail 与这些现有的程序互操作。据我所知,很少现有的程序支持 RFC 2231。
如果你选择违反 MIME 规范是为了与其他也违反了 MIME的程序互操作,那么 JavaMail 会给您所有需要的工具来完成这件事。
编码文件名的解决方法是简单的:
mbp.setFileName(MimeUtility.encodeText(filename));
解码文件名的解决方法同样简单:
String filename = MimeUtility.decodeText(part.getFileName());
阅读邮件——IMAP
问. 我尝试在 IMAP 服务器上运行演示程序,但我得到了错误。
答:首先检查在 IMAP 服务器上确实有一个电子邮件账户。与系统管理员协商有关它的事情。通过在代码中调用 session 对象上的 setDebug(true) 方法来打开调试模式。这将造成在屏幕上转储 IMAP 协议跟踪。把这个跟踪发送给我们。对于说明问题,这个跟踪是非常有用的。如果可以的话,请将有关你的 IMAP 服务器的供应商信息发送给我们。
问. 当我提取带有较大附件的消息时,IMAP 提供程序似乎丢失了数据。
答:这是由于在 IMAP 服务器的部分提取实现中有 bug。为了解决这个服务器 bug,请将“mail.imap.partialfetch”属性设置为 false。参考 JavaMail 包的 NOTES.txt,获取进一步信息。
问. IMAP 提供程序是否缓存检索的数据?
答:只有在必要的时候,IMAP 提供程序才从服务器上提取消息的数据(javax.mail.FetchProfile 可用于优化这一点)。一旦提取了标题和 bodystructure(主体结构)信息,它就总是缓存在 Message 对象中。不过,没有缓存bodypart(主体部分)的内容。因此每次客户端请求内容时(使用 getContent() 或使用 getInputStream()),就会向服务器发出一个新的 FETCH 请求。这是由于消息的内容可能比较大,因此如果缓存了大量消息的这些内容,就可能导致系统很快用完内存,因为垃圾收集器不能释放被引用的对象。客户端应该注意到这一点,而且如果需要的话,自己维持检索的内容。
问. 我想在文件夹间移动消息。我应该使用 appendMessages() 或 copyMessages() 吗?
答:在下面的这种情况下,使用 copyMessages()——要复制或移动的消息属于某个文件夹,而该方法就是在这个文件夹中被调用的。这是因为一些实现可以优化这个操作,前提是源和目标文件夹属于相同的后端 Store(存储)。否则使用 appendMessages()。
问. 有时检索大消息主体似乎是低效的。
答:如果你正在使用 Sun IMAP 提供程序,就可以试着增加 mail.imap.fetchsize 属性(当前默认值是 16K)。这将导致从服务器提取的数据是一些较大的块区。注意,当你这样做时,要冒这样的危险——JVM 可能用完内存。
问. 当加载这种大二进制附件时,我得到了 OutOfMemory 错误。
答:在启动时增大最大 JVM 堆大小。如果使用来自 Sun 的标准 JVM,那就使用“-mx”选项。不要保持不使用消息“内容”的引用。在某些情形下,可以尽量将消息内容作为原始字节进行流转化(使用 getInputStream()),使用它们,然后丢弃已使用的数据块区。
问. 当在主体部分 (bodypart) 上调用 getContent() 时,我为什么会得到 UnsupportedDataTypeException?
答:getContent() 方法返回一个 Java 对象,它代表 bodypart(主体部分)内容。为了让它工作,必须注册对应内容的 MIME 类型的 JAF DataContentHandler。否则将引发 UnsupportedDataTypeException。在这种情形下,可以使用 getInputStream() 方法来将内容作为字节流检索。参阅 JAF 文档,获取有关如何创建和注册自己的 DataContentHandlers 的细节。
问. 当在包含文本数据的主体部分 (bodypart) 上调用 getContent() 时,为什么会得到 UnsupportedEncodingException?
答:当使用 getContent() 时, 文本主体部分(例如主类型是“text”的主体部分)返回 Unicode 字符串对象。通常,这些主体部分内部用某些非 Unicode 字符集保持文本数据。JavaMail(通过 "text/plain" DataContentHandler)试着将那些数据转换成 Unicode 字符串。基础 JDK 的字符集可用于做这种工作。如果 JDK 不支持特别的字符集,那么就引发 UnsupportedEncodingException。在这种情况下,可以使用 getInputStream() 方法来将内容作为字节流检索。下面是一个例子: String s;if (part.isMimeType("text/plain")) { try { s = part.getContent(); } catch (UnsupportedEncodingException uex) { InputStream is = part.getInputStream(); /* * Read the input stream into a byte array. * Choose a charset in some heuristic manner, use * that charset in the java.lang.String constructor * to convert the byte array into a String. */ s = convert_to_string(is); } catch (Exception ex) { // Handle other exceptions appropriately }}
发送邮件——SMTP
问. 我如何回复消息?
答:为了回复消息,请使用 Message 对象上的 reply 方法。这个方法将返回一个新的对象,对象中的标题已经针对回复做了恰当设置。你将需要自己提供消息的内容。
问. 我如何转发消息?
答:用于转发消息的方法取决于你要怎样表示要转发的消息。简单的办法是创建一个新的 MimeMessage,并适当地为它提供地址,然后将现有的消息作为附件放在新消息中。为了将原始消息放在新消息中,比如可以使用下面的代码:
MimeBodyPart mbp = new MimeBodyPart(); mbp.setContent(forwardedMsg, "message/rfc822"); mp.addPart(mbp);但是如果你想创建新的消息,并在新消息中包括原始消息的文本,可能也要用 "> " 来缩进,那将需要提取原始消息主体中的数据,并进行相应的处理。你可能也想取得原始消息的其他附件,并将它们添加到新消息中。
问. 我如何发送 HTML 邮件?
答:在分发中包括了大量演示程序,它们展示了如何发送 HTML 邮件。如果想发送简单消息,它具有 HTML 而不是纯文本,那请参见 demo(演示)目录中的 sendhtml.java 程序。如果想将 HTML 文件作为附件发送,请参见 sendfile.java 示例,它展示了如何将任何文件作为附件发送。
问. 我如何发送具有不同字体和颜色的格式化文本的邮件?
答:最简单的办法是使用 HTML 文本发送消息。参见 上面。
问. 我如何发送具有纯文本和 HTML 文本的邮件,让每个邮件的阅读者可以选择适合它的格式?
答:你想要发送 MIME multipart/alternative 消息。你构造了这样的一条消息,构造方式基本上与构造 multipart/mixed 消息相同,它使用了 MimeMultipart 对象,而该对象又是使用 new MimeMultipart("alternative") 来构造的。然后在 multipart(多部分)中,把 text/plain 主体部分作为第一部分插入,并且把 text/html 作为第二部分插入。参阅 RFC2046,获取这一消息的结构的细节。
问. 我如何发送包含图像的 HTML 邮件?
答:最简单的办法是发送带有图像标签的 HTML 文本,标签引用了公共 Web 站点。在这种方法中,在消息中并没有真正包括图像,因此当用户阅读消息时,如果没有连接到 Internet,那将不能看到图像。
另外,你也可以构造 MIME multipart/related 消息。参阅 RFC2387,获取这种消息结构的细节。
问. Transport 方法 send 和 sendMessage 之间有什么区别?
答:send() 方法是一个静态方法,可以直接使用,而不需要 Transport 对象的实例。它用于常见、简单的场合,比如使用默认传输发送单条消息。从内部讲,send() 方法首先调用消息上的 saveChanges() 方法。然后创建合适的新 Transport 对象,调用 Transport 的 connect() 方法,调用 Transport 的 sendMessage() 方法来实际发送消息,接着调用 Transport 的 close() 方法,最后丢弃 Transport 对象的新实例,并由垃圾收集器收集(实际上,还有比那更加复杂的,但那是一般的想法)。
如你可以看到,静态 send() 便利 (convenience) 方法是建立在更加通用的每实例 sendMessage() 方法的基础上的。有许多原因可以让应用程序直接使用 sendMessage() 方法。最常见的原因是为了通过在单个连接期间发送多条消息 来提高性能,或者为了手动管理连接以提供验证信息。当使用 sendMessage() 方法时,产生的最常见错误是,忘记在要发送的消息上调用 saveChanges() 方法。
问. 我需要验证到 SMTP 服务器,因此我调用了 trans.connect(host, user, password),然后调用 trans.send(msg) 发送消息,但它却不能工作。
答:你应该调用 msg.saveChanges(),然后调用 trans.sendMessage(msg, addrs) 来发送消息。如 上面 所描述,send 方法是一个静态便利方法,它会获得自己的 Transport 对象,并创建自己的连接用于发送消息;它没有使用与某些 Transport 对象有关的连接,并且它是通过该 Transport 对象得到调用的。当然不要忘记将 mail.smtp.auth 属性设置为 true 来启用 SMTP 验证!
问. 我修改了一条消息,但标题却没有反映修改。
答:在创建新消息或修改现有消息后,应该调用 saveChanges()。这将导致重新设置标题以反映变更。注意,Transport.send(Message) 方法隐式调用了这个方法。因此如果你正在做的是发送已修改的消息,就可以跳过调用 saveChanges()。saveChanges() 可能是一个昂贵的操作(特别是对于较大或深度嵌套的消息),因此只在需要时才调用它。
问. 我正在使用 sendMessage() 方法发送消息,但在消息中的文本前后却出现奇怪的一些行,并且我的附件也在消息体中出现。
答:通常这些行像下面这样:
--928176543.952742998030.JavaMail.name@host像 上面 那样,在创建新消息后,在使用 Transport.sendMessage() 方法发送消息之前,必须调用 saveChanges() 方法。静态 Transport.send() 方法将自动调用 Message.saveChanges() 方法。
问. 我为新消息的 Message-ID 标题设置了特定值。但当我发送这条消息时,却重写了那个标题。
答:saveChanges() 将为 Message-ID 字段设置新值,重写所设置的任何值。如果需要设置自己的 Message-ID 并保留它,就必须创建自己的 MimeMessage 子类,重写 updateHeaders() 方法,并使用这个子类的一个实例。 class MyMessage extends MimeMessage { ... protected void updateHeaders() throws MessagingException { super.updateHeaders(); setHeader("Message-ID", "my-message-id"); } ...}
问. 当发送创建的新消息时,为什么会得到 UnsupportedDataTypeException?
答:你可能使用 setContent(Object o, String type) 方法设置了消息的一些内容。为了让它能工作,必须为指定“类型”注册 JAF DataContentHandler。如果不这样做,将获得 UnsupportedDataTypeException。参阅 JAF 文档,获取进一步信息。
问. 当发送消息时,如何能够显式地设置 SMTP FROM: 属性?
答:mail.smtp.from 属性可用于设置 SMTP FROM: 属性。如果没有设置这个属性,就使用消息的 From 属性。如果多个线程需要同时发送邮件,并且每个线程需要设置 From 属性,那么每个线程就必须使用自己的 Session 对象,它具有自己的 Properties 对象。然后可以在每个 Session 对象的 各个 Properties 对象上独立设置 mail.smtp.from 属性(同样对每个线程做这样的设置)。
问. 我想重复发送消息,并且每次发送给一组不同的收件人。但调用 Transport.send(Message) 却导致每次都创建一个新的 Transport 会话。在本例中,这是一个次优办法,我如何来解决它?
答:创建合适的 Transport 对象的实例,然后连上它并重复调用 sendMessage() 方法,例如: MimeMessage msg = ...;// construct messagemsg.saveChanges();Transport t = session.getTransport("smtp");t.connect();for (int i = 0; .....) { t.sendMessage(msg, new Address[] { recipients[i] });}t.close();
问. 当试图发送消息时,我得到了 “MessagingException: 501 HELO requires domain address”(MessagingException: 501 HELO 要求域地址)。
答:在 SMTP HELO 命令中,SMTP 提供程序使用 InetAddress.getLocalHost().getHostName() 的结果。如果那个调用不能返回任何数据,就不会在 HELO 命令中发送任何名称。检查你的 JDK 和名称服务器配置,确保那个调用返回正确数据。从 JavaMail 1.1.3 开始,你也可以设置 mail.smtp.localhost 属性,并可以把设置为想用于 HELO 命令的名称。
问. 如果将消息发送到错误的地址,为什么我会获得 SendFailedException 或 TransportEvent,指出地址是错误的?
答:在 Internet 上没有端到端验证。通常要将消息转发到几个邮件服务器,然后才到达特定的邮件服务器,该服务器决定了它是否可以传送消息。如果在这些后面的步骤中的某个步 骤发生了错误,那么通常会将消息作为不可传送返回给发件人。一个成功的“发送”只表明邮件服务器已经接受了消息,并将试着传送它。
问. 当消息不能被传送时,就会返回一个失败消息。我如何检测这些“回弹”消息?
答:虽然有一个 Internet 标准用于报告这样的错误(multipart/report MIME 类型,参阅 RFC1892),但还没有广泛实现它。RFC1211 深入讨论了这个问题,包括了大量的例子。
在 Internet 电子邮件中,特定的邮箱或用户名是否存在,只能由传送消息的最终服务器决定。消息可能通过几个中继服务器(它们不能检测错误),然后再到达最终服务器。通 常,当最终服务器检测到这一错误,它会返回一个消息给原始消息的发送人,指出失败的原因。有许多 Internet 标准讨论了这种传送状态通知 (Delivery Status Notifications),但大量服务器不支持这些新标准,相反使用特别技术来返回这种错误消息。这使得将“回弹”消息与产生问题的原始消息相互关联 起来非常困难(注意,这个问题与 JavaMail 完全无关)。
有许多技术和试探法用于处理这一问题,但它们都不是完美的。一种技术是 Variable Envelope Return Paths,http://cr.yp.to/proto/verp.txt 描述了这一技术。
问. 当创建 InternetAddress 对象时,如果地址是非法的,为什么不会获得异常?
答:InternetAddress 类只检查地址的语法。如 上面 所讨论,InternetAddress 类不能决定地址是否做为合法地址实际存在。如果应用程序运行在防火墙背后或目前没有连接到 Internet,那么甚至不能验证主机名。
问. 当试图发送消息时,我为什么会获得 javax.mail.SendFailedException: 550 Unable to relay for 我的地址?
答:这 不是 JavaMail 的问题。这是来自你的 SMTP 邮件服务器的错误回复。它指出没有配置你的邮件服务器,使得你可以通过它发送邮件。通常会配置组织的邮件服务器,使得组织中的邮件可以发送到组织中的其他 地址,或者发送到组织外部的地址。通常它也使得来自组织外部地址的邮件可以发送(或中继)到 也是组织外部的另一地址。邮件服务器的配置决定了是否允许这种中继,以及哪些地址应该当成内部地址与外部地址。
问. 当试图发送消息到(例如)Yahoo时,为什么我会获得一个错误,指出“连接被拒绝”?
答:你试图连接 的计算机很可能没有运行邮件服务器。如果你试图连接到 Web 邮件服务,比如 Yahoo,通常你就不能使用 Web 主机名(例如 "yahoo.com"),因为这台主机没有运行所要求的邮件服务器。相反,你需要知道运行所要求邮件服务器的主机的名称;联系你的 Web 邮件提供商获取这一信息。你可以从如下地址找到 Yahoo 的这一信息: http://help.yahoo.com/help/us/mail/pop/pop-02.html。 注意,有些 Web 邮件提供商没有提供这一服务,相反,只允许通过浏览器访问邮件。
如果你没有试图连接到 Web 邮件账户,而是试图连接到本地网络中的一台主机,那很有可能是试图连接的主机没有运行邮件服务器。 有时如果忘记设置(比如)"mail.smtp.host" 属性,这种情况就发生了,它将导致你试图连接到“localhost”。大多数 Windows 计算机没有运行邮件服务器,不过,许多 UNIX(Solaris 和 Linux 等)计算机是运行它的。因此在 Windows 计算机上,试图连接到“localhost”通常会失败,并获得“连接被拒绝”。
问. 当发送邮件时,为什么会失败,并得到一个异常,异常中包括像“553 To send mail, first check your mail with a valid POP account”(553 为发送邮件,首先检查邮件具有合法的 POP 账户)这样的消息?
答:为 了防止它们用于发送垃圾邮件,一些邮件服务器会要求使用有效的 POP3 账户,并要求要先登录那个账户,然后才可以让你通过邮件服务器发送邮件。在 JavaMail 中,这种处理是简单的。当你知道正在处理这样的邮件服务器时,确保连接到那台邮件服务器上的 POP3 Store,然后再发送邮件。
POP3
问. 我可以从哪里找到 POP3 的支持?
答:在 JavaMail 1.2 及以上版本中包括了 POP3 提供程序。POP3 提供程序也可从多个第三方提供商获得。请浏览我们的 第三方产品清单,获取进一步信息。
问. 我想在 POP3 服务器上删除消息。我在这些消息上设置了 DELETED 标志。然后调用 expunge() 方法,但我得到了MethodNotSupportedException。当使用 Sun 的 POP3 提供程序时,我如何删除消息?
答:POP3 提供程序不支持 expunge() 方法。不过,可以关闭 expunge 标志设为 true 的文件夹。也就是调用 folder.close(true)。
问. 在从 Sun POP3 提供程序获得的消息中,我如何可以检索 POP3 UIDL?
答:对于 POP3 提供程序,这是可能的。参见 com.sun.mail.pop3 包文档,获取进一步信息。
问. 对于 POP3,我如何知道哪里消息是新的?
答:POP3 协议不提供任何永久标志的支持,因此 RECENT 标志是没有用的。com.sun.mail.pop3 包文档讨论了几个策略,它们可用于处理这一问题。
问. 当使用 POP3 时,为什么 hasNewMessages() 总是返回 false?
答:POP3 协议没有提供办法来决定文件夹是否有新消息。
问. 我创建了 MessageCountListener(如 monitor 程序中所展示的),但在我的 POP3 INBOX(收件箱)中,我从未得到通知。
答:当 INBOX 打开时,POP3 协议不允许客户端查看传送到 INBOX 的新消息。应用程序必须关闭 INBOX,然后重新打开它, 才可以查看新消息。对于 POP3,使用 MessageCountListener 接口,你从不会得到新邮件的通知。参见 com.sun.mail.pop3 包文档(在 JavaMail 下载包的 docs/sundocs 目录),获取进一步信息。
问. 当使用 POP3 时,为什么 getReceivedDate() 返回 null?
答:POP3 协议不提供有关何时接收消息的信息。通过查看一些消息标题,比如 Received 标题,可能可以猜到接收日期,但这非常不可靠。
问. 当使用 POP3 时,我得到了有关 SocketFetcher 类的抱怨。
答:很有可能在 CLASSPATH 中有多个版本的 pop3.jar 或 mail.jar。检查 CLASSPATH 的设置,如果你正在使用 JDK 1.2 或更新版本,检查 JDK 中的 "jre/lib/ext" 目录。确保你只有 JavaMail 1.2 mail.jar 可用并且没有 pop3.jar。只有对于较老版本的 JavaMail,pop3.jar 才是必要的。
问. 当使用 POP3 时,我得到了有关 contentStream 字段的抱怨。
答:错误通常像下面这样:
java.lang.NoSuchFieldError: contentStream atcom.sun.mail.pop3.POP3Message.getContentStream(POP3Message.java:115)像 上面 一样,使用混合版本的 POP3 提供程序和 mail.jar。在包括 POP3 提供程序的较新版本 mail.jar 之前,你可能在 CLASSPATH 中有了较老版本。
问. 除了 POP3 服务器上的 INBOX(收件箱)外,您如何访问或创建文件夹?
答:不能。POP3 服务器只支持每个用户一个邮箱。大多数使用 POP3 的邮件阅读者也维护着本地消息存储,他们可以将传入的消息(来自 POP3 INBOX)复制到存储中,并允许你将消息写到其他文件夹。参见 本项目,获取有关本地存储提供程序的进一步消息。
servlet 中的 JavaMail
问. 我可以在 servlet 中使用 JavaMail 吗?
答:可以。参阅上面的“安装和配置”一节获取进一步信息。JavaMail API 也是 Java 2 Platform, Enterprise Edition (J2EE) 所要求的一部分。当在 J2EE 产品中使用 JavaMail,不需要任何的安装和配置,就这么简单!
applet 中的 JavaMail
问. 我可以在 applet 中使用 JavaMail 吗?
答:可以。JavaMail 1.3 可以在 applet 中工作,这些 applet 可运行于 Netscape 和 Internet Explorer。
问. 在 applet 中,使用 JavaMail 的安全问题是什么?
答:在 applet 中使用 JavaMail,它的一个最大问题是默认的 applet 安全限制。这些限制只允许 applet 连接到特定的主机,而该主机是从中下载它们的主机。因此,为了让这样的 applet 使用 JavaMail,邮件服务器将需要位于特定的计算机,该计算机与从中下载 applet 的 Web 服务器相同。
问. OK,可能我确实不想使用 applet,那我应该怎么做?
答:通常,我们推荐使用 servlet (或 JSP)来收集邮件消息,并使用 JavaMail 来发送它。JavaMail 下载包中包括了演示 servlet,它例举了这种方法。Java Developer Connection 上的 电子邮件 Web 应用程序演示程序 例举了使用 JavaServer Pages 的另一种方法。
问. 什么是 JavaMail API?
答:JavaMail API 是一组抽象的 API,它可用于构建邮件系统。API 提供了一个平台无关以及协议无关的框架,用于构建基于 Java 技术的电子邮件客户端应用程序。JavaMail API 提供了一些工具,用于阅读和发送电子邮件。服务提供程序实现了特定的一些协议。在 JavaMail API 软件包中,包括了几个服务提供程序;另外还有其他的一些服务提供程序可供使用。JavaMail API 是作为 Java 可选包实现的,它可用在 JDK 1.1.6 中,以后还可用在任何操作系统上。JavaMail API 也是 Java 2 Platform, Enterprise Edition (J2EE) 中的必需部分。
问. 我如何获得 JavaMail API 的实现?
答: Sun 提供了无版权的参考实现(二进制形式),开发人员可以使用和发行。参考实现包括核心的 JavaMail 包 和 IMAP、POP3 和 SMTP 服务提供程序。
问. 我如何发送反馈或评论?
答:发送电子邮件到 javamail@Sun.COM。也可以考虑加入我们的低容量通告邮件列表 (low-volume announce mailing list) javamail-announce@java.sun.com。在我们的 Web 页 http://java.sun.com/products/javamail 中有关于如何加入的说明。
问. javax.activation 在哪里?
答: javax.activation 是 JavaBeans Activation Framework (JAF) 的一部分。JAF 是 JavaBeans "Glasgow" 规范的一部分(参见 http://java.sun.com/beans/glasgow/jaf.html)。JavaMail API 将 JAF 用于数据内容处理。
问. JavaMail API 需要什么 JDK?
答:JavaMail API 要求 JDK/JRE 1.1.x 或更高版本。我们已经在 Sun Microsystems JDK 1.1.6 至 JDK 1.1.7 上测试过这个实现,而这些 JDK 运行于 Solaris SPARC、Microsoft Windows 95/NT 和 RedHat Linux 4.2。我们也已经在 Java 2 SDK, Standard Edition (J2SE SDK) 版本 1.2、1.3 和 1.4 上做过测试,而这些 JDK 运行于 Solaris SPARC。JavaMail API 是 Java 的可选包,它们不是核心 J2SE 的一部分,但在 J2EE 中却包括了它。参见 http://java.sun.com/j2se,获取关于 JDK 1.1 和 J2SE 的进一步信息。
问. 从哪里能找到我喜欢的操作系统的 JavaMail 版本?
答:JavaMail 是完全用 Java 编写的,并将运行在特定的操作系统上,该操作系统支持所需版本的 JDK。对于不同的操作系统,不需要任何特殊的版本。
问. JavaMail API 可以在浏览器中工作吗?
答:在 Netscape 和 Internet Explorer 中,我们已经成功运行过 JavaMail 1.3。
问. 我可以使用 JavaMail 来从 Web 邮件账户(比如 Yahoo 或 Hotmail)中阅读邮件吗?
答:不 一定。许多基于 Web 的电子邮件服务只提供在浏览器中用 HTTP 进行访问。这些服务不能使用 JavaMail 来访问。如果服务也提供 POP3 或 IMAP 访问,可能不可以使用 JavaMail 来访问它。 联系你的 Web 电子邮件服务提供商,获取更详细的信息。也可参阅 JHTTPMail 项目,获得 JavaMail 的开放源代码提供程序,它提供了对 Hotmail 的访问。另一个有用的工具是 MrPostman——提供到 Hotmail 和 Yahoo! Mail 的 POP3 访问的代理服务器,这个工具可从如下站点获得:http://mrbook.org/mrpostman/。
问. 什么是 IMAP?
答:IMAP 是 Internet 消息访问协议 (Internet Message Access Protocol)。它是访问电子邮件消息的一种方法,而这些消息存储在(可能是共享的)邮件服务器上。换句话说,它允许“客户端”电子邮件程序访问远程 的消息存储,并且在访问时,就像它们就在本地一样。IMAP 由 RFC2060 定义。有关进一步信息,请参阅 http://www.imap.org/。
问. 什么是 SMTP?
答:SMTP 就是简单邮件传输协议 (Simple Mail Transfer Protocol)。它可用于不同邮件主机间 RFC822 样式的消息的传输,以及用于将新消息提交给一台主机,然后由它来传送该消息。SMTP 在广泛使用中(起源于 1982 年),并由 RFC821 定义。
问. 什么是 MIME?
答:MIME 和 RFC822 是一些标准,它们用于描述电子邮件消息,而这些消息是跨 Internet 进行发送的。javax.mail.internet 子包(它是 JavaMail API 的一部分)提供了这两个包的完整实现。MIME 是由下述 RFC 指定的:RFC2045、RFC2046 和 RFC2047。
问. 什么是 POP3?
答:POP3 是邮局协议 (Post Office Protocol V3)。POP3 是一个非常有限的协议,用于访问单个邮箱。它在功能上比 IMAP 差了许多。POP3 应用非常广泛,并由 RFC1939 定义。
问. 对 MAPI、NNTP、Lotus Notes 和其他服务提供程序的支持怎样?
答:目前我们还没有计划实现这些提供程序中的任何一个。不过,JavaMail API 的体系结构提供一些功能,可以容易地插入第三方服务提供程序。我们的希望是第三方将通过编写针对其他标准和专有协议的提供程序来包括 JavaMail API。 参阅 第三方产品 页面,获取这些提供程序的最新列表。
问. 我如何在本地磁盘上存储邮件消息?
答:“本地存储提供程序”可用于将邮件消息存储在本地磁盘中。JavaMail API 不包括这一提供程序,但有几个提供程序可从第三方获得,它们针对不同的本地存储格式,比如 MH 和 Mbox。参阅我们的 第三方产品 页面,获取这些提供程序的最新列表。
问. 我可以从哪里找到有关 Sun 协议提供程序的文档?
答:针对 IMAP、POP3 和 SMTP 的 Sun 协议提供程序支持许多特性,它们不是 JavaMail API 规范的一部分。在 JavaMail 1.3 下载包中包括了这些协议提供程序的文档,这些文档是以 javadoc 格式包括的,并位于 docs/javadocs 目录。针对每个协议提供程序包的包级文档描述了一些属性,这些属性由协议提供程序支持。此外,协议提供程序包括了一些类和方法,应用程序可以使用它们来利用特定于提供程序的特性。注意,这些属性、类和方法的使用使得程序不可移植;它可能只与 Sun 的 JavaMail API 实现一起工作。
问. JavaMail API 实现是否完全免费?我可以把它与我的产品一起发行吗?
答:可以。当前版本的 JavaMail API 实现是完全免费的,你可以在产品中包括它。这一版本也包括 IMAP、POP3 和 SMTP 提供程序。请一定阅读 许可,并保证理解它。JavaBeans Activation Framework 也是在类似的许可下免费使用的。
问. 我可以获得 JavaMail API 实现的源代码吗?
答:在 SCSL 许可下,现在 JavaMail API 参考实现源代码可作为 J2EE 的一部分获得。可从 这里 下载它。
问. JavaMail 是否符合 Y2K?
答:是的。 Sun 的 JavaMail 实现是符合 Option-3 Y2K 的。请参看 JavaMail 包中的 NOTES.txt 文件,获取进一步信息。
问. 我的问题不在这里,我应该在哪里查找?
答:除 JavaMail API 规范和 javadocs 外(可从 我们的 Web 主页 获得并且是包括在下载包中的),不要忘记查阅 JavaMail API 包中包括的 README.txt 和 NOTES.txt 文件,获取其他的重要信息。
javamail-interest mailing list 是另一个信息来源。参阅 我们的 Web 主页,获取有关如何订阅的说明以及指向文档的链接。
Java Developer Connection 维护 JavaMail 论坛,请参阅 http://forum.java.sun.com。注意,JavaMail 团队管理 javamail-interest 邮件列表,但不管理 JavaMail 论坛。
jGuru 也维护 JavaMail FAQ,可在如下地址找到它:http://www.jguru.com/faq/JavaMail。
安装和配置
问. 我如何安装 JavaMail API 实现?
答:解压缩分发的 zip 文件,并编辑 CLASSPATH 环境变量以包含 mail.jar 文件,该文件包括在 JavaMail API 分发中。你也需要 JavaBeans Activation Framework(参见下面)的实现。参阅 README 文件(在分发中),获得其他细节和例子。
问. JavaMail 是否包括所有必要的邮件服务器?
答:不是,JavaMail API 包不包括任何邮件服务器。为了使用 JavaMail API 包,你将需要访问 IMAP 或 POP3 邮件服务器(用于阅读邮件)和/或 SMTP 邮件服务器(用于发送邮件)。这些邮件服务器通常由 Internet 服务提供商提供,或者作为组织网络基础结构的一部分。如果你不必访问这样的邮件服务器,请往下看。
问. 从哪里可以获得必要的邮件服务器?
答:Sun Java System Messaging Server 可用于 Solaris 和 Windows 平台。华盛顿大学的 IMAP 服务器支持多种平台 (UNIX、32 位 Windows 等)。可从如下地址获取源代码:ftp://ftp.cac.washington.edu/imap/imap.tar.Z。其他的许多供应商提供了支持 Internet 标准的邮件服务器。可以从 IMAP Connection 和 Internet Mail Consortium 获得更多信息。
问. 我应该使用什么主机名、用户名或密码?
答:我们不提供邮件服务器让你使用。你必须使用自己的邮件服务器,或者使用 Internet 服务提供商或你所工作的公司提供的邮件服务器。网络管理员可能给你一些必要的信息用于配置 JavaMail,以便同邮件服务器一起工作。
问. 我如何配置 JavaMail 通过代理服务器工作?
答:大多数代理服务器只支持 HTTP 协议。JavaMail 没有使用 HTTP 协议来阅读或发送邮件。使用代理服务器的一个主要原因是为了允许企业网络中的 HTTP 请求通过企业防火墙。防火墙通常会阻止对 Internet 的大多数访问,但允许来自代理服务器的请求通过。此外,企业网络内部的邮件服务器将为邮件执行类似的功能,通过 SMTP 接收消息,然后将它们转发到 Internet 上的最终目的地,以及接收传入的消息,然后将它们发送到合适的内部邮件服务器。
如果你的代理服务器支持 SOCKS V4 或 V5 协议 (http://www.socks.nec.com/aboutsocks.html, RFC1928),并允许匿名连接,可以告诉 Java 运行时把所有的 TCP socket 直接连接到 SOCKS 服务器。参阅 http://java.sun.com/j2se/1.4/docs/guide/net/properties.html,获取 socksProxyHost 和 socksProxyPort 属性的最新文档。这些是系统级属性,而不是 JavaMail 会话属性。当调用应用程序时,它们可以从命令行中设置,例如:java -DsocksProxyHost=myproxy ...。这个工具可用于指出从 JavaMail 到 SOCKS 代理服务器进行 SMTP、IMAP 和 POP3 通信。注意,设置这些属性将告诉 所有 TCP socket 连接到 SOCKS 代理,在应用程序的其他方面上,这可能会带来负面影响。
假如没有这样的 SOCKS 服务器,如果想使用 JavaMail 来直接访问防火墙外部的邮件服务器,那将需要配置防火墙来允许这一访问。一个简单的 HTTP 代理 Web 服务器是足够的。
问. 如何在 Windows NT 中设置 CLASSPATH?
答:详细说明可从 这里 获得。
问. 当试图在 Linux 中运行程序时,得到了非常奇怪的错误消息,而且程序运行失败了。错误在哪里?
答:通常,错误消息看起来像下面这样:
Exception in thread "main"java.lang.VerifyError:(Class:com/sun/mail/pop3/POP3Store,method: finalize Signature :()V)Illegal use of nonvirtual function call问题是由于在 Linux 上,使用的 unzip 命令是有 bug 的版本,这样解压缩 JavaMail 下载包时,unzip 命令破坏了 mail.jar 文件。获取更新版本的 unzip 命令或使用 JDK 的 jar 命令来解压缩下载包。
问. 在运行于 SecurityManager 下面的应用程序中,我如何使用 JavaMail;我必须授予应用程序和 JavaMail 什么权限?
答:在 具有 SecurityManager 的 JDK 1.2(或更新版本)中,当使用 JavaMail 时,JavaMail 读取 mail.jar 文件中的配置文件有时会失败。在从 activation.jar 文件中读取配置文件时,JavaBeans Activation Framework 可能也有相同的问题。这些默认配置文件是作为“资源”文件存储的,并且存储在 jar 文件的 META-INF 目录中。
有许多调试技术可用于决定这是否是个问题。设置 Session 属性“mail.debug”为 true(或调用 session.setDebug(true)), 将导致 JavaMail 在试图加载各个配置文件时打印调试消息。形如“DEBUG: can't load default providers file”(DEBUG: 不能加载默认提供程序文件) 的消息指出这个问题可能存在。同样,设置 System 属性“javax.activation.debug”为“true”(例如,通过使用 "java -Djavax.activation.debug=true ..." 来运行程序),将导致 JAF 在试图加载各个资源文件时打印调试消息。最后,通过设置 system 属性“java.security.debug” 为“access:failure”(例如,通过使用“java -Djava.security.debug=access:failure ...”来运行程序),JDK 可以产生有用的调试输出。
除了读取配置文件的必要权限外,应用程序(和 JavaMail)也将需要一定的权限才可以连接到它使用的邮件服务器。如果应用程序使用 System 属性来配置 JavaMail(例如,像许多 JavaMail 演示程序所做的那样,通过传递从 System.getProperties() 中返回的 Properties 对象到 Session 构造函数),它也将需要一定的权限才可以使用 System Properties 对象。另外,应用程序可以使用自己的 Properties 对象,以及确信设置 "mail.from" 属性 或 "mail.user" 和 "mail.host" 属性(参见 InternetAddress.getLocalAddress() 方法)。
在 JDK 1.2 SecurityManager 中,为了使应用程序能够使用 JavaMail,应用程序、JavaMail 和 JAF 将需要某些权限,比如下面的一些权限(一定要使用适当的值替换主机名和路径名);把这些权限添加到应用程序使用的安全策略文件中。
grant { // following two permissions allow // access to default config files permission java.io.FilePermission "/path/to/mail.jar", "read"; permission java.io.FilePermission "/path/to/activation.jar", "read"; // following to use SMTP permission java.net.SocketPermission "SMTPHOST:25", "connect,resolve"; // following to use IMAP permission java.net.SocketPermission "IMAPHOST:143", "connect,resolve"; // following to use POP3 permission java.net.SocketPermission "POP3HOST:110", "connect,resolve"; // following needed if System.getProperties() is used permission java.util.PropertyPermission "*", "read,write";};
问. 如何配置 Web 服务器来运行 JavaMail 演示 servlet?
答:针对以下 Web 服务器的指导说明可从这里获得:
Tomcat
Apache with JServ
iPlanet Web Server
Java Web Server
问. 当在 servlet 中使用 JavaMail 时,未找到任何的 JavaMail 类。我已经在服务器的 CLASSPATH 中添加了 mail.jar?
答:当改变 CLASSPATH 时,通常有必要完全重启 Web 服务器。
问. 我的 servlet 可以找到 JavaMail 类,但 JavaMail 抱怨它不能找到针对“smtp”或“imap”的服务提供程序或地址类型“rfc822”。
答:通常这是因为 JavaMail 无法访问 mail.jar 中的配置文件,而这可能是由于安全权限问题造成的;参见 本条目,获取更多的细节。也保证你没有提取 mail.jar 内容;在服务器的 CLASSPATH 中,应该包括未更改的 mail.jar 文件。
问. 在哪里可以找到 jws.jar?我已经安装了 Java Web Server 2.0,并试图运行 JavaMailServlet。README 文件指示我在 CLASSPATH 中添加 jws.jar。
答:jws.jar 不再与 Java Web Server 一起发行(在以前版本中,它们是一起发行的),因此不需要在 CLASSPATH 中添加它。只要在 CLASSPATH 中添加 mail.jar 和 activation.jar,然后重启 Java Web Server。
编程
问. 从哪里可以学习有关 Internet 邮件的基础知识,它是我高效编写 JavaMail 程序需要知道的?
答:参见我们的 Web 页中提及的 参考图书 中的一本,获得有关 Internet 电子邮件、MIME、SMTP、IMAP 和 POP3 等的背景信息。
问. 如何调试使用 JavaMail API 的应用程序?
答:可通过在代码中调用 Session 对象上的 setDebug(true) 方法来打开调试模式。这将造成在控制台中打印调试消息,包括协议跟踪信息。如果您认为在 JavaMail 中发现了 bug,就将这个跟踪及以下信息发送给我们:重生成问题的测试用例、使用的平台、使用的 JDK 的版本和使用的邮件服务器 (IMAP, SMTP) 的名称和版本。
问. 如何发送带有附件的消息?
答:带有附件的消息采用 MIME 多部分消息来表示,其中第一部分是消息的主体,其他的部分是附件。有大量的例子,它们展示了如何在演示程序中构建这种消息,JavaMail 下载包包含了这些演示程序。
问. 如何阅读带有附件的消息并保存附件?
答:如前面描述,在 MIME 中,带有附件的消息是作为多部分消息表示的。在简单的例子中,Message 对象的 getContent 方法的结果将是一个 MimeMultipart 对象。多部分对象的第一个主体部分将是消息的主文本。其他的主体部分将是附件。msgshow.java 演示程序展示了如何在消息中遍历所有的多部分对象,并提取各个主体部分数据。getDisposition 方法将给你一个提示,指出主体部分是否应该内嵌显示,或者应该将其考虑成附件(但请注意,不是所有的发件人都提供这一信息)。
为了将主体部分中的数据保存到文件(比如),请使用 getInputStream 方法来访问附件内容,并将数据复制到 FileOutputStream。
注意,也有一些更复杂的情形也要处理。例如,一些发件人把主体作为纯文本和 html 发送。通常,这将作为 multipart/alternative 内容(和 MimeMultipart 对象)出现在简单的文本主体部分的位置中。经过数字签名或加密的消息甚至会更复杂。处理所有的这些情形可能具有挑战性。请参考各种 MIME 规范和我们主页上列出的其他 参考资料。
问. 什么是“中断支持”(disconnected support)?
答:支持中断操作的邮件客户端将允 许用户访问远程消息存储(比如 IMAP)中消息,缓存这些消息中的一些消息的(部分)到本地,然后断开到服务器的连接。当处在断开连接状态中,邮件客户端可以访问已经缓存的消息,可能 也可以删除它们或将它们保存到其他文件夹。当邮件客户端下一次连接到远程消息存储时,本地所做变更会与远程存储同步。同样,中断支持可以允许客户端在断开 与服务器连接时“发送”消息,在到服务器的连接可用前,消息会进行排队。也请参阅 RFC1733。
问. 如何使用 JavaMail API 来支持中断操作?
答:JavaMail API 规范定义了一些接口,邮件客户端可以使用这些接口来支持中断操作。我们的 IMAP 提供程序实现了这些接口(UIDFolder 接口)。
问. 我如何使用 JavaMail API 来发送安全的电子邮件?
答:JavaMail API 目前不支持发送或接收安全电子邮件。JavaMail API 的体系结构使得可以在以后很容易地添加这一支持,我们可以添加支持,第三方也可以添加支持。有关当前电子邮件安全标准(S/MIME 和 PGP)的信息,可以从如下站点找到: http://www.imc.org/smime-pgpmime.html。 请浏览我们的 第三方产品 页,获取来自其他供应商的解决方案。
问. writeTo() 方法生成了消息文件,但消息中的一些行既不是数据的规范 MIME 表示(即使用 CRLF 来结束行),又没有使用我的平台的规范行分隔符(例如 UNIX 上的“\n”)。如果我需要这些表示时,如何获得它们当中的任何一种表示?
答:不管是哪种情形,你都将需要创建合适的 FilterOutputStream 对象来传递给 writeTo()。FilterOutputStream 将需要接受特定的一些行,它们具有任何常见终止符,然后写出另外的一些特定行,它们只具有期望的行终止符。下面是这种过滤器的一些例子。NewlineOutputStream 转换到本地平台的行终止符,当将消息写到文件时,它是有用的。CRLFOutputStream 转换到 MIME 规范 CRLF 终止符,当需要规范 MIME 格式时(比如计算数字签名),它是有用的。
问. 我可以使用 JavaMail API 来实现邮件服务器吗?
答:JavaMail API 设计目的不是帮你实现邮件服务器。但是,对你来说,一些实用工具类,比如 MIME 消息解析类,可能是有用的。通常您会发现,JavaMail API 是在“简单”而不是在“强有力”方面出错。对于邮件客户端,那是合适的,但对于邮件服务器,结果可能是不同的。
问. 我可以使用 JavaMail API 在我的邮件服务器上添加新的用户账户、删除用户账户或改变用户账户的密码吗?
答:JavaMail API 不包括任何工具,用于添加、删除或修改用户账户。在这一方面是没有标准的,每个邮件服务器对它的处理是不同的。
问. 为什么 MimeMessage 类没有实现 Serializable,这样我就可以将消息序列化到磁盘,并在以后读回它?
答:JavaMail API 是在现有电子邮件系统上面设计的,并使用了现有的消息格式。对于这样的一些实现,使用 Java 序列化能力既不是必要的,也不是有用的,因此,不能将它作为 JavaMail API 的目标来考虑。
有关序列化 Message 的困难部分是保留某些指针,它们指向 Folder(文件夹)、Store(存储)和 Session(会话)。如果只想保存消息的内容,而不是对象本身,消息的 writeTo 方法将为你完成这一切。如果想根据序列化消息创建整个电子邮件系统,您应该能够编写 Message 等的子类,并在子类中实现 Serializable。
如果想序列化自己的引用了 MimeMessages的其他对象,那么你的对象的 writeObject 方法可以使用 MimeMessage 的 writeTo 方法,对象的 readObject 方法可以使用 MimeMessage 构造函数,该构造函数会得到 InputStream。在构造 MimeMessage 时,你的类将需要提供一个 Session。
问. 我如何编写服务提供程序?
答:请阅读服务提供程序文档,获取一些细节信息。通常,如果想编写 Store 提供程序,那就编写 javax.mail.Store 和 javax.mail.Folder的子类,也可能要编写 javax.mail.Message 及其他一些类的子类。对于 Transport 提供程序,编写 javax.mail.Transport 的子类,也可能需要编写 javax.mail.Message 及其他的一些类的子类。然后在 javamail.providers 注册表中,添加描述提供程序的条目。如果对编写特别的服务提供程序感兴趣,并且它所针对的协议或邮件系统目前没有得到 JavaMail API 实现的支持,请通过 javamail@Sun.COM 联系我们。
问. 我在登录 Microsoft Exchange 服务器时碰到了麻烦,我确信正在使用的用户名和密码是正确的,我做错了什么?
答:当登录 Exchange 时,需要使用比简单登录名更多的用户名。例如,如果你的电子邮件地址是“J.User@server.com”,Windows NT 登录名是“juser”,NT 域名是“dom”,而且 Exahange 邮箱名是“Joe User”,那么在使用 JavaMail 登录时,你将需要使用用户名 “dom\juser\J.User”。
问. 在发送二进制文件前,我如何编码它?当收到它时,我又如何解码它?
答:你不必这样的做!JavaMail 将自动决定合适的编码用于消息部分,然后才发送消息。而且当读取它们时,将自动解码消息部分。getInputStream 方法将返回解码数据。
问. 如果我不需要自己编码和解码附件,我应该何时使用 MimeUtility 方法?
答:在 JavaMail 没有自动处理的情况下,MimeUtility 方法是有用的。经常发生的这样的一种情形是文件名的编码。基本的 MIME spec(规范)不允许按某种方式编码标题参数值(比如文件名参数),该方式与(比如)编码 Subject(主题)标题相同。这限制了参数值,从而限制了文件名到 ASCII。但一些发件人却实际使用 MIME 文本编码来做非 ASCII 文件名的编码工作。想与这种非标准发件人互操作的应用程序可以使用 encodeText 方法来编码文件名,然后调用 MimeBodyPartsetFileName 方法,而且可以使用 decodeText 方法来解码返回的文件名。
问. 尽管 JavaMail 完成了所有的编码和解码工作,但我仍需要手动控制一些主体部分的编码。
答:在少数的场合需要控制编码,这里有几个方法来重写 JavaMail 的默认行为。下面是一个简单的方法。在创建整个消息后,调用 msg.saveChanges(),然后使用像 mbp.setHeader("Content-Transfer-Encoding", "base64") 的语句来强制对指定主体部分做 base64 编码。
另一种办法是编写 MimeBodyPart 的子类,并重写 updateHeaders 方法,让它首先调用 super.updateHeaders(),然后像上面那样设置 Content-Transfer-Encoding 标题。
问. 为什么 JavaMail 没有在非 ASCII 字符集中正确编码和解码文件名?
答:文件名是作为参数存储在 MIME 标题中的。形如 =?ISO-8859-15?B?5OTkLUluZm8ucGRm?= 的编码文件名不是 MIME spec(规范)的一部分。形如 =?A?B?C?= 的文件名是一个完全有效的文件名,而不是一个不正确编码的文件名。JavaMail 没有编码和解码文件名,因为这样做会违反 MIME 规范。
基本的 MIME 规范不允许编码参数。RFC 2231 定义了一种新的方式,使得可以在 MIME 标题中包括编码参数,包括文件名。它与下面的事实方式不兼容:许多应用程序非法编码文件名。支持 RFC 2231 将不允许 JavaMail 与这些现有的程序互操作。据我所知,很少现有的程序支持 RFC 2231。
如果你选择违反 MIME 规范是为了与其他也违反了 MIME的程序互操作,那么 JavaMail 会给您所有需要的工具来完成这件事。
编码文件名的解决方法是简单的:
mbp.setFileName(MimeUtility.encodeText(filename));
解码文件名的解决方法同样简单:
String filename = MimeUtility.decodeText(part.getFileName());
阅读邮件——IMAP
问. 我尝试在 IMAP 服务器上运行演示程序,但我得到了错误。
答:首先检查在 IMAP 服务器上确实有一个电子邮件账户。与系统管理员协商有关它的事情。通过在代码中调用 session 对象上的 setDebug(true) 方法来打开调试模式。这将造成在屏幕上转储 IMAP 协议跟踪。把这个跟踪发送给我们。对于说明问题,这个跟踪是非常有用的。如果可以的话,请将有关你的 IMAP 服务器的供应商信息发送给我们。
问. 当我提取带有较大附件的消息时,IMAP 提供程序似乎丢失了数据。
答:这是由于在 IMAP 服务器的部分提取实现中有 bug。为了解决这个服务器 bug,请将“mail.imap.partialfetch”属性设置为 false。参考 JavaMail 包的 NOTES.txt,获取进一步信息。
问. IMAP 提供程序是否缓存检索的数据?
答:只有在必要的时候,IMAP 提供程序才从服务器上提取消息的数据(javax.mail.FetchProfile 可用于优化这一点)。一旦提取了标题和 bodystructure(主体结构)信息,它就总是缓存在 Message 对象中。不过,没有缓存bodypart(主体部分)的内容。因此每次客户端请求内容时(使用 getContent() 或使用 getInputStream()),就会向服务器发出一个新的 FETCH 请求。这是由于消息的内容可能比较大,因此如果缓存了大量消息的这些内容,就可能导致系统很快用完内存,因为垃圾收集器不能释放被引用的对象。客户端应该注意到这一点,而且如果需要的话,自己维持检索的内容。
问. 我想在文件夹间移动消息。我应该使用 appendMessages() 或 copyMessages() 吗?
答:在下面的这种情况下,使用 copyMessages()——要复制或移动的消息属于某个文件夹,而该方法就是在这个文件夹中被调用的。这是因为一些实现可以优化这个操作,前提是源和目标文件夹属于相同的后端 Store(存储)。否则使用 appendMessages()。
问. 有时检索大消息主体似乎是低效的。
答:如果你正在使用 Sun IMAP 提供程序,就可以试着增加 mail.imap.fetchsize 属性(当前默认值是 16K)。这将导致从服务器提取的数据是一些较大的块区。注意,当你这样做时,要冒这样的危险——JVM 可能用完内存。
问. 当加载这种大二进制附件时,我得到了 OutOfMemory 错误。
答:在启动时增大最大 JVM 堆大小。如果使用来自 Sun 的标准 JVM,那就使用“-mx”选项。不要保持不使用消息“内容”的引用。在某些情形下,可以尽量将消息内容作为原始字节进行流转化(使用 getInputStream()),使用它们,然后丢弃已使用的数据块区。
问. 当在主体部分 (bodypart) 上调用 getContent() 时,我为什么会得到 UnsupportedDataTypeException?
答:getContent() 方法返回一个 Java 对象,它代表 bodypart(主体部分)内容。为了让它工作,必须注册对应内容的 MIME 类型的 JAF DataContentHandler。否则将引发 UnsupportedDataTypeException。在这种情形下,可以使用 getInputStream() 方法来将内容作为字节流检索。参阅 JAF 文档,获取有关如何创建和注册自己的 DataContentHandlers 的细节。
问. 当在包含文本数据的主体部分 (bodypart) 上调用 getContent() 时,为什么会得到 UnsupportedEncodingException?
答:当使用 getContent() 时, 文本主体部分(例如主类型是“text”的主体部分)返回 Unicode 字符串对象。通常,这些主体部分内部用某些非 Unicode 字符集保持文本数据。JavaMail(通过 "text/plain" DataContentHandler)试着将那些数据转换成 Unicode 字符串。基础 JDK 的字符集可用于做这种工作。如果 JDK 不支持特别的字符集,那么就引发 UnsupportedEncodingException。在这种情况下,可以使用 getInputStream() 方法来将内容作为字节流检索。下面是一个例子: String s;if (part.isMimeType("text/plain")) { try { s = part.getContent(); } catch (UnsupportedEncodingException uex) { InputStream is = part.getInputStream(); /* * Read the input stream into a byte array. * Choose a charset in some heuristic manner, use * that charset in the java.lang.String constructor * to convert the byte array into a String. */ s = convert_to_string(is); } catch (Exception ex) { // Handle other exceptions appropriately }}
发送邮件——SMTP
问. 我如何回复消息?
答:为了回复消息,请使用 Message 对象上的 reply 方法。这个方法将返回一个新的对象,对象中的标题已经针对回复做了恰当设置。你将需要自己提供消息的内容。
问. 我如何转发消息?
答:用于转发消息的方法取决于你要怎样表示要转发的消息。简单的办法是创建一个新的 MimeMessage,并适当地为它提供地址,然后将现有的消息作为附件放在新消息中。为了将原始消息放在新消息中,比如可以使用下面的代码:
MimeBodyPart mbp = new MimeBodyPart(); mbp.setContent(forwardedMsg, "message/rfc822"); mp.addPart(mbp);但是如果你想创建新的消息,并在新消息中包括原始消息的文本,可能也要用 "> " 来缩进,那将需要提取原始消息主体中的数据,并进行相应的处理。你可能也想取得原始消息的其他附件,并将它们添加到新消息中。
问. 我如何发送 HTML 邮件?
答:在分发中包括了大量演示程序,它们展示了如何发送 HTML 邮件。如果想发送简单消息,它具有 HTML 而不是纯文本,那请参见 demo(演示)目录中的 sendhtml.java 程序。如果想将 HTML 文件作为附件发送,请参见 sendfile.java 示例,它展示了如何将任何文件作为附件发送。
问. 我如何发送具有不同字体和颜色的格式化文本的邮件?
答:最简单的办法是使用 HTML 文本发送消息。参见 上面。
问. 我如何发送具有纯文本和 HTML 文本的邮件,让每个邮件的阅读者可以选择适合它的格式?
答:你想要发送 MIME multipart/alternative 消息。你构造了这样的一条消息,构造方式基本上与构造 multipart/mixed 消息相同,它使用了 MimeMultipart 对象,而该对象又是使用 new MimeMultipart("alternative") 来构造的。然后在 multipart(多部分)中,把 text/plain 主体部分作为第一部分插入,并且把 text/html 作为第二部分插入。参阅 RFC2046,获取这一消息的结构的细节。
问. 我如何发送包含图像的 HTML 邮件?
答:最简单的办法是发送带有图像标签的 HTML 文本,标签引用了公共 Web 站点。在这种方法中,在消息中并没有真正包括图像,因此当用户阅读消息时,如果没有连接到 Internet,那将不能看到图像。
另外,你也可以构造 MIME multipart/related 消息。参阅 RFC2387,获取这种消息结构的细节。
问. Transport 方法 send 和 sendMessage 之间有什么区别?
答:send() 方法是一个静态方法,可以直接使用,而不需要 Transport 对象的实例。它用于常见、简单的场合,比如使用默认传输发送单条消息。从内部讲,send() 方法首先调用消息上的 saveChanges() 方法。然后创建合适的新 Transport 对象,调用 Transport 的 connect() 方法,调用 Transport 的 sendMessage() 方法来实际发送消息,接着调用 Transport 的 close() 方法,最后丢弃 Transport 对象的新实例,并由垃圾收集器收集(实际上,还有比那更加复杂的,但那是一般的想法)。
如你可以看到,静态 send() 便利 (convenience) 方法是建立在更加通用的每实例 sendMessage() 方法的基础上的。有许多原因可以让应用程序直接使用 sendMessage() 方法。最常见的原因是为了通过在单个连接期间发送多条消息 来提高性能,或者为了手动管理连接以提供验证信息。当使用 sendMessage() 方法时,产生的最常见错误是,忘记在要发送的消息上调用 saveChanges() 方法。
问. 我需要验证到 SMTP 服务器,因此我调用了 trans.connect(host, user, password),然后调用 trans.send(msg) 发送消息,但它却不能工作。
答:你应该调用 msg.saveChanges(),然后调用 trans.sendMessage(msg, addrs) 来发送消息。如 上面 所描述,send 方法是一个静态便利方法,它会获得自己的 Transport 对象,并创建自己的连接用于发送消息;它没有使用与某些 Transport 对象有关的连接,并且它是通过该 Transport 对象得到调用的。当然不要忘记将 mail.smtp.auth 属性设置为 true 来启用 SMTP 验证!
问. 我修改了一条消息,但标题却没有反映修改。
答:在创建新消息或修改现有消息后,应该调用 saveChanges()。这将导致重新设置标题以反映变更。注意,Transport.send(Message) 方法隐式调用了这个方法。因此如果你正在做的是发送已修改的消息,就可以跳过调用 saveChanges()。saveChanges() 可能是一个昂贵的操作(特别是对于较大或深度嵌套的消息),因此只在需要时才调用它。
问. 我正在使用 sendMessage() 方法发送消息,但在消息中的文本前后却出现奇怪的一些行,并且我的附件也在消息体中出现。
答:通常这些行像下面这样:
--928176543.952742998030.JavaMail.name@host像 上面 那样,在创建新消息后,在使用 Transport.sendMessage() 方法发送消息之前,必须调用 saveChanges() 方法。静态 Transport.send() 方法将自动调用 Message.saveChanges() 方法。
问. 我为新消息的 Message-ID 标题设置了特定值。但当我发送这条消息时,却重写了那个标题。
答:saveChanges() 将为 Message-ID 字段设置新值,重写所设置的任何值。如果需要设置自己的 Message-ID 并保留它,就必须创建自己的 MimeMessage 子类,重写 updateHeaders() 方法,并使用这个子类的一个实例。 class MyMessage extends MimeMessage { ... protected void updateHeaders() throws MessagingException { super.updateHeaders(); setHeader("Message-ID", "my-message-id"); } ...}
问. 当发送创建的新消息时,为什么会得到 UnsupportedDataTypeException?
答:你可能使用 setContent(Object o, String type) 方法设置了消息的一些内容。为了让它能工作,必须为指定“类型”注册 JAF DataContentHandler。如果不这样做,将获得 UnsupportedDataTypeException。参阅 JAF 文档,获取进一步信息。
问. 当发送消息时,如何能够显式地设置 SMTP FROM: 属性?
答:mail.smtp.from 属性可用于设置 SMTP FROM: 属性。如果没有设置这个属性,就使用消息的 From 属性。如果多个线程需要同时发送邮件,并且每个线程需要设置 From 属性,那么每个线程就必须使用自己的 Session 对象,它具有自己的 Properties 对象。然后可以在每个 Session 对象的 各个 Properties 对象上独立设置 mail.smtp.from 属性(同样对每个线程做这样的设置)。
问. 我想重复发送消息,并且每次发送给一组不同的收件人。但调用 Transport.send(Message) 却导致每次都创建一个新的 Transport 会话。在本例中,这是一个次优办法,我如何来解决它?
答:创建合适的 Transport 对象的实例,然后连上它并重复调用 sendMessage() 方法,例如: MimeMessage msg = ...;// construct messagemsg.saveChanges();Transport t = session.getTransport("smtp");t.connect();for (int i = 0; .....) { t.sendMessage(msg, new Address[] { recipients[i] });}t.close();
问. 当试图发送消息时,我得到了 “MessagingException: 501 HELO requires domain address”(MessagingException: 501 HELO 要求域地址)。
答:在 SMTP HELO 命令中,SMTP 提供程序使用 InetAddress.getLocalHost().getHostName() 的结果。如果那个调用不能返回任何数据,就不会在 HELO 命令中发送任何名称。检查你的 JDK 和名称服务器配置,确保那个调用返回正确数据。从 JavaMail 1.1.3 开始,你也可以设置 mail.smtp.localhost 属性,并可以把设置为想用于 HELO 命令的名称。
问. 如果将消息发送到错误的地址,为什么我会获得 SendFailedException 或 TransportEvent,指出地址是错误的?
答:在 Internet 上没有端到端验证。通常要将消息转发到几个邮件服务器,然后才到达特定的邮件服务器,该服务器决定了它是否可以传送消息。如果在这些后面的步骤中的某个步 骤发生了错误,那么通常会将消息作为不可传送返回给发件人。一个成功的“发送”只表明邮件服务器已经接受了消息,并将试着传送它。
问. 当消息不能被传送时,就会返回一个失败消息。我如何检测这些“回弹”消息?
答:虽然有一个 Internet 标准用于报告这样的错误(multipart/report MIME 类型,参阅 RFC1892),但还没有广泛实现它。RFC1211 深入讨论了这个问题,包括了大量的例子。
在 Internet 电子邮件中,特定的邮箱或用户名是否存在,只能由传送消息的最终服务器决定。消息可能通过几个中继服务器(它们不能检测错误),然后再到达最终服务器。通 常,当最终服务器检测到这一错误,它会返回一个消息给原始消息的发送人,指出失败的原因。有许多 Internet 标准讨论了这种传送状态通知 (Delivery Status Notifications),但大量服务器不支持这些新标准,相反使用特别技术来返回这种错误消息。这使得将“回弹”消息与产生问题的原始消息相互关联 起来非常困难(注意,这个问题与 JavaMail 完全无关)。
有许多技术和试探法用于处理这一问题,但它们都不是完美的。一种技术是 Variable Envelope Return Paths,http://cr.yp.to/proto/verp.txt 描述了这一技术。
问. 当创建 InternetAddress 对象时,如果地址是非法的,为什么不会获得异常?
答:InternetAddress 类只检查地址的语法。如 上面 所讨论,InternetAddress 类不能决定地址是否做为合法地址实际存在。如果应用程序运行在防火墙背后或目前没有连接到 Internet,那么甚至不能验证主机名。
问. 当试图发送消息时,我为什么会获得 javax.mail.SendFailedException: 550 Unable to relay for 我的地址?
答:这 不是 JavaMail 的问题。这是来自你的 SMTP 邮件服务器的错误回复。它指出没有配置你的邮件服务器,使得你可以通过它发送邮件。通常会配置组织的邮件服务器,使得组织中的邮件可以发送到组织中的其他 地址,或者发送到组织外部的地址。通常它也使得来自组织外部地址的邮件可以发送(或中继)到 也是组织外部的另一地址。邮件服务器的配置决定了是否允许这种中继,以及哪些地址应该当成内部地址与外部地址。
问. 当试图发送消息到(例如)Yahoo时,为什么我会获得一个错误,指出“连接被拒绝”?
答:你试图连接 的计算机很可能没有运行邮件服务器。如果你试图连接到 Web 邮件服务,比如 Yahoo,通常你就不能使用 Web 主机名(例如 "yahoo.com"),因为这台主机没有运行所要求的邮件服务器。相反,你需要知道运行所要求邮件服务器的主机的名称;联系你的 Web 邮件提供商获取这一信息。你可以从如下地址找到 Yahoo 的这一信息: http://help.yahoo.com/help/us/mail/pop/pop-02.html。 注意,有些 Web 邮件提供商没有提供这一服务,相反,只允许通过浏览器访问邮件。
如果你没有试图连接到 Web 邮件账户,而是试图连接到本地网络中的一台主机,那很有可能是试图连接的主机没有运行邮件服务器。 有时如果忘记设置(比如)"mail.smtp.host" 属性,这种情况就发生了,它将导致你试图连接到“localhost”。大多数 Windows 计算机没有运行邮件服务器,不过,许多 UNIX(Solaris 和 Linux 等)计算机是运行它的。因此在 Windows 计算机上,试图连接到“localhost”通常会失败,并获得“连接被拒绝”。
问. 当发送邮件时,为什么会失败,并得到一个异常,异常中包括像“553 To send mail, first check your mail with a valid POP account”(553 为发送邮件,首先检查邮件具有合法的 POP 账户)这样的消息?
答:为 了防止它们用于发送垃圾邮件,一些邮件服务器会要求使用有效的 POP3 账户,并要求要先登录那个账户,然后才可以让你通过邮件服务器发送邮件。在 JavaMail 中,这种处理是简单的。当你知道正在处理这样的邮件服务器时,确保连接到那台邮件服务器上的 POP3 Store,然后再发送邮件。
POP3
问. 我可以从哪里找到 POP3 的支持?
答:在 JavaMail 1.2 及以上版本中包括了 POP3 提供程序。POP3 提供程序也可从多个第三方提供商获得。请浏览我们的 第三方产品清单,获取进一步信息。
问. 我想在 POP3 服务器上删除消息。我在这些消息上设置了 DELETED 标志。然后调用 expunge() 方法,但我得到了MethodNotSupportedException。当使用 Sun 的 POP3 提供程序时,我如何删除消息?
答:POP3 提供程序不支持 expunge() 方法。不过,可以关闭 expunge 标志设为 true 的文件夹。也就是调用 folder.close(true)。
问. 在从 Sun POP3 提供程序获得的消息中,我如何可以检索 POP3 UIDL?
答:对于 POP3 提供程序,这是可能的。参见 com.sun.mail.pop3 包文档,获取进一步信息。
问. 对于 POP3,我如何知道哪里消息是新的?
答:POP3 协议不提供任何永久标志的支持,因此 RECENT 标志是没有用的。com.sun.mail.pop3 包文档讨论了几个策略,它们可用于处理这一问题。
问. 当使用 POP3 时,为什么 hasNewMessages() 总是返回 false?
答:POP3 协议没有提供办法来决定文件夹是否有新消息。
问. 我创建了 MessageCountListener(如 monitor 程序中所展示的),但在我的 POP3 INBOX(收件箱)中,我从未得到通知。
答:当 INBOX 打开时,POP3 协议不允许客户端查看传送到 INBOX 的新消息。应用程序必须关闭 INBOX,然后重新打开它, 才可以查看新消息。对于 POP3,使用 MessageCountListener 接口,你从不会得到新邮件的通知。参见 com.sun.mail.pop3 包文档(在 JavaMail 下载包的 docs/sundocs 目录),获取进一步信息。
问. 当使用 POP3 时,为什么 getReceivedDate() 返回 null?
答:POP3 协议不提供有关何时接收消息的信息。通过查看一些消息标题,比如 Received 标题,可能可以猜到接收日期,但这非常不可靠。
问. 当使用 POP3 时,我得到了有关 SocketFetcher 类的抱怨。
答:很有可能在 CLASSPATH 中有多个版本的 pop3.jar 或 mail.jar。检查 CLASSPATH 的设置,如果你正在使用 JDK 1.2 或更新版本,检查 JDK 中的 "jre/lib/ext" 目录。确保你只有 JavaMail 1.2 mail.jar 可用并且没有 pop3.jar。只有对于较老版本的 JavaMail,pop3.jar 才是必要的。
问. 当使用 POP3 时,我得到了有关 contentStream 字段的抱怨。
答:错误通常像下面这样:
java.lang.NoSuchFieldError: contentStream atcom.sun.mail.pop3.POP3Message.getContentStream(POP3Message.java:115)像 上面 一样,使用混合版本的 POP3 提供程序和 mail.jar。在包括 POP3 提供程序的较新版本 mail.jar 之前,你可能在 CLASSPATH 中有了较老版本。
问. 除了 POP3 服务器上的 INBOX(收件箱)外,您如何访问或创建文件夹?
答:不能。POP3 服务器只支持每个用户一个邮箱。大多数使用 POP3 的邮件阅读者也维护着本地消息存储,他们可以将传入的消息(来自 POP3 INBOX)复制到存储中,并允许你将消息写到其他文件夹。参见 本项目,获取有关本地存储提供程序的进一步消息。
servlet 中的 JavaMail
问. 我可以在 servlet 中使用 JavaMail 吗?
答:可以。参阅上面的“安装和配置”一节获取进一步信息。JavaMail API 也是 Java 2 Platform, Enterprise Edition (J2EE) 所要求的一部分。当在 J2EE 产品中使用 JavaMail,不需要任何的安装和配置,就这么简单!
applet 中的 JavaMail
问. 我可以在 applet 中使用 JavaMail 吗?
答:可以。JavaMail 1.3 可以在 applet 中工作,这些 applet 可运行于 Netscape 和 Internet Explorer。
问. 在 applet 中,使用 JavaMail 的安全问题是什么?
答:在 applet 中使用 JavaMail,它的一个最大问题是默认的 applet 安全限制。这些限制只允许 applet 连接到特定的主机,而该主机是从中下载它们的主机。因此,为了让这样的 applet 使用 JavaMail,邮件服务器将需要位于特定的计算机,该计算机与从中下载 applet 的 Web 服务器相同。
问. OK,可能我确实不想使用 applet,那我应该怎么做?
答:通常,我们推荐使用 servlet (或 JSP)来收集邮件消息,并使用 JavaMail 来发送它。JavaMail 下载包中包括了演示 servlet,它例举了这种方法。Java Developer Connection 上的 电子邮件 Web 应用程序演示程序 例举了使用 JavaServer Pages 的另一种方法。