邮件服务器的分类

按照通讯协议分类

  • SMTP服务器:将邮件发送给邮件服务器,服务器将邮件发送给客户,此时发送和接受是一对,都是使用的同一种服务器,就好比我们传呼快递员上门取件,淘宝商委派递送员将货物送上门
  • POP3/IMAP服务器:打个比方,用户去快递站点取包裹,问有没有我的包裹,站点的人再去帮你找包裹,找到后给到客户,那么站点就相当于POP3/IMAP服务器

    那么还是举一个简单的栗子来说明SMTP和POP3的使用过程。
    marco想给lisa发一封业务邮件,marco使用的是163邮箱,lisa是sina邮箱,那么marco发送的邮件首先会通过简单邮件传输协议(Simple Mail Transfer Protocol, SMTP) 发送给sina的服务器,sina服务器收到邮件后首先会进行存储,那么当lisa登录邮箱之后他的email会自动请求询问服务器,有没有我的邮件?
    服务器则会查找收件人是lisa的邮件,并将邮件发送给她,此时使用的协议则是Post Office Protocol 3*(支持“离线”邮件处理*)Internet Mail Access Protocol(交互式邮件存取协议)简称POP3/IMAP。

Java Mail

JavaMail API 按照功能可以划分为如下三大类:

  • 创建和解析邮件的API
  • 发送邮件的API
  • 接收邮件的API

而这四大类API中有四大核心类帮助我们完成JavaMail程序的构建

JavaMail 的四大核心类

Message 类(javax.mail.Message):
创建和解析邮件的核心 API,这是一个抽象类,通常使用它的子类javax.mail.internet.MimeMessage 类。它的实例对象表示一份电子邮件。客户端程序发送邮件时,首先使用创建邮件的 JavaMail API 创建出封装了邮件数据的 Message 对象,然后把这个对象传递给邮件发送API(Transport 类) 发送。客户端程序接收邮件时,邮件接收API把接收到的邮件数据封装在Message 类的实例中,客户端程序在使用邮件解析API从这个对象中解析收到的邮件数据。

Transport 类(javax.mail.Transport)
发送邮件的核心API 类,它的实例对象代表实现了某个邮件发送协议的邮件发送对象,例如 SMTP 协议,客户端程序创建好 Message 对象后,只需要使用邮件发送API 得到 Transport 对象,然后把 Message 对象传递给 Transport 对象,并调用它的发送方法,就可以把邮件发送给指定的 SMTP 服务器。

Store 类(javax.mail.Store)
接收邮件的核心 API 类,它的实例对象代表实现了某个邮件接收协议的邮件接收对象,例如 POP3 协议,客户端程序接收邮件时,只需要使用邮件接收 API 得到 Store 对象,然后调用 Store 对象的接收方法,就可以从指定的 POP3 服务器获得邮件数据,并把这些邮件数据封装到表示邮件的 Message 对象中。

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

那么接下来就演示一个实战的简单发送邮件的项目

public class MailTest {
	//发件人账户名
    public static String senderAccount = "userName@sohu.com";
    //发件人账户密码
    public static String senderPassword = "password";
    
	public static void main(String[] args) throws Exception {
		//新建一个Properties类用于配置邮件发送所需要的属性和内容
		Properties props = new Properties();
		//配置mail的auth认证属性,设置为true告诉我们的程序,登录邮箱是要认证的
		props.setProperty("mail.smtp.auth", "true");
		//发件人的邮箱的 SMTP 服务器地址
		props.setProperty("mail.host","smtp.sohu.com"); 
		//传输需要使用的协议,指定传输协议为smtp
		props.setProperty("mail.transport.protocol", "smtp");
		
		//通过prop配置对象new一个Session会话,并在Session中配置账户和密码,也可以选择在transport.connect(user, password)中设置
		Session session = Session.getInstance(props, new Authenticator() {
			protected PasswordAuthentication getPasswordAuthentication() {
				return new PasswordAuthentication(senderAccount, senderPassword);
			}
		});
		//设置调试属性为true则会调试
		session.setDebug(true);
		
		//调用createMimeMessage()方法新建一封邮件
		String Content = "I'm marco~";
		MimeMessage message = createMimeMessage(session, "userName@sohu.com", "userName@sina.cn", Content);
		
		//通过会话对象获取传输对象(就好比传呼递送员上门取件)
		Transport transport = session.getTransport();
		transport.connect();
		//调用transport的sendMessage方法发送邮件
		//发送邮件,并发送到所有收件人地址,message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
		transport.sendMessage(message,message.getAllRecipients());
		transport.close();
	}
	
	 public static MimeMessage createMimeMessage(Session session, String sendMail, String receiveMail,String Content) throws Exception {
	        //创建一封邮件,Message为抽象类,所以需要new它的子实现类MimeMessage
	        MimeMessage message = new MimeMessage(session);
	        //From: 发件人(昵称有广告嫌疑,避免被邮件服务器误认为是滥发广告以至返回失败,请修改昵称)
	        message.setFrom(new InternetAddress(sendMail,"sentFrom","utf-8"));
	        //To: 收件人(可以增加多个收件人、抄送、密送,CC:抄送人,BCC:密送)   
	        message.setRecipients(MimeMessage.RecipientType.TO, new Address[]{new InternetAddress(receiveMail,"sentTo","UTF-8")});
	        //Subject: 邮件主题(标题有广告嫌疑,为了避免被邮件服务器误认为是滥发广告以至返回失败,建议修改标题)
	        message.setSubject("Working Email", "UTF-8");
	        //Content: 邮件正文(可以使用html标签)(内容有广告嫌疑,为了避免被邮件服务器误认为是滥发广告以至返回失败,建议修改发送内容)
	        message.setContent(Content, "text/html;charset=utf-8");
	        //设置发件时间
	        message.setSentDate(new Date());
	        //保存设置
	        message.saveChanges();
	        return message;
	    }
}

当然,我们也可以将一些固定的信息放置在properties文件当中

senderAccount=userName@sohu.com
senderPassword=password
mail.smtp.auth=true
mail.host=smtp.sohu.com
mail.transport.protocol=smtp

这样我们在Listener中就可以在服务器启动的时候将配置文件加载进来
新建一个ConfigListener实现ServletContextListener接口

public void contextInitialized(ServletContextEvent sce) {
	ServletContext servletContext = sce.getServletContext();
	try {
		prop.load(AutoSendMail.class.getClassLoader().getResourceAsStream("sentMail.properties"));
		servletContext.setAttribute("prop", prop);
prop.getProperty("mail.transport.protocol"));
	} catch (IOException e) {
		e.printStackTrace();
	}
}

这样我们在AutoSendMail的Servlet中通过ServletContext的getAttribute()方法获取账号密码就可以啦
以下是完整版的获取多个用户的邮箱信息,进行群发邮件的功能

@WebServlet("/AutoSendMail")
public class AutoSendMail extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private static Properties prop = new Properties();
	Map<String,String[]> rcptMap;//模拟客户注册后自动发送邮件,存放客户注册xxx软件时注册的账号和密码
	
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	@SuppressWarnings("unchecked")
	protected void doGet(HttpServletRequest request, HttpServletResponse response) {
		Properties prop = (Properties)request.getServletContext.getAttribute("prop");
		String senderAccount = prop.getProperty("senderAccount");
		String senderPassword = prop.getProperty("senderPassword");
		rcptMap = (Map<String, String[]>) request.getAttribute("rcptMap");
		System.out.println("AutoSendEmail-------->" + rcptMap);
		if(!rcptMap.isEmpty()) {
			//通过prop配置对象new一个Session会话,并在Session中配置账户和密码,也可以选择在transport.connect(user, password)中设置
			Session session = Session.getInstance(prop, new Authenticator() {
				protected PasswordAuthentication getPasswordAuthentication() {
					return new PasswordAuthentication(senderAccount, senderPassword);
				}
			});
			//设置调试属性为true则会调试
			session.setDebug(true);
			
			//调用createMimeMessage()方法新建一封邮件
			MimeMessage message;
			Transport transport = null;
			try {
				message = createMimeMessage(session, senderAccount, rcptMap);
				//通过会话对象获取传输对象(就好比传呼递送员上门取件)
				transport = session.getTransport();
				transport.connect();
				//调用transport的sendMessage方法发送邮件
				//发送邮件,并发送到所有收件人地址,message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
				transport.sendMessage(message,message.getAllRecipients());
				/*注册成功之后,跳到主页面,进行登录*/
				PrintWriter out = response.getWriter();
			    out.println("<html>");    
			    out.println("<script>");    
			    out.println("window.open ('"+request.getContextPath()+"/index.jsp','_parent')");    
			    out.println("</script>");    
			    out.println("</html>");  
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					transport.close();
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public static MimeMessage createMimeMessage(Session session, String sendMail, Map<String, String[]> rcptMap) throws Exception {
        //创建一封邮件,Message为抽象类,所以需要new它的子实现类MimeMessage
		MimeMessage message = new MimeMessage(session);
        //From: 发件人(昵称有广告嫌疑,避免被邮件服务器误认为是滥发广告以至返回失败,请修改昵称)
        message.setFrom(new InternetAddress(sendMail,"Auto-Send","UTF-8"));
        //To: 收件人(可以增加多个收件人、抄送、密送,CC:抄送人,BCC:密送)   
        List<Address> addressList = new ArrayList<Address>();
        if(!rcptMap.isEmpty()) {
        	 Set<String> userNames = rcptMap.keySet();
        	 Iterator<String> iterator = userNames.iterator();
        	 while (iterator.hasNext()) {
				String rcptMail = iterator.next();
				String userName = rcptMap.get(rcptMail)[0];//虚拟的客户注册我们的软件的账户
				String password = rcptMap.get(rcptMail)[1];//虚拟的客户注册我们的软件的密码
				InternetAddress internetAdd = new InternetAddress(rcptMail, userName, "UTF-8");
				addressList.add(internetAdd);
				String Content = "恭喜您注册成功!!请记住您的登录账户和密码<br>账户: " + userName + "<br>密码: " + password;
				message.setContent(Content, "text/html;charset=utf-8");
			}
            Address[] address = (Address[]) addressList.toArray(new Address[addressList.size()]);
            message.setRecipients(MimeMessage.RecipientType.TO, address);
        }
        //Subject: 邮件主题(标题有广告嫌疑,为了避免被邮件服务器误认为是滥发广告以至返回失败,建议修改标题)
        message.setSubject("Marco Management System Register");
        //Content: 邮件正文(可以使用html标签)(内容有广告嫌疑,为了避免被邮件服务器误认为是滥发广告以至返回失败,建议修改发送内容)
        //设置发件时间
        message.setSentDate(new Date());
        //保存设置
        message.saveChanges();
        return message;
    }
	

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
}