demand

Implement the adding, deleting, modifying and checking of periodic message push policies, and send emails according to the policy content.

The implementation process

In fact, from the beginning, we intended to use the Java built-in timer to implement the timer, but we ran into problems immediately, either using the schedule method or scheduleAtFixedRate method, once the set time has passed, both methods will execute first. The scheduleAtFixedRate method, in particular, calculates a difference between the set past time and the present time, and then executes it as many times as the number of time intervals that the difference contains. This is clearly incompatible with demand.

If the current time is 2021-02-19 10:52:00 then the following code will print 5+1 times.

  public static void main(String[] args) throws ParseException {
        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("The 2021-02-19 10:00:00");
        new Timer().scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run(a) {
                System.out.println("Ha ha");
            }
        },date,1000*60*10);
    }
Copy the code

In addition, there is not a single timing policy in the requirement, and there is the increase, deletion, change and check of the scheduled task. If the timer continues to be used, thread pool may be introduced to solve the problem, which becomes complicated. And that’s where Quartz comes in.

Quartz

Quartz is an open source project, developed entirely in Java, that can be used to perform timed tasks, similar to java.util.timer. However, compared with Timer, Quartz adds many functions, but compared with Timer, I think the most important thing is that each scheduled task can be easily managed.

Below is my general understanding of Quartz.

You are advised to create the QuartzUtil class for easy management.

public class Quartz {


    @Autowired
    private  Scheduler scheduler;


    public void addJob(String jobName, String jobGroup, String triggerName, String triggerGroup, String strDate, String fre, PushStrategyPO pushStrategyPO) throws InterruptedException{
        try {
            DateFormat bf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date=null;
            try {
                date = bf.parse(strDate);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            if (null == date || StringUtils.isEmpty(fre)) {
                return;
            }

            String cronExpress="";

            if (fre.equals("d")){
                cronExpress = getEveryDayCron(date);
            }
            if (fre.equals("w")){
                cronExpress = getEveryWeekCron(date);
            }

// Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // Create a task: define the task details
            JobDetail jobDetail=null;
            if(pushStrategyPO.getType()==0){
                  jobDetail = JobBuilder.newJob(CustomerEmailJob.class).withIdentity(jobName, jobGroup).build();
            }
            if(pushStrategyPO.getType()==1){
                jobDetail = JobBuilder.newJob(ContractEmailJob.class).withIdentity(jobName, jobGroup).build();
            }

            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            jobDataMap.put("fre",fre);
            jobDataMap.put("pushStrategyPO",pushStrategyPO);
            CronTrigger trigger = TriggerBuilder
                    .newTrigger()
                    .withIdentity(triggerName, triggerGroup)    // Create an identifier
                    // Triggers a task every second
                    .withSchedule(CronScheduleBuilder.cronSchedule(cronExpress))
                    .build();
            // Register tasks and triggers with the scheduler
            scheduler.scheduleJob(jobDetail, trigger);
            if (!scheduler.isShutdown()) {
                scheduler.start();
            }
        } catch (SchedulerException e) {
            // TODO Auto-generated catch blocke.printStackTrace(); }}private  String formatDateByPattern(Date date,String dateFormat){
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        String formatTimeStr = null;
        if(date ! =null) {
            formatTimeStr = sdf.format(date);
        }
        return formatTimeStr;
    }
    /***
     * convert Date to cron ,eg.  "0 07 10 15 1 ? 2016"
     * @paramDate: indicates the time *@return* /
    public   String getEveryDayCron(Date date){
        String dateFormat="ss mm HH * * ?";
        return formatDateByPattern(date, dateFormat);
    }

    /** ** will be sent on Monday by default@param date
     * @return* /
    public   String getEveryWeekCron(Date date){
        String dateFormat="ss mm HH ? * 2";
        return formatDateByPattern(date, dateFormat);
    }

    public  void modifyJobTime(String jobName, String jobGroupName, String triggerName, String triggerGroupName, String cron) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            if (trigger == null) {
                return;
            }
            String oldTime = trigger.getCronExpression();
            if(! oldTime.equalsIgnoreCase(cron)) {/ / triggers
                TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                // Trigger name, trigger group
                triggerBuilder.withIdentity(triggerName, triggerGroupName);
                triggerBuilder.startNow();
                // Trigger time setting
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
                // Create the Trigger object
                trigger = (CronTrigger) triggerBuilder.build();
                Method 1: Change the trigger time of a taskscheduler.rescheduleJob(triggerKey, trigger); }}catch (Exception e) {
            throw newRuntimeException(e); }}/** * Function: Remove a task **@param jobName
     * @param jobGroupName
     * @param triggerName
     * @param triggerGroupName
     */
    public  void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
        try {

            TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
            // Stop the trigger
            scheduler.pauseTrigger(triggerKey);
            // Remove the trigger
            scheduler.unscheduleJob(triggerKey);
            // Delete the task
            scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));

            System.out.println("removeJob:"+JobKey.jobKey(jobName));

        } catch (Exception e) {
            throw newRuntimeException(e); }}/** ** Function: Start all scheduled tasks */
    public  void startJobs(a) {
        try {
            scheduler.start();
        } catch (Exception e) {
            throw newRuntimeException(e); }}/** * Disable all scheduled tasks */
    public  void shutdownJobs(a) {
        try {
            if (!scheduler.isShutdown()) {
                scheduler.shutdown();
            }
        } catch (Exception e) {
            throw newRuntimeException(e); }}public List<String> getSchedulerJobs(a) {
        List<String> jobGroupNames=null;
       try {
            jobGroupNames = scheduler.getJobGroupNames();

        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return jobGroupNames;
    }


    public  String createJobID(int n )
    {
        String val = "";
        Random random = new Random();
        for ( int i = 0; i < n; i++ )
        {
            String str = random.nextInt( 2 ) % 2= =0 ? "num" : "char";
            if ( "char".equalsIgnoreCase( str ) )
            { // Generate letters
                int nextInt = random.nextInt( 2 ) % 2= =0 ? 65 : 97;
                // System.out.println(nextInt + "!!!!" ); 1,0,1,1,1,0,0
                val += (char) ( nextInt + random.nextInt( 26)); }else if ( "num".equalsIgnoreCase( str ) )
            { // Generate numbers
                val += String.valueOf( random.nextInt( 10)); }}returnval; }}Copy the code

