【Spring】定时任务与拦截器(定时发送邮箱实例)

Spring定时任务与拦截器

定时任务

定时任务是一种在计算机中按计划自动执行的任务。它允许设置一个特定的时间,当系统时间达到这个设定时,系统会自动执行相关的程序或脚本。定时任务在很多场景下都非常有用,比如系统维护、数据备份、定期更新等。

在Spring Boot中实现定时任务,有两种常见方案。

使用Spring @Scheduled注解

使用@Scheduled注解的基本步骤:

  1. 开启定时任务支持:在Spring Boot的主类或者任何配置类上添加@EnableScheduling注解。
  2. 创建定时任务:在需要定时执行的方法上添加@Scheduled注解,并配置相应的属性。

@Scheduled注解支持的属性有:

  • fixedRate:定义任务执行的频率,单位为毫秒。
  • fixedDelay:定义任务完成后下一次任务开始执行的延迟时间,单位为毫秒。
  • cron:使用cron表达式定义任务的执行时间。
@Configuration     // 标记配置类
@EnableScheduling  // 开启定时任务
public class SaticScheduleTask {
    @Resource
    UserService userService;
    // 添加定时任务 每隔5秒调用一次
    @Scheduled(cron = "0/5 * * * * ?")
    public void configureTasks() {
        userService.addUser();
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

cron = [秒] [分] [时] [日] [月] [周] [年]

说明必填允许填写的值允许的通配符
0 - 59- * /
0 - 59- * /
0 - 23- * /
1 - 31- * ? / L W
1 - 12 or JAN - DEC- * /
1 - 7 or SUN - SAT- * ? / L #
×1970 - 2099- * /
  • ?:表示不指定值,即不关心某个字段的取值时使用。需要注意的是,月份中的日期和星期可能会起冲突,因此在配置时这两个得有一个是?
  • *****:表示所有值,例如:在秒的字段上设置 *,表示每一秒都会触发
  • :用来分开多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
  • -:表示区间,例如在秒上设置 “10-12”,表示 10,11,12秒都会触发
  • /:用于递增触发,如在秒上面设置"5/15" 表示从5秒开始,每增15秒触发(5,20,35,50)
  • #:序号(表示每月的第几个周几),例如在周字段上设置"6#3"表示在每月的第三个周六,(用在母亲节和父亲节再合适不过了)周字段的设置,若使用英文字母是不区分大小写的 ,即 MON 与 mon 相同
  • L:表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会自动判断是否是润年),在周字段上表示星期六,相当于"7"或"SAT"(注意周日算是第一天)。如果在"L"前加上数字,则表示该数据的最后一个。例如在周字段上设置"6L"这样的格式,则表示"本月最后一个星期五"
  • W:表示离指定日期的最近工作日(周一至周五),例如在日字段上设置"15W",表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发,如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。

(注,“W"前只能设置具体的数字,不允许区间”-")

L 和 W 可以一组合使用。如果在日字段上设置"LW",则表示在本月的最后一个工作日触发

0 0 2 1 * ?
	表示每月 1 日的凌晨 2 点执行。
0 15 10 ? * MON-FRI
	表示周一到周五每天上午 10:15 执行。
0 15 10 ? 6L 2019-2020
	表示 2019-2020 年每个月的最后一个星期五上午 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 * * 2019
	表示 2019 年的每天上午 10:15 执行。

创建多线程定时任务

@Configuration     // 标记配置类,兼备Component的效果。
@EnableScheduling  // 开启定时任务
@EnableAsync       // 开启多线程
public class SaticScheduleTask {
    @Resource
    UserService userService;
    // 添加定时任务
    @Scheduled(cron = "0/5 * * * * ?")
    @Async         // 异步方法 异步调用 默认为同步
    @Transactional // 添加事务
    public void configureTasks() {
        userService.addUser();
        System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
    }
}

根据 Spring 的文档说明,默认采用的是单线程的模式的。所以在 Java 应用中,绝大多数情况下都是通过同步的方式来实现交互处理的。

当多个任务的执行势必会相互影响。例如,如果 A 任务执行时间比较长,那么 B 任务必须等到 A 任务执行完毕后才会启动执行。又如在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在 spring3.x 之后,已经内置了 @Async 来完美解决这个问题。

用定时任务编写一个定时发送邮箱的功能。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
@Configuration
@EnableScheduling //开启定时任务
@EnableAsync //开启异步任务
public class ScheduledConfig {

    @Async
    @Scheduled(cron = "0/5 * * * * ?")
    public void sendEmail(){
        try {
            System.out.println(Thread.currentThread().getName());
            JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
            javaMailSender.setHost("smtp.qq.com");
            javaMailSender.setPort(465);
            javaMailSender.setUsername("发送者邮箱");
            javaMailSender.setPassword("发送者密码");
            javaMailSender.setDefaultEncoding("UTF-8");

            Properties properties = new Properties();
            properties.setProperty("mail.smtp.timeout", "30000");
            properties.setProperty("mail.smtp.auth", "true");
            properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            javaMailSender.setJavaMailProperties(properties);

            // 构建一个邮件对象
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            // true表示构建一个可以带附件的邮件对象
            MimeMessageHelper mimeMessageHelper = null;
            mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            // 设置邮件主题
            mimeMessageHelper.setSubject("这是一封测试邮件");
            // 设置邮件发送者
            mimeMessageHelper.setFrom("发送者邮箱");

            // 设置邮件接收者,可以有多个接收者
            mimeMessageHelper.addTo("接收者邮箱");
            // 设置邮件抄送人,可以有多个抄送人
        	// mimeMessageHelper.addCc("xxx@qq.com");
        	// 设置邮件隐秘抄送人,可以有多个隐秘抄送人
        	// mimeMessageHelper.addBcc("xxx@qq.com");


            // 设置邮件发送日期
            mimeMessageHelper.setSentDate(new Date());
            // 设置邮件的正文
            mimeMessageHelper.setText("<p>猜猜我是谁,嘿嘿</p>", true);

            // 发送邮件
            javaMailSender.send(mimeMessage);
        }catch (Exception e){
            throw  new EmailException("邮件发送失败");
        }

        System.out.println("这是发送邮件功能...."+ LocalDateTime.now());
    }
}

QQ邮箱授权码:SMTP/IMAP服务

在 QQ 邮箱安全设置中开启 POP3/IMAP/SMTP/Exchange/CardDAV 服务,生成的授权码即密码。

在这里插入图片描述

/**
 * @Description: 全局异常
 */
@ControllerAdvice(basePackages = "com.hz")
@Slf4j
public class GlobalException {
    
