JavaMail学习6 发送邮件

来源:互联网 发布:焦作网络诈骗 编辑:程序博客网 时间:2024/06/05 00:55

1.JavaMail API中提供了一些专门的类来实现邮件发送功能,JavaMail应用程序开发人员只需要使用JavaMailAPI中的少数几个类就可以完成邮件的发送,sun公司也提供了一个用于查询DNS信息的JNDI服务程序,我们只要通过使用JNDIapi调用这个DNS查询的JNDI服务器程序,就可以或得某个域中的邮件服务器。

 

2.邮件发送API的体系结构

JavaMail API中定义了一个java.mail.Transport类,它专门用来执行邮件发送任务,这个类的实例对象封装了某种邮件发送协议的底层实施细节,应用程序调用这个类中的方法就可以把Message对象中封装的邮件数据对象发送指定的SMTP服务器,使用JavaMail发送邮件时涉及到的API之间的工作关系如下:

个各类的功能及相互关系如下:

1.从Session对象中获得实现了某种邮件发送协议的Transport对象;

2.使用Session对象创建Message对象,并调用Message对象的方法封装邮件数据。

3.连接指定的SMTP服务器,调用Transport对象中的邮件发送方法发送Message对象中封装的邮件数据

 

3.Session类

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

 3.1 getInstane与getDefaultInstance方法:

getInstane与getDefaultInstance方法是Session类中的静态方法,它们都可用于获得Session类的实例对象,由于Session类的构造函数是私有的,所以,应用程序必须调用getInstane或getDefaultInstance静态方法获得Session实例对象,getInstane与getDefaultInstance方法个有两种重载形式,它们的语法定义如下:

~~public static Session getInstance(java.util.Properties props);

~~public static Session getInstance (java.util.Properties props,Authenticator authenticator);

~~public static Session getDefaultInstance(java.util.Properties props);

~~public static Session getDefaultInstance(java.util.Properties props,Authenticator authenticator);

 getInstane与getDefaultInstance方法的区别在于getDefaultInstance返回一个Session对象后,先把这个Session对象安装成一个默认的Session对象,以后每次调用getDefaultInstance方法都将返回这个默认的Session对象,而getInstane方法则是每次调用后都将返回一个新的Session对象,两个方法都将接收一个Properties对象作为参数,

Properties对象中保存了实例化Session对象所需的应用程序环境信息,以及客户端与邮件服务器建立连接所必须的会话信息,这些信息被 称为JavaMail属性,他们的属性名作为Properties对象的关键字进行保存。下面介绍了一些常用JavaMail的属性:

mail.smtp.host:指定连接的服务器主机名。

mail.transport.protocol:指定采用的邮件发送协议。

mail.store.protocol:指定接收的邮件协议

mail.smtp.auth:指定客户端是否向邮件服务器提交认证

第二个方法除了接收一个Properties对象作为参数外,还接收一个Authenticator对象作为参数,Authenticator主要用于提供用户认证信息,调用第二个方法创建Session对象时,将把作为第二个参数传入的Authenticator对象注册到该Session对象中,以后使用这个Session对象的JavaMail客户端程序要向邮件服务器提交认证信息时,将调用该Session对象中注册的Authenticator对象,从中获得用户认证信息后传递给邮件服务器

 3.2 getTransport方法:

getTransport方法用于返回实现了某种具体邮件服务器协议的Transport对象,Transport对象对象可以完成底层的邮件发送细节,getTransport方法有多种重载形式,其中常用的两个语法定义如下:

~~public Transport getTransport();

~~public Transport getTransport(java.lang.String propocol);

第一个方法返回的Transport对象,将根据Session对象中的mail.transport.protocol属性指定邮件协议进行创建,第二个方法返回的Transport对象,将根据参数protocol指定的邮件协议进行创建,Transport类是一个抽象类,两个方法返回的Transport对象实际上都是实现了某种邮件传输协议的Transport子类的实例对象。

3.3 getStore方法:

getStore方法用于返回实现了某种具体邮件接收协议的Store对象,Store对象可以完成底层的邮件接收协议,getStore方法有多重重载形式,它们的语法定义如下:

~~public Store getStore();

~~public Store getStore(java.lang.String propocol);

 第一个方法返回的Store对象将根据mail.transport.protocol属性指定的邮件协议进行创建,第二个方法返回的Store对象,将根据参数propocol指定的邮件协议进行创建,与Transport对象一样,Store类也是一个抽象类,这两个方法返回的Store对象实际上都是实现了某种邮件服务器协议的Store对象。

3.4 setDebug方法:

setDebug方法用于打开JavaMail API的调式功能,它们的语法定义如下:

~~public void setDebug(boolean debug);

当调用setDebug方法并将其参数设置为true时,JavaMail API将把其运行过程和邮件服务器的交互命令信息输入到运行窗口,这个功能对JavaMail的调试功能非常有用。

 4.Transport类:

javax.mail.Transport类继承了java.mail.Service类,它用于连接SMTP服务器,并把包含在Message对象中的邮件数据发送到SMTP服务器。Transport类是一个抽象类,在程序中运行的实际是其具体的实现之类的实例对象,不同的实现之类实现不同的邮件发送协议,Sun公司在mail.jar包提供了一个com.sun.mail.smtp.SMTPTransport类,这个类就是实现了SMTP协议的底层细节的Transport的子类,其实我们在编写邮件发送协议的时候通常并不需要这个Transport类的子类名称,调用Session类中的getTransport方法就可以获得实现了某种传输协议的Transport子类的实例对象,例如,将mail.transport.protocol属性设置为SMTP时,getTransport方法将创建并返回SMTPTransport类的实例对象,下面是Transport类的常用方法:

4.1  connect方法:

connect方法用于建立与邮件服务器的连接,它有三中重载形式,只要语法定义如下:

~~public void connect();

~~public void connect(String host,String user,String password);

~~public void connect(String host,int port,String user,String password);

这些方法实际上都是从java.mail.Service类中继承的,第一个方法用于保存在Session对象中的与网络相关的JavaMail属性连接邮件服务器,第三个方法使用指定的邮件主机名,用户名,和密码连接邮件服务器,连接的端口号选择邮件的默认端口号

 4.2 sendMessage方法:

sendMessage方法用于向指定的邮件地址发送邮件,它的语法定义如下:

~~public abstract void sendMessage(Message msg , Address[] address);

 参数msg指定代表邮件内容的Message对象,参数address指定了一个代表邮件地址的Address类型的数组,这个数组中可以指定一个或多个发件人的地址,因此sendMessage可以用来向多个邮件地址同时发送一封相同的邮件,Transport对象与邮件服务器建立连接后,可以在同一个连接上多次调用sendMessage方法,这样就可以在一个连接上向邮件服务器发送多次邮件。

sendMessage是一个非静态方法,它必须得到Transport实例对象后才可以调用,sendMessage方法在发送邮件前不会自动调用Message.saveChanges()方法,JavaMail必须在调用这个方法前,调用代表被发送邮件的Message对象的saveChanges()方法。

4.3 close()方法:

close()方法用于断开与邮件服务器的连接,它的语法定义如下:

~~public void close();

close()方法也是从java.mail.Service类中继承的

4.4 send方法:

 send方法提供了一种发送邮件的简单方式,它是Transport类中的静态方法,有两种重载方式,它们的语法定义如下:

~~public static void send(Message msg);

~~public static void send(Message msg,Address[] address);

 应用程序可以直接调用Transport.send()方法发送邮件,send()方法执行邮件发送任务时,它首先从参数Message对象中获得Session对象(创建MimeMessage对象时为其构造方法传入Session对象)

 ,然后调用Session.getTransport方法获得用于发送邮件的Transport实例对象,接着在使用保存在Session对象中的与网络连接相关的JavaMail属性,调用Transport对象的connect方法连接方法连接邮件服务器,然后调用Transport对象的sendMesssage方法发送邮件,最后调用close方法断开与邮件服务器的连接。可见,send方法内部一次调用了getTransport,connect,sendMessage,和close方法,它可以作为发送邮件的一种简单方法。

由于第一个send方法中没有指定收件人,所以,它将调用传入的Message参数对象的getAllRecipients()方法,从中获得邮件消息内定义的所有收件人,然后把邮件发送给这些收件人;第二个send方法把邮件发送给参数address中指定的邮件列表,而不会考虑邮件消息内定义的收件人地址。

send方法是一个静态方法,它可以直接通过Transport类进行调用,send方法在发送邮件前都会调用Message.saveChanges()方法将保存在Message对象中的数据生成MIME邮件消息内容,send方法的缺点是每调用一次,都会与邮件服务器建立建立和断开连接即再一次网络连接上只发送一封邮件,如果要向同一个邮件服务器连续发送多封邮件,发送每封邮件时都要与邮件服务器建立和断开网络连接。

 4.5 邮件发送程序的编程实例:

使用JavaMail API发送邮件只需要执行如下3步:

1.创建包含邮件服务器的网络连接信息的Session对象

2.创建代表邮件内容的Message对象

3.从Session对象中获得Transport对象,并调用它的方法发送Message对象

 

下面来编写一个客户端邮件发送程序:

 

package com.jt.mail;

import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.mail.internet.MimeMultipart;

/**
 * @author jt
 *2016-1-19 下午9:04:41
 *邮件发送程序
 */