Problems with SpringBoot integration

The job implementation class will not be injected into the Spring container even if @bean or @component is added. The job implementation class will not be injected into the Spring container.

Introduction of depend on

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

The Job implementation class reintegrates spring’s QuartzJobBean

public class sendEmailJob extends QuartzJobBean {

    private static final Logger logger = LoggerFactory.getLogger(sendEmailJob.class);
    
    @Autowired
    private ProjectServiceImpl projectService;

    @Autowired
    private SendEmailUtil sendEmailUtil;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {... }Copy the code

When you need to pass a parameter to the Job implementation class

Because the Job implementation class overrides the method, it cannot add the passed parameters to the parameters. In this case, you need to pass the parameter through the task manager, and the scheduler passes the parameter through the JobExecutionContext during timed execution.

            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            jobDataMap.put("fre",fre);
            jobDataMap.put("pushStrategyPO",pushStrategyPO);
Copy the code
        JobDataMap jobDataMap =jobExecutionContext.getJobDetail().getJobDataMap();
        String fre = jobDataMap.getString("fre");
        PushStrategyPO pushStrategyPO = (PushStrategyPO) jobDataMap.get("pushStrategyPO");
Copy the code

It needs to be reloaded into schedule when the service restarts

When the service is restarted, the database needs to be read and each scheduled task is reloaded into task management.

@Component
public class ApplicationRunnerImpl implements ApplicationRunner {

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

    @Autowired
    private MessageService messageService;

    @Autowired
    private Quartz quartz;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Query the policy table, including project management and contract management
        logger.info("Querying all pushStrategies for mysql...");

        ArrayList<PushStrategyPO> pushStrategyPOS = messageService.queryAllJob();

        logger.info("pushStrategies is"+pushStrategyPOS.toString());

        logger.info("Begin to start all timed task...");
        for (PushStrategyPO ps : pushStrategyPOS) {
          // Start all scheduled tasks
            String jobName=ps.getJobId();
            String jobGroup=ps.getJobId()+"-Group";
            String triggerName=ps.getJobId()+"-Trigger";
            String triggerGroup=ps.getJobId()+"-TriggerGroup";
            String frequency = ps.getFrequency();
            String setDate = ps.getSetDate();
            quartz.addJob(jobName,jobGroup,triggerName,triggerGroup,setDate,frequency,ps);
        }

        List<String> schedulerJobs = quartz.getSchedulerJobs();
        logger.info("there are "+schedulerJobs.size()+"job in schedule");
        for (String schedulerJob : schedulerJobs) {
            logger.info("job is :"+schedulerJob); }}}Copy the code