赞
踩
https://yq.aliyun.com/articles/40344#12
摘要: 一、JavaMail概述: JavaMail是由Sun定义的一套收发电子邮件的API,不同的厂商可以提供自己的实现类。但它并没有包含在JDK中,而是作为JavaEE的一部分。 厂商所提供的JavaMail服务程序可以有选择地实现某些邮件协议,常见的邮件协议包括: SMTP:简单...
JavaMail是由Sun定义的一套收发电子邮件的API,不同的厂商可以提供自己的实现类。但它并没有包含在JDK中,而是作为JavaEE的一部分。
厂商所提供的JavaMail服务程序可以有选择地实现某些邮件协议,常见的邮件协议包括:
SMTP:简单邮件传输协议,用于发送电子邮件的传输协议;
POP3:用于接收电子邮件的标准协议;
IMAP:互联网消息协议,是POP3的替代协议。
这三种协议都有对应SSL加密传输的协议,分别是SMTPS,POP3S和IMAPS。
除JavaMail服务提供程序之外,JavaMail还需要JAF(JavaBeans Activation Framework)来处理不是纯文本的邮件内容,这包括MIME(多用途互联网邮件扩展)、URL页面和文件附件等内容。
mail.jar:此JAR文件包含JavaMail API和Sun提供的SMTP、IMAP和POP3服务提供程序;
activation.jar:此JAR文件包含JAF API和Sun的实现。
在研究 JavaMail API 的细则之前,让我们回顾用于 API 的协议。基本上,您会逐渐熟悉并喜爱的协议有四个:
* SMTP
* POP
* IMAP
* MIME
您还将碰到 NNTP 和其它协议。理解所有协议的基本知识将有助于您理解如何使用 JavaMail API。虽然不了解这些协议您照样可以用这个 API,却不能够克服那些基础协议的局限性。如果我们精选的协议不能支持某种性能,JavaMail API 决不能魔术般的将这种性能添加上去。(您很快就会看到,在处理 POP 时这将成为一个难题。)
2、SMTP
简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)由 RFC 821 定义。它定义了发送电子邮件的机制。在 JavaMail API 环境中,您基于 JavaMail 的程序将和您的公司或因特网服务供应商的(Internet Service Provider's,ISP's)SMTP 服务器通信。SMTP 服务器会中转消息给接收方 SMTP 服务器以便最终让用户经由 POP 或 IMAP 获得。这不是要求 SMTP 服务器成为开放的中继,尽管 SMTP 服务器支持身份验证,不过还是得确保它的配置正确。像配置服务器来中继消息或添加删除邮件账号这类任务的实现,JavaMail API 中并不支持。
3、POP
POP 代表邮局协议(Post Office Protocol)。目前用的是版本 3,也称 POP3,RFC 1939 定义了这个协议。POP 是一种机制,因特网上大多数人用它得到邮件。它规定每个用户一个邮箱的支持。这就是它所能做的,而这也造成了许多混淆。使用POP 时,用户熟悉的许多性能并不是由 POP 协议支持的,如查看有几封新邮件消息这一性能。这些性能内建于如 Eudora 或Microsoft Outlook 之类的程序中,它们能记住一些事,诸如最近一次收到的邮件,还能计算出有多少是新的。所以当使用JavaMail API 时,如果您想要这类信息,您就必须自己算。
4、IMAP
IMAP 是更高级的用于接收消息的协议。在 RFC 2060 中被定义,IMAP 代表因特网消息访问协议(Internet Message Access Protocol),目前用的是版本 4,也称 IMAP4。在用到 IMAP 时,邮件服务器必需支持这个协议。不能仅仅把使用 POP 的程序用于 IMAP,并指望它支持 IMAP 所有性能。假设邮件服务器支持 IMAP,基于 JavaMail 的程序可以利用这种情况 — 用户在服务器上有多个文件夹(folder),并且这些文件夹可以被多个用户共享。
因为有这一更高级的性能,您也许会认为所有用户都会使用 IMAP。事实并不是这样。要求服务器接收新消息,在用户请求时发送到用户手中,还要在每个用户的多个文件夹中维护消息。这样虽然能将消息集中备份,但随着用户长期的邮件夹越来越大,到磁盘空间耗尽时,每个用户都会受到损失。使用 POP,就能卸载邮件服务器上保存的消息了。
5、MIME
MIME 代表多用途因特网邮件扩展标准(Multipurpose Internet Mail Extensions)。它不是邮件传输协议。但对传输内容的消息、附件及其它的内容定义了格式。这里有很多不同的有效文档:RFC 822、RFC 2045、RFC 2046 和 RFC 2047。作为一个JavaMail API 的用户,您通常不必对这些格式操心。无论如何,一定存在这些格式而且程序会用到它。
6、NNTP及其他
因为 JavaMail API 将供应商和所有其它的东西分开了,您就能轻松添加额外的协议支持。Sun 保留了一张第三方供应商列表,他们利用了 Sun 不提供超出(out-of-the-box)支持范围的协议。您会找到 NNTP(网络新闻传输协议)[新闻组]、S/MIME(安全多用途因特网邮件扩展)及其它支持。
JavaMail对收发邮件进行了高级的抽象,形成了一些关键的的接口和类,它们构成了程序的基础,下面我们分别来了解一下这些最常见的对象。
由于JavaMail需要和邮件服务器进行通信,这就要求程序提供许多诸如服务器地址、端口、用户名、密码等信息,JavaMail通过Properties对象封装这些属性西信息。如下面的代码封装了两个属性信息:
1
2
3
|
Properties props =
new
Properties();
props.put(
"mail.smtp.host"
,
"smtp.sina.com.cn"
);
props.put(
"mail.smtp.auth"
,
"true"
);
|
针对不同的的邮件协议,JavaMail规定了服务提供者必须支持一系列属性,下表是针对SMTP协议的一些常见属性(属性值都以String类型进行设置,属性类型栏仅表示属性是如何被解析的):
属性名 | 属性类型 | 说明 |
mail.stmp.host | String | SMTP服务器地址,如smtp.sina.com.cn |
mail.stmp.port | int | SMTP服务器端口号,默认为25 |
mail.stmp.auth | boolean | SMTP服务器是否需要用户认证,默认为false |
mail.stmp.user | String | SMTP默认的登陆用户名 |
mail.stmp.from | String | 默认的邮件发送源地址 |
mail.stmp.socketFactory.class | String | socket工厂类类名,通过设置该属性可以覆盖提供者默认的实现,必须实现javax.net.SocketFactory接口 |
mail.stmp.socketFactory.port | int | 指定socket工厂类所用的端口号,如果没有规定,则使用默认的端口号 |
mail.smtp.socketFactory.fallback | boolean | 设置为true时,当使用指定的socket类创建socket失败后,将使用java.net.Socket创建socket,默认为true |
mail.stmp.timeout | int | I/O连接超时时间,单位为毫秒,默认为永不超时 |
其他几个协议也有类似的一系列属性,如POP3的mail.pop3.host、mail.pop3.port以及IMAP的mail.imap.host、mail.imap.port等。更详细的信息请查看com.sun.mail.smtp、com.sun.mail.pop3和com.sun.mail.imap这三个包的Javadoc:http://java.sun.com/products/javamail/javadocs/index.html。
Session是一个很容易被误解的类,这归咎于混淆视听的类名。千万不要以为这里的Session像HttpSession一样代表真实的交互会话,但创建Session对象时,并没有对应的物理连接,它只不过是一对配置信息的集合。Session的主要作用包括两个方面:
1)接收各种配置属性信息:通过Properties对象设置的属性信息;
2)初始化JavaMail环境:根据JavaMail的配置文件,初始化JavaMail环境,以便通过Session对象创建其他重要类的实例。
所以,如果把Session更名为Configure也许更容易理解一些。JavaMail提供者在Jar包的META-INF目录下,通过以下文件提供了基本配置信息,以便session能够根据这个配置文件加载提供者的实现类:
javamail.providers和javamail.default.providers;
javamail.address.map和javamail.default.address.map。
下面是Sun提供者java.mail.default.providers文件的配置信息(位于mail.jar中):
1
2
3
4
5
6
7
8
9
|
# JavaMail IMAP provider Sun Microsystems, Inc
protocol=imap; type=store; sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc;
protocol=imaps; type=store; sun.mail.imap.IMAPSSLStore; vendor=Sun Microsystems, Inc;
# JavaMail SMTP provider Sun Microsystems, Inc
protocol=smtp; type=transport; 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; sun.mail.pop3.POP3Store; vendor=Sun Microsystems, Inc;
protocol=pop3s; type=store; 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实例:
1
2
3
|
Properties props = System.getProperties();
props.setProperty(
"mail.transport.protocol"
,
"smtp"
); …
Session session = Session.getDefaultInstance(props);
|
邮件操作只有发送或接收两种处理方式,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入参指定邮件协议、邮件服务器、端口、用户名和密码信息,请看下面的代码:
1
2
3
|
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() 方法,就能使用类的 缺省 版本:
1
|
Transport.send(message);
|
或者,您也可以从针对您的协议的会话中获得一个特定的实例,传递用户名和密码(如果不必要就不传),发送消息,然后关闭连接。
1
2
3
4
5
|
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 使用什么协议:
1
2
3
|
// Store store = session.getStore("imap");
Store store = session.getStore(
"pop3"
);
store.connect(host, username, password);
|
连接到 Store 之后,接下来,您就可以获取一个 Folder,您必需先打开它,然后才能读里面的消息。
1
2
3
|
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() 的输出却包含消息头。
1
|
System.out.println(((MimeMessage)message).getContent());
|
同样的,你还可以通过相应的API获取邮件的发信人、收信人、标题等内容:
1
2
3
4
5
6
7
|
String from = InternetAddress.toString(msg.getFrom());
String replyTo = InternetAddress.toString(msg.getReplyTo());
String to = InternetAddress.toString(msg.getRecipients(Message.RecipientType.TO));
String subject = msg.getSubject();
Date sentDate = msg.getSentDate();
Date receDate = msg.getReceivedDate();
Enumeration headers = msg.getAllHeaders();
|
注意:此种方式获取到的from、to、subject以及附件名字如果包含非英文字符,则可能为乱码,因此需要进行转码后才能正确显示,详细情况请参见4. 8乱码处理章节。
一旦读完邮件,要关闭与 folder 和 store 的连接。
1
2
|
folder.close(aBoolean);
store.close();
|
传递给 folder 的 close() 方法的 boolean 表示是否清除已删除的消息从而更新 folder。
一旦获得 Session 对象,就可以继续创建要发送的消息。这由 Message 类来完成。因为 Message 是个抽象类,您必需用一个子类,多数情况下为 javax.mail.internet.MimeMessage。MimeMessage 是个能理解 MIME 类型和头的电子邮件消息,正如不同 RFC 中所定义的。虽然在某些头部域非 ASCII 字符也能被译码,但 Message 头只能被限制为用 US-ASCII 字符。
要创建一个 Message,请将 Session 对象传递给 MimeMessage 构造器:
1
|
MimeMessage message =
new
MimeMessage(session);
|
注意:还存在其它构造器,如用按 RFC822 格式的输入流来创建消息。
一旦获得消息,您就可以设置各个部分,因为 Message 实现 Part 接口(且 MimeMessage 实现 MimePart )。设置内容的基本机制是 setContent() 方法,同时使用参数,分别代表内容和 mime 类型:
1
|
message.setContent(
"Hello"
,
"text/plain"
);
|
但如果,您知道您在使用 MimeMessage,而且消息是纯文本格式,您就可以用 setText() 方法,它只需要代表实际内容的参数,( MIME 类型缺省为 text/plain):
1
|
message.setText(
"Hello"
);
|
后一种格式是设置纯文本消息内容的首选机制。至于发送其它类型的消息,如 HTML 文件格式的消息,我们首选前者。
用 setSubject() 方法设置 subject(主题):
1
|
message.setSubject(
"First"
);
|
下面的代码演示了创建一个简单邮件信息的过程:
1
2
3
4
|
Message msg =
new
MimeMessage(session);
msg.setSubject(
"Test Title"
);
msg.setText(
"How are you!"
);
msg.setSentDate(
new
Date());
|
一旦您创建了 Session 和 Message,并将内容填入消息后,就可以用 Address 确定信件地址了。和 Message 一样,Address 也是个抽象类。您用的是 javax.mail.internet.InternetAddress 类。
若创建的地址只包含电子邮件地址,只要传递电子邮件地址到构造器就行了。
1
|
Address address =
new
InternetAddress(
"president@whitehouse.gov"
);
|
若希望名字紧挨着电子邮件显示,也可以把它传递给构造器:
1
|
Address address =
new
InternetAddress(
"president@whitehouse.gov"
,
"George Bush"
);
|
需要为消息的 from 域和 to 域创建地址对象。除非邮件服务器阻止,没什么能阻止你发送一段看上去是来自任何人的消息。
一旦创建了 address(地址),将它们与消息连接的方法有两种。如果要识别发件人,您可以用 setFrom() 和 setReplyTo() 方法。
1
|
message.setFrom(address)
|
需要消息显示多个 from 地址,可以使用 addFrom() 方法:
1
2
|
Address address[] = ...;
message.addFrom(address);
|
若要识别消息 recipient(收件人),您可以使用 addRecipient() 方法。除 address(地址)外,这一方法还请求一个 Message.RecipientType。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。