java 发送邮件 详解

常见的邮件协议包括 :
SMTP : 简单邮件传输协议,用于发送电子邮件的传输协议
POP3 : 用于接收电子邮件的标准协议
IMAP : 互联网消息访问协议,是 POP3 的替代协议
这三种协议都有对应 SSL 加密传输的协议,分别是 SMTPS、 POP3S 和 IMAPS

JavaMail 体系结构
java 发送邮件 详解_第1张图片
除 JavaMail 的核心包之外, JavaMail 还需要 JAF( JavaBeans Activation Framework)来处理不是纯文本的邮件内容。这包括 MIME(多用途互联网邮件扩展)、 URL 页面和文件附件等内容

JavaMail 是由 Sun 定义的一套收发电子邮件的 API,不同的厂商可以提供自己的实现类。但它并没有包含在 JDK 中,而是作为 Java EE 的一部分。

JavaMail 的关键对象
1> Properties : 属性对象,其中封装服务器地址、 端口、 用户名、 密码等信息,具体相关信息如下 :
属性名
属性类型
说明
mail.smtp.host
String
SMTP 服务器地址,如 smtp.sina.com.cn
mail.smtp.port
int
SMTP 服务器端口号,默认为 25
mail.smtp.auth
boolean
SMTP 服务器是否需要用户认证,默认为 false
mail.smtp.user
String
SMTP 默认的登录用户名
mail.smtp.from
String
默认的邮件发送源地址
mail.smtp.socketFactory.class
String
socket 工厂类类名,通过设置该属性可以覆盖提供者默认的实现。必须实现 javax.net.SocketFactory 接口
mail.smtp.socketFactory.port
int
指定 socket 工厂类所用的端口号,如果没有设定,则使用默认的端口
mail.smtp.socketFactory.fallback
boolean
设置为 true 时,当使用指定的 socket 类创建 socket 失败后,将使用 java.net.Socket 创建 socket。默认为 true
mail.smtp.timeout
int
I/O 连接超时时间,单位为毫秒,默认为永不超时

Session : 会话对象,是一堆配置信息的集合
Transport 和 Store : 传输和存储



使用 JavaMail 发送邮件实例
1> 引入 JavaMail 相关包
< dependency >
    < groupId > javax.mail groupId >
    < artifactId > mail artifactId >
    < version > 1.4 version >
dependency >

