一 基础认知

JavaMail API是Sun公司为方便Java开发人员在应用程序中实线邮件发送和接收功能而通过的一套标准开发包,它支持一些常用的邮件协议,如SMTP、POP3、IMAP和MIME等。

JavaMail API按其功能划分通常可分为如下三大类:

  1. 创建和解析邮件内容的API :Message类是创建和解析邮件的核心API,它的实例对象代表一封电子邮件;
  2. 发送邮件的API:Transport类是发送邮件的核心API类,它的实例对象代表实现了某个邮件发送协议的邮件发送对象,例如SMTP协议;
  3. 接收邮件的API:Store类是接收邮件的核心API类,它的实例对象代表实现了某个邮件接收协议的邮件接收对象,例如POP3协议。

二 详细认知

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

JavaMail对收发邮件进行了高级的抽象,形成了一些关键的的接口和类,它们构成了程序的基础,下面我们分别来了解一下这些最常见的对象。

1. Properties:属性对象

由于JavaMail需要和邮件服务器进行通信,这就要求程序提供许多诸如服务器地址、端口、用户名、密码等信息,JavaMail通过Properties对象封装这些属性信息。如下面的代码封装了两个属性信息:

Properties props = new Properties();
props.put("mail.smtp.host", "smtp.sina.com.cn");
props.put("mail.smtp.auth", "true");

1)SMTP Configuration Properties

Name

Type

Description

mail.smtp.user

String

Default user name for SMTP. SMTP默认的登陆用户名

mail.smtp.host

String

The SMTP server to connect to. SMTP服务器地址

mail.smtp.port

int

The SMTP server port to connect to, if the connect() method doesn't explicitly specify one. Defaults to 25. SMTP服务器端口号,默认为25

mail.smtp.connectiontimeout

int

Socket connection timeout value in milliseconds. Default is infinite timeout.

mail.smtp.timeout

int

Socket I/O timeout value in milliseconds. Default is infinite timeout. I/O连接超时时间,单位为毫秒,默认为永不超时

mail.smtp.from

String

Email address to use for SMTP MAIL command. This sets the envelope return address. Defaults to msg.getFrom() or InternetAddress.getLocalAddress(). NOTE: mail.smtp.user was previously used for this. 默认的邮件发送源地址

mail.smtp.localhost

String

Local host name. Defaults to InetAddress.getLocalHost().getHostName(). Should not normally need to be set if your JDK and your name service are configured properly.

mail.smtp.ehlo

boolean

If false, do not attempt to sign on with the EHLO command. Defaults to true. Normally failure of the EHLO command will fallback to the HELO command; this property exists only for servers that don't fail EHLO properly or don't implement EHLO properly.

mail.smtp.auth

boolean

If true, attempt to authenticate the user using the AUTH command. Defaults to false. SMTP服务器是否需要用户认证,默认为false

mail.smtp.dsn.notify

String

The NOTIFY option to the RCPT command. Either NEVER, or some combination of SUCCESS, FAILURE, and DELAY (separated by commas).

mail.smtp.dsn.ret

String

The RET option to the MAIL command. Either FULL or HDRS.

mail.smtp.allow8bitmime

boolean

If set to true, and the server supports the 8BITMIME extension, text parts of messages that use the "quoted-printable" or "base64" encodings are converted to use "8bit" encoding if they follow the RFC2045 rules for 8bit text.

mail.smtp.sendpartial

boolean

If set to true, and a message has some valid and some invalid addresses, send the message anyway, reporting the partial failure with a SendFailedException. If set to false (the default), the message is not sent to any of the recipients if there is an invalid recipient address.

2)IMAP Configuration Properties

Name

Type

Description

mail.imap.user

String

Default user name for IMAP.

mail.imap.host

String

The IMAP server to connect to.

mail.imap.port

int

The IMAP server port to connect to, if the connect() method doesn't explicitly specify one. Defaults to 143.

mail.imap.partialfetch

boolean

Controls whether the IMAP partial-fetch capability should be used. Defaults to true.

mail.imap.fetchsize

int

Partial fetch size in bytes. Defaults to 16K.

mail.imap.connectiontimeout