public class HtmlMessageSender {
 //定义建立连接的信息
 String protocol="smtp";
 //String server="smtp.sina.com";
 String from="邮件地址";
 String to="邮件地址";
 String subject="Mail test";
 String body="<a href='http://www.baidu.com'>欢迎大家访问我的网站</a><br/>" +
   "<img src='E:\\我的照片\\桂林\\aff\\jt.jpg'>";
 
 
 
 
 /**
  * @param args
  * @throws MessagingException
  */
 public static void main(String[] args) throws MessagingException {
  String server="smtp.sina.com";
  String user="邮箱用户名";
  String password="密码";
  
  HtmlMessageSender htmlSender=new HtmlMessageSender();
  Session session=htmlSender.createSession();
  MimeMessage message=htmlSender.createMimeMessage(session);
  
  //获得Transport对象,并连接邮件服务器发送邮件
  Transport trnsport=session.getTransport();
  trnsport.connect(server,user,password);
  trnsport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
  trnsport.close();
 }

 //创建Session对象
 public Session createSession(){
  Properties props=new Properties();
  props.setProperty("mail.transport.protocol", protocol);
  //props.setProperty("mail.smtp.host", server);
  /**
   * 必须将mail.smtp.auth的属性设置为true,SMTPTransport对象才会
   * 向SMTP服务器提交用户认证信息,这个信息可以从JavaMail的javadocs
   * 文档中的com.sum.mail.smtp包的帮助页面中可以查到
   */
  props.setProperty("mail.smtp.auth", "true");
  Session session=Session.getDefaultInstance(props);
  session.setDebug(true);
  return session;
 }
 
 //创建邮件的Mimessage对象
 public MimeMessage createMimeMessage(Session session) throws MessagingException{
  MimeMessage msg=new MimeMessage(session);
  //设置邮件头部的发件人信息
  msg.setFrom(new InternetAddress(from));
  //设置邮件头部的收件人信息
  msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
  //设置邮件的主题
  msg.setSubject(subject);
  
  //创建MIME消息体对象,消息体中的内容是关联的
  MimeMultipart multipart=new MimeMultipart("related");
  //创建消息体的body部分
  MimeBodyPart htmlBody=new MimeBodyPart();
  htmlBody.setContent(body,"text/html;charset=gb2312");
  multipart.addBodyPart(htmlBody);
  
  //创建图片附件信息
  MimeBodyPart gitBody=new MimeBodyPart();
  FileDataSource ds=new FileDataSource("E:\\我的照片\\桂林\\aff\\1.jpg");
  gitBody.setDataHandler(new DataHandler(ds));
  gitBody.setContentID("1.jpg");
  multipart.addBodyPart(gitBody);
  
  msg.setContent(multipart);
  msg.saveChanges();
  return msg;
 }
 
}

 4.5 Authenticator类的应用:

在JavaMail中除了可以通过Transport.connect(host,user,password)方法在连接SMTP服务器时直接传递用户认证信息,还可以借助Authenticator类来获得用户认证信息。

 4.5.1 Authenticator类:

Authenticator类用于代表一个可以对外提供用户认证信息的对象,它提供的用户认证信息封装在一个PasswordAuthenticator类型的对象中,调用getInstance(java.util.Properties props,Authenticator authenticator)方法创建Session对象时,将把第二个参数传入的Authenticator对象注册到该Session对象中,以后,使用这个Session对象的JavaMail客户端程序要向邮件服务器提交认证信息时,将调用Session对象中注册的Authenticator对象,从中获得用户认证信息后传递给邮件服务器。

Authenticator类最常用的一个方法定义如下:

~~protected PasswordAuthenticator getPasswordAuthenticator();

getPasswordAuthenticator方法用于对外返回一个PasswordAuthenticator对象,这个PasswordAuthenticator对象中封装了用户用户认证信息(用户名和密码)。Authenticator类是一个抽象类,传递给getInstance方法的Authenticator对象只能是其实现之类的实例对象。Authenticator类中定义的getPasswordAuthenticator方法返回值为null,Authenticator之类必须覆盖这个方法,实现之类在覆盖的getPasswordAuthenticator方法中,需要将用户认证信息封装在一个getPasswordAuthenticator对象中并作为返回值返回。Authenticator类的实现之类通常留给JavaMail应用程序开发人员去编写,以便最终可以由应用开发人员来决定具体如何获取和提供用户认证信息。Authenticator类只是提供了获取用户认证信息的方法,即Authenticator类本身不包含用户认证消息,它只是提供了创建用户认证信息的方法。这就好比你要吃面包,我不是直接给你面包,而是给你安排一位会做面包的师傅,按照这种方式设计的程序将具有低耦合和高内聚的灵活性,例如,如果你想吃欧洲面包,那么,我给你安排一位欧洲面包的面包师,如果你想吃亚洲面包,我则给你安排一位亚洲面包师。关于如何制作亚洲面包和欧洲面包的过程,则封装在面包师的大脑中和由面包师直接去完成,你我都不用去考虑。

