Skip to content
Advertisement

How to trigger dynamic scheduling jobs in Java and cancel them?

How can I invoke a job dynamatically and cancel them at ease? Can I trigger a delayed task that runs at a specific moment, and cancel them if the moment has not passed by, just behaving like the alarm clock?

Advertisement

Answer

I have found another enterprise job-scheduling framework for this task, which is called PowerJob. With the OpenAPI it provides, delayed tasks could be easily created and canceled. Source codes are available here.
Firstly, init the project with its guidance.

Then, create our own project. We will need both PowerJob-worker and PowerJob-client.
So dependencies in Pom file is like:

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

        <dependency>
            <groupId>com.github.kfcfans</groupId>
            <artifactId>powerjob-client</artifactId>
            <version>3.4.3</version>
        </dependency>

        <dependency>
            <groupId>com.github.kfcfans</groupId>
            <artifactId>powerjob-worker-spring-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>7.0.0.Final</version>
        </dependency>

Entities used:

Response class:

import com.alibaba.fastjson.JSONObject;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
public class Response {
    private Integer code;
    private String message;
    private JSONObject info;
    
    public static Response success(JSONObject data) {
        return Response.builder().code(200).message("success").info(data).build();
    }
    
    public static Response error(JSONObject data) {
        return Response.builder().code(500).message("fail").info(data).build();
    }
}

The Alarm class:

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AlarmClock {
    private Long id;
    
    @NotBlank(message = "username should not be blank.")
    private String username;
    
    private String clockName;
    
    @NotNull(message = "Delay should not be null.")
    private Long delayMillis;
    
    private Long instanceId;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    
    @Override
    public String toString() {
        return JSON.toJSONStringWithDateFormat(this, JSON.DEFFAULT_DATE_FORMAT);
    }
}

The task is like:

import com.alibaba.fastjson.JSON;
import com.github.kfcfans.powerjob.worker.core.processor.ProcessResult;
import com.github.kfcfans.powerjob.worker.core.processor.TaskContext;
import com.github.kfcfans.powerjob.worker.core.processor.sdk.BasicProcessor;
import com.github.kfcfans.powerjob.worker.log.OmsLogger;
import com.github.powerjobdemo.entity.AlarmClock;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class AlarmClockTask implements BasicProcessor {
    
    public static final DateTimeFormatter STANDARD_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
    @Override
    public ProcessResult process(TaskContext taskContext) throws Exception {
        OmsLogger omsLogger = taskContext.getOmsLogger();
        String instanceParams = taskContext.getInstanceParams();
        omsLogger.info("instance params:{}", instanceParams);
        AlarmClock alarmClock = JSON.parseObject(instanceParams, AlarmClock.class);
        assert alarmClock != null;
        String username = alarmClock.getUsername();
        omsLogger.info("Current time is:{}", STANDARD_DATE_TIME.format(LocalDateTime.now()));
        omsLogger.info("Clock info: id:{}, name:{}, creator:{}", alarmClock.getId(), alarmClock.getClockName(), username);
        return new ProcessResult(true, String.format("User: %s running an alarm clock.", username));
    }
}

Service interface is like:

import com.alibaba.fastjson.JSONObject;
import com.github.powerjobdemo.entity.AlarmClock;

public interface ClockService {
    /**
     * Add alarm clock.
     *
     * @param alarmClock alarm clock
     * @return json
     */
    JSONObject addAlarmClock(AlarmClock alarmClock);
    
    /**
     * Cancel alarm clock.
     *
     * @param alarmClock alarm clock
     */
    JSONObject cancelAlarmClock(AlarmClock alarmClock);
    
    /**
     * Query all alarm clocks.
     *
     * @param username username
     * @return list
     */
    JSONObject queryAll(String username);
}

Service impl class is like:

import com.alibaba.fastjson.JSONObject;
import com.github.kfcfans.powerjob.client.OhMyClient;
import com.github.kfcfans.powerjob.common.response.ResultDTO;
import com.github.powerjobdemo.entity.AlarmClock;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

@Service
public class ClockServiceImpl implements ClockService {
    
    public static final DateTimeFormatter CLOCK_NAME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss");
    