	/**
     * 处理自定义异常 IdException
     */
    @ExceptionHandler(IdException.class)
    @ResponseBody
    public ResultJSON handleException(IdException e){
        log.error("异常信息:",e);
        return ResultJSON.error("请求失败");
    }
    
    /**
     * 处理邮件发送异常 EmailException
     */
    @ExceptionHandler(EmailException.class)
    @ResponseBody
    public ResultJSON handleException(EmailException e){
        log.error("异常信息:",e);
        return ResultJSON.error("邮件发送失败");
    }

    /**
     * 处理运行时异常 RuntimeException
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public ResultJSON handleException(RuntimeException e){
        log.error("异常信息:",e);
        return ResultJSON.error("请求失败");
    }
}
/**
 * @Description: 自定义异常
 */
public class EmailException extends RuntimeException{
    public EmailException(String msg){
        super(msg);
    }
}

然后对方每隔 5 秒就会收到一封邮件,终止服务,发送停止。

在这里插入图片描述

使用Quartz框架

Quartz 是一个功能丰富的任务调度库,它提供了比 Spring 的@Scheduled更高级的特性,如作业存储、作业集群、触发器等。

添加依赖:在pom.xml中添加 Quartz 的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

配置Quartz:在application.propertiesapplication.yml中配置 Quartz。

# application.properties
spring.quartz.job-store-type = memory

定义Job和Trigger:创建一个继承自QuartzJobBean的类,并定义触发器。

public class MyJob extends QuartzJobBean {
    private final MyService myService;
    @Autowired
    public MyJob(MyService myService) {
        this.myService = myService;
    }
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        myService.performTask();
    }
}

然后在配置类中定义 JobDetail 和 Trigger。

@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail myJobDetail() {
        return JobBuilder.newJob(MyJob.class)
                .withIdentity("myJob")
                .storeDurably()
                .build();
    }

    @Bean
    public Trigger myJobTrigger() {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder
                .simpleSchedule()
                .withIntervalInSeconds(10)
                .repeatForever();

        return TriggerBuilder.newTrigger()
                .forJob(myJobDetail())
                .withIdentity("myTrigger")
                .withSchedule(scheduleBuilder)
                .build();
    }
}

@Scheduled 注解简单易用,适用于简单的定时任务,Quartz 则更适合复杂的调度需求。

拦截器

Spring MVC 的处理拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。

过滤器

Servlet 技术的一部分,属 于Java EE 规范,用于对请求进行预处理和后处理。过滤器可以过滤所有的请求,包括静态资源(如HTML、CSS、JavaScript文件)和动态请求。通常用于处理请求的通用任务,如日志记录、权限检查、事务管理等。多个过滤器可以组成一个过滤链,按照配置的顺序执行。

  • 配置:在 web.xml 中配置,可以针对 URL 模式进行设置。

拦截器

Spring MVC 框架的一部分,仅限于 Spring MVC 环境下使用。拦截器仅拦截控制器(Controller)中的请求处理方法,不会拦截对静态资源的请求。通常用于处理与 Spring MVC 相关的任务,如用户登录验证、数据绑定、请求和视图的预处理和后处理等。多个拦截器同样可以组成拦截器链,按照添加的顺序执行。

  • 配置:通常通过 Spring 的配置文件或注解进行配置。

在 SpringBoot 中使用拦截器

1、实现 HandlerInterceptor 接口

@Component
public class LoginInterceptor implements HandlerInterceptor {
    // preHandle 是请求执行前执行的
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入拦截器");
        return true;
    }
    // postHandler 是请求结束执行的 当 preHandle 返回 true 才会执行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle......");
    }
        
    // afterCompletion 是视图渲染完成后才执行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("afterCompletion......");
    }
}

2、实现 WebMvcConfigurer 接口配置拦截路径

三种方式:

  • 继承 WebMvcConfigurerAdapter spring5.0(已弃用,不推荐)
  • 实现 WebMvcConfigurer(推荐)
  • 继承 WebMvcConfigurationSupport(会导致 springboot 自动配置失效)
 @Configuration
public class WebJavaBeanConfiguration implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    public void addInterceptors(InterceptorRegistry registry) {
        // addInterceptor 需要一个实现 HandlerInterceptor 接口的拦截器实例
        registry.addInterceptor(loginInterceptor)
            .addPathPatterns("/**") // 设置拦截器的过滤路径规则(对所有请求都拦截)
            .excludePathPatterns("/user/login") // 设置不需要拦截的过滤规则
            .excludePathPatterns("/user/logout");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值