扩展:(上面提到了抽象类,那么我们就来复习一下抽象和接口的区别:

面向对象设计的重点在抽象,那么Java接口和Java抽象类就有他必然的存在性了。Java接口(interface)和Java抽象类(abstract class)代表的就是抽象类型,就是我们需要提出的抽象层的具体表现形式。OOP面向对象编程就是,如果要提高程序的复用性,增加程序的可维护性,可扩展性,就是面向接口的编程,面向抽象的编程,正确的使用接口,抽象类这些有用的抽象类型作为你结构层次的顶层。

区别:java接口和java抽象类的最大一个区别就是Java抽象类可以提供某些方法的部分实现,而Java接口不可以(接口中只能定义方法,不能写具体的实现,而abstract calss则是可以只定义方法,又可以有具体的实现方法,这也是Java抽象类的优点,这个优点非常有用,如果向一个抽象类里加入一个新的具体的方法时,那么它所有的之类都一下子都得到了这个新方法,而java接口做不到这一点,如果想一个java接口加入一个新方法,所有实现这个接口的之类就必须实现这个方法,否则无法编译通过,这显然是java接口的缺点。一个抽象类的实现只能由这个抽象类的之类给出,也就是说,这个实现处在抽象类所定义出的继承的等级结构中,而由于java语言的单继承性,所以抽象类作为类型定义的工具效率就大打折扣了,这一点java接口的优势就体现出来了,任何一个实现java接口的所有方法的之类都可以具有这个接口类型,而一个类可以任意实现多个接口,从而这个类就具有了多种类型,(使用抽象类,那么继承这个抽象类的之类的类型就比较单一,因为之类只能是单继承抽象类,而之类能同时实现多个接口,所以类型比较多,java接口和java抽象类都可以定义对象,但是只能用他们的具体实现类来进行实例化,可以总结得出java接口是混合类型的理想工具,混合类型表名一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为)

结合上面java接口和java抽象类的各自有点,经典的设计模式就出来了:声明类型的工作仍然有java接口来做,但同时给出一个java抽象类且实现了这个接口,而其他属于这个抽象类型的具体实现类可以选择实现这个java接口,也可以选择继承这个抽象类,也就是说在层次结构中,java接口在最上面,下来就是java抽象类,这下两个的优点都发挥最大极致了,这个模式就是缺省适配模式,在java语言 API中运用了这种模式,而且全部遵循一定的命名规范:Abstarct+接口名(A extends AbstractB implements interfaceC

那么A可以选择实现(@Override)抽象类B中的方法,也可以选择不实现)

java接口和java抽象类的存在就是为了具体类的实现和继承,如果你准备写一个类去继承另一个类的话,那么你的程序设计就有很大问题,java抽象类就是为了继承而存在的,它的抽象方法就是为了强制之类必须去实现的,使用java抽象类和java接口进行变量的类型声明,参数的类型声明,方法的返还类型说明,以及数据类型的转换,而不要用java具体的类进行变量的声明,参数的类型声明,方法的返还说明,数据类型的转换等。

4.5.2  PasswordAuthentication类:

PasswordAuthentication类用于封装用户认证信息(用户名和密码),其中定义了一些如下的方法:

~~public PasswordAuthentication(String username ,String password)唯一的公有构造方法,根据指定的用户名和密码创建PasswordAuthentication实例对象

~~public String getUserName();getUserName()用于返回PasswordAuthentication对象中保存的用户名。

~~public String getPassword();getPassword方法用于返回PasswordAuthentication对象中保存的密码。

 

 4.5.3 Authenticator类的编程实例:

如果调用Transport类中的无参数connect方法连接邮件服务器,或则调用Transport.send静态方法直接发送邮件,当邮件服务器需要认证信息时,这两个方法都不能直接向邮件服务器提供用户认证信息时,在这种情况下,就需要在创建Session对象时向其中注册一个Authenticator之类的实例对象,以便connect方法和send方法从中获得用户认证信息后传递给邮件服务器。

如果应用程序希望通过Authenticator类的方式向邮件服务器提交认证信息,可以按照以下步骤和思路进行编写:

1.编写抽象类Authenticator类的实现之类,在之类中覆盖父类的getPasswordAuthenticator方法,,并返回用户封装的用户名和密码的PasswordAuthentication对象

2.调用Session.getInstance(Properties props,Authenticator authenticator)方法获得Session类的实例对象,并把Authenticator对象注册到Session对象中。

3.使用Session对象创建代表邮件消息内容的Message对象。

4.调用Transport.send静态方法发送Message对象中的邮件消息内容。send方法将从Message对象中获得Session对象的引用,然后调用该Session对象中注册的Authenticator对象,从中获得用户的认证信息之后传递给邮件服务器。在这里,也可以不用调用Transport.send静态方法直接发送邮件,而是先调用Session对象的getTransport方法获得一个Transport实例对象,接着调用Transport对象的sendMessage方法发送邮件,最后调用Transport对象的close方法关闭与邮件服务器的连接,Transport对象的无参connect方法也调用Session对象中注册的Authenticator对象,从中获得用户认证消息并传递给邮件服务器。

 

下面是Authenicator的编程实例:

package com.jt.mail;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;

/**
 * @author jt
 *2016-1-22 下午8:52:04
 *编写MyAuthenticator实现Authenticator类
 */
public class MyAuthenticator extends Authenticator{
 
 String username=null;
 String password=null;
 
 //通过MyAuthenticator类的构造方法接收外部传递的用户信息
 public MyAuthenticator(String username ,String password){
  this.username=username;
  this.password=password;
 }
 //覆盖父类的getPasswordAuthenticator方法
 @Override
 protected PasswordAuthentication getPasswordAuthentication() {
  //使用外部传入的用户名和密码创建PasswordAuthentication对象实例
  return new PasswordAuthentication(username,password);
 }
}

 

package com.jt.mail;

import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;

/**
 * @author jt
 *2016-1-22 下午8:59:12
 *测试Authenticator类
 */
public class AuthenticatorDemo {

 /**
  * @param args
  * @throws MessagingException
  * @throws AddressException
  */
 public static void main(String[] args) throws AddressException, MessagingException {
  String smtpServe="smtp.sina.com";
  String protocol="smtp";
  String username="jiangtao7913";
  String password="13714834509yi";
  String from="jiangtao7913@sina.com";
  String to="651101060@qq.com";
  String subject="Authenticator Demo";
  String body="authenticator Demo";
  
  //创建Session对象
  Properties props=new Properties();
  props.setProperty("mail.host", smtpServe);
  props.setProperty("mail.transport.protocol", protocol);
  props.setProperty("mail.smtp.auth", "true");
  
  MyAuthenticator myAutnenticator=new MyAuthenticator(username,password);
  Session session=Session.getDefaultInstance(props,myAutnenticator);
  //创建代表邮件的MimeMessage对象
  MimeMessage msg=new MimeMessage(session);
  msg.setFrom(new InternetAddress(from));
  msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
  msg.setSentDate(new Date());
  msg.setSubject(subject);
  msg.setText(body);
  //保存并生成邮件内容
  msg.saveChanges();
  
  /**
   * 由于Session对象中注册了Authenticator
   * 可以从Authenticator对象中获得用户认证信息,
   * 所以这里直接调用Transport.send类的静态方法发送邮件
   */
  Transport.send(msg);
  /**
   * 也可以使用下面的代码先创建Transport对象,
   * 在使用无参的connect方法连接邮件服务器和发送邮件
   * Transport transport=session.getTransport();
   * transport.sendMessage(msg,
   * msg.getRecipients(Message.RecipientType.TO));
   * transport.close();
   */
 }

}

扩张:在邮件处理系统中使用Authenicator类向邮件服务器提交认证信息时可以提高程序的可扩展性,使程序的认证方式变得更加灵活,应用程序可以通过查询数据库,或则弹出对话框的方式,甚至从一个加密文件中读取用户认证信息。当应用程序的开发者想改变程序的认证信息获取方式时,只需要重新编写一个Authenicator类的之类,并将这个之类的实例对象注册到Session对象中即可,这实际上是设计模式的策略模式,主程序将获取用户认证信息的方式委托给了另外一个对象(策略对象来完成),当获取用户认证信息的方式发生改变时,不用修改主程序,只需要编写一个新的策略对象。例如,对于以上程序,我们想要弹出一个输入对话框来收集用户的认证信息,只需要按上例子修改MyAuthenicator类,然后将它的实例对象注册到Session对象中就可以了。

package com.jt.mail;

import java.util.StringTokenizer;

import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.swing.JOptionPane;

/**
 * @author jt
 *2016-1-22 下午9:37:49
 *策略方式实现MyAuthenicator
 */
public class MyAuthenticator1 extends Authenticator{

 @Override
 protected PasswordAuthentication getPasswordAuthentication() {
  String username,password;
  String result=
    JOptionPane.showInputDialog("请输入用户名和密码,中间用','分隔");
  StringTokenizer st=new StringTokenizer(result,",");
  username=st.nextToken();
  password=st.nextToken();
  return new PasswordAuthentication(username,password);
 }
 
}

4.6 为邮件发送程序配置代理:

 有时候一些网络环境限定用户的计算机只能通过代理服务器访问Internet,代理服务器的工作机制如下:

 

 在使用代理服务器的情况下,用户使用网络客户端软件访问Internet上的服务器时,用户提交的请求都不会直接发送给目标主机,而是先发给代理服务器,代理服务器接收了用户的请求之后,在向目标主机发出这一请求。代理服务器接收到目标主机返回的数据后,将数据发送给最初发出这一条请求的主机,并将这些数据保存到代理服务器的缓存中,用户使用代理服务器的好处在于:

1.提高访问速度

2.代理服务器可以起到防火墙的作用

3.通过代理服务器访问一些不能直接访问的网站。

4.具有一定的隐身效果

随着每一种网络应用协议的工作方式不同,它们的代理实现机制也会不同,例如HTTP协议和FTP协议的工作机制有很大区别,它们的代理实现程序也有很大区别。现在常见的代理服务器同时支持HTTP 协议和FTP协议的代理,同一个代理服务器在不同的端口号上代理不同协议的请求,HTTP代理和FTP代理分别针对HTTP请求和FTP请求的专用代理,它们可以分别正常代理HTTP请求和FTP请求,如果将其他协议的请求也交给HTTP代理或FTP代理,代理服务器则不一定完成代理的功能。提示:某些网络应用协议与HTTP协议的工作机制非常相似,都是由客户端主动发送请求信息,服务器被动响应数据,这种网络应用协议使用HTTP代理服务器时也能正常工作。人们后来设计了一种通用的代理服务程序,称之为SOCKS代理,SOCKS代理可以处理各种网络协议的代理请求。

java虚拟机本身提供了网络代理的方面的支持,只要配置了socksProxyHost这个java虚拟机系统属性,这个虚拟机发出的所有Socket网络连接就不在直接连接到目标计算机,而是连接到socksProxyHost属性指定的代理服务器的默认代理端口1080,这就是说,一个java网络应用程序的代码不需要进行任何修改,只要在启动java虚拟机时设置了socksProxyHost系统属性,这个java网络应用程序就会使用socksProxyHost系统属性指定的代理服务器进行连接和通信,否则,这个网络程序按照通常的方式与目标计算机进行连接和通信。

如果网络环境限定我们只能通过代理服务器上网,那么我们在前面编写的javaMail程序也必须进行正确的代理设置后才能向外成功发送邮件,为JavaMail程序配置代理不需要修改程序,只需要在启动javaMail程序时将socksProxyHost系统属性设置为代理服务器的IP地址即可,如果代理服务器使用的端口号不是默认的1080,那么在启动javaMail程序还需要设置一个socksProxyPort系统属性,让socksProxyPort系统属性等于代理服务器监听的端口号。

 

4.7 SMTP服务器功能的邮件发送程序:

所谓的SMTP服务器功能的邮件发送程序,就是指邮件发送程序本身就像一台SMTP服务器那样直接对外发送邮件,不需要依赖其他的SMTP服务器对外发送程序,其工作过程如下:

从上图可以看出,具有SMTP服务器功能的邮件发送程序会根据收件人的地址域名,直接连接到该域的SMTP服务器和进行邮件发送,由于众多收件人地址通常都属于多个不同的域,所以具有SMTP服务器功能的邮件发送程序需要与多个域的SMTP服务器进行通信,由于在程序开发和安装时根本就无法预测众多收件人的地址,因此,具有SMTP服务器功能的邮件发送程序可能需要与哪些SMTP服务器进行通信也是不可预知的,这就导致了它在功能与纯碎的邮件客户端发送程序有两个重要的区别:

1.不能手工预先设置它所要连接的SMTP服务器,只能在发送邮件时根据收件人地址的域名临时向DNS服务器查询所要连接的SMTP服务器

2.在向其他接收邮件的SMTP服务器发送邮件时,其他的SMTP服务器不需要用户认证信息,道理很简单,一个SMTP服务器不可能在其他众多的SMTP服务器上开设账号,如果一个SMTP服务器需要其他SMTP服务器传递认证信息的话,那么其他SMTP服务器根本就无法与之通信。

从具有SMTP服务器功能的邮件发送程序与客户端邮件发送程序的功能区别上可以看到,前者与后者在编程上的最大区别就是要通过程序来自动获得收件人地址所在域的SMTP服务器和不用向其连接的SMTP服务器提交用户认证信息,要在程序中获得收件人地址所在域的SMTP服务器,这需要通过程序代码去查询DNS服务器并获得收件所在域的MX记录,Sun公司开发了一个用于查询DNS信息的JNDI服务程序,我们只要通过使用JNDI API调用这个用于DNS查询的JNDI服务程序,就可以获得某个域中的所有DNS信息,在编写JNDI API程序之前,我们必须对JNDI有个基本的认识。

4.7.2  JNDI的基本应用:

JNDI是JAVA Naming and Directory interface(java命名和目录接口)的缩写,它是应用程序提供命名和目录访问服务的API(Application Programming Interface 应用程序编程接口)

1.命名的概念和应用:

JNDI中的命名,就是将java对象以某个名称的形式绑定到一个容器环境中,然后调用容器环境(Context)的查找(lookup)方法又可以查找出某个名称所绑定的java对象。

也许会有点奇怪,自己创建java对象,为什么又把它查询出来?

在真实的项目应用中,通常由系统程序或框架程序先将资源对象绑定JNDI环境中,以后在该系统或框架中运行的模块程序就可以从JNDI环境中查找这些资源对象了。例如,Tomcat服务器在启动时可以创建一个连接到某种数据库系统的数据源对象(DataSource),并将该数据源(DataSource)对象绑定到JNDI环境中,以后在这个Tomcat环境中运行的servlet或JSP程序就可以直接JNDI环境中查询出这个数据源对象(DataSource)进行使用,而不用关心数据源对象是如何创建出来的,这种方式大大增加了系统的可维护性,当数据库系统的连接参数发生改变时,这只是Tomcat系统管理人员一个人要关心的事情,而与所有的应用程序开发人员无关。

容器环境(Context)本身就是一个java对象,它可以通过一个名称绑定到另外一个容器环境(Context)中,将一个Context对象绑定到另外一个Context对象中,这就形成了一个父子级联的关系,多个Context对象最终可以级联成一个树状结构,树中的每个Context对象中都可以绑定若干个Java对象,如下图所示:

 

 

 

图中的每个方框分别代表一个Context对象,他们绑定的名称分别为a,b,c,d,e,b和c是a的子Context,d是b的子Context,e又是d的Context,图中方框内的每个椭圆代表一个java对象,他们也都有绑定一个名称,这些名称分别为dog,pig,sheep等。在同一个Context内不能绑定两个相同名称的java对象,在不同的Context中可以出现同名的绑定对象,可见,Context树的级联结构与文件系统中的目录结构非常类似,Context与其中绑定的java对象的关系也非常类似于文件系统中的目录与文件的关系。

从上图可以看出,要想得到Context树中的java对象,必须先得到其所在的Context对象,只要得到Context对象,就可以调用它的查询(lookup)方法来获得绑定对象。另外,调用某个Context对象的lookup方法也可以获得Context树中的任意一个Context对象,这只需要在lookup方法中指定相应的Context路径即可,在JNDI不存在“根”Context的概念,也就是说执行JNDI操作不是从一个“根”Context对象开始,而是可以从Context树中的任意一个Context开始,无论如何,程序必须获得一个作为操作入口的Context对象后才能执行各种JNDI命名操作。,为此,JNDI API中提供了一个InitialContext类创建用作JNDI命名操作的入口Context对象,Context是一个接口,Context对象实际上是Context某个实现类的实例对象,选择这个具体的Context实现类来创建其实例对象的过程由一Context工厂类来实现,这个工厂类的类名可以通过JNDI的环境属性java.naming.factory.initial指定,也可以根据Context的操作方法的url参数的Scheme来选择。

2 .  目录的概念与应用:

JNDI的目录(Directory)与文件系统中的目录在概念上有很大不同,JNDI中的目录(Directory)是指将一个对象的所有属性信息保存到一个容器环境中,JNDI的目录原理JNDI的命名原理非常相似,主要的区别在于目录容器环境中保存的对象的属性信息,而不是对象本身。所以,目录提供的是对属性的各种操作,事实上,JNDI的目录与命名往往是结合一起使用,JNDI API中提供的代表目录容器环境的类为DirContext,DirContext是Context的子类,显然它除了能完成目录相关的操作外,也能完成所有命名操作,DirContext是对Context的扩展,它在Context的基础上增加了对目录属性的操作功能,可以在其中绑定绑定对象的属性信息和查找对象的属性信息,JNDI中的目录(Directory)

的结构示意图如下所示:

上图中的每个最外层的方框分别代表一个DirContext对象,他们绑定的名称分别为a,b,b是a的子DirContext对象,上图中的各个最外层的方框内的小椭圆分别代表一个java对象,各个里层的方框分别代表一个对象的属性,从名称为a的DirContext中的内容可以看到,一个DirContext容器环境中既可以绑定对象本身,也可以绑定对象的属性信息,绑定的对象和绑定的对象属性完全是两个独立的事物,即使他们的绑定名称相同,他们操作也是完全独立的,另外,一个属性可以有多个属性值,例如,dog对象的category属性就是设置两个属性值:meat和pet,从名称为b的DirContext中的内容可以看到,一个DirContext容器环境中也可以只绑定对象的属性信息,而不绑定任何对象本身,与Context操作原理类似,JNDIAPI中提供了一个InitialDirContext类来创建用作JNDI命名与目录属性操作的入口。

 

3.用于DNS查询的JNDI服务程序:

JNDI API是面向应用程序开发人员的编程接口,它在运行时需要调用某个具体的JNDI服务器,JNDI API与JNDI服务器之间的关系犹如JDBC与JDBC驱动程序之间的关系,从JDK1.3开始,JDK就集中了JNDI API,从JDK1.4以及更高的JDK版本来开发DNS信息查询程序时,不需要下载和安装JNDI API和用于DNS查询的JNDI服务程序,Sun公司提供的用于查询DNS 信息的JNDI 服务程序,将某个域名的DNS信息以属性的形式绑定到代表该域名的DirContext对象上,下面是JNDI程序的例子:

package com.jt.mail;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/**
 * @author jt
 *2016-1-23 下午6:43:28
 *使用JNDI api获取DNS信息
 */
public class DNSQuery {

 /**
  * @param args
  * @throws NamingException
  */
 public static void main(String[] args) throws NamingException {
  /**
   * 第一个参数指定要查询 的域或主机名,第二个参数指定查询的DNS服务器
   * 为了程序的简单易读性省略了严格的参数错误检查
   */
  String domain=args[0];
  String dnsServer=args.length<2?" ":("//"+args[1]);
  
  //通过环境属性来指定Context的工厂类
  Hashtable ht=new Hashtable();
  ht.put(Context.INITIAL_CONTEXT_FACTORY,
    "com.sun.jndi.dns.DnsContextFactory");
  ht.put(Context.PROVIDER_URL, "dns:"+dnsServer);
  DirContext ctx=new InitialDirContext(ht);
  //分别获取包含所有属性和只包含MX属性的Attributes对象
  Attributes attrAll=ctx.getAttributes(domain);
  Attributes attrMx=ctx.getAttributes(domain,new String[]{"MX"} );
  
  /**
   * 上面的整段代码也可以用下面这段程序代码来替代,
   * 下面这段程序代码通过查询URL中的Scheme信息来自动选择Context的工厂类
   */
//  DirContext ctx1=new InitialDirContext();
//  Attributes attrAll1=ctx1.getAttributes("dns:"+dnsServer+"/"+domain);
//  Attributes attrMx1=ctx1.getAttributes("dns:"+dnsServer+"/"+domain,
//    new String[]{"MX"});
  System.out.println("打印出域:"+domain+"的Attributes对象中的信息:");
  System.out.println(attrAll);
  System.out.println("-------------");
  System.out.println("*打印只检索域*"+domain+"的MX记录的Attributes对象:");
  System.out.println(attrMx);
  System.out.println("---------");
  System.out.println("逐一打印出Attributes对象中的各个属性:");
  NamingEnumeration attributes=attrAll.getAll();
  while(attributes.hasMore())
  {
   System.out.println(attributes.next());
  }
  System.out.println("-----------");
  //直接调用get方法从attrMx集合检索MX属性
  Attribute attrMx1=attrAll.get("MX");
  System.out.println(attrMx1);
  
  System.out.println("----------");
  //获取MX属性的第一个值
  System.out.println("获取MX属性的第一个值:");
  String recordMX=(String)attrMx1.get();
  System.out.println(recordMX);
  //从MX属性的第一个值中提取邮件服务器地址
  System.out.println("从MX属性的第一个值中提取邮件服务器地址");
  String smtpServer=recordMX.substring(recordMX.indexOf(" ")+1);
  System.out.println(smtpServer);
 }

}

结果为:

4.编写具有SMTP服务器功能的邮件发送程序:

package com.jt.mail;

import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/**
 * @author jt
 *2016-1-23 下午7:53:28
 *
 */
public class SMTPSender {

 /**
  * @param args
  * @throws MessagingException
  * @throws AddressException
  * @throws NamingException
  */
 public static void main(String[] args) throws AddressException, MessagingException, NamingException {
  //下面是邮件要群发给的多个收件人地址
  String[] to={"",""};
  //创建Session对象
  Properties props=new Properties();
  //
  props.setProperty("mail.smtp.localhost", "mail.itcast.cn");
  Session session=Session.getInstance(props);
  session.setDebug(true);
  Message msg=createMessage(session);
  for(int i=0;i<to.length;i++)
  {
   sendMessage(session,msg,to[i]);
  }

 }

 public static Message createMessage(Session session) throws AddressException, MessagingException{
  String from="";
  String subject="test";
  String body="test!!";
  //创建代表邮件的MimeMessage对象,不包含收件人地址
  MimeMessage msg=new MimeMessage(session);
  msg.setFrom(new InternetAddress(from));
  msg.setSentDate(new Date());
  msg.setSubject(subject);
  msg.setText(body);
  return msg; 
 }
 
 public static void sendMessage(Session session,Message msg,String to) throws AddressException, MessagingException, NamingException{
  //设置邮件内容的收件人并生成邮件消息内容
  msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
  msg.saveChanges();
  
  //连接收件人地址所在的SMTP服务器
  Transport transport=session.getTransport();
  String domian=to.substring(to.indexOf("@")+1);
  String smtpServer=getSmtpServer(domian,null);
  transport.connect(smtpServer, null, null);
  transport.sendMessage(msg, msg.getRecipients(Message.RecipientType.TO));
  transport.close();
 }
 
 public static String getSmtpServer(String domian,String dns) throws NamingException{
  DirContext cts=new InitialDirContext();
  Attributes attrMx=null;
  if(dns!=null){
   attrMx=cts.getAttributes("dns:"+"//"+dns+"/"+domian,new String[]{"MX"});
  }else{
   attrMx=cts.getAttributes("dns:"+"/"+domian,new String[]{"MX"});
  }
  String recordeMX=(String)attrMx.get("MX").get();
  String smtpServer=recordeMX.substring(recordeMX.indexOf(" ")+1);
  return smtpServer;
  
 }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0 0
原创粉丝点击