    private static final List<AlarmClock> alarmClockList = new CopyOnWriteArrayList<>();
    
    private final AtomicLong clockCount = new AtomicLong();
    
    @Resource
    private OhMyClient ohMyClient;
    
    @Value("${powerjob.task.id}")
    private Long taskId;
    
    @Override
    public JSONObject addAlarmClock(AlarmClock alarmClock) {
        String formattedName = CLOCK_NAME_FORMATTER.format(LocalDateTime.now());
        alarmClock.setClockName("Clock-" + formattedName);
        long id = clockCount.addAndGet(1L);
        alarmClock.setCreateTime(LocalDateTime.now());
        Long delayMillis = alarmClock.getDelayMillis();
        ResultDTO<Long> longResultDTO = ohMyClient.runJob(taskId, alarmClock.toString(), delayMillis);
        alarmClock.setInstanceId(longResultDTO.getData());
        alarmClockList.add(alarmClock);
        JSONObject data = new JSONObject();
        data.put("id", id);
        return data;
    }
    
    @Override
    public JSONObject cancelAlarmClock(AlarmClock alarmClock) {
        Long instanceId = alarmClock.getInstanceId();
        assert instanceId != null;
        ohMyClient.cancelInstance(instanceId);
        alarmClockList.removeIf(clock -> Objects.equals(instanceId, clock.getInstanceId()));
        JSONObject data = new JSONObject();
        data.put("instanceId", instanceId);
        return data;
    }
    
    @Override
    public JSONObject queryAll(String username) {
        List<AlarmClock> clockList = alarmClockList.stream()
                .filter(alarmClock -> StringUtils.equals(alarmClock.getUsername(), username))
                .collect(Collectors.toList());
        JSONObject data = new JSONObject();
        data.put("data", clockList);
        data.put("count", clockList.size());
        return data;
    }
}

And finally, controller is like:

import com.github.powerjobdemo.entity.AlarmClock;
import com.github.powerjobdemo.entity.Response;
import com.github.powerjobdemo.service.ClockService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@CrossOrigin
public class AlarmClockController {
    
    @Resource
    private ClockService clockService;
    
    @PostMapping(value = "/api/v1/alarm/clock/add")
    public Response addAlarmClock(@RequestBody AlarmClock alarmClock) {
        return Response.success(clockService.addAlarmClock(alarmClock));
    }
    
    @PostMapping(value = "/api/v1/alarm/clock/cancel")
    public Response cancelAlarmClock(@RequestBody AlarmClock alarmClock) {
        return Response.success(clockService.cancelAlarmClock(alarmClock));
    }
    
    @GetMapping(value = "/api/v1/alarm/clock/query")
    public Response queryAlarmClock(@RequestParam String username) {
        return Response.success(clockService.queryAll(username));
    }
}

So we could post the api /api/v1/alarm/clock/add for adding new alarm clock. For example, create an alarm clock that runs 600 seconds later.

curl --location --request POST 'http://localhost:8080/api/v1/alarm/clock/add' 
--header 'Content-Type: application/json' 
--data-raw '{
  "username": "Jimmy",
  "delayMillis": 600000
}'

Response is like:

{
    "code": 200,
    "message": "success",
    "info": {
        "id": 1
    }
}

And then query.

curl --location --request GET 'http://localhost:8080/api/v1/alarm/clock/query?username=Jimmy'

Response is like:

{
    "code": 200,
    "message": "success",
    "info": {
        "data": [
            {
                "id": 1,
                "username": "Jimmy",
                "clockName": "Clock-20210118-002804",
                "delayMillis": 600000,
                "instanceId": 231210525113975104,
                "createTime": "2021-01-18 00:28:04"
            }
        ],
        "count": 1
    }
}

To cancel the clock:

Post the cancel API:

curl --location --request POST 'http://localhost:8080/api/v1/alarm/clock/cancel' 
--header 'Content-Type: application/json' 
--data-raw '{"instanceId": "231210525113975104"}'

The response:

{
    "code": 200,
    "message": "success",
    "info": {
        "instanceId": 231210525113975104
    }
}

We could see all the instances on the web page, which is helpful. Task instance list

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement