Java自定义定时任务

一 前言

​ 最近看到项目中的定时任务感觉还是很有意思的,就简单的记录一下,我们开发中会遇到多模块的定时任务,但是这些定时任务都有着相似的逻辑,大量的定时任务都要去写这些相似的逻辑,就降低了代码的解耦性,那我们可以把这些相似的逻辑提取出来,抽成一个抽象类,其他的类只需要去继承这个抽象类,无需再去写那些重复的逻辑,只需关注具体的业务实现,当然这些事Java, Spring ,Quartz,XXL-Job等都帮我们设计好了,我们也可以自己搞来玩玩!

1.新建定时任务接口

public interface Task {
    /**
    * @Description 
    * @Param [request, response]
    * @return void
    **/
    void execute();

    /**
    * @Description
    * @Param []
    * @return java.lang.String
    **/
    String getTaskName();

}

2.抽象定时工厂类

@Slf4j
public abstract class AbstractTask implements Task {

    @Resource
    private RedisLockRegistry redisLockRegistry;

    private static final String TASK_LOCK_KEY_PREFIX = "task-lock:";
    private static final String TASK_LOCK_KEY_SEPARATOR = "^_^";

    @Override
    public void execute() {
        //获取定时任务名
        String taskName = getTaskName();
        //获取定时任务锁
        String lockKey = fetchTaskLockKey(taskName);
        Lock lock = redisLockRegistry.obtain(lockKey);
        if (lock.tryLock()){
            Stopwatch stopwatch = Stopwatch.createStarted();
            try {
                log.info("Starting execute task {}", taskName);
                run();
            } catch (Exception e) {
                log.error("Task {} execute error with exception", taskName, e);
                throw e;
            } finally {
                stopwatch.stop();
                log.info("Task {} execute completed and use time {} ms", taskName, stopwatch.elapsed(TimeUnit.MILLISECONDS));
                lock.unlock();
            }
        }

    }

    protected  abstract void run();

    /***
    * @Description 获取定时任务锁 key
    * @Date 2023/3/24
    * @Param [taskName]
    * @return java.lang.String
    **/
    protected String fetchTaskLockKey(String taskName) {
        if (StringUtils.isBlank(taskName)) {
            throw new BusinessException("TaskName must not be empty", SystemErrorCode.ERROR);
        }
        return TASK_LOCK_KEY_PREFIX + TASK_LOCK_KEY_SEPARATOR + taskName;
    }
}

3.具体定时业务实现类

@Slf4j
@AllArgsConstructor
@Component
public class OrderOverTimeTask extends AbstractTask{
    @Override
    public String getTaskName() {
        return TaskFactoryConstant.TASK_NAME_ORDER_OVERTIME;
    }

    @Override
    protected void run() {
        //逻辑业务
    }
}

4.定时工厂

@Component
public class TaskFactory {

    @Resource
    private OrderOverTimeTask orderOverTimeTask;

    public Task getTask(String taskName) {
        if (AxinStringUtils.isNotBlank(taskName)) {
            switch (taskName) {
                case "order_overtime":
                    return orderOverTimeTask;
                default:
                    break;
                // 其他定时任务
            }
        }
        throw new RuntimeException("获取定时任务失败");
    }

}

5.公共业务逻辑

@Slf4j
@AllArgsConstructor
@Service
public class TaskServiceImpl implements TaskService {

    final private TaskFactory taskFactory;

    final private JsonMapper jsonMapper;

    @Override
    public void execute(HttpServletRequest request, HttpServletResponse response) {

        try {
            //签名验证
            final String json = SignatureUtils.getJson(request);
            String signature = SignatureUtils.getSignature(request);
            if (AxinStringUtils.isBlank(signature)) {
                signature = SignatureUtils.getSign(request);
            }
            final String innerKey = "hb_task";
            final boolean verify = SignatureUtils.verify(json, signature, innerKey);
            if (!verify) {
                throw new BusinessException(RequestErrorCode.INVALID_SIGNATURE);
            }
            final String taskName = jsonMapper.getNode(json, "qname", String.class);
            //获取对应定时任务
            final Task task = taskFactory.getTask(taskName);
            //响应前端
            writeJobServer(response);
            try {
                //执行定任务
                task.execute();
            } catch (Exception e) {
            }
        } catch (Exception e) {
            log.error("Task service execute error with exception", e);
        }

    }

    public  void writeJobServer(HttpServletResponse resp) {
        try {
            final JobServerResultDTO jobServerResult = JobServerResultDTO.builder()
                    .respCode("0000")
                    .respDesc("接收JobServer任务成功")
                    .build();
            final String json = JsonMapper.create().toJson(jobServerResult);
            resp.getOutputStream().write(json.getBytes(StandardCharsets.UTF_8));
            resp.getOutputStream().close();
        } catch (Exception e) {
            log.error("Quartz rcv Response to client error", e);
        }
    }
}

6.单次处理多个定时任务,通过异步线程池处理,需在具体的定时业务任务类中调用处理;

@Slf4j
@Service
public class AsyncTaskServiceImpl implements AsyncTaskService {