int

Socket connection timeout value in milliseconds. Default is infinite timeout.

mail.imap.timeout

int

Socket I/O timeout value in milliseconds. Default is infinite timeout.

mail.imap.statuscachetimeout

int

Timeout value in milliseconds for cache of STATUS command response. Default is 1000 (1 second). Zero disables cache.

mail.imap.appendbuffersize

int

Maximum size of a message to buffer in memory when appending to an IMAP folder. If not set, or set to -1, there is no maximum and all messages are buffered. If set to 0, no messages are buffered. If set to (e.g.) 8192, messages of 8K bytes or less are buffered, larger messages are not buffered. Buffering saves cpu time at the expense of short term memory usage. If you commonly append very large messages to IMAP mailboxes you might want to set this to a moderate value (1M or less).

mail.imap.connectionpoolsize

int

Maximum number of available connections in the connection pool. Default is 1.

mail.imap.connectionpooltimeout

int

Timeout value in milliseconds for connection pool connections. Default is 45000 (45 seconds).

mail.imap.separatestoreconnection

boolean

Flag to indicate whether to use a dedicated store connection for store commands. Default is false.

mail.imap.connectionpool.debug

boolean

Flag to toggle debugging of the connection pool. Default is false.

3)Pop3 Configuration Properties

Name

Type

Description

mail.pop3.user

String

Default user name for POP3.

mail.pop3.host

String

The POP3 server to connect to.

mail.pop3.port

int

The POP3 server port to connect to, if the connect() method doesn't explicitly specify one. Defaults to 110.

mail.pop3.connectiontimeout

int

Socket connection timeout value in milliseconds. Default is infinite timeout.

mail.pop3.timeout

int

Socket I/O timeout value in milliseconds. Default is infinite timeout.

mail.pop3.rsetbeforequit

boolean

Send a POP3 RSET command when closing the folder, before sending the QUIT command. Useful with POP3 servers that implicitly mark all messages that are read as "deleted"; this will prevent such messages from being deleted and expunged unless the client requests so. Default is false.

mail.pop3.message.class

int

Class name of a subclass of com.sun.mail.pop3.POP3Message. The subclass can be used to handle (for example) non-standard Content-Type headers. The subclass must have a public constructor of the form MyPOP3Message(Folder f, int msgno) throws MessagingException.

2. Session:会话对象

Session类用于定义整个应用程序所需的环境信息,以及收集客户端与邮件服务器建立网络连接的会话信息,如邮件服务器的主机名、端口号、采用的邮件发送和接收协议等。Session对象根据这些信息构建用于邮件收发的Transport和Store对象,以及为客户端创建Message对象时提供信息支持。

JavaMailSenderImpl发送邮件失败 javamail api_API

Session是一个很容易被误解的类,这归咎于混淆视听的类名。千万不要以为这里的Session像HttpSession一样代表真实的交互会话,但创建Session对象时,并没有对应的物理连接,它只不过是一对配置信息的集合。Session的主要作用包括两个方面:

1)接收各种配置属性信息:通过Properties对象设置的属性信息;

2)初始化JavaMail环境:根据JavaMail的配置文件,初始化JavaMail环境,以便通过Session对象创建其他重要类的实例。

所以,如果把Session更名为Configure也许更容易理解一些。JavaMail提供者在Jar包的META-INF目录下,通过以下文件提供了基本配置信息,以便session能够根据这个配置文件加载提供者的实现类:

  1. javamail.providers和javamail.default.providers;
  2. javamail.address.map和javamail.default.address.map。

下面是Sun提供者java.mail.default.providers文件的配置信息(位于mail.jar中):

# JavaMail IMAP provider Sun Microsystems, Inc
protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc;
protocol=imaps; type=store; class=com.sun.mail.imap.IMAPSSLStore; vendor=Sun Microsystems, Inc;
# JavaMail SMTP provider Sun Microsystems, Inc
protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Sun Microsystems, Inc;
protocol=smtps; type=transport;    class=com.sun.mail.smtp.SMTPSSLTransport; vendor=Sun Microsystems, Inc;
# JavaMail POP3 provider Sun Microsystems, Inc
protocol=pop3; type=store; class=com.sun.mail.pop3.POP3Store; vendor=Sun Microsystems, Inc;
protocol=pop3s; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Sun Microsystems, Inc;

