百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Spring boot 发送邮件及原理

toyiye 2024-06-22 20:13 9 浏览 0 评论

一、实战

1 邮箱准备工作,下面以qq邮箱做示例,其他邮箱同理

1.1 登录邮箱进入找到设置

1.2 找到邮箱中的POP3/IMAP/SMTP服务设置,并开启POP3/SMTP,IMAP/SMTP服务


此时,邮箱设置已经完成。

1.3 最后查看qq邮箱POP3和SMTP服务器


注:如需了解POP3、SMTP、IMAP协议,可以查看第三部分。

2 代码实战

2.1 搭建spring boot项目,引入spring-boot-starter-mail依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2.2 配置application.properties中的邮件配置

#邮箱服务器地址
spring.mail.host=smtp.qq.com
#用户名
spring.mail.username=XXXXX@qq.com
#密码
spring.mail.password=XXXXXXX
# hudmmejunplchdea
spring.mail.default-encoding=UTF-8
spring.mail.port=587
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
#以谁来发送邮件
mail.fromMail.addr=XXXXXX@qq.com

2.3 编写发送邮件服务及其实现类(通过spring boot mail 提供的JavaMailSender)

package com.ganhuojun.demo.service;
import java.io.ByteArrayOutputStream;
public interface MailService {
    /**
     * 发送邮件
     * @param to 目的地
     * @param subject 主题
     * @param content 内容
     * @param os 附件
     * @param attachmentFilename 附件名
     * @throws Exception
     */
    public void sendMail(String to, String subject, String content, ByteArrayOutputStream os, String attachmentFilename, Boolean isHtml) throws Exception;

    public void sendMail(String[] to, String subject, String content, ByteArrayOutputStream os, String attachmentFilename, Boolean isHtml) throws Exception;
}

实现类

package com.ganhuojun.demo.service.impl;
import com.ganhuojun.demo.service.MailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

import javax.mail.internet.MimeMessage;
import java.io.ByteArrayOutputStream;
@Component
public class MailServiceImpl implements MailService {
    private static Logger logger = LoggerFactory.getLogger(MailServiceImpl.class);

    @Autowired
    private JavaMailSender mailSender;

    @Value("${mail.fromMail.addr}")
    private String from;

    @Override
    public void sendMail(String to, String subject, String content, ByteArrayOutputStream os, String attachmentFilename, Boolean isHtml) throws Exception {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, isHtml);
            if (null != os) {
                //附件
                InputStreamSource inputStream = new ByteArrayResource(os.toByteArray());
                helper.addAttachment(attachmentFilename, inputStream);
            }

            mailSender.send(message);
            logger.info("简单邮件已经发送。");
        } catch (Exception e) {
            logger.error("发送简单邮件时发生异常!", e);
            throw new Exception("发送简单邮件时发生异常!",e);
        }
    }

    @Override
    public void sendMail(String[] to, String subject, String content, ByteArrayOutputStream os, String attachmentFilename, Boolean isHtml) throws Exception {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from);
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(content, isHtml);
            if (null != os) {
                //附件
                InputStreamSource inputStream = new ByteArrayResource(os.toByteArray());
                helper.addAttachment(attachmentFilename, inputStream);
            }

            mailSender.send(message);
            logger.info("简单邮件已经发送。");
        } catch (Exception e) {
            logger.error("发送简单邮件时发生异常!", e);
            throw new Exception("发送简单邮件时发生异常!",e);
        }
    }
}

2.4 编写测试用例

package com.ganhuojun.demo.service;

import com.ganhuojun.demo.DemoApplication;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.web.WebAppConfiguration;

@SpringBootTest(classes = DemoApplication.class)
@WebAppConfiguration
public class MailServiceTest {
    @Autowired
    MailService mailService;

    @Test
    public void sendMail(){
        try {
            mailService.sendMail("xxxx@qq.com","标题","邮件内容",null,null,false);
        }catch (Exception e){
            System.out.println("fail");
        }
        System.out.println("success");
    }
}

2.5 运行会发现已经收到邮件

二、源码解读

很多人都会有个疑问,我们知道spring-boot-starter-mail,但是我不知道从哪去入手源码,下面我们从原理上为大家分析一下。

1 了解spring boot的starter机制

如果大家了解spring boot的starter立刻就知道其运行原理。本文不对starter机制详细介绍,如果有需要了解的可以自行查询。我们简单描述下:

spring boot项目在Application类上都会@SpringBootApplication注解,该注解会自动引入@EnableAutoConfiguration注解,从而开启spring boot的默认配置,@EnableAutoConfiguration在spring-boot-autoconfigure包中。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

由于@EnableAutoConfiguration注解使用了@Import({AutoConfigurationImportSelector.class}),如果查看源码,可以最终查看到其读取了META-INF/spring.factories中的数据

源码如下:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

我们查看spring-boot-autoconfigure包中spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
……
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
……

此时豁然开朗,原来我们spring boot mail的入口就是MailSenderAutoConfiguration这个类,下面我们重点分析。

