专业的JAVA编程教程与资源

网站首页 > java教程 正文

Spring boot 发送邮件及原理(springboot发送outlook邮件)

temp10 2024-09-14 12:50:16 java教程 12 ℃ 0 评论

一、实战

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

1.1 登录邮箱进入找到设置

Spring boot 发送邮件及原理(springboot发送outlook邮件)

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服务器即可。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表