SchedulingConfigurer实现动态定时,导致ApplicationRunner无效解决
作者:暴躁码农 发布时间:2021-12-01 11:26:10
SchedulingConfigurer实现动态定时,导致ApplicationRunner无效
问题描述
当通过SchedulingConfigurer接口实现动态定时任务后,发现ApplicationRunner接口实现的逻辑不生效了,断点不进,说明ApplicationRunner接口实现的方法并没有执行。
问题解释
SchedulingConfigurer接口是使用Spring实现动态定时任务必然的一步,而ApplicationRunner接口为的是在容器(服务)启动完成后,进行一些操作,同样效果的还有接口CommandLineRunner,那么是因为啥导致实现SchedulingConfigurer接口后ApplicationRunner和CommandLineRunner的接口实现就不生效了呢?
原因剖析
导致ApplicationRunner和CommandLineRunner接口失效的原因还要看他俩的实现原理。
首先我们要明确一个概念,那就是虽然它俩名字含有Runner也有run方法,但它并不是Runnable,先看下源码
package org.springframework.boot;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* Interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
* within the same application context and can be ordered using the {@link Ordered}
* interface or {@link Order @Order} annotation.
*
* @author Phillip Webb
* @since 1.3.0
* @see CommandLineRunner
*/
@FunctionalInterface
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}
@FunctionalInterface注解说明了ApplicationRunner是个函数式接口,不了解的童鞋看下java8
而CommandLineRunner源码同样也是这样,源码如下
package org.springframework.boot;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* Interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
* within the same application context and can be ordered using the {@link Ordered}
* interface or {@link Order @Order} annotation.
* <p>
* If you need access to {@link ApplicationArguments} instead of the raw String array
* consider using {@link ApplicationRunner}.
*
* @author Dave Syer
* @see ApplicationRunner
*/
@FunctionalInterface
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;
}
所以ApplicationRunner与CommandLineRunner除了名字以外的唯一区别就是入参不同 那么它俩的实现方法在什么时候执行的呢?简单说就是在ApplicationContext.run()方法中,会调用callRunners方法。
该方法获取所有实现了ApplicationRunner和CommandLineRunner的接口bean,然后依次执行对应的run方法,并且是在同一个线程中执行。因此如果有某个实现了ApplicationRunner接口的bean的run方法一直循环不返回的话,后续的代码将不会被执行。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
所以存在猜测,SchedulingConfigurer的实现方式影响了这俩,看看SchedulingConfigurer的实现方法
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
for(SysTimedTaskEntity timedTaskEntity : timedTaskDao.selectAll()){
Class<?> clazz;
Object task;
try {
clazz = Class.forName(timedTaskEntity.getTaskPath());
task = context.getBean(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("sys_timed_task表数据" + timedTaskEntity.getTaskPath() + "有误", e);
} catch (BeansException e) {
throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "未纳入到spring管理", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
// 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
String cronExpression = timedTaskEntity.getTaskCron();
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
}
}
其实原因很简单,就是因为SchedulingConfigurer使用的是单线程的方式,taskRegistrar.addTriggerTask添加完会阻塞,导致后面的ApplicationRunner和CommandLineRunner无法执行。
解决办法
1.只需要修改下SchedulingConfigurer实现
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
for(SysTimedTaskEntity timedTaskEntity : timedTaskDao.selectAll()){
Class<?> clazz;
Object task;
try {
clazz = Class.forName(timedTaskEntity.getTaskPath());
task = context.getBean(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("sys_timed_task表数据" + timedTaskEntity.getTaskPath() + "有误", e);
} catch (BeansException e) {
throw new IllegalArgumentException(timedTaskEntity.getTaskPath() + "未纳入到spring管理", e);
}
Assert.isAssignable(ScheduledOfTask.class, task.getClass(), "定时任务类必须实现ScheduledOfTask接口");
// 可以通过改变数据库数据进而实现动态改变执行周期
taskRegistrar.addTriggerTask(((Runnable) task),
triggerContext -> {
String cronExpression = timedTaskEntity.getTaskCron();
return new CronTrigger(cronExpression).nextExecutionTime(triggerContext);
}
);
/** 解决办法如下 */
// 手动创建线程池,防止SchedulingConfigurer导致系统线程阻塞
taskRegistrar.setScheduler(new ScheduledThreadPoolExecutor(10, new ThreadFactory() {
int counter = 0;
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r,"数据统计-Thread-"+counter);
counter++;
return thread;
}
}));
}
}
SpringBoot的ApplicationRunner问题
在开发中可能会有这样的情景。需要在容器启动的时候执行一些内容。比如读取配置文件,数据库连接之类的。
SpringBoot给我们提供了两个接口来帮助我们实现这种需求。
这两个接口分别为CommandLineRunner和ApplicationRunner。他们的执行时机为容器启动完成的时候。
这两个接口中有一个run方法,我们只需要实现这个方法即可。
这两个接口的不同之处在于:ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。
目前我在项目中用的是ApplicationRunner。是这么实现的:
package com.jdddemo.demo.controller;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class JDDRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(args);
System.out.println("这个是测试ApplicationRunner接口");
}
}
执行结果如下:
来源:https://blog.csdn.net/qq_38402364/article/details/110003452