这个配置文件提供了以下四个方面的信息:

protocol:协议名称;

type:协议类型;

class:对应该操作类型的实现类;

vendor:厂商名称。

Session在加载配置文件时会按照以下优先级顺序进行:

1)首先使用<JAVA_HOME>/lib中的javamail.providers;

2)如果1)不存在相应的配置文件,使用类路径下mail.jar中META-INF目录下的javamail.providers;

3)如果2)不存在相应的配置文件,使用类路径下的mail.jar中META-INF目录下的javamail.default.providers;

所以开发者可以在<JAVA_HOME>/lib目录下提供配置文件覆盖mail.jar/META-INF目录中厂商的配置。但是,一般情况下,我们无须这样做。

Session通过JavaMail配置文件以及程序中设置的Properties对象构建一个邮件处理环境,后续的处理将在Session基础上进行。Session拥有多个静态工厂方法用于创建Session实例。

  • static Session getDefaultInstance(Properties props, Authenticator authenticator):

当JVM中已经存在默认的Session实例中,直接返回这个实例,否则创建一个新的Session实例,并将其作为JVM中默认Session实例。

这个API很诡异,我们将对它进行详细的讲解。由于这个默认Session实例可以被同一个JVM所有的代码访问到,而Session中本身又可能包括密码、用户名等敏感信息在内的所有属性信息,所以后续调用也必须传入和第一次相同的Authenticator实例,否则将抛出java.lang.SecurityException异常。

如果第一次调用时Authenticator入参为null,则后续调用通过null的Authenticator入参或直接使getDefaultInstance(Properties props)即可返回这个默认的Session实例。

值得一提的是,虽然后续调用也会传入Properties,但新属性并不会起作用,如果希望采用新的属性值,则可以通过getDefaultInstance(Properties props)创建一个新的Session实例达到目的。

Authenticator在这里承担了两个功能:

首先,对JVM中默认Session实例进行认证保护,后续调用执行getDefaultInstance(Properties props, Authenticator authenticator)方法时必须和第一次一样;

其次,在具体和邮件服务器交互时,又作为认证的信息;

  • static Session getDefaultInstance(Properties props):

返回JVM中默认的Session实例,如果第一次创建Session未指定Authenticator入参,后续调用可以使用该访问获取Session;

  • static Session getInstance(Properties props, Authenticator authenticator):

创建一个新的Session实例,它不会在JVM中被作为默认实例共享;

  • static Session getInstance(Properties props):

根据相关属性创建一个新的Session实例,未使用安全认证信息;

 

Session是JavaMail提供者配置文件以及设置属性信息的“容器”,Session本身不会和邮件服务器进行任何的通信。所以在一般情况下,我们仅需要通过getDefaultInstance()获取一个共享的Session实例就可以了,下面的代码创建了一个Session实例:

Properties props = System.getProperties();
props.setProperty("mail.transport.protocol", "smtp");
......
Session session = Session.getDefaultInstance(props);

3. Transport和Store:传输和存储

邮件操作只有发送或接收两种处理方式,JavaMail将这两种不同操作描述为传输(javax.mail.Transport)和存储(javax.mail.Store),传输对应邮件的发送,而存储对应邮件的接收。

Session提供了几个用于创建Transport和Store实例的方法,在具体讲解这些方法之前,我们事先了解一下Session创建Transport和Store的内部机制。我们知道提供者在javamail.providers配置文件中为每一种支持的邮件协议定义了实现类,Session根据协议类型(stmp、pop3等)和邮件操作方式(传输和存储)这两个信息就可以定位到一个实例类上。比如,指定stmp协议和transport类型后,Session就会使用com.sun.mail.smtp.SMTPTransport实现类创建一个Transport实例,而指定pop3协议和store类型时,则会使用com.sun.mail.pop3.POP3Store实例类创建一个Store实例。Session提供了多个重载的getTransport()和getStore()方法,这些方法将根据Session中Properties属性设置情况进行工作,影响这两套方法工作的属性包括:

属性名

说明

mail.transport.protocol

默认的邮件传输协议,例如,smtp

mail.store.protocol

默认的存储邮件协议,例如:pop3

mail.host

默认的邮件服务地址,例如:192.168.67.1

mail.user

默认的登陆用户名,例如:zapldy

下面,我们再回头来了解Session的getTransport()和getStore()的重载方法。

  • Transport getTransport():

当Session实例设置了mail.transport.protocol属性时,该方法返回对应的Transport实例,否则抛出javax.mail.NoSuchProviderException。

  • Transport getTransport(String protocol):

如果Session没有设置mail.transport.protocol属性,可以通过该方法返回指定类型的Transport,如transport = session.getTransport(“smtp”)。

如果Session中未包含Authenticator,以上两方法创建的Transport实例和邮件服务器交互时必须显示提供用户名/密码的认证信息。如果Authenticator非空,则可以在和邮件服务器交互时被作为认证信息使用。除了以上两种提供认证信息的方式外,Session还可以使用以下的方法为Transport提供认证信息。

Transport getTransport(URLName url):

用户可以通过URLName入参指定邮件协议、邮件服务器、端口、用户名和密码信息,请看下面的代码:

URLName urln = new URLName(“smtp”, “smtp.sina.com.cn”, 25, null, “masterspring2”, “spring”);
Transport transport = session.getTransport(urln);

这里,指定了邮件协议为smtp,邮件服务器是smtp.sina.com.cn,端口为25,用户名/密码为masterspring2/spring。

      

消息发送的最后一部分是使用  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) 设置调试标志。

 

用 Session 获取消息与发送消息开始很相似。但是,在 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(); 看上去是个很慢的操作,它从服务器上读取每一条消息,但仅在你实际需要消息的一部分时,消息的内容才会被检索。

一旦有了要读的 Message,您可以用 getContent() 来获取其内容,或者用 writeTo() 将内容写入流。getContent() 方法只能得到消息内容,而 writeTo() 的输出却包含消息头。

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

一旦读完邮件,要关闭与 folder 和 store 的连接。

folder.close(aBoolean);
store.close();

传递给 folder 的 close() 方法的 boolean 表示是否清除已删除的消息从而更新 folder。

 

4. Message:消息对象

一旦获得 Session 对象,就可以继续创建要发送的消息。这由 Message 类来完成。因为 Message 是个抽象类,您必需用一个子类,多数情况下为 javax.mail.internet.MimeMessage。MimeMessage 是个能理解 MIME 类型和头的电子邮件消息,正如不同 RFC 中所定义的。虽然在某些头部域非 ASCII 字符也能被译码,但 Message 头只能被限制为用 US-ASCII 字符。

要创建一个 Message,请将 Session 对象传递给 MimeMessage 构造器:

MimeMessage message = new MimeMessage(session);

注意:还存在其它构造器,如用按 RFC822 格式的输入流来创建消息。

一旦获得消息,您就可以设置各个部分,因为 Message 实现 Part 接口(且 MimeMessage 实现 MimePart )。设置内容的基本机制是 setContent() 方法,同时使用参数,分别代表内容和 mime 类型:

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

但如果,您知道您在使用 MimeMessage,而且消息是纯文本格式,您就可以用 setText() 方法,它只需要代表实际内容的参数,( MIME 类型缺省为 text/plain):

message.setText("Hello");

后一种格式是设置纯文本消息内容的首选机制。至于发送其它类型的消息,如 HTML 文件格式的消息,我们首选前者。

用 setSubject() 方法设置 subject(主题):

message.setSubject("First");

setFrom :设置邮件的发件人

setRecipient :设置邮件的发送人、抄送人、密送人

三种预定义的地址类型是:

Message.RecipientType.TO :收件人

Message.RecipientType.CC :抄送人

Message.RecipientType.BCC :密送人

setSubject :设置邮件的主题

setContent :设置邮件内容

setText :如果邮件内容是纯文本,可以使用此接口设置文本内容。

下面的代码演示了创建一个简单邮件信息的过程:

