赞
踩
传统spring定时任务采用的是@Sechedu
注解去实现,但是该注解只能指定固定的时间任务,例如:配置了2s执行一次,那么永远只能是每两秒执行一次
但是在有些特殊场景下需要一些动态定时任务,例如:最初配置了2s执行一次,在执行任务中,修改配置为5秒执行一次,那么就需要动态的加载配置,使任务动态的变成5s执行一次
要想实现动态定时任务,就需要借助Spring
的SchedulingConfigurer
接口,该方式可以实现动态时间,定时任务,具体原理如下图:
如下图所示
定时任务数据库表设计
要想实现定时任务主要是实现SchedulingConfigurer
接口,然后重写configureTasks
方法
在configureTasks
方法中,ScheduledTaskRegistrar
通过addTriggerTask
来添加触发器任务,从而去执行。而addTriggerTask
方法有两个参数,一个是要执行得任务,一个是触发器
具体配置代码如下:
- package net.test.api.job;
-
-
- import io.lettuce.core.dynamic.support.ReflectionUtils;
- import java.lang.reflect.Method;
- import java.util.Date;
- import java.util.List;
- import net.test.demo.common.framework.SpringContext;
- import net.test.dao.entity.demo.ScheduleSetting;
- import net.test.dao.mapper.demo.ScheduledSettingMapper;
- import org.apache.commons.collections4.CollectionUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.scheduling.Trigger;
- import org.springframework.scheduling.TriggerContext;
- import org.springframework.scheduling.annotation.SchedulingConfigurer;
- import org.springframework.scheduling.config.ScheduledTaskRegistrar;
- import org.springframework.scheduling.support.CronTrigger;
- import org.springframework.stereotype.Component;
-
- @Component
- public class CronSchedule implements SchedulingConfigurer {
-
- @Autowired
- private ScheduledSettingMapper scheduledSettingMapper;
-
- @Override
- public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
- // 获取所有任务
- List<ScheduleSetting> scheduleList = scheduledSettingMapper.getScheduleList();
- System.out.println(scheduleList.size());
- if (CollectionUtils.isNotEmpty(scheduleList)){
- for (ScheduleSetting s : scheduleList){
- scheduledTaskRegistrar.addTriggerTask(getRunnable(s), getTrigger(s));
- }
- }
- }
-
- /**
- * 转换首字母小写
- *
- * @param str
- * @return
- */
- public static String lowerFirstCapse(String str) {
- char[] chars = str.toCharArray();
- chars[0] += 32;
- return String.valueOf(chars);
- }
-
- private Runnable getRunnable(ScheduleSetting scheduleSetting){
- return new Runnable() {
- @Override
- public void run() {
- Class<?> clazz;
- try {
- clazz = Class.forName(scheduleSetting.getBeanName());
- String className = lowerFirstCapse(clazz.getSimpleName());
- Object bean = SpringContext.getBean(className);
- Method method = ReflectionUtils.findMethod(bean.getClass(), scheduleSetting.getMethodName());
- if (StringUtils.isNotEmpty(scheduleSetting.getMethodParams())) {
- ReflectionUtils.invokeMethod(method, bean, scheduleSetting.getMethodParams());
- } else {
- ReflectionUtils.invokeMethod(method, bean);
- }
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- };
- }
-
- private Trigger getTrigger(ScheduleSetting scheduleSetting){
- return new Trigger() {
- @Override
- public Date nextExecutionTime(TriggerContext triggerContext) {
- CronTrigger trigger = new CronTrigger(scheduleSetting.getCronExpression());
- Date nextExec = trigger.nextExecutionTime(triggerContext);
- return nextExec;
- }
- };
- }
- }
在开发的过程中,经常会遇到需要使用定时器的问题,比如需要定时向任务表写任务。但是项目是部署到集群环境下的,如果不做处理,就会出现定时任务重复执行的问题。问题产生的原因:由于我们项目同时部署在多台集群机器上,因此到达指定的定时时间时,多台机器上的定时器可能会同时启动,造成重复数据或者程序异常等问题。
该问题的解决方案可能有以下几种,仅供参考:
该方案操作简单,但只适合临时解决以下,正常生产环境应该没人会采取该方案。
通过数据库的锁机制,来获取任务执行状态,先更新,后执行的原则,可以实现避免任务重新执行的目标。
我们最终决定采用的方案。
- public void doSomething() {
- Boolean locked = false;
- try {
- // 获取锁
- lock = redisTemplate.opsForValue().setIfAbsent(AsyncExportConstants.LOCK_KEY, AsyncExportConstants.LOCK_VALUE, 30, TimeUnit.SECONDS);
- if (locked) {
- //todo 执行业务逻辑
- }
- } finally {
- // 释放锁
- if (locked) {
- redisTemplate.delete(AsyncExportConstants.LOCK_KEY);
- }
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。