猜你喜欢
- 最近在做图片相关的应用,所以就各方积累到一些常用的操作,一般来说会有多种方式来实现这一功能,比如 1.采用色度变换 2.
- 看到群里还有小伙伴说公司里还特别建了800+人的群在处理...好在很快就有了缓解措施和解决方案。同时,log4j2官方也是速度影响发布了最新
- 在项目中经常会遇到对按钮、自定义控件的 Item 等防止多次重复的点击的问题,下面做一个小结。方法1:使用 RxJava 的 throttl
- 本文实例为大家分享了java实现人机猜拳游戏的具体代码,供大家参考,具体内容如下完成人机猜拳互动游戏的开发,用户通过控制台输入实现出拳,电脑
- 前言最近参加了21天打卡活动,希望可以让自己养成写博客的习惯…ArrayList和LinkedListArrayLis
- 1.实现如图所示的单选效果由于Android提供的单选按钮radiobutton只能单行或单列显示,且样式并不美观,故可用GridView进
- List查询JAVA中从数据库中取数据,根据MyBits返回结果主要有两种类型的List,一种是List<Entity>,还一种
- 今天一位同事想写一个全屏幕截图的代码。当然要实现的第一步是能够获取整个屏幕的位图,记得Win32 API的CreateDC, BitBlt等
- 运用Java编写代码将一个大文件切割成指定大小的小文件思路:对已知文件进行切割操作 –> 得到多个碎片文件使用:1、 1个字节输入流
- 本文实例讲述了Java基于JDBC实现事务,银行转账及货物进出库功能。分享给大家供大家参考,具体如下:1. 转账业务转账必须执行2个sql语
- 本文实例为大家分享了抢红包源码,供大家参考,具体内容如下1. 正确获取红包流程2. 软件介绍2.1 效果图:2.2 功能介绍2.2.1 账号
- 说明:实现功能:(1)屏幕右半部分上滑,声音变大,下滑,声音变小 屏幕左半部分上滑,亮度变大,下滑,亮度变小(2)如果亮度>1或者小于
- 本文实例讲述了Android编程实现仿优酷圆盘旋转菜单效果的方法。分享给大家供大家参考,具体如下:目前,用户对安卓应用程序的UI设计要求越来
- 一、增删改1、增加<!-- 添加用户--><insert id="saveUser" paramete
- 完整代码:https://github.com/iyuanyb/Downloader多线程下载及断点续传的实现是使用 HTTP/1.1 引入
- 本文实例为大家分享了C#实现简易计算器功能的具体代码,供大家参考,具体内容如下实现页面布局和数值初始化using System;using
- 目录1、数组本质2、指针3、字符数组4、char * 与 char a[ ]5、char ** 和char *a[]6、C语言中char s
- 桶排序桶排序是计数排序的升级,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过函数的某种映射关系,将待排序数组中
- WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对
- 前言大家都知道,不少仪器工作站可xls文件和2007+的xl以将数据导出为Excel文件,包括97-2003版本的sx文件。采集Excel文