    private final LazyTraceAsyncTaskExecutor lazyTraceAsyncTaskExecutor;

    public AsyncTaskServiceImpl(BeanFactory beanFactory, AsyncTaskExecutor asyncTaskExecutor) {
        this.lazyTraceAsyncTaskExecutor = new LazyTraceAsyncTaskExecutor(beanFactory, asyncTaskExecutor);
    }

    @Override
    public <T, R> List<R> execute(List<T> tasks, Function<T, R> executor) {
        List<R> result = Lists.newArrayList();
        List<CompletableFuture<R>> completableFutures = Lists.newArrayList();
        if (AxinCollectionUtils.isNotEmpty(tasks)) {
            //遍历任务集合
            tasks.forEach(task -> {
                if (AxinObjectUtils.isNotEmpty(task)) {
                    //异步执行
                    CompletableFuture<R> completeAsync = CompletableFuture.supplyAsync(() ->
                            executor.apply(task), lazyTraceAsyncTaskExecutor
                    ).exceptionally(throwable -> {
                        log.info("异步任务执行失败:{}", throwable.getMessage());
                        return null;
                    }).whenCompleteAsync((r, e) -> Optional.ofNullable(r).ifPresent(result::add));
                    //将线程添加集合
                    completableFutures.add(completeAsync);
                }
            });
        }

        if (AxinCollectionUtils.isNotEmpty(completableFutures)) {
            //获取所有线程执行结果
            CompletableFuture<R>[] futures = (CompletableFuture<R>[]) completableFutures.toArray(new CompletableFuture[tasks.size()]);
            CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(futures);
            try {
                voidCompletableFuture.get(60 * 60, TimeUnit.SECONDS);
            } catch (Exception e) {
                log.error("异步任务结果获取失败:{}", e);
            }
        }

        return result;
    }
}

7.多个定时任务处理

@Slf4j
@AllArgsConstructor
@Component
public class OrderQueryTask extends AbstractTask {


    private final PayConfig payConfig;
    private final OrderQueryPay orderQueryPay;
    private final OrderQueryRefund orderQueryRefund;
    private final PayOrderMapper payOrderMapper;
    private final RefundOrderMapper refundOrderMapper;
    private final AsyncTaskService asyncTaskService;

    @Override
    protected void run() {
        final String orderQueryBackOff = payConfig.getPay().getOrderQueryBackOff();
        final List<Pair<String, String>> segments = SegmentFrequency.create(orderQueryBackOff).segments();
        asyncTaskService.execute(segments, item -> {
            executeTask1(item);
            executeTask2(item);
            return true;
        });
    }

    private void executeTask1(Pair<String, String> timeParam) {
        final String start = timeParam.getLeft();
        final String end = timeParam.getRight();
        final List<Integer> status = Lists.newArrayList(OrderStatusEnum.ORDER_STATUS_UN_PAY.getStatus(), OrderStatusEnum.ORDER_STATUS_ERROR.getStatus());
        final List<String> busChannels = payOrderMapper.findOrderQueryPayBusChannel(start, end, status);
        final List<Pair<String, Integer>> executeResult = asyncTaskService.execute(busChannels, busChannel -> {
            final AtomicInteger sum = new AtomicInteger();
            ShardingExecutors.apply(LIMIT).execute((index, limit, context) -> {
                index = index * limit;
                final List<PayOrder> payOrders = payOrderMapper.findBusChannelOrderQueryPayLimit(busChannel, start, end, status, index, limit);
                orderQueryPay.query(payOrders);
                final int size = payOrders.size();
                sum.addAndGet(size);
                return size;
            });
            return Pair.of(busChannel, sum.get());
        });
        log.info("Execute pay order query completed <timeRange={}, result={}>", timeParam, executeResult);
    }

    private void executeTask2(Pair<String, String> timeParam) {
        final String start = timeParam.getLeft();
        final String end = timeParam.getRight();
        final List<Integer> status = Lists.newArrayList(OrderStatusEnum.ORDER_STATUS_REFUNDING.getStatus(), OrderStatusEnum.ORDER_STATUS_REFUND_ERROR.getStatus());
        final List<String> busChannels = refundOrderMapper.findOrderQueryRefundBusChannel(start, end, status);
        final List<Pair<String, Integer>> executeResult = asyncTaskService.execute(busChannels, busChannel -> {
            final AtomicInteger sum = new AtomicInteger();
            ShardingExecutors.apply(LIMIT).execute((index, limit, context) -> {
                index = index * limit;
                final List<RefundOrder> refundOrders = refundOrderMapper.findBusChannelOrderQueryRefundLimit(busChannel, start, end, status, index, limit);
                orderQueryRefund.query(refundOrders);
                final int size = refundOrders.size();
                sum.addAndGet(size);
                return size;
            });
            return Pair.of(busChannel, sum.get());
        });
        log.info("Execute refund order query completed <timeRange={}, result={}>", timeParam, executeResult);
    }

    @Override
    public String taskName() {
        return TaskFactory.TASK_TEST;
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值