2 邮件配置类MailSenderAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({MimeMessage.class, MimeType.class, MailSender.class})
@ConditionalOnMissingBean({MailSender.class})
@Conditional({MailSenderAutoConfiguration.MailSenderCondition.class})
@EnableConfigurationProperties({MailProperties.class})
@Import({MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class})
public class MailSenderAutoConfiguration {
    public MailSenderAutoConfiguration() {
    }

    static class MailSenderCondition extends AnyNestedCondition {
        MailSenderCondition() {
            super(ConfigurationPhase.PARSE_CONFIGURATION);
        }

        @ConditionalOnProperty(
            prefix = "spring.mail",
            name = {"jndi-name"}
        )
        static class JndiNameProperty {
            JndiNameProperty() {
            }
        }

        @ConditionalOnProperty(
            prefix = "spring.mail",
            name = {"host"}
        )
        static class HostProperty {
            HostProperty() {
            }
        }
    }
}

@ConditionalOnClass,@ConditionalOnMissingBean,@Conditional这些配置均表示MailSenderAutoConfiguration创建的条件(引入spring-boot-starter-mail会满足其条件)

@EnableConfigurationProperties({MailProperties.class})此处即导入mail的配置,我们看下MailProperties.class

@ConfigurationProperties(
    prefix = "spring.mail"
)
public class MailProperties {
    
    private static final Charset DEFAULT_CHARSET;
    
    /**
     * SMTP server host. For instance, `smtp.example.com`.
     */
    private String host;

    /**
     * SMTP server port.
     */
    private Integer port;

    /**
     * Login user of the SMTP server.
     */
    private String username;

    /**
     * Login password of the SMTP server.
     */
    private String password;

    /**
     * Protocol used by the SMTP server.
     */
    private String protocol = "smtp";

    /**
     * Default MimeMessage encoding.
     */
    private Charset defaultEncoding = DEFAULT_CHARSET;

    /**
     * Additional JavaMail Session properties.
     */
    private Map<String, String> properties = new HashMap<>();

    /**
     * Session JNDI name. When set, takes precedence over other Session settings.
     */
    private String jndiName;
    
    // 此处getter和setter省略
}

这里解释了我们实战中的配置文件为什么都是spring.mail开头,属性有哪些,各个字段什么含义,源码中都可以看到。

那我们发送邮件底层使用的接口JavaMailSender,是在哪交给spring管理的呢?

当然是@Import({ MailSenderJndiConfiguration.class, MailSenderPropertiesConfiguration.class })为我们做的,核心如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.mail", name = "host")
class MailSenderPropertiesConfiguration {

    @Bean
    @ConditionalOnMissingBean(JavaMailSender.class)
    JavaMailSenderImpl mailSender(MailProperties properties) {
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        applyProperties(properties, sender);
        return sender;
    }
    // 此处忽略其他代码
    ……
}

从而为我们构建了JavaMailSender

3 了解JavaMailSenderImpl的send方法

 public void send(MimeMessage mimeMessage) throws MailException {
     this.send(mimeMessage);
 }

public void send(MimeMessage... mimeMessages) throws MailException {
    this.doSend(mimeMessages, (Object[])null);
}

此处send方法重载,如何理解,第一个send会不会死循环?

答案是肯定的,那我们怎么理解此处这样写的用意?

debug后发现上面send会跳转到下面的send方法中,百思不解。

考虑到源码中可能有注释进行解释,我点击了右上角的download sources

然后,当我们下载一下源码后此处代码会变成,瞬间柳暗花明

@Override
public void send(MimeMessage mimeMessage) throws MailException {
    send(new MimeMessage[] {mimeMessage});
}

@Override
public void send(MimeMessage... mimeMessages) throws MailException {
    doSend(mimeMessages, null);
}

因此我们得出结论,idea默认查看的class文件和源码还是有所区别,建议查看源码的时候全部download sources

最后发送都是通过doSend方法进行,最终底层通sun公司底层的SMTPTransport进行传输,不再分析,有兴趣的可以咨询研究。

三、POP3、SMTP、IMAP的区别

POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本,它规定怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电子协议。它是因特网电子邮件的第一个离线协议标准,POP3允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时删除保存在邮件服务器上的邮件,而POP3服务器则是遵循POP3协议的接收邮件服务器,用来接收电子邮件的。

SMTP是“Simple Mail Transfer Protocol”,即简单邮件传输协议。它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。SMTP 服务器就是遵循 SMTP 协议的发送邮件服务器。

IMAP全称是Internet Mail Access Protocol,即交互式邮件存取协议,它是跟POP3类似邮件访问标准协议之一。不同的是,开启了IMAP后,您在电子邮件客户端收取的邮件仍然保留在服务器上,同时在客户端上的操作都会反馈到服务器上,如:删除邮件,标记已读等,服务器上的邮件也会做相应的动作。所以无论从浏览器登录邮箱或者客户端软件登录邮箱,看到的邮件以及状态都是一致的。

简单的来说POP3是收取邮件的,SMTP是发送邮件的,因此第二部分我们实现了发送邮件只需要配置SMTP服务器即可。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码