Hello,大胸弟们,我们又又又见面了,今天攀哥继续为大家分享一下SpringBoot的教程,没点关注的宝宝,点一下关注。 

🌲 定时任务简介

🌿 定时任务使用场景

        我们在编写SpringBoot应⽤中经常会遇到这样的场景,⽐如:我需要定时地发送⼀些短信、邮件之类 的操作,也可能会定时地检查和监控⼀些标志、参数等。

        比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式。

        定时任务在实际项目开发中很常见,并且定时任务可以在各种场景中应用,通过自动化操作和任务的规模化管理,提高效率、可靠性和工作质量。可以减少手动操作,避免疏忽和错误,并节省时间和人力资源的投入 。

🌿 定时任务的优点

  • 简单易用:使用注解驱动的方式,简化了定时任务的配置和管理。通过添加@Scheduled注解,可以将普通的方法标记为定时任务,而不需要手动编写定时任务调度的代码。
  • 内置任务调度器:Spring Boot内置了一个轻量级的任务调度器,可以方便地执行定时任务。它提供了线程池、任务管理和并发处理等功能,可以高效地管理和执行任务。
  • 灵活的配置选项:@Scheduled注解支持各种配置选项,例如cron表达式、fixedRate、fixedDelay等,可以非常灵活地定义任务的触发时间和频率。这使得开发人员能够根据具体需求精确控制定时任务的执行方式

🌲 定时任务的使用

🌿 简单案例

        SpringBoot为我们提供了@EnableScheduling 和 @Scheduled两个注解来实现定时任务,用法比较简单:

  • 在启动器上添加注解@EnableScheduling开启定时任务
package com.moxuan.boot_03_async;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAsync
@EnableScheduling
public class Boot03AsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(Boot03AsyncApplication.class, args);
    }

}
  • 创建一个java类作为定时任务的类,然后再在类中写上任务方法,在方法上加上注解@Scheduled

@Scheduled注解中的属性值:

  • fixedRate: 固定频率执行,单位毫秒
  • fixedDelay: 延迟执行,单位毫秒
  • cron: cron表达式,指定表达式所表示的时间执行
package com.moxuan.boot_03_async.jobs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * 定时任务
 *
 * 第一位,表示秒,取值0-59
 * 第二位,表示分,取值0-59
 * 第三位,表示小时,取值0-23
 * 第四位,日期天/日,取值1-31
 * 第五位,日期月份,取值1-12
 * 第六位,星期,取值1-7,1表示星期天,2表示星期一
 * 第七位,年份,可以留空,取值1970-2099
 *
 */
@Component
public class HeartbeatJob {
    private static final Logger logger = LoggerFactory.getLogger(HeartbeatJob.class);

    /**
     * 检查状态1
     */
    @Scheduled(cron = "0 35 14 * * ?")
    public void checkState1() {
        logger.info(">>>>> cron中午14:35上传检查开始....");
        logger.info(">>>>> cron中午14:35上传检查完成....");
    }


    int count =0;
    /**
     * 检查状态2
     */
//    @Scheduled(fixedRate = 3000)
    public void checkState2() {
        count += 1;
        logger.info(">>>>> fixedRate 每隔3秒检查....当前检查第"+count+"次");
    }

//    @Scheduled(fixedDelay = 5000)
    public void checkStat3(){
        logger.info(">>>>> fixedDelay 这是一个5秒后执行的....");
    }

}

🌿 cron 表达式

Cron 表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron 有如下两种语法格式:

  • 秒 分 小时 日期 月份 星期 年
  • 秒 分 小时 日期 月份 星期

👇 各字段的含义:

字段

允许值

允许的特殊字符

