SpringBoot 异步、定时、邮件任务


📧 异步、定时、邮件任务


在我们的工作中,常常会用到异步处理任务,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。还有一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息。还有就是邮件的发送,微信的前身也是邮件服务呢?这些东西都是怎么实现的呢?其实 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);
}

📚 References


文章作者: Gtwff
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Gtwff !
  目录