异步任务
异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。
编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况
-
创建一个新的 SpringBoot 项目,导入 Web 依赖
-
创建 service 包以及 controller 包
-
在 service 中创建 AsyncService,并对其模拟异步
package com.aze.service; import org.springframework.stereotype.Service; @Service public class AsyncService { public void hello(){ try { Thread.sleep(3000); // 3s } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("数据正在处理中..."); } }
-
在 controller 包中,创建 AsyncController
package com.aze.controller; import com.aze.service.AsyncService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class AsyncController { @Autowired AsyncService asyncService; @RequestMapping("/hello") public String hello(){ asyncService.hello(); return "OK!"; } }
-
启动项目进行测试,访问
http://localhost:8080/hello
,发现进入/hello
需要 3s 才会显示成功,同步等待这就导致了用户体验差了 -
如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可
package com.aze.service; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncService { @Async public void hello(){ try { Thread.sleep(3000); // 3s } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("数据正在处理中..."); } }
-
添加了以上注解,SpringBoot 就会自动的开启一个线程池进行调用,为了使上面添加的注解生效,还需要在主程序上开启这个注解
package com.aze; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync @SpringBootApplication public class Springboot10TestApplication { public static void main(String[] args) { SpringApplication.run(Springboot10TestApplication.class, args); } }
-
启动项目进行测试,访问
http://localhost:8080/hello
,发现瞬间响应成功,后台代码依然可以完成指定任务
邮件任务
邮件发送
- 需要引入 spring-boot-start-mail
- SpringBoot 自动配置 MailSenderAutoConfiguration
- 定义 MailProperties 内容,配置在 application.yml 中
- 自动装配 JavaMailSender
- 测试邮件发送
步骤:
-
导入 pom 的相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
在
spring-boot-starter-mail
中可以看到,引入了的依赖jakarta.mail
<dependency> <groupId>com.sun.mail</groupId> <artifactId>jakarta.mail</artifactId> <version>1.6.5</version> <scope>compile</scope> </dependency>
-
查看自动配置类:MailSenderAutoConfiguration
点进
MailSenderJndiConfiguration
这个类,发现这个类中存在 bean,JavaMailSenderImpl
点进配置文件看看,
MailProperties
-
修改 application.yaml 文件,配置邮箱发送的相关配置
spring: mail: # 邮箱账号 username: xxx@qq.com # 邮箱的授权码 password: xxx host: smtp.qq.com # QQ 邮箱需要配置 ssl properties: mail: smtp: ssl: enable: true
注意:获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
-
在单元测试中进行编写邮件,进行测试
package com.aze; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; @SpringBootTest class Springboot10TestApplicationTests { @Autowired JavaMailSenderImpl mailSender; @Test void contextLoads() { // 简单邮件:只包含标题和内容 SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); // 邮件标题 simpleMailMessage.setSubject("这里是标题!"); // 邮件内容 simpleMailMessage.setText("这里是邮件的内容!"); // 发送到哪 simpleMailMessage.setTo("1031952566@qq.com"); // 哪里发送的 simpleMailMessage.setFrom("1031952566@qq.com"); mailSender.send(simpleMailMessage); } @Test void test() throws MessagingException { // 复杂邮件:包含标题、内容、页面标签内容、附件等 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true); // 标题 helper.setSubject("这还是一封邮件"); // 内容 helper.setText("<h2 style='color:red'>这还是邮件内容</h2>",true); // 附件 helper.addAttachment("图片1.png",new File("C:\\Users\\Hasaki\\Desktop\\1.png")); helper.addAttachment("图片2.png",new File("C:\\Users\\Hasaki\\Desktop\\1.png")); // 发送到哪 helper.setTo("1031952566@qq.com"); // 哪里发送的 helper.setFrom("1031952566@qq.com"); mailSender.send(mimeMessage); } }
-
一个一个进行测试!
定时任务
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。
- TaskExecutor接口
- TaskScheduler接口
两个注解:
- @EnableScheduling
- @Scheduled
cron表达式:
字段 | 允许值 | 允许的特殊符号 |
---|---|---|
秒 | 0~59 | , - * / |
分 | 0~59 | , - * / |
小时 | 0~23 | , - * / |
日期 | 1~31 | , - * / ? L W C |
月份 | 1~12 | , - * / |
星期 | 0~7 或者 SUN~SAT(0和7都是SUN) | , - * / ? L C # |
特殊符号 | 含义 |
---|---|
, | 枚举 |
- | 区间 |
* | 任意 |
/ | 步长 |
? | 日/星期冲突匹配 |
L | 最后 |
W | 工作日 |
C | 和 calendar 联系后计算过的值 |
# | 星期,4#2:第二个星期四 |
测试:
-
创建一个ScheduledService,存放一个 hello 方法,每两秒执行
package com.aze.service; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service public class ScheduledService { // 秒 分 时 日 月 周几 // 0 * * * * MON-FRI // 注意 cron 表达式的用法; @Scheduled(cron = "0/2 * * * * ?") public void hello(){ System.out.println("hello!"); } }
-
写完定时任务之后,我们需要在主程序上增加
@EnableScheduling
开启定时任务功能package com.aze; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling //开启基于注解的定时任务 @EnableAsync @SpringBootApplication public class Springboot10TestApplication { public static void main(String[] args) { SpringApplication.run(Springboot10TestApplication.class, args); } }
-
进行测试
cron 表达式:https://www.bejson.com/othertools/cron/
常用的表达式:
- 0/2 * * * * ? 表示每2秒 执行任务
- 0 0/2 * * * ? 表示每2分钟 执行任务
- 0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
- 0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
- 0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
- 0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
- 0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
- 0 0 12 ? * WED 表示每个星期三中午12点
- 0 0 12 * * ? 每天中午12点触发
- 0 15 10 ? * * 每天上午10:15触发
- 0 15 10 * * ? 每天上午10:15触发
- 0 15 10 * * ? 每天上午10:15触发
- 0 15 10 * * ? 2005 2005年的每天上午10:15触发
- 0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
- 0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
- 0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
- 0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
- 0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
- 0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
- 0 15 10 15 * ? 每月15日上午10:15触发
- 0 15 10 L * ? 每月最后一日的上午10:15触发
- 0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
- 0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
- 0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发