秒(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

🌲 定时邮件发送

🌿 发送邮件的步骤

邮件发送,在我们的日常开发中,也非常的多,Springboot 帮我们做了支持:

  • 邮件发送需要引入 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.mail

# 你的邮箱地址
spring.mail.username=XXXXXX@qq.com
# 你的QQ授权码
spring.mail.password=XXXXXX
# 邮箱服务器
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

获取授权码:在QQ邮箱中的 设置 -> 账户 -> 开启 pop3 和 smtp 服务

发送简单的文本邮件,在util包下新建一个EmailUtil类,专用来发送邮件(注意在EmailUtil类上添加@Component注解 ):

@Autowired
private JavaMailSender javaMailSender;

/**
     * 向用户邮箱发送短信
     *
     * @param email 收件人邮箱
     */
public  void sendEmailMessage(String email) {
    MimeMessage message = javaMailSender.createMimeMessage();
    try {
        //获取验证码 存入redis
    
        //邮箱发送内容组成
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        helper.setSubject("【墨轩学习网】愿世间美好与您紧紧相拥");
        helper.setText("早上好!我喜欢你很久了", true);
        helper.setTo(email);
        helper.setFrom("墨轩学习网" + '<' + "1032974410@qq.com" + '>');
        javaMailSender.send(message);
        logger.info("邮件发送成功!!");
    } catch (MessagingException e) {
        logger.error("邮件发送失败!!");
    }
}

在定时任务中发送邮件

@Autowired
EmailUtil emailUtil;

@Scheduled(cron = "0 47 22 * * ?")
public void sendEmail(){
    emailUtil.sendEmailMessage("278421824@qq.com");
}

上面的案例中虽然可以发送邮件,但是发送的简单的文本,在实际开发中往往需要按照某种格式的邮件方式去发送,比如以html的方式发送给客户,接下来我们看看如何按照html模板发送邮件:

在项目中新建ftl目录,在其中新建邮件模板:mail.ftl

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="email code">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<!--邮箱验证码模板-->
<body>
<div style="background-color:#ECECEC; padding: 35px;">
    <table cellpadding="0" align="center"
           style="width: 800px;height: 100%; margin: 0px auto; text-align: left; position: relative; border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; font-size: 14px; font-family:微软雅黑, 黑体; line-height: 1.5; box-shadow: rgb(153, 153, 153) 0px 0px 5px; border-collapse: collapse; background-position: initial initial; background-repeat: initial initial;background:#fff;">
        <tbody>
        <tr>
            <th valign="middle"
                style="height: 25px; line-height: 25px; padding: 15px 35px; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: RGB(148,0,211); background-color: RGB(148,0,211); border-top-left-radius: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px;">
                <font face="微软雅黑" size="5" style="color: rgb(255, 255, 255); ">【墨轩学习网】愿世间再无难学的技术</font>
            </th>
        </tr>
        <tr>
            <td style="word-break:break-all">
                <div style="padding:25px 35px 40px; background-color:#fff;opacity:0.8;">

                    <h2 style="margin: 5px 0px; ">
                        <font color="#333333" style="line-height: 20px; ">
                            <font style="line-height: 22px; " size="4">
                                尊敬的用户:</font>{0}
                        </font>
                    </h2>
                    <!-- 中文 -->
                    <p>您好!感谢您使用墨轩学习网,{1}</p><br>

                    <div style="width:100%;margin:0 auto;">
                        <div style="padding:10px 10px 0;border-top:1px solid #ccc;color:#747474;margin-bottom:20px;line-height:1.3em;font-size:12px;">
                            <p>墨轩学习网研发团队</p>
                            <p>联系我们:1032974410@qq.com</p>
                            <br>
                            <p>此为系统邮件,请勿回复<br>
                            </p>
                            <!--<p>©***</p>-->
                        </div>
                    </div>
                </div>
            </td>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

编写邮箱工具类:

package com.moxuan.boot_03_async.util;


import com.moxuan.boot_03_async.jobs.HeartbeatJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.MessageFormat;

@Component
public class EmailUtil {
    // 日志记录员
    private static final Logger logger = LoggerFactory.getLogger(EmailUtil.class);

    /**
     * 读取邮件模板
     * 替换模板中的信息
     *
     * @param ftl 需要发送的模板
     * @return
     */
    public String buildContent(String ftl,Object ... objects) {
        //加载邮件html模板
        Resource resource = new ClassPathResource(ftl);
        InputStream inputStream = null;
        BufferedReader fileReader = null;
        StringBuffer buffer = new StringBuffer();
        String line = "";
        try {
            inputStream = resource.getInputStream();
            fileReader = new BufferedReader(new InputStreamReader(inputStream));
            while ((line = fileReader.readLine()) != null) {
                buffer.append(line);
            }
        } catch (Exception e) {
            logger.info("发送邮件读取模板失败{}", e);
        } finally {
            if (fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        //替换html模板中的参数
        return MessageFormat.format(buffer.toString(), objects);
    }


    @Autowired
    private JavaMailSender javaMailSender;

    /**
     * 向用户邮箱发送短信
     *
     * @param email 收件人邮箱
     */
    public  void sendEmailMessage(String email,String ftl,Object ... obj) {
        MimeMessage message = javaMailSender.createMimeMessage();
        try {
            //获取验证码 存入redis

            //邮箱发送内容组成
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setSubject("【墨轩学习网】愿世间美好与您紧紧相拥");
            helper.setText(buildContent(ftl,obj), true);
            helper.setTo(email);
            helper.setFrom("墨轩学习网" + '<' + "1032974410@qq.com" + '>');
            javaMailSender.send(message);
            logger.info("邮件发送成功!!");
        } catch (MessagingException e) {
            logger.error("邮件发送失败!!");
        }
    }


}

在定时任务中发送邮件

@Autowired
EmailUtil emailUtil;

@Scheduled(cron = "0 34 22 * * ?")
public void sendEmail(){
    emailUtil.sendEmailMessage("278421824@qq.com","ftl/mail.ftl","默默","早上好,今天又是元气满满的一天");
}

效果如下:

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