springboot 整合Quartz实现定时任务实例

在做项目时往往会有定时任务,比如某某时间做什么,多少秒应该怎么样之类的。

spring支持多种定时任务的实现,下面我们来介绍使用spring的定时器和使用quartz定时器。

1、使用spring的定时器

spring自带支持定时器的任务实现,其可通过简单配置来使用到简单的定时任务。

@EnableScheduling:标注启动定时任务。

@Scheduled(cron = "0 */1 * * * * ")  定义某个定时任务(每1分钟执行一次

在类名前启动定时任务。加注解@EnableScheduling

在需要定时的方法类中加定时任务注解@Scheduled(cron = ""

例子:

/**
*定时检查离线设备

**/

@Component

@EnableScheduling
public class EquipmentSchedule {

    // 每天6点到23点间每10秒执行一次
    @Scheduled(cron = "*/10 * 6-23 * * *")
    public void run() {
        // 所有未恢复的离线记录
        List<EquipmentOffline> listNotRecovered = equipmentOfflineService.getListNotRecovered();
        List<Equipment> equipments;
        List<Equipment> equipmentModifyList = new ArrayList<>();
        equipments = equipmentService.getEquiByRedis();// 从缓存中获取所有设备
        // 循环处理每一台设备
        for (Equipment equipment : equipments) {
            String equId = equipment.getEquId();
            int equStatus = UnionPayHeartbeatUtil.getEquStatus(equId, configService.getEquipmentHeartbeatTimeout(),
                    client);
            EquipmentOffline equipmentOffline = get(equId, listNotRecovered);
            if (-1 == equStatus) {
                if (null == equipmentOffline) {
                    equipmentOffline = equipmentOfflineService.offline(equId, "", "");
                } else {
                    if (equipment.getStatus() != equStatus) {
                        equipment.setStatus(-1);
                        equipmentModifyList.add(equipment);
                    }
                }
            } else if (1 == equStatus) {
                if (null == equipmentOffline) {
                    if (equipment.getStatus() != equStatus) {
                        equipment.setStatus(1);
                        equipmentModifyList.add(equipment);
                    }
                } else {
                    equipmentOffline = equipmentOfflineService.online(equipmentOffline);
                }
            }
        }

        equimentModifyBatch.batchModfiyEquipment(equipmentModifyList);
    }

使用quartz实现定时任务

Quartz设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。

SimpleTrigger:指定从某一个时间开始,以一定的时间间隔(单位是毫秒)执行的任务。它适合的任务类似于:9:00 开始,每隔1小时,执行一次。

CronTrigger:适合于更复杂的任务,它支持类型于Linux Cron的语法(并且更强大)。基本上它覆盖了以上三个Trigger的绝大部分能力(但不是全部)—— 当然,也更难理解。它适合的任务类似于:每天0:00,9:00,18:00各执行一次。

首先加quartz依赖

<dependency>

  <groupId>org.quartz-scheduler</groupId>

  <artifactId>quartz</artifactId>

</dependency>

由于我们使用的是spring-boot框架,其目的是做到零配置文件,所以我们不使用xml文件的配置文件来定义一个定时器,而是使用向spring容器暴露bean的方式。

向spring容器暴露所必须的bean

import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
@EnableScheduling
public class SchedledConfiguration {

    @Bean("mySchedulerFactoryBean")
    public SchedulerFactoryBean schedulerFactoryBean() {
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        return schedulerFactoryBean;
    }
    
//    @Autowired
//    SchedulerFactoryBean schedulerFactoryBean;
    @Bean
    public Scheduler scheduler(@Qualifier("mySchedulerFactoryBean") SchedulerFactoryBean schedulerFactoryBean) {
        return schedulerFactoryBean.getScheduler();
    }

}
 

SchedulerFactoryBean:主要的管理的工厂,这是最主要的一个bean。quartz通过这个工厂来进行对各触发器的管理。

controller类

import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;

@RestController
@RequestMapping("/timer")
@Api(tags = "定时任务管理")
public class TimerController {

    public final Logger log = Logger.getLogger(ModuleTimerApplication.class);

    @Autowired
    private TimerService timerService;

    @Autowired
    private SessionService sessionService;

    @Autowired
    private Scheduler scheduler;

@GetMapping(value = "/list")
    @ApiOperation(value = "分页获取列表", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> list(
            @ApiParam(value = StaticFinalStringUtil.AFC_SPECIAL_STRING) @PageableDefault(page = 0, size = 10) Pageable pageable,
            @ApiParam(value = "任务名", required = false) @RequestParam(name = "timerName", required = false) String timerName,
            @ApiParam(value = "创建时间", required = false) @RequestParam(name = "createTime", required = false) String createTime,
            @ApiParam(value = "修改时间", required = false) @RequestParam(name = "lastReviseTime", required = false) String lastReviseTime) {
        try {

            Page<Timer> timers = timerService.list(pageable, timerName, createTime, lastReviseTime);
            List<Timer> list = timers.getContent();
            for (Timer timer : list) {
                timer.setStatus(String.valueOf(getTriggerStatus(timer).ordinal()));
            }
            return new JsonResponse<>(timers);
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    @PostMapping(value = "/add")
    @ApiOperation(value = "新增", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> add(HttpServletRequest request, @ApiParam("") @RequestBody Timer timer) {
        try {
            User user = sessionService.getLoginUser(request);
            timer.setCreateTime(new Date());
            timer.setCreator(user.getUserName());
            timer.setLastReviser(user.getUserName());
            timer.setLastReviseTime(new Date());
            // 添加
            timer = timerService.save(timer);
            // 启动
            if ("1".equals(timer.getPlanStatus())) {
                QuartzUtil.startup(timer, scheduler, log);
            }
            return new JsonResponse<>(timer);
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    @PostMapping(value = "/update")
    @ApiOperation(value = "更新", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> update(HttpServletRequest request, @RequestBody Timer t, @RequestParam String id) {
        try {
            Timer timer = timerService.findById(id);
            if (timer == null) {
                return new JsonResponse<>(400, "找不到该条数据");
            } else {
                Timer oldTimer = new Timer();
                BeanUtils.copyProperties(timer, oldTimer);

                User user = sessionService.getLoginUser(request);
                timer.setTimerName(t.getTimerName());
                timer.setCron(t.getCron());
                timer.setDescription(t.getDescription());
                timer.setIsConcurrent(t.getIsConcurrent());
                timer.setLastReviser(user.getUserName());
                timer.setLastReviseTime(new Date());
                timer.setPlanStatus(t.getPlanStatus());
                timer.setServiceUrl(t.getServiceUrl());
                timer.setStatus(t.getStatus());
                timer.setTimeDesc(t.getTimeDesc());
                timer = timerService.save(timer);

                deal(oldTimer, timer);

                return new JsonResponse<>(timer);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    private void deal(Timer oldTimer, Timer newTimer) throws SchedulerException {
        if (isChanged(oldTimer, newTimer)) {
            if ("1".equals(oldTimer.getPlanStatus()) && "0".equals(newTimer.getPlanStatus())) {
                shutdown(oldTimer);
            } else if ("1".equals(oldTimer.getPlanStatus()) && "1".equals(newTimer.getPlanStatus())) {
                shutdown(oldTimer);
                QuartzUtil.startup(newTimer, scheduler, log);
            } else if ("0".equals(oldTimer.getPlanStatus()) && "1".equals(newTimer.getPlanStatus())) {
                QuartzUtil.startup(newTimer, scheduler, log);
            }
        }
    }

    private boolean isChanged(Timer oldTimer, Timer newTimer) {
        if (oldTimer.getIsConcurrent().equals(newTimer.getIsConcurrent())) {
            return true;
        }
        if (oldTimer.getCron().equals(newTimer.getCron())) {
            return true;
        }
        if (oldTimer.getServiceUrl().equals(newTimer.getServiceUrl())) {
            return true;
        }
        if (oldTimer.getPlanStatus().equals(newTimer.getPlanStatus())) {
            return true;
        }
        return false;
    }

    @PostMapping("/delete")
    @ApiOperation(value = "删除", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> delete(@ApiParam(value = "") @RequestParam(required = true) String id) {
        try {
            Timer timer = timerService.findById(id);
            if (null == timer) {
                return new JsonResponse<>(400, "没有对应记录【id=" + id + "】");
            }
            shutdown(timer);// 删除前先停止运行
            timerService.delete(id);
            return new JsonResponse<>(200, "删除成功");
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }
 

    @PostMapping(value = "/startup")
    @ApiOperation(value = "启动", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> startup(@ApiParam("定时任务id") @RequestParam String id) {
        try {
            Timer timer = timerService.findById(id);
            if (null == timer) {
                return new JsonResponse<>(400, "没有对应记录【id=" + id + "】");
            }
            QuartzUtil.startup(timer, scheduler, log);
            return new JsonResponse<>(timer);
        } catch (SchedulerException e) {
            return new JsonResponse<>(400, "操作失败!原因:" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    @PostMapping(value = "/shutdown")
    @ApiOperation(value = "关闭", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> shutdown(@ApiParam("定时任务id") @RequestParam String id) {
        try {
            Timer timer = timerService.findById(id);
            if (null == timer) {
                return new JsonResponse<>(400, "没有对应记录【id=" + id + "】");
            }
            shutdown(timer);
            return new JsonResponse<>(timer);
        } catch (SchedulerException e) {
            return new JsonResponse<>(400, "操作失败!原因:" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    private void shutdown(Timer timer) throws SchedulerException {
        JobKey jobKey = QuartzUtil.getJobKey(timer);
        scheduler.deleteJob(jobKey);
        log.info("shutdown:" + timer.getTimerId());
    }

    @PostMapping(value = "/pause")
    @ApiOperation(value = "暂停", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> pause(@ApiParam("定时任务id") @RequestParam String id) {
        try {
            Timer timer = timerService.findById(id);
            if (null == timer) {
                return new JsonResponse<>(400, "没有对应记录【id=" + id + "】");
            }
            pause(timer);
            return new JsonResponse<>(timer);
        } catch (SchedulerException e) {
            return new JsonResponse<>(400, "操作失败!原因:" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    private void pause(Timer timer) throws SchedulerException {
        JobKey jobKey = QuartzUtil.getJobKey(timer);
        scheduler.pauseJob(jobKey);
        log.info("pause:" + timer.getTimerId());
    }

    @PostMapping(value = "/resume")
    @ApiOperation(value = "恢复", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> resume(@ApiParam("定时任务id") @RequestParam String id) {
        try {
            Timer timer = timerService.findById(id);
            if (null == timer) {
                return new JsonResponse<>(400, "没有对应记录【id=" + id + "】");
            }
            resume(timer);
            return new JsonResponse<>(timer);
        } catch (SchedulerException e) {
            return new JsonResponse<>(400, "操作失败!原因:" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    private void resume(Timer timer) throws SchedulerException {
        JobKey jobKey = QuartzUtil.getJobKey(timer);
        scheduler.resumeJob(jobKey);
        log.info("resume:" + timer.getTimerId());
    }

    @PostMapping(value = "/runOnce")
    @ApiOperation(value = "立即运行一次", produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonResponse<?> runOnce(@ApiParam("定时任务id") @RequestParam String id) {
        try {
            Timer timer = timerService.findById(id);
            if (null == timer) {
                return new JsonResponse<>(400, "没有对应记录【id=" + id + "】");
            }
            runOnce(timer);
            return new JsonResponse<>(timer);
        } catch (SchedulerException e) {
            return new JsonResponse<>(400, "操作失败!原因:" + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            return new JsonResponse<>(500, e.getMessage());
        }
    }

    private void runOnce(Timer timer) throws SchedulerException {
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withRepeatCount(0);// 只执行1次,即重复0次
        SimpleTrigger trigger = TriggerBuilder.newTrigger().withSchedule(scheduleBuilder).build();
        JobDetail jobDetail = JobBuilder.newJob(timer.getIsConcurrent() ? QuartzConcurrentJob.class : QuartzJob.class)
                .build();
        jobDetail.getJobDataMap().put(QuartzUtil.JOB_DATA_MAP, timer);
        scheduler.scheduleJob(jobDetail, trigger);
        log.info("runOnce:" + timer.getTimerId());
    }

    private TriggerState getTriggerStatus(Timer timer) throws SchedulerException {
        return scheduler.getTriggerState(QuartzUtil.getTriggerKey(timer));
    }
}
 

QuartzUtil定时任务工具类

import java.io.IOException;
import java.text.ParseException;
import java.util.TimeZone;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.log4j.Logger;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;

public class QuartzUtil {
    
    public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("Asia/Shanghai");
    
    public static final String JOB_DATA_MAP = "gxmisJobDataMap";

    public static String formatUrl(String url){
        if(!url.startsWith("http://")){
            url = "http://" + url;
        }
        return url;
    }
    
    public static void execute(JobExecutionContext context, PoolingHttpClientConnectionManager cm, Logger log) throws JobExecutionException {
        Timer timer = (Timer) context.getMergedJobDataMap().get(JOB_DATA_MAP);
        if(null == timer){
            JobKey jobKey = context.getTrigger().getJobKey();
            log.warn("定时任务【"+jobKey.getName()+"】缺少任务信息,将停止执行!");
            try {
                context.getScheduler().deleteJob(jobKey);
            } catch (SchedulerException e) {
                log.error(e.getMessage());
            }
        }else{
            String url = timer.getServiceUrl();
            if(CommonUtil.isEmtpy(url)){
                // 什么也不做
            }else{
                url = QuartzUtil.formatUrl(url);
                HttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
                HttpGet post = new HttpGet(url);
                try {
                    HttpResponse response = httpClient.execute(post);
                    if(200 == response.getStatusLine().getStatusCode()){
                        // TODO 确定返回内容
                        log.debug(CommonUtil.read(response.getEntity().getContent()));
                    }else{
                        log.warn("timer:"+timer.getTimerId()+"\t请求失败:"+timer.getServiceUrl());
                    }
                } catch (IOException e) {
                    log.warn(e.getMessage());
                }
            }
        }
    }
    
    public static void startup(Timer timer, Scheduler scheduler, Logger log) throws SchedulerException {
        TriggerKey triggerKey = getTriggerKey(timer);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (null == trigger) {
            JobKey jobKey = getJobKey(timer);
            JobDetail jobDetail = JobBuilder
                    .newJob(timer.getIsConcurrent() ? QuartzConcurrentJob.class : QuartzJob.class).withIdentity(jobKey)
                    .build();
            jobDetail.getJobDataMap().put(QuartzUtil.JOB_DATA_MAP, timer);
            CronExpression cronExpression = getCronExpression(timer.getCron());
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            trigger = TriggerBuilder.newTrigger().withDescription(timer.getTimerName()).withIdentity(triggerKey)
                    .withSchedule(scheduleBuilder).build();
            scheduler.scheduleJob(jobDetail, trigger);
            log.info("startup:"+timer.getTimerId());
        }
    }
    
    private static CronExpression getCronExpression(String cronExpression) {
        try {
            CronExpression ce = new CronExpression(cronExpression);
            ce.setTimeZone(TIME_ZONE);
            return ce;
        } catch (ParseException e) {
            throw new RuntimeException("CronExpression '" + cronExpression + "' is invalid.", e);
        }
    }
    
    public static TriggerKey getTriggerKey(Timer timer) {
        return TriggerKey.triggerKey(timer.getTimerId());
    }

    public static JobKey getJobKey(Timer timer) {
        return JobKey.jobKey(timer.getTimerId());
    }
}
 

定义JobDetail & Job:定义任务数据,执行逻辑。但是job分为可并发和不可并发

可并发:我们有个job,每2分钟执行一次,但是job本身就要执行5分钟,这个时候,quartz默认设置是并发的,所以它又会开一个线程来执行。这样往往会导致我们执行的数据不正确。

不可并发:希望多线程时,能够控制同一时刻相同JOB只能有一个在执行。原因是由于我的执行频率是每30秒,然而有可能30后该JOB还没结素,结果另一线程再次启用同一JOB下的方法,我希望在前一个此JOB结束后再进行下一次调用。

可并发job

import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.log4j.Logger;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 可并发执行的job
 *
 */
public class QuartzConcurrentJob implements Job {

    public final Logger log = Logger.getLogger(ModuleTimerApplication.class);

    @Autowired
    PoolingHttpClientConnectionManager cm;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        QuartzUtil.execute(context, cm, log);
    }
}
 

不可并发job

import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.log4j.Logger;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;

import com.gxmis.afc.moduletimer.ModuleTimerApplication;
import com.gxmis.afc.moduletimer.utils.QuartzUtil;

/**
 * 
 * 不可并发执行的job
 *
 */
@DisallowConcurrentExecution
public class QuartzJob implements Job {

    public final Logger log = Logger.getLogger(ModuleTimerApplication.class);

    @Autowired
    PoolingHttpClientConnectionManager cm;

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        QuartzUtil.execute(context, cm, log);
    }
}
 

初始化定时任务

import java.util.List;

import org.apache.log4j.Logger;
import org.quartz.Scheduler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
 

/**
 * 初始化定时任务
 */
@Component
// @Order(1)
public class QuartzInitializer implements CommandLineRunner {

    private static final Logger logger = Logger.getLogger(ModuleTimerApplication.class);

    @Autowired
    TimerService timerService;

    @Autowired
    Scheduler scheduler;

    @Override
    public void run(String... paramArrayOfString) {
        List<Timer> list = timerService.findAll();
        for(Timer timer : list){
            if("1".equals(timer.getPlanStatus())){
                try {
                    QuartzUtil.startup(timer, scheduler, logger);
                } catch (Exception e) {
                    logger.error(e.getMessage());
                }
            }
        }
    }
}
 

CommandLineRunner :

在实际项目开发中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求。 

为了解决这样的问题,Spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来实现。

猜你喜欢

转载自blog.csdn.net/U2133048/article/details/82963301