学习背景
学习是要有目的性的,漫无目的的学习会让你成长的很慢。我的学习背景是这样的,上游系统需要将他们的商品数据推送到我们系统(400万左右的数据量),我们保存的同时,需要将这部分数据推送到ERP系统。因为数据量太大,在推送过程中难免会遇到服务连接超时等一系列的问题,遇到问题时,我们需要将推送失败的数据进行记录,后续专门处理这部分数据。
处理失败数据的方式有两种:手动推送;定时任务推送。手动推送太烦人了,所以选择定时任务推送。领导又要求定时任务可以控制好开关,还能控制推送的时间以及频率,在这里选择了动态定时任务。
在这里介绍两种不同的方式,去实现动态定时任务:
1. 通过ScheduledFuture实现
2. XXL-Job
一、ScheduledFuture实现动态定时任务
1.创建定时任务实体类Cron
我在创建这里实体类的时候,设计的比较简单,具体包含下面几个字段:1.oid:通过雪花算法生成,不重复,无实际意义
2.cronClass:真正的定时任务业务代码所在的全路径
3.cron:定时任务表达式
4.cronDesc:定时任务的描述
5.cronStatus:定时任务的状态,用来控制定时任务是否开启,0-关闭,1-开启
2.编写维护定时任务的服务
这部分接口主要包含:新增定时任务;修改定时任务;多条件查询定时任务;按照oid去开启以及关闭定时任务。这些接口需要向外提供rest服务,之后再生成环境容易进行维护。3.创建配置类
@Slf4j
@Configuration
public class ScheduleConfig {
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Autowired
ICronService cronService;
/**
* 存放所有启动定时任务对象,极其重要
*/
private HashMap<String, ScheduledFuture<?>> scheduleMap = new HashMap<>();
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
/**
* 初始化定时任务
*/
@Bean
public void initScheduleTask(){
log.info("初始化定时任务");
/**
* 这部分涉及内部代码,不予展示,主要实现的功能:
* 调用第二部编写的接口,查询出当前全部的定时任务List<Cron>
* 去调用startCron()方法,开启定时任务
**/
}
/**
* @param crons
* 动态设置定时任务方法
*
* 此方法是真正的动态实现启停和时间周期的关键,你可以针对自己的业务来调用,
* 你对库中的动态数据修改后来调用此方法,每个Cron对象必须要包含,
* 执行周期(cron.getCron()),启停状态(cron.getCronStatus()),执行的类(cron.getCronClass())
*/
public void startCron(List<Cron> crons){
try {
//遍历所有库中动态数据,根据库中class取出所属的定时任务对象进行关闭,每次都会把之前所有的定时任务都关闭,根据新的状态重新启用一次,达到最新配置
for (Cron cron : crons){
ScheduledFuture<?> scheduledFuture = scheduleMap.get(cron.getCronClass());
//一定判空否则出现空指针异常,
if (scheduledFuture != null){
scheduledFuture.cancel(true);
}
}
//因为下边存储的是新的定时任务对象,以前的定时任务对象已经都停用了,所以旧的数据没用清除掉,这步可以不处理,因为可以是不可重复要被覆盖
//scheduleMap.clear();
//遍历库中数据,之前已经把之前所有的定时任务都停用了,现在判断库中如果是启用的重新启用并读取新的数据,把开启的数据对象保存到定时任务对象中以便下次停用
for (Cron cron : crons){
//判断当前定时任务是否有效,COMMON_API_WRAPPER_STATIC_VALUE.VALIDFLAG.TRUE为有效标识
if (cron.getCronStatus().equals(1)) {
//开启一个新的任务,库中存储的是全类名(包名加类名)通过反射成java类,读取新的时间
ScheduledFuture<?> future = threadPoolTaskScheduler.schedule((Runnable) Class.forName(cron.getCronClass()).newInstance(), new CronTrigger(cron.getCron()));
//这一步非常重要,之前直接停用,只停用掉了最后启动的定时任务,前边启用的都没办法停止,所以把每次的对象存到map中可以根据key停用自己想要停用的
scheduleMap.put(cron.getCronClass(),future);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.创建Runnable实现类,完成真正的业务逻辑
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("业务执行了" + new Date());
}
}
5.错误总结
1.在配置类中,在开始是在初始化方法上面使用的@PostConstruct注解,想要达到的结果就是:初始化的时候,将定时任务开启。因为在初始化方法当中,需要查询全部的定时任务,既然需要查询,就得注入业务接口的Bean,在查询时,报了空指针异常。通过查阅资料发现:@PostConstruct注释的方法,会在容器加载Servlet时候运行,并且只会运行一次。并且是在【构造方法】之后,init之前运行。
明白了:在执行@PostConstruct修饰的方法的时候,Spring容器还没有初始化Bean,所以注入的是null的,导致调用查询接口时,报空指针异常。
2.在Runable的实现类中,实现的具体的业务是:查询出所有推送失败的数据,再次进行推送。在这里也使用到了查询接口,在注入Spring的Bean的时候,还是注入不进来。
在代码中是通过threadPoolTaskScheduler.schedule(Runnable task, Trigger trigger);方法去开启的定时任务,通过查看源码发现,这个方法中创建Runable对象时,是new出来的,所以这个对象不是Spring的Bean,所以这个Runable实现类中注入不进来Spring Bean。
解决方式:获取Spring容器,从容器中拿到具体的Bean。
***Service service = SpringContextUtil.getBean(业务接口名称.class);
@Component
public class SpringContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = applicationContext;
}
}
/**
* 获取applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
public static Object createBean(String name, Object object) {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
try {
beanFactory.removeBeanDefinition(name);
} catch (NoSuchBeanDefinitionException e) {
}
beanFactory.registerSingleton(name, object);
return beanFactory.getBean(name);
}
}