2> 
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.util.Properties;
public class MailUtils  {
    private String host = "" ; // smtp服务器
    private String from = "" ; // 发件人地址
    private String to = "" ; // 收件人地址
    private String affix = "" ; // 附件地址
    private String affixName = "" ; // 附件名称
    private String user = "" ; // 用户名
    private String pwd = "" ; // 密码
    private String subject = "" ; // 邮件标题
    public void setAddress(String from, String to, String subject)  {
        this . from = from;
        this . to = to;
        this . subject = subject;
    }
    public void setAffix(String affix, String affixName)  {
        this . affix = affix;
        this . affixName = affixName;
    }
    public void send(String host, String user, String pwd, String content)  {
        this . host = host;
        this . user = user;
        this . pwd = pwd;
        Properties props = new Properties();
        // 设置发送邮件的邮件服务器的属性(这里使用网易的smtp服务器)
        props.put( "mail.smtp.host" , host);
        // 需要经过授权,也就是有户名和密码的校验,这样才能通过验证(一定要有这一条)
        props.put( "mail.smtp.auth" , "true" );
        // 用刚刚设置好的props对象构建一个session
        Session session = Session. getDefaultInstance (props);
        // 有了这句便可以在发送邮件的过程中在console处显示过程信息,供调试使
        // 用(你可以在控制台(console)上看到发送邮件的过程)
        session.setDebug( true );
        // 用session为参数定义消息对象
        MimeMessage message = new MimeMessage(session);
        try  {
            // 加载发件人地址
            message.setFrom( new InternetAddress( from ));
            // 加载收件人地址
            message.addRecipient(Message.RecipientType. TO , new InternetAddress( to ));
            // 加载标题
            message.setSubject( subject );
            // 向multipart对象中添加邮件的各个部分内容,包括文本内容和附件
            Multipart multipart = new MimeMultipart();
            // 设置邮件的文本内容
            BodyPart contentPart = new MimeBodyPart();
            contentPart.setText(content);
            multipart.addBodyPart(contentPart);
            // 添加附件
            BodyPart messageBodyPart = new MimeBodyPart();
            DataSource source = new FileDataSource( affix );
            // 添加附件的内容
            messageBodyPart.setDataHandler( new DataHandler(source));
            // 添加附件的标题
            // 这里很重要,通过下面的Base64编码的转换可以保证你的中文附件标题名在发送时不会变成乱码
            sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();
            messageBodyPart.setFileName( "=?GBK?B?" + enc.encode( affixName .getBytes()) + "?=" );
            multipart.addBodyPart(messageBodyPart);
            // 将multipart对象放到message中
            message.setContent(multipart);
            // 保存邮件
            message.saveChanges();
            // 发送邮件
            Transport transport = session.getTransport( "smtp" );
            // 连接服务器的邮箱
            transport.connect(host, user, pwd);
            // 把邮件发送出去
            transport.sendMessage(message, message.getAllRecipients());
            transport.close();
        }
        catch (Exception e)  {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)  {
        MailUtils cn = new MailUtils();
        // 设置发件人地址、收件人地址和邮件标题
        cn.setAddress( " [email protected] " , " [email protected] " , "一个带附件的JavaMail邮件" );
        // 设置要发送附件的位置和标题
        cn.setAffix( "/Users/chenshun131/Desktop/附录A.pdf" , "123.pdf" );
        /**
         * 设置smtp服务器以及邮箱的帐号和密码
         * 用QQ 邮箱作为发生者不好使 (原因不明)
         * 163 邮箱可以,但是必须开启  POP3/SMTP服务 和 IMAP/SMTP服务
         * 因为程序属于第三方登录,所以登录密码必须使用163的授权码
         */
        // 注意: [授权码和你平时登录的密码是不一样的]
        cn.send( " smtp.163.com " , " [email protected] " , "" , "邮件的具体内容在此" );
    }
}

直接使用 JavaMail 编写邮件收发程序并不是一件轻松的事情,这归咎于 JavaMail 零散而复杂的 API 以及各种强制需要处理的检查型异常。Spring 对使用 JavaMail 发送邮件进行了很大程度的简化,它为 80%的需求提供了简单的处理方法,剩下的需求则可以通过直接调用 JavaMailAPI 完成

Spring 在 org.springframework.mail 包里通过 MailMessage 和 MailSender 这两个高层抽象接口描述了,两个最重要的内容 : 邮件消息和邮件发送者

MailMessage : 抽象的邮件消息,该接口描述了邮件消息的通用模型,允许开发者通过多个简洁的属性设置方法填充邮件消息的各项内容
MailMessage 有两个实现类: SimpleMailMessage 和 MimeMailMessage,前者都是完全符合 Bean 风格的实现类,后者通过内置 JavaMail 的 MimeMessage 提供实现。在发送简单文本型邮件时, SimpleMailMessage 就可以满足要求,如果 发送复杂的邮件,则可以利用 MimeMailMessage 或直接使用MimeMessage

MailSender : 抽象的邮件发送者,邮件发送者负责将邮件发送到指定的地址上,该接口只用于发送简单的邮件。如果需要发送复杂的邮件,则需要使用 JavaMailSender 子接口

Spring 的邮件异常体系
java 发送邮件 详解_第2张图片

Spring 在 org.springframework.mail.javamail 包下提供了对 JavaMail 邮件系统的支持。首先,通过 JavaMailSenderImpl 可以方便地创建 JavaMail 环境;其次,通过 MimeMessageHelper 构造出 MimeMessage 对象

向邮件中添加附件或内嵌文件时,文件对应的 MIME 类型是一个很重要的信息,因为 Outlook、Foxmail 等邮件客户端软件必须根据 MIME 类型决定如何处理邮件中的内嵌文件。文件扩展名和 MIME 类型的对应关联在 activation.jar/META-INF 目录下的
mimetypes.default 文件中定义 :
MIME 类型的规范名
文件扩展名
text/html
html htm HTML HTM
text/plain
txt text TXT TEXT
image/gif
gif GIF
image/ief
ief
image/jpeg
jpeg jpg jpe JPG
image/tiff
tiff tif
image/x-xwindowdump
xwd
application/postscript
ai eps ps
application/rtf
rtf
application/x-tex
tex
application/x-texinfo
texinfo texi
application/x-troff
t tr roff
audio/basic
au
audio/midi
midi mid
audio/x-aifc
aifc
audio/x-aiff
aif aiff
audio/x-mpeg
mpeg mpg
audio/x-wav
wav
video/mpeg
mpeg mpg mpe
video/quicktime
qt mov
video/x-msvideo
avi

注 : 在 http://www.w3school.com.cn/media/media_mimeref.asp  可以找到所有 MIME 类型的信息

发送邮件频繁发送可能会被邮箱服务器识别为垃圾邮件,如 163邮箱可能会出现  554 DT:SPM 发送的邮件内容包含了未被许可的信息,或被系统识别为垃圾邮件。请检查是否有用户发送病毒或者垃圾邮件;

通过 Spring 的 JavaMail 发送各种形式的邮件
例如如下代码
@Autowired
private JavaMailSender sender ;
public void sendSimpleMail() { // 发送纯文本邮件
    SimpleMailMessage msg = new SimpleMailMessage();
    msg.setFrom( " [email protected] " );
    msg.setTo( " [email protected] " );
    msg.setReplyTo( " [email protected] " );
    msg.setCc( " [email protected] " );
    msg.setSubject( "注册成功" );
    msg.setText( "恭喜,您在宝宝淘论坛已经注册成功!您的用户ID为:1234567890" );
    sender .send(msg);
}
public void sendHtmlMail() throws MessagingException { // 发送 HTML 类型的邮件
    MimeMessage msg = sender .createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(msg, false , "utf-8" ); // 推荐使用 utf-8 编码
    helper.setFrom( " [email protected] " );
    helper.setTo( new String[]{ " [email protected] " , " [email protected] " });
    helper.setSubject( "注册成功" );
    String htmlText = ""
            + " \" content-type \" content= \" text/html; charset=utf-8 \" >"
            + "" + "恭喜,您在宝宝淘论坛已经注册成功!您的用户ID为:"
            + "1234567890 " ;
    helper.setText(htmlText, true );
    sender .send(msg);
}
/**
* 对于一个 Web 应用程序来说,一般情况下,我们不推荐使用内嵌文件的邮件,用
* 户大可将这些资源文件放在一台 Web 资源服务器上,然后简单地通过 URL 来引用这
* 些文件。这带来了明显的好处:邮件体积缩小很多,并且提高了邮件的收发效率,同
* 时邮件的展现效果并不会受到影响
*/
public void sendInlineMail() throws MessagingException { // 发送带内嵌文件的邮件
    MimeMessage msg = sender .createMimeMessage();
    // 内嵌文件邮件是 multipart类型,第二个入参需要设置为 true
    MimeMessageHelper helper = new MimeMessageHelper(msg, true , "utf-8" );
    helper.setFrom( " [email protected] " );
    helper.setTo( new String[]{ " [email protected] " , " [email protected] " });
    helper.setSubject( "宝宝淘论坛注册成功" );
    String htmlText = ""
            + " \" content-type \" content= \" text/html; charset=utf-8 \" >"
            + "" + "欢迎访问宝宝淘论坛!(我想显示一些其他信息)"
            + "
\" cid:img01 \" >
"
+ "" ;
    helper.setText(htmlText, true );
    ClassPathResource img = new ClassPathResource( "bbt.gif" );
    helper.addInline( "img01" , img);
    sender .send(msg);
}
public void sendAttachmentMail() throws Exception { // 发送带附件的邮件
    MimeMessage msg = sender .createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(msg, true , "utf-8" );
    helper.setFrom( " [email protected] " );
    helper.setTo( new String[]{ " [email protected] " , " [email protected] " });
    helper.setSubject( "宝宝淘论坛注册成功" );
    helper.setText( "欢迎访问宝宝淘论坛!" );
    ClassPathResource file1 = new ClassPathResource( "bbt.zip" );
    helper.addAttachment( "file01.zip" , file1.getFile());
    ClassPathResource file2 = new ClassPathResource( "file.doc" );
    helper.addAttachment( "file02.doc" , file2.getFile());
    sender .send(msg);
}
// 双版本邮件,在 Foxmail 中,用户在默认情况下看到纯文件版本的邮件,可以通过点击工具栏的“ HTML”按钮查看 HTML 版本的邮件
public void sendAlternativeMail() throws Exception { // 发送纯文本和 HTML 双版本的邮件
    MimeMessagePreparator mmp = new MimeMessagePreparator() {
        public void prepare(MimeMessage msg) throws Exception {
            MimeMessageHelper helper = new MimeMessageHelper(msg, true , "utf-8" );
            helper.setFrom( " [email protected] " );
            helper.setTo( new String[]{ " [email protected] " , " [email protected] " });
            helper.setSubject( "注册成功" );

            MimeMultipart mmPart = new MimeMultipart( "alternative" ); // 创建双版本邮件内容
            msg.setContent(mmPart);

            // 创建纯文本版本的邮件体
            BodyPart plainTextPart = new MimeBodyPart();
            plainTextPart.setText( "欢迎访问宝宝淘论坛!(创建纯文本版本的邮件体)" );
            mmPart.addBodyPart(plainTextPart);

            // 创建 HTML 版本的邮件体
            BodyPart htmlPart = new MimeBodyPart();
            String htmlText = ""
                    + " \" content-type \" content= \" text/html; charset=utf-8 \" >"
                    + ""
                    + "欢迎访问宝宝淘论坛! (创建 HTML 版本的邮件体)" + "" ;
            htmlPart.setContent(htmlText, "text/html;charset=utf-8" );
            mmPart.addBodyPart(htmlPart);
        }
    };
    sender .send(mmp);
}

需要在 Spring 工程中引入两个库
< dependency >
    < groupId > javax.mail groupId >
    < artifactId > mail artifactId >
    < version > 1.4 version >
dependency >
< dependency >
    < groupId > org.freemarker groupId >
    < artifactId > freemarker artifactId >
    < version > 2.3.23 version >
dependency >

在 spring-mvc.xml 中配置发送者邮件相关信息
< bean id ="sender"
      class ="org.springframework.mail.javamail.JavaMailSenderImpl"
      p :host =" smtp.163.com "
      p :username ="chenshun131"
      p :password ="xxxx" >
    < property name ="javaMailProperties" >
        < props >
            < prop key ="mail.smtp.auth" > true prop >
        props >
    property >
bean >

在实际应用中发送邮件
邮件发送程序直接在代码中构建邮件内容,对于一些简单的邮件来说,这种方式是可行的。但如果邮件体的内容很复杂,这种方式的弊端将马上暴露出来 —— 直接使用 Servlet 构造复杂内容网页非常复杂

对于 HTML 格式的邮件来说,除少部分内容外,大部分的 HTML 代码都是固定的,因此,在实际应用中,常采用的办法是制作好邮件模板,在发送邮件时,通过模板解析构建最终邮件内容

由于发送邮件相对来说是比较重量级的操作,它受限于邮件服务器和网络的性能,可能需要好几秒的时间。如果直接在业务流程的线程中采用同步的方式发送邮件,业务流程的响应速度将会受到很大的影响。为了降低这种影响,在实际的应用系统中,一般需要采用异步邮件发送方式,使用单独的线程发送邮件,甚至使用 JMS 消息提交邮件发送任务,由单独的邮件发送服务器负责实际邮件发送的任务

使用邮件模板
在开源领域, Velocity 和 FreeMarker 是广泛使用的两个模板框架,由于 FreeMarker 的后发优势其更受青睐度。模版的原理其实很简单,就是用动态的数据替换模板中的特殊标签,生成最终的内容

Spring 为 Freemarker 提供了一个 FreeMarkerConfigurer,通过这个类可以方便地创建Freemarker 的基础设施,然后就可以在此基础上获取 Freemarker 的基础组件实例

例如如下代码 :
@Autowired
private JavaMailSender sender ;
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer ;
@Test
public void sendTemplateMail() throws MessagingException {
    MimeMessage msg = sender .createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(msg, false , "utf-8" );
    helper.setFrom( " [email protected] " );
    helper.setTo( new String[]{ " [email protected] " , " [email protected] " });
    helper.setSubject( "宝宝淘论坛注册成功:基于模板" );
    String htmlText = getMailText( "1234567890" ); // 使用模板产生HTML 邮件体内容
    helper.setText(htmlText, true );
    sender .send(msg);
}
private String getMailText(String userId) { // 通过模板构造邮件内容
    String htmlText = null ;
    try {
        Template tpl = freeMarkerConfigurer .getConfiguration().getTemplate( "registerUser.ftl" ); // 通过指定模板名获取 Freemarker 模板实例
        Map map = new HashMap(); // 通过 Map 传递动态数据
        map.put( "userId" , userId); // 注意动态数据的名字必须和模板标签中指定属性相匹配
        htmlText = FreeMarkerTemplateUtils. processTemplateIntoString (tpl, map); // 解析模板并替换动态数据,产生最终的内容
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return htmlText;
}

在 spring-mvc.xml 中配置 freemarker发送者邮件相关信息
< bean id ="freeMarkerConfigurer"
      class ="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"
      p :templateLoaderPath ="classpath:mailTemplate/" >
    < property name ="freemarkerSettings" >
        < props >
            < prop key ="template_update_delay" > 1800 prop >
            < prop key ="default_encoding" > UTF-8 prop >
            < prop key ="locale" > zh_CN prop >
        props >
    property >
bean >

registerUser.ftl 中的信息
< html >
    < head >
       < meta http-equiv= "content-type" content= "text/html; charset=utf-8" >
    head >
   < body >
        恭喜,您在宝宝淘论坛已经注册成功!您的用户ID为: < font size= '20' size= '30' > ${ userId } font >
   body >
html >

异步发送邮件
例如如下代码 :
@Autowired
private TaskExecutor taskExecutor ; // 拥有异步执行能力的任务执行器
@Test
public void sendAsyncMail() {
    taskExecutor .execute( new Runnable() {
        public void run() {
            try {
                sendTemplateMail(); // 异步调用 sendTemplateMail()方法发送邮件
                System. out .println( "邮件发送成功!" );
            } catch (Exception e) {
                System. out .println( "邮件发送失败!,异常信息:" + e.getMessage());
            }
        }
    });
}

在 spring-mvc.xml 中配置任务执行器信息
< bean id ="taskExecutor"
      class ="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"
      p :corePoolSize ="10"
      p :maxPoolSize ="30" />

你可能感兴趣的:(Java)