上午课程开始:
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
大家要求知识回顾以及telnet发送邮件的过程。
回顾:
outlook要指定smtp服务器,在搜狐上开邮箱服务器就是搜狐的,登陆上搜狐邮件服务器后,如果要给新浪的朋友发邮件,
邮件是通过搜狐服务器发送给新浪服务器的(smtp),而不是通过outlook直接发送给新浪服务器的;
outlook经过搜狐发送是连接搜狐的smtp服务器,搜狐发给新浪也是发给了smtp服务器。
新浪smtp服务器收到邮件后把邮件存到硬盘上,接收者找pop3服务器来收邮件
outlook中设置sohu的smtp服务器地址通过网上帮助可以得到,搜狐是如何找到新浪的smtp服务器地址的呢?需要去问DNS
域名服务器来找到新浪的smtp服务器地址,DNS相当于一个大的数据库记录着ip地址对应的主机名,里面有很多种类别的信息,
最常见的是主机名对应的ip地址,还有的记录是记录一个域名的邮件服务器是什么,这叫做mx记录(mail box)。之所以可以
知道新浪的邮件服务器,是因为它们都在DNS中登记了,这样才可以找到它们……
一个邮件服务器是如何知道其他域的邮件服务器的? 靠的就是DNS服务器中的mx记录!
通常说的邮件服务器就是smtp,不是pop3,pop3的功能非常小,非常的有限。
新浪有两种smtp服务器,一种专门面对outlook个人用户,一种专门面对其他smtp邮件服务器
但是新浪为什么不把他的面向outlook的服务器登记成mx记录呢?因为这个登记成mx记录的邮件服务器是要求身份验证的,
如果其他服务器找到了这个服务器的话,还需要输入用户名和密码来验证,这是不符合邮件服务器之间消息传递的原理的,
smtp邮件服务器之间的联系是通过DNS的mx记录,它们之间的邮件的传送是不需要身份验证的!
发送一封邮件的步骤:
1 联上邮件服务器(分两种方式来作)
(1)outlook发送方式:查看别人的smtp地址,并在别人的邮件服务器上面注册帐户才行
(2)邮件服务器发送方式:把自己本地作为一个邮件服务器来向指定的邮件服务器发送邮件,也就是使用nslookup命令
来获得某个域的邮件服务器
使用nslookup命令步骤如下:(nslookup命令后可以指定一个服务器的ip地址)
nslookup->(在里面敲help的话回车后可以出现帮助信息)
set type=mx-> (查找DNS的mx记录)
sina.com.cn-> 可以得到新浪的邮件服务器!
set type后面的mx是邮件服务器,还有其他类型
ptr:ip地址对应的主机名,ns表示域名服务器,a表示主机名对应的ip地址
输入 sina.com 211.162.31.8->可以得到新浪的ip地址
server 211.162.31.8-> 设置自己的DNS服务器。
这时候再set type=mx后再输入sina.com后就是查找的新浪的邮件服务器
有三个:
freemx1.sinamail.sina.com.cn internet address = 202.108.3.230
freemx2.sinamail.sina.com.cn internet address = 202.108.3.231
freemx3.sinamail.sina.com.cn internet address = 202.108.3.232
每次执行的时候这三个顺序可能不一样。
这样就通过连接上nslookup得到了新浪的邮件服务器的ip地址了。
接下来是给新浪发送邮件(不需要用户名和密码,因为是把自己作为一个smtp服务器来给新浪的
smtp服务器传送邮件)
当然可以通过这种方式群发垃圾邮件给新浪,但是不久之后新浪就会把你的ip加入到黑名单中……就自然
无法发送邮件了,而且不单如此,新浪还会把你的ip告诉其他邮件服务器厂商,都封掉你的ip
先exit退出nslookup命令
然后telnet freemx1.sinamail.sina.com.cn 25->
ehlo mail.itcast.cn->
mail from: <
[email protected]>-> 指定假想的发件人
rcpt to: <
[email protected]> 指定收件人
data-> 邮件数据
from:
[email protected]
to:
[email protected]
subject: hello
你好!!
.-> 这个“.”表示邮件数据结束
然后bye->
quit->就可以了。
----------------------------------------
怎样发送一封邮件?
从Session中获得指定邮件发送协议的Transport对像
把封装了邮件数据的Message对象传递给Transport对象
调用Transport对象中的方法,连接指定的smtp服务器,并执行邮件发送任务。
public static Session getInstance(java.util.Properties props)
public static Session getDefaultInstance(java.util.Properties props,Authenticator authenticator)
注意Authenticator中包含了
Properties对象中保存了实例化的Session对象所需要的应用程序的环境信息,以及客户端和邮件服务器链接的会话
信息,也叫作javaMail属性,它们被作为Properties对象的关键字来引用。
Properties对象中的常用属性如下:
mail.smtp.host 指定连接的邮件服务器的主机名
mail.transport.protocol 指定采用的邮件发送协议
mail.store.protocol 指定采用的邮件接收协议
mail.smtp.auth 指定客户端是否向邮件服务器提交认证
-----------------------------
现在说点题外话:
import java.util.Collection
class Test
{
public static void main(String[] args) throws Exception
{
//Class.forName()是加载任何类型的类,然后要new出一个实例来
Collection c = (Collection)Class.forName(args[0]).newInstance();
c.add(new Object());
c.add(new Object());
System.out.println(c.size());
}
};
也就是说,args[0]那里可以动态的加载各种集合类,比如java.util.HashSet
如果执行的是java Test java.util.HashSet的话,结果是2。
如果执行的是java Test java.util.ArrayList的话,结果是2。
---------------
如果改造一下程序,如下所示:
class Person
{
public boolean equals(Object obj)
{
return true ; //这样一来的话,任何一个Person的实例对象都和其他的对象具有相等的equal值,
} //而且任何一个Person的实例对象都具有相同的hashCode值叫做12345
public int hashCode()
{
return 12345;
}
};
import java.util.Collection
class Test
{
public static void main(String[] args) throws Exception
{
//Class.forName()是加载任何类型的类,然后要new出一个实例来
//这里是用接口来引用一个具体实现子类的对象!
Collection c = (Collection)Class.forName(args[0]).newInstance();
c.add(new Person());
c.add(new Person());
System.out.println(c.size());
}
};
//这也就是典型的面向接口编程,很灵活,也较易扩展。
这时候,
如果执行的是java Test java.util.HashSet的话,结果是1。
如果执行的是java Test java.util.ArrayList的话,结果是2。
解释:向HashSet里面放对象是这样一个过程,先根据hasCode方法来找到内存位置,如果那个位置有
对象的话,那么我再利用equals方法去比较这两个对象,如果返回true的话,就不往里面放了,否则的话,若
返回false,则覆盖HashSet中的那个内存位置的原来的对象。
应用场景:一个牛人写了这个程序,另外n个菜鸟使用这个程序,但是提供一些自己的类(比如上面的ArrayList和HashSet)
插入到这个程序中,结果就出现了千姿百态的效果,这就类似与我们这些使用人家统一的一套框架从而产生出我们各个公司的不同的
项目的结果。
大家的运行流程都一样,都是往集合里面插入两个对象,但是每家的集合不同,这就类似大家都是Web项目,
都是提交表单和处理表单,都是在表单校验错误时,回到表单注册页面显示错误,既然这样,有人就把这个
固定的事情给包办了,但是又允许各家提供自己的表单页面,例如儿童用品店的,医院的,学校的页面等等。
也就是说,写框架的人才能很好的理解面向接口的编程具有很好的扩展性,上面这个程序可以被多个用户去
扩展使用
------------------------------------
getTransport方法:相当于DriverManager的根据URL来找Collection,其重载方法有7、8个,下面
两个比较的典型
public Transport getTransport()
public Transport getTransport(java.lang.String protocol)
Transport类是一个抽象类,返回的实际是其具体的实现子类,不同的子类实现不同的协议。第一个方法返回的是
Transport对象,是实现Session对象中的mail.transport.protocol属性指定的邮件协议的Transport子类
的实例对像;第二个方法返回的Transport对象是实现参数protocol指定的邮件协议的Transport子类的实例对象。
getStroe方法:实现具体协议的邮件接收对象
也和上面的getTransport()方法具备同样的格式
-----------------
Transport类的常用方法:
connect方法
public void connect() :
public void connect(String host,String user,String password)
public void connect(String host , int port , String user,String password)
close方法:
public void close()
public static void send(Message msg);
public static void send(Message msg , Address[] addresses)
上面这两个send方法在发送邮件以前都会调用Message.saveChanges()方法,
如果要使用这个静态方法的话,那么msg中必须得包含有用户名和密码的信息,
必须使用session的getDefaultInstance(java.util.Properties props,Authenticator auth)
方法,这样session中才能使用用户名和密码
send方法很方便,只需要Message对象,然后直接调用静态方法send来发送,
---------------
如果不使用Transport的静态send方法来发送邮件的话,得这么作
Transport transport = session.getTransport();
transport.connect(smtpServer,username,password);
transport.sendMessage(message,message.getRecipients(Message.RecipientType.TO))
transport.close();
如果在之前调用了setDebug(true)方法的话会打印出运行后的调试具体信息来
------------------------
如果要使用Transport的静态send方法来发送邮件的话,需要Authenticator类
我们需要自己定义一个MyAuthenticator类来继承Authenticator类
在这个自己定义的类中需要手工覆盖那个getAuthenticatorPassword方法
MyAuthenticator my = new MyAuthenticator(username,password);
session.getDefaultInstance(session,my)
Message message = new MimeMessage(session);
然后就可以Transport.send(message);来进行了。
------------------------
再次说点题外话:
authenticate 认证 (比如验证用户名、密码之类)
authorization 授权 (认证通过后能够访问哪些地方叫做授予权限)
-----------------------------------------
编写跨代理的邮件发送程序:
java里面如何跨代理:
ServerSocket(对方ip,对方端口port)
java -d 可以配置代理服务器以及代理服务器端口
也就是java -d proxy port
java程序要过代理只需要在最后的java命令后用-d的方式来指定代理服务器和其端口就可以了,
这是为java虚拟机来配置的代理,以后的程序就会运行到这个代理上去。
我们是在为java虚拟机配置代理,而不是在为程序配置代理,但是一个java虚拟机上面通常只能运行
一个java程序,让java虚拟机的网络出口使用代理
socksProxyPort 默认端口是1080
java -DproxySet=true -DSocketsProxyHost=192.168.1.200 -DSocketsProxyPort=80 MyTest
用上述命令行来配置运行程序
当然也可以用下面的方法来设置系统属性
Properties d = System.getProperties();
d.setProperty
-------------------------------------------
-------------------------------------------
例子1的代码:
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Message;
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.MimeMultipart;
public class SendHtml
{
public static void main(String[] args) throws Exception
{
String smtpServer = "smtp.sina.com.cn";//邮件服务器主机名
String protocol = "smtp";//邮件传输协议
String username = "it315_test";//登陆用户名
String password = "123456";//登陆密码
String from = "
[email protected] ";//发件人地址
String to = "
[email protected] ";//收件人地址
String subject = "HTML测试";//邮件主题
String body = "<a href=http://www.it315.org>" +
"欢迎大家访问我们的网站</a></br>" +
"</br><img src=\"cid:image\">";
//下面的代码用一个createSession方法来返回。
//创建应用程序的环境属性,并实例化Session对象
Properties props = new Properties();
props.setProperty("mail.transport.protocol", protocol);
props.setProperty("mail.host", smtpServer);
props.put("mail.smtp.auth","true");
Session session = Session.getDefaultInstance(props, null);
session.setDebug(true);
//创建代表邮件的MimeMessage对象
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to));
message.setSubject(subject);
MimeMultipart multipart = new MimeMultipart("related");
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setContent(body,"text/html;charset=gb2312");
multipart.addBodyPart(htmlBodyPart);
MimeBodyPart gifBodyPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource("d:\\attach\\logo.gif");
gifBodyPart.setDataHandler(new DataHandler(fds));
//不必使用如下语句设置图片的类型,因为JAF框架中的DataSource能够返回其类型。
//gifBodyPart.setHeader("content-Type","image/gif");
gifBodyPart.setContentID("image");-
multipart.addBodyPart(gifBodyPart);
message.setContent(multipart);
message.saveChanges();
//获得Transport对象,并连接邮件服务器发送邮件
Transport transport = session.getTransport();
transport.connect(smtpServer,username,password);
transport.sendMessage(message,
message.getRecipients(Message.RecipientType.TO));
transport.close();
}
}
----------------------------------------------
例子2的源代码:
public static void main(String[] args) throws Exception
{
String smtpServer = "smtp.sina.com.cn";//邮件服务器
String protocol = "smtp";//邮件传输协议
String username = "it315_test";//登陆用户名
String password = "123456";//登陆密码
String from = "
[email protected] ";//发件人地址
String to = "
[email protected] ";//收件人地址
String subject = "HTML测试";//邮件主题
String body = "<a href=http://www.it315.org>" +
"欢迎大家访问我们的网站</a></br>" +
"</br><img src=\"cid:image\">";
//创建应用程序的环境属性,并实例化Session对象
Properties props = new Properties();
props.setProperty("mail.transport.protocol", protocol);
props.setProperty("mail.host", smtpServer);
props.put("mail.smtp.auth","true");
MyAuthenticator MyAuth = new MyAuthenticator(username,password);
Session session = Session.getDefaultInstance(props, MyAuth);
session.setDebug(true);
//创建代表邮件的MimeMessage对象
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to));
message.setSubject(subject);
MimeMultipart multipart = new MimeMultipart("related");
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setContent(body,"text/html;charset=gb2312");
multipart.addBodyPart(htmlBodyPart);
MimeBodyPart gifBodyPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource("d:\\attach\\logo.gif");
gifBodyPart.setDataHandler(new DataHandler(fds));
gifBodyPart.setHeader("content-Type","image/gif");
gifBodyPart.setContentID("image");
multipart.addBodyPart(gifBodyPart);
message.setContent(multipart);
message.saveChanges();
//发送邮件
Transport.send(message);
}
/*过代理用如下方式启动程序即可
java -DproxySet=true -DScoksProxyHost=221.15.5.67 -DScoksProxyPort=80 MyTest
*/
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
public class MyAuthenticator extends Authenticator
{
String username=null;
String password=null;
//通过MyAuthenticator类的构造函数向PasswordAuthentication类传递用户信息
public MyAuthenticator(String user,String pass)
{
username = user;
password = pass;
}
//覆盖Authenticator类中的getPasswordAuthentication方法
protected PasswordAuthentication getPasswordAuthentication()
{
//使用构造函数中的用户名和密码创建PasswordAuthentication对象
return new PasswordAuthentication(username, password);
}
}
-------------------------------------
张老师今天的总结:
1.读java mail包中的readme.txt,了解到mail.jar中的类分为java mail API和service Provider,不要Provider的jar包,编译程序也不会有任何问题。
2.由Message.RecipientType引出内部类知识的讲解:
想想"没有变量引用外部类,但有变量引用其中的内部类"的效果是通过怎样的代码产生的?
Interface Xxx
{
void func();
}
class Outer
{
int age;
//内部类通常应实现一个接口,这样外部的引用变量的类型就可以定义成那个接口
public class Inner implements Xxx
{
void func()
{
System.out.println(age);
}
}
// 只需将返回的类型定义接口类型,从而可以隐藏具体的实现类名,增强可扩展性
//面向接口编程,不要面向类编程
Xxx getXxxInstance()
{
return new Inner();
}
}
Xxx in = new Outer().getXxxIntance();
...
in.func
//内部类能够访问外部类的成员变量,那一定有个外部类对象存储!
public class Test
{
public static void main(String [] args)
{
/*Outer.Inner in = new Outer.Inner();
System.out.println(in);*/
Outer out = new Outer();
Outer.Inner in = out.new Inner();
System.out.println(in);
System.out.println(Integer.toHexString(in.hashCode()));
}
}
讲解上面代码时引出了打印一个对象的原理,实现toString方法的理由。
还引出了静态内部类,静态内部类不能访问外部类的非静态成员变量的理由。
3.想想JDK中的java.awt.Color类中定义的常量的代码是怎样的?
Color
{
public Color(int red,int green,int blue)
{
}
public static Color RED = new Color(255,0,0);
}
由此类推,不难明白Message.RecipientType中定义的TO、CC、BCC常量是怎么回事了?
5.由new MIMEMessage()必须接受一个Session对象作为参数,觉得不太合理,而引出PrintWriter的原理与其在
JDK 1.4版本时的设计缺陷
6.由下面的代码引出一种替代方式,关键是要自己能够写出这种替代方式的代码!
msg.setRecipients(Message.RecipientType.TO,
InternetAddress.parse("
[email protected]"));
msg.setRecipients(Message.RecipientType.TO,
new InternetAddress[]{new InternetAddress("
[email protected]")});
由看setRecipients方法的帮助文档,
setRecipients(Message.RecipientType type, InternetAddress [] addresses)
{
}
同时看到一种重载形式:
setRecipients(Message.RecipientType type, java.lang.String addresses)
讲解了这个重载方法内部的实现细节,让大家知道一种思想(有一个方法做具体细节,其他重载方法
对参数进行一些方便转换后,再调用那个方法)
setRecipients(Message.RecipientType type, java.lang.String addresses)
{
//内部还是调用上面的那个方法,只是把参数转了一下,以便为外面提供更多的操作手段!
setsetRecipients(type,InternetAddress.parse(addresses));
}
5.MimeBodyPart类的setContentId实际上内部也是调用SetHeader方法
作业1:生成一个带图片的邮件,分别用Content-Location头和Content-Id来实现。
作业2:想想"没有变量引用外部类,但有变量引用其中的内部类"的效果是通过怎样的代码产生的?
明天讲解eclipse生成的java源代码在命令行中出现乱码和不能编译的原因。
-----------------------------------------
下午课程开始:
Session的getDefaultInstance与getInstance方法:二者都有两种重载形式,只不过第二种方法每一次
调用都会产生一个新的session;第一种方法只是第一次产生一个新的Session,以后每次再被调用的时候,
都会用第一次创建的那个Session。
张老师领着我们从头到尾的作一个邮件发送程序:
1 Session
2 Message
3 Transport.send(message);
import javax.mail.Transport ;
import javax.mail.Message ;
import javax.mail.internet.MimeMessage ;
public class MailSender
{
public static void main(String[] args)
{
Properties props = new Properties();
props.setProperty("mail.transport.protocol","smtp");
props.setProperty("mail.host","smtp.sina.com.cn");
props.put("mail.smtp.auth","true"); //这一句话很重要!!!!
Authenticator auth ; //下面这种写法很值得注意,Authenticator是个抽象类
//大括号括起来的是继承它子类的具体实现,其中重写了一个方法。
Session session = Session.getInstance(props,new Authenticator(){
@Override //重写
protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication("username","password");
}
}
);
session.setDebug(true);
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("
[email protected]"));
msg.setRecipients(Message.RecipientType.TO,"
[email protected]");
msg.setSubject("收到否?");
msg.setSentDate(new Date());
msg.setText("这是邮件的具体内容");
msg.saveChanges(); //其实这里不用写这句话,Transport.send方法调用前会自动去进行这个方法的调用
Transport.send(msg);
}
};
在eclipse中配置虚拟机的参数:
-DProxySet=true -DSocksProxyHost=192.168.1.200 -DSocksProxyPort=808
------------------------------
又一个花絮:
public class Person
{
public boolean equals(Person another)
{
}
public int hashCode()
{
return 12345
}
};
HashSet hs = new HashSet();
hs.add(new Person());
hs.add(new Person());
注意这一次hs.size()方法中返回的值可就是2了!
因为Person中并没有重写父类Object的equals方法,而是重载了equals方法,
因为Object中的equals方法里面的入口参数是Object类型,不是Person类型!!
所以这就是重载了!
这样往hashSet里面插入的时候,找到相同的hashCode值后调用的equals方法
是父类的,而不是Person类中重载的这个equals方法,记住!
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ecaterina/archive/2006/12/12/1440345.aspx