本篇来学习在企业开发过程中经常会使用到的一些功能,如邮件发送、定时任务等功能。
邮件发送 邮件发送是一个非常常见的功能,注册时的身份认证、重要通知发送等都会使用到邮件来发送。Sun公司提供了JavaMail用来实现邮件发送,但是配置烦琐, Spring中提供了JavaMailSender用来简化邮件配置,而SpringBoot则提供了MailSenderAutoConfiguration对邮件的发送做了进一步简化。接来下就来看看如何在SpringBoot中发送邮件。
发送前的准备 注意本篇以QQ邮箱为例,向读者介绍邮件的发送过程。使用QQ邮件发送邮件,首先要申请开通POP3/SMTP服务或者IMAP/SMTP服务。可以看到无论是哪种都需要使用SMTP协议,SMTP全称Simple Mail Transfer Protocol
,也就是简单邮件传输协议,它定义了邮件客户端软件与SMTP服务器之间,以及SMTP服务器与SMTP服务器之间的通信规则。举个例子,用户(test@qq.com
)先将邮件投递到腾讯的SMTP服务器,这个过程就使用了SMTP协议,然后腾讯的SMTP服务器将邮件投递到网易的SMTP服务器,这个过程依然使用了SMTP协议,SMTP服务器就是用来接收邮件的。而POP3全称为Post Office Protocol3,也就是邮局协议,它定义了邮件客户端与POP3服务器之间的通信规则。该协议在什么场景下会用到呢?当邮件到达网易的SMTP服务器之后,用户(admin@163.com)
需要登录服务器查看邮件,这个时候就用上该协议了:邮件服务商会为每一个用户提供专门的邮件存储空间, SMTP服务器收到邮件之后,将邮件保存到相应用户的邮件存储空间中,如果用户要读取邮件,就需要通过邮件服务商的POP3邮件服务器来完成。至于IMAP协议,则是对POP3协议的扩展,功能更强,作用类似。
下面介绍QQ邮箱开通POP3/SMTP服务或者IMAP/SMTP服务的步骤。
第一步,登录QQ邮箱,然后依次单击顶部的设置按钮和账户按钮,如下图所示:
第二步,在账户选项卡下方找到POP3/SMTP服务,单击后方的“开启”按钮,这里笔者已经全部打开了:
单击“开启”按钮后,按照引导步骤发送短信,操作成功后,会获取一个授权码,如下图所示,记得将授权码保存下来后面会使用到它。
这个授权码就是以后登录的密钥,以后登录不再需要密码了。
邮件发送 接下来将是通过构建一个SpringBoot项目来完成邮件发送的功能。
环境搭建 使用spring Initializr
构建工具构建一个SpringBoot的Web应用,名称为mailspringboot
,然后在pom.xml文件中添加如下依赖:
1 2 3 4 5 6 7 8 9 <!--添加mail依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
接着修改application.properties
配置文件,在其中新增邮件基本信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 设置邮件服务器主机 spring.mail.host=smtp.qq.com # 设置邮件服务器端口(可以是465或者587) spring.mail.port=465 # 设置邮件服务器用户名 spring.mail.username=xxxx@qq.com # 设置邮件服务器密码(注意必须填写授权码) spring.mail.password= # 设置邮件默认编码格式 spring.mail.default-encoding=UTF-8 # 设置邮件的SSL连接配置 spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory # 开启debug,便于开发者查看邮件发送日志 spring.mail.properties.mail.debug=true
在上面笔者配置了邮件服务器的地址、端口(可以是465或者587)、用户的账号和密码以及默认编码、SSL连接配置等,最后开启debug,这样便于开发者查看邮件发送日志。注意SSL的配置可以在QQ邮箱帮助中心看到相关文档,如下图所示:
完成这些配置后,基本的邮件发送环境就搭建成功了,接下来就可以发送邮件了。邮件从简单到复杂有多种类型,下面分别予以学习。
发送简单邮件 新建一个component包,并在其中新建一个MailService
类用来封装邮件的发送,相应的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component public class MailService { @Autowired JavaMailSender javaMailSender; public void sendSimpleMail(String from,String to,String cc,String subject,String text){ SimpleMailMessage simpleMsg = new SimpleMailMessage(); simpleMsg.setFrom(from); simpleMsg.setTo(to); simpleMsg.setCc(cc); simpleMsg.setSubject(subject); simpleMsg.setText(text); javaMailSender.send(simpleMsg); } }
解释一下上述代码的含义:
JavaMailSender
是SpringBoot在MailSenderPropertiesConfiguration
类中配置好的,该类在Mail自动配置类MailSenderAutoConfiguration
中导入,因此这里可以直接注入JavaMailSender
进行使用。
注意这里定义的sendSimpleEmail
方法的5个参数分别表示邮件发送者,收件人,抄送人,邮件主题以及邮件内容。cc和bcc的区别可以点击 这里 进行查看。
简单邮件可以直接构建出一个SimpleMailMessage
对象,然后设置它对应的属性,设置完成后即可通过 JavaMailSender
对象的send方法将邮件发送出去。
接下来对这个MailService进行单元测试,相应的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RunWith(SpringRunner.class) @SpringBootTest public class MailServiceTest { @Autowired MailService mailService; @Test public void sendSimpleMailTest(){ mailService.sendSimpleMail( "aaaaa@qq.com", "bbbbb@qq.com", "ccccc@qq.com", "简单邮件发送测试主题", "SimpleMailSender邮件测试内容" ); } }
之后运行该测试方法,可以看到邮件发送成功:
SpringBoot的控制台也有对应的发送日志信息:
发送带附件的邮件 要发送一个带附件的邮件也非常容易,通过调用Attachment方法即可添加附件,该方法调用多次即可添加多个附件。只需要在MailService类中新增如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /**带有附件的邮件*/ public void sendAttachFileMail(String from, String to, String subject, String text, File file){ MimeMessage mimeMsg = javaMailSender.createMimeMessage(); try { MimeMessageHelper helper = new MimeMessageHelper(mimeMsg,true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(text); helper.addAttachment(file.getName(),file); javaMailSender.send(mimeMsg); } catch (MessagingException e) { e.printStackTrace(); } }
可以看到这里使用了MimeMessageHelper
,它简化了邮件配置,它的构造方法的第二个参数true表示构造一个mutipart message
类型的邮件,mutipart message
类型的邮件包含多个正文、附件以及内嵌资源,邮件的表现形式更加丰富。最后通过addAttachment
方法来添加附件。
接下来对这个sendAttachFileMail
方法进行单元测试,相应的代码为:
1 2 3 4 5 6 7 8 9 @Test public void sendAttachFileTest(){ mailService.sendAttachFileMail( "aaaaa@qq.com", "bbbbb@qq.com", "带有附件形式的邮件发送测试主题", "AttachFileSender邮件测试内容", new File("C:\\Users\\Administrator\\Desktop\\avatar.jpg")); }
之后运行该测试方法,可以看到邮件发送成功:
发送正文带图片的邮件 注意这里所指的“发送带图片资源的邮件”,其中的图片不是以附件的形式,而是在正文中插入的图片,开发者可以使用FileSystemResource
来实现这一功能。在MailService类中新增如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /**正文中带有图片的邮件*/ public void sendTextWithImage(String from, String to, String subject, String text, String[] srcPath,String[] resIds){ if(srcPath.length!=resIds.length){ System.out.println("对不起,发送失败"); return; } MimeMessage mimeMsg = javaMailSender.createMimeMessage(); try { MimeMessageHelper helper = new MimeMessageHelper(mimeMsg,true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(text,true); for(int i=0;i<srcPath.length;i++){ FileSystemResource resource = new FileSystemResource(new File(srcPath[i])); helper.addInline(resIds[i],resource); } javaMailSender.send(mimeMsg); } catch (MessagingException e) { System.out.println("对不起,发送失败"); } }
在发送邮件时,分别传入图片资源路径和资源id,通过FileSystemResource
构造静态资源,然后调用addInline
方法将资源添加到邮件对象中。注意在调用MimeMessageHelper
对象的setText
方法时,第二个参数true表示邮件的正文是HTML格式的,该参数不传默认为false。
接下来对这个sendTextWithImage
方法进行单元测试,相应的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void sendTextWithImageTest(){ mailService.sendTextWithImage( "aaaaa@qq.com", "bbbbb@qq.com", "正文带有图片的邮件发送测试主题(图片)", "<div>你好,这是一封正文带有图片的邮件:"+ "这是图片1:<div><img src='cid:p01'/></div>"+ "这是图片2:<div><img src='cid:p02'/></div>"+ "</div>", new String[]{"C:\\Users\\Administrator\\Desktop\\avatar.jpg","C:\\Users\\Administrator\\Desktop\\one.png"}, new String[]{"p01","p02"} ); }
邮件的正文是一段HTML文本,用cid标注出两个静态资源,分别为p01和p02。
之后运行该测试方法,可以看到邮件发送成功:
使用FreeMarker构建邮件模板 其实自己观察就会发现前面正文中携带图片这是采用了字符串拼接而成的HTML,这种不但容易出错,且不易维护,使用HTML模板可以很好地解决这个问题。使用FreeMarker
构建邮件模板,首先需要添加FreeMarker
依赖:
1 2 3 4 5 <!--添加freemarker依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
然后在MailService
类中新增如下方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /**使用FreeMaker构建邮件模板的邮件*/ public void sendHtmlMail(String from,String to,String subject,String text){ MimeMessage mimeMsg = javaMailSender.createMimeMessage(); try { MimeMessageHelper helper = new MimeMessageHelper(mimeMsg,true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(text,true); javaMailSender.send(mimeMsg); } catch (MessagingException e) { System.out.println("对不起,发送失败"); } }
接下来在resources目录下创建ftlh
目录作为模板存放位置,在该目录下创建mailtemplate.ftlh
作为邮件模板,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <div>邮箱激活</div> <div>您的注册信息为: <table border="1px"> <tr> <td>用户名</td> <td>${username}</td> </tr> <tr> <td>用户性别</td> <td>${gender}</td> </tr> </table> </div> <div> <a href="http://www.baidu.com">确认信息无误后,请点击本链接激活邮箱</a> </div>
既然使用到了用户信息,那么需要创建一个User实体类,其中的代码为:
1 2 3 4 5 public class User { private String username; private String gender; //getter、setter和toString方法 }
接下来对这个sendHtmlMail
方法进行单元测试,相应的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Test public void sendHtmlMailTest(){ try { Configuration configuration = new Configuration(Configuration.VERSION_2_3_30); ClassLoader loader = MailspringbootApplication.class.getClassLoader(); configuration.setClassLoaderForTemplateLoading(loader,"ftlh"); Template template = configuration.getTemplate("mailtemplate.ftlh"); StringWriter mail = new StringWriter(); User user = new User(); user.setUsername("余思"); user.setGender("男"); template.process(user,mail); mailService.sendHtmlMail( "aaaaa@qq.com", "bbbbb@qq.com", "使用FreeMaker构建邮件模板的邮件", mail.toString() ); } catch (Exception e) { e.printStackTrace(); } }
首先配置FreeMaker模板的位置,配置模板文件,然后结合User对象来渲染模板,将模板结果发送出去,之后运行该测试方法,可以看到邮件发送成功:
使用Thymeleaf构建邮件模板 既然可以使用FreeMaker构建邮件模板,那么自然也能使用Thymeleaf来构建邮件模板,使用Thymeleaf构建邮件模板相对来说更加的方便。使用Thymeleaf构建邮件模板,首先需要添加Thymeleaf依赖:
1 2 3 4 5 <!--添加thymeleaf依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
注意Thymeleaf的邮件模板默认位置在resources/templates
目录下,创建对应的html目录,然后创建模板mailtemplate.html
文件,里面的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>邮件</title> </head> <body> <div>邮箱激活</div> <div>您的注册信息为: <table border="1px"> <tr> <td>用户名</td> <td th:text="${username}"></td> </tr> <tr> <td>用户性别</td> <td th:text="${gender}"></td> </tr> </table> </div> <div> <a href="http://www.baidu.com">确认信息无误后,请点击本链接激活邮箱</a> </div> </body> </html>
接下来对之前那个sendHtmlMail
方法进行单元测试,注意这里是使用Thymeleaf模板引擎,相应的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Autowired TemplateEngine templateEngine; @Test public void sendHtmlMailByThymeleafTest(){ Context context = new Context(); context.setVariable("username","余思"); context.setVariable("gender","男"); String mail = templateEngine.process("/html/mailtemplate.html",context); mailService.sendHtmlMail( "2810706745@qq.com", "2131247535@qq.com", "使用FreeMaker构建邮件模板的邮件", mail ); }
不同于FreeMaker,Thymeleaf提供了TemplateEngine来对模板进行渲染,通过Context构造模板中变量需要的值,这种方式比FreeMaker构建模板更加方便。
之后运行该测试方法,可以看到邮件发送成功:
以上介绍的这几种不同的邮件发送方式基本上能满足大部分的业务需求,开发人员在实际过程中需要结合实际情况来选择合适的方式。
定时任务 定时任务是企业级开发中最常用的功能之一,如定时统计订单数、数据库备份、定时发送短信和邮件、定时统计博客访客等,简单的定时任务可以直接通过Spring中的@Scheduled
注解来实现,复杂的定时任务则可以通过集成Quartz
([kwɔːts])来实现,下面分别予以介绍。
@Scheduled @Scheduled
是由Spring提供的定时任务注解,使用方便,配置简单,可以解决工作中的大部分定时任务需求,使用方式如下:
第一步,创建工程并添加依赖。 使用spring Initializr
构建工具构建一个SpringBoot的Web应用,名称为quartzspringboot
,然后添加如下依赖:
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
第二步,开启定时任务。 在项目启动类上添加@EnableScheduling
注解,相应的代码为:
1 2 3 4 5 6 7 @SpringBootApplication @EnableScheduling public class QuartzspringbootApplication { public static void main(String[] args) { SpringApplication.run(QuartzspringbootApplication.class, args); } }
第三步,配置定时任务。 定时任务主要是通过@Scheduled
注解来进行配置。新建一个component包,并在其中创建一个MySchedule
类,其中的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class MySchedule { @Scheduled(fixedDelay = 1000) public void fixedDelay(){ System.out.println("fixedDelay:"+new Date()); } @Scheduled(fixedRate = 2000) public void fixedRate(){ System.out.println("fixedRate:"+new Date()); } @Scheduled(initialDelay = 1000,fixedRate = 2000) public void initialDelay(){ System.out.println("initialDelay:"+new Date()); } @Scheduled(cron = "0 * * * * ?") public void cron(){ System.out.println("cron:"+new Date()); } }
简单解释一下上述代码的含义:
通过@Scheduled
注解来标注一个定时任务,其中fixedDelay=1000
表示在当前任务执行结束1秒后开启另一个任务,fixedRate=2000
表示在当前任务执行2秒后开启另一个定时任务,initialDelay=1000
则表示首次执行的延迟时间。
在@Scheduled
注解中也可以使用cron表达式,cron="0 * * * * ?"
表示该定时任务每分钟执行一次。
配置完成后,接下来启动SpringBoot项目,定时任务部分打印日志如下所示:
Quartz Quartz简介 Quartz是一个功能丰富的开源作业调度库,它由Java写成,可以集成在任何Java应用程序中,包括JavaSE和JavaEE等。使用Quartz可以创建简单或者复杂的执行计划,它支持数据库、集群、插件以及邮件,且支持cron表达式,具有极高的灵活性。SpringBoot中集成Quartz和Spring中集成Quartz非常相似,主要提供了三个Bean:JobDetail、Trigger以及SchedulerFactory。
整合SpringBoot 在前面的项目中添加quartz依赖:
1 2 3 4 5 <!--添加quartz依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
接着新建两个Job,其中MyFirstJob
是一个普通的JavaBean,因此可以先添加@Component
注解将其注册到Spring容器中:
1 2 3 4 5 6 @Component public class MyFirstJob { public void sayHello(){ System.out.println("MyFirstJob--->sayHello:"+new Date()); } }
另一个Job为MySecondJob
,它继承抽象类QuartzJobBean
,且需要实现该类中的executeInternal
方法,该方法在任务被调用的时候使用,相应的代码为:
1 2 3 4 5 6 7 8 9 10 public class MySecondJob extends QuartzJobBean { private String name; public void setName(String name){ this.name=name; } @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("hello---->name:"+new Date()); } }
接下来新建一个config包,并在其中创建QuartzConfig
类用于对JobDetail
和Trigger
进行配置,相应的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Configuration public class QuartzConfig { @Bean MethodInvokingJobDetailFactoryBean jobDetailOne(){ MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean(); bean.setTargetBeanName("myFirstJob"); bean.setTargetMethod("sayHello"); return bean; } @Bean JobDetailFactoryBean jobDetailTwo(){ JobDetailFactoryBean bean = new JobDetailFactoryBean(); bean.setJobClass(MySecondJob.class); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("name","envy"); bean.setJobDataMap(jobDataMap); bean.setDurability(true); return bean; } @Bean SimpleTriggerFactoryBean simpleTrigger(){ SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean(); bean.setJobDetail(jobDetailOne().getObject()); bean.setRepeatCount(3); bean.setStartDelay(1000); bean.setRepeatInterval(2000); return bean; } @Bean CronTriggerFactoryBean cronTrigger(){ CronTriggerFactoryBean bean = new CronTriggerFactoryBean(); bean.setJobDetail(jobDetailTwo().getObject()); bean.setCronExpression("* * * * * ?"); return bean; } @Bean SchedulerFactoryBean schedulerFactory(){ SchedulerFactoryBean bean = new SchedulerFactoryBean(); SimpleTrigger simpleTrigger = simpleTrigger().getObject(); CronTrigger cronTrigger = cronTrigger().getObject(); bean.setTriggers(simpleTrigger,cronTrigger); return bean; } }
解释一下上述代码的含义:
JobDetail
的配置方式方式:第一种方式通过MethodInvokingJobDetailFactoryBean
类配置 JobDetail
,只需要指定Job的实例名称和要调用的方法即可,注册这种方式无法在创建 JobDetail
时传递参数;第二种方式是通过 JobDetailFactoryBean
来实现的,这种方式只需要指定 JobClass
即可,当然可以通过 JobDataMap
传递参数到Job中,Job中只需要提供属性名,并且提供一个相应的set方法即可接收到参数。
Trigger
有多种不同实现,这里展示两种最常使用的Trigger
:SimpleTrigger
和CronTrigger
,这两种Trigger
分别使用SimpleTriggerFactoryBean
和CronTriggerFactoryBean
进行创建。在SimpleTriggerFactoryBean
对象中,首先设置JobDetail
,然后通过setRepeatCount
配置任务循环次数,setStartDelay
配置任务启动延迟时间,setRepeatInterval
配置任务的时间间隔。在CronTriggerFactoryBean
对象中,则主要配置JobDetail
和Cron表达式。
最后通过SchedulerFactoryBean
创建SchedulerFactory
对象,然后配置Trigger
即可。
经过这几步的配置,定时任务就配置成功了。接下来启动SpringBoot项目,可以看到控制台输出一些信息:
可以看到MyFirstJob在第一次运行以后,每间隔2秒执行了3次后就不再执行了,MySecondJob则每秒执行一次,一直执行下去。