📧 异步、定时、邮件任务
在我们的工作中,常常会用到异步处理任务,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。还有一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息。还有就是邮件的发送,微信的前身也是邮件服务呢?这些东西都是怎么实现的呢?其实 SpringBoot 都给我们提供了对应的支持,我们上手使用十分的简单,只需要开启一些注解支持,配置一些配置文件即可!那我们来看看吧~
1. 异步任务
新建一个带有 Web 模块的 Spring Boot 项目,创建一个 service.AsyncService
类,编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况:
@Service
public class AsyncService {
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务进行中...");
}
}
创建 controller.AsyncController
类:
@RestController
public class AsyncController {
@Autowired
AsyncService asyncService;
@GetMapping("/hello")
public String hello(){
asyncService.hello();
return "success";
}
}
访问 http://localhost:8080/hello 进行测试,3 秒后出现 success,这是同步等待的情况。
❓ 需求:我们如果想让用户立即得到消息而不需要进行等待,可以在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的话,太麻烦了,我们只需要在我们的方法上加一个简单的注解 @Async
即可,如下:
@Service
public class AsyncService {
@Async // 告诉 Spring 这是一个异步方法
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("业务进行中...");
}
}
SpringBoot 就会自己开一个线程池,进行调用。但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync
,开启异步注解功能:
@SpringBootApplication
@EnableAsync // 开启异步注解功能
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
访问 http://localhost:8080/hello 进行测试,网页立即响应而不会等待 3 秒,且后台代码依旧执行。
2. 定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
TaskExecutor
接口TaskScheduler
接口
两个注解:
@EnableScheduling
@Scheduled
① cron 表达式
Cron 表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron 有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year
Seconds Minutes Hours DayofMonth Month DayofWeek
👇 各字段的含义:
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59的整数 | , - * / 四个字符 |
分(Minutes) | 0~59的整数 | , - * / 四个字符 |
小时(Hours) | 0~23的整数 | , - * / 四个字符 |
日期(DayofMonth) | 1~31的整数(但是你需要考虑你月的天数) | ,- * ? / L W C 八个字符 |
月份(Month) | 1~12的整数或者 JAN-DEC | , - * / 四个字符 |
星期(DayofWeek) | 1~7的整数或者 SUN-SAT (1=SUN) | , - * ? / L C # 八个字符 |
年(可选,留空)(Year) | 1970~2099 | , - * / 四个字符 |
每一个域都可以使用数字,也还可以出现如下特殊字符,它们的含义分别是:
*
:表示匹配该域的任意值,假如在Minutes域使用*
, 即表示每分钟都会触发事件。?
:只能用在 DayofMonth 和 DayofWeek 两个域。它也匹配域的任意值,但实际不会。因为 DayofMonth 和 DayofWeek 会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法:
13 13 15 20 * ?
, 其中最后一位只能用?
,而不能使用*
,如果使用*
表示不管星期几都会触发,实际上并不是这样。-
:表示范围,例如在 Minutes 域使用5-20
,表示从5分到20分钟每分钟触发一次/
:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20
, 则意味着从第 5 分钟触发一次,然后每隔 20 分钟触发一次,
:表示列出枚举值值。例如:在 Minutes 域使用5,20
,则意味着在5和20分每分钟触发一次。L
:表示最后,只能出现在 DayofWeek 和 DayofMonth 域,如果在 DayofWeek 域使用5L
,意味着在最后的一个星期四触发。W
:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W
,如果 5 日是星期六,则将在最近的工作日:星期五,即 4 日触发。如果 5 日是星期天,则在6日(周一)触发;如果 5 日在星期一 到星期五中的一天,则就在 5 日触发。另外一点,W
的最近寻找不会跨过月份。LW
:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。#
:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如4#2
,表示某月的第二个星期三。
✍ 举几个例子:
每隔5秒执行一次:
*/5 * * * * ?
每隔1分钟执行一次:
0 */1 * * * ?
每天23点执行一次:
0 0 23 * * ?
每天凌晨1点执行一次:
0 0 1 * * ?
每月1号凌晨1点执行一次:
0 0 1 1 * ?
每月最后一天23点执行一次:
0 0 23 L * ?
每周星期天凌晨1点实行一次:
0 0 1 ? * L
在26分、29分、33分执行一次:
0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:
0 0 0,13,18,21 * * ?
表示在每月的1日的凌晨2点调度任务:
0 0 2 1 * ? *
表示周一到周五每天上午10:15执行作业:
0 15 10 ? * MON-FRI
表示2002-2006年的每个月的最后一个星期五上午10:15执行:
0 15 10 ? 6L 2002-2006
② 示例
创建一个 ScheduledService
,里面存在一个定时执行的方法:
@Service
public class ScheduleService {
@Scheduled(cron = "0 * * * * MON-FRI") // 工作日执行
public void hello(){
System.out.println("hello...");
}
}
写完定时任务之后,我们需要在主程序上增加 @EnableScheduling
开启定时任务功能:
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTaskApplication.class, args);
}
}
3. 邮件任务
邮件发送,在我们的日常开发中,也非常的多,Springboot 帮我们做了支持:
- 邮件发送需要引入
spring-boot-start-mail
- SpringBoot 自动配置
MailSenderAutoConfiguration
- 定义
MailProperties
内容,配置在application.yml
中 - 自动装配
JavaMailSender
- 测试邮件发送
✍ 示例:
1)首先引入 pom 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
点进去 spring-boot-starter-mail
,可以看到 jakarta.mail
:
查看自动配置类:MailSenderAutoConfiguration
:
查看 MailSenderJndiConfiguration
:
然后我们去看下 MailPropertires
:
**2)显然,需要我们在全局配置文件中配置 spring.mail
**:
spring.mail.username=1912420914@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true
📡 获取授权码:在QQ邮箱中的 设置 -> 账户 -> 开启 pop3 和 smtp 服务
3)Spring 单元测试:
@SpringBootTest
class DemoApplicationTests {
@Autowired
JavaMailSenderImpl mailSender;
@Test
void contextLoads() {
//邮件设置1:一个简单的邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("测试:邮件任务测试");
message.setText("Hello");
message.setTo("1912420914@qq.com");
message.setFrom("1912420914@qq.com");
mailSender.send(message);
}
}
查看邮件,邮件接收成功。
👍 还可以通过 MIME 协议发送带有复杂内容的邮件:
@Test
public void contextLoads2() throws MessagingException{
// 邮件设置2:一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("测试:邮件任务测试");
helper.setText("<b style='color:red'>今天 7:30 来开会</b>",true);
// 发送附件
helper.addAttachment("1.jpg",new File(""));
helper.addAttachment("2.jpg",new File(""));
helper.setTo("1912420914@qq.com");
helper.setFrom("1912420914@qq.com");
mailSender.send(mimeMessage);
}