Message msg = new MimeMessage(session);
msg.setSubject("Test Title");
msg.setText("How are you!");
msg.setSentDate(new Date());

JavaMail API使用javax.mail.Message类来表示一封邮件,Message类是一个抽象类,所以我们需要使用其子类javax.mail.internet.MimeMessage类来创建Message类的实例对象,如果我们创建的是一个简单文本邮件,那么MimeMessage类就可以满足我们的需求了,但是如果需要创建一封包含内嵌资源或者是带附件的复杂邮件,则需要使用到JavaMail API中的MimeMessage、javax.mail.internet.MimeBodyPart和javax.mail.internet.MimeMultipart等类。

  1. MimeMessage类表示整封邮件
  2. MimeBodyPart类表示邮件的一个MIME消息
  3. MimeMultipart类表示一个由多个MIME消息组合成的组合MIME消息。

这三个类的工作关系如下图所示:

JavaMailSenderImpl发送邮件失败 javamail api_API_02

 

虽然应用程序开发者在使用JavaMailAPI创建邮件内容时,通常只需要使用MimeMessage,MimeBodyPart和MimeMultipart这3个主要的类,但是了解他们的类继承关系也是必要的。下图列出了这三个类的继承关系以及常用方法。

JavaMailSenderImpl发送邮件失败 javamail api_java_03

MimeMultipart有两种构造函数

public MimeMultipart()
public MimeMultipart(String subtype)

第一种是无参数的,其默认的实例对象的MIME类型为mixed,第二种制定一个类型来创建MimeMultipart类的实例对象,其有三种常用的类型:mixed,related,alternative,这三种类型在MIME中的组合关系如下所示:

JavaMailSenderImpl发送邮件失败 javamail api_配置文件_04

 

5. Address:地址

一旦您创建了 Session 和 Message,并将内容填入消息后,就可以用 Address 确定信件地址了。和 Message 一样,Address 也是个抽象类。您用的是 javax.mail.internet.InternetAddress 类。

若创建的地址只包含电子邮件地址,只要传递电子邮件地址到构造器就行了。

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

若希望名字紧挨着电子邮件显示,也可以把它传递给构造器:

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

需要为消息的 from 域和 to 域创建地址对象。除非邮件服务器阻止,没什么能阻止你发送一段看上去是来自任何人的消息。

一旦创建了 address(地址),将它们与消息连接的方法有两种。如果要识别发件人,您可以用 setFrom() 和 setReplyTo() 方法。

message.setFrom(address)

需要消息显示多个 from 地址,可以使用 addFrom() 方法:

Address address[] = ...;
message.addFrom(address);

若要识别消息 recipient(收件人),您可以使用 addRecipient() 方法。除 address(地址)外,这一方法还请求一个 Message.RecipientType。

message.addRecipient(type, address)

三种预定义的地址类型是:

Message.RecipientType.TO
Message.RecipientType.CC
Message.RecipientType.BCC

如果消息是发给副总统的,同时发送一个副本(carbon copy)给总统夫人,以下做法比较恰当:

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 中定义的)或验证邮件交换(mail exchange,MX)记录,但这些功能不属于 JavaMail API。

6. Authenticator:认证者

与 java.net 类一样,JavaMail API 也可以利用 Authenticator 通过用户名和密码访问受保护的资源。对于JavaMail API 来说,这些资源就是邮件服务器。JavaMail Authenticator 在 javax.mail 包中,而且它和 java.net 中同名的类 Authenticator 不同。两者并不共享同一个 Authenticator,因为JavaMail API 用于 Java 1.1,它没有 java.net 类别。

要使用 Authenticator,先创建一个抽象类的子类,并从 getPasswordAuthentication() 方法中返回 PasswordAuthentication 实例。创建完成后,您必需向 session 注册 Authenticator。然后,在需要认证的时候,就会通知 Authenticator。您可以弹出窗口,也可以从配置文件中(虽然没有加密是不安全的)读取用户名和密码,将它们作为 PasswordAuthentication 对象返回给调用程序。

Properties props = new Properties();
// fill props with any information
Authenticator auth = new MyAuthenticator();
Session session = Session.getDefaultInstance(props, auth);