- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
"Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris." - Larry Wall 。
“程序员的美德:懒惰,不耐烦以及老子天下第一。” —— 拉里·沃尔 。
在 SpringBoot 项目中,我们可以通过 @EnableScheduling 注解开启调度任务支持,并通过 @Scheduled 注解快速地建立一系列定时任务.
@Scheduled 支持下面三种配置执行时间的方式:
Cron
表达式来执行。 最常用的应该是第一种方式,基于 Cron 表达式的执行模式,因其相对来说更加灵活.
默认情况下, @Scheduled 注解标记的定时任务方法在初始化之后,是不会再发生变化的。Spring 在初始化 bean 后,通过后处理器拦截所有带有 @Scheduled 注解的方法,并解析相应的的注解参数,放入相应的定时任务列表等待后续统一执行处理。到定时任务真正启动之前,我们都有机会更改任务的执行周期等参数。换言之,我们既可以通过 application.properties 配置文件配合 @Value 注解的方式指定任务的 Cron 表达式,亦可以通过 CronTrigger 从数据库或者其他任意存储中间件中加载并注册定时任务。这是 Spring 提供给我们的可变的部分.
但是我们往往要得更多。能否在定时任务已经在执行过的情况下,去动态更改 Cron 表达式,甚至禁用某个定时任务呢?很遗憾,默认情况下,这是做不到的,任务一旦被注册和执行,用于注册的参数便被固定下来,这是不可变的部分.
既然创造之后不可变,那就毁灭之后再重建吧。于是乎,我们的思路便是,在注册期间保留任务的关键信息,并通过另一个定时任务检查配置是否发生变化,如果有变化,就把“前任”干掉,取而代之。如果没有变化,就保持原样.
先对任务做个简单的抽象,方便统一的识别和管理:
public interface IPollableService {
/**
* 执行方法
*/
void poll();
/**
* 获取周期表达式
*
* @return CronExpression
*/
default String getCronExpression() {
return null;
}
/**
* 获取任务名称
*
* @return 任务名称
*/
default String getTaskName() {
return this.getClass().getSimpleName();
}
}
最重要的便是 getCronExpression() 方法,每个定时服务实现可以自己控制自己的表达式,变与不变,自己说了算。至于从何处获取,怎么获取,请诸君自行发挥了。接下来,就是实现任务的动态注册:
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer, ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(SchedulingConfiguration.class);
private static ApplicationContext appCtx;
private final ConcurrentMap<String, ScheduledTask> scheduledTaskHolder = new ConcurrentHashMap<>(16);
private final ConcurrentMap<String, String> cronExpressionHolder = new ConcurrentHashMap<>(16);
private ScheduledTaskRegistrar taskRegistrar;
public static synchronized void setAppCtx(ApplicationContext appCtx) {
SchedulingConfiguration.appCtx = appCtx;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
setAppCtx(applicationContext);
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
this.taskRegistrar = taskRegistrar;
}
/**
* 刷新定时任务表达式
*/
public void refresh() {
Map<String, IPollableService> beanMap = appCtx.getBeansOfType(IPollableService.class);
if (beanMap.isEmpty() || taskRegistrar == null) {
return;
}
beanMap.forEach((beanName, task) -> {
String expression = task.getCronExpression();
String taskName = task.getTaskName();
if (null == expression) {
log.warn("定时任务[{}]的任务表达式未配置或配置错误,请检查配置", taskName);
return;
}
// 如果策略执行时间发生了变化,则取消当前策略的任务,并重新注册任务
boolean unmodified = scheduledTaskHolder.containsKey(beanName) && cronExpressionHolder.get(beanName).equals(expression);
if (unmodified) {
log.info("定时任务[{}]的任务表达式未发生变化,无需刷新", taskName);
return;
}
Optional.ofNullable(scheduledTaskHolder.remove(beanName)).ifPresent(existTask -> {
existTask.cancel();
cronExpressionHolder.remove(beanName);
});
if (ScheduledTaskRegistrar.CRON_DISABLED.equals(expression)) {
log.warn("定时任务[{}]的任务表达式配置为禁用,将被不会被调度执行", taskName);
return;
}
CronTask cronTask = new CronTask(task::poll, expression);
ScheduledTask scheduledTask = taskRegistrar.scheduleCronTask(cronTask);
if (scheduledTask != null) {
log.info("定时任务[{}]已加载,当前任务表达式为[{}]", taskName, expression);
scheduledTaskHolder.put(beanName, scheduledTask);
cronExpressionHolder.put(beanName, expression);
}
});
}
}
重点是保存 ScheduledTask 对象的引用,它是控制任务启停的关键。而表达式“-”则作为一个特殊的标记,用于禁用某个定时任务。当然,禁用后的任务通过重新赋予新的 Cron 表达式,是可以“复活”的。完成了上面这些,我们还需要一个定时任务来动态监控和刷新定时任务配置:
@Component
public class CronTaskLoader implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(CronTaskLoader.class);
private final SchedulingConfiguration schedulingConfiguration;
private final AtomicBoolean appStarted = new AtomicBoolean(false);
private final AtomicBoolean initializing = new AtomicBoolean(false);
public CronTaskLoader(SchedulingConfiguration schedulingConfiguration) {
this.schedulingConfiguration = schedulingConfiguration;
}
/**
* 定时任务配置刷新
*/
@Scheduled(fixedDelay = 5000)
public void cronTaskConfigRefresh() {
if (appStarted.get() && initializing.compareAndSet(false, true)) {
log.info("定时调度任务动态加载开始>>>>>>");
try {
schedulingConfiguration.refresh();
} finally {
initializing.set(false);
}
log.info("定时调度任务动态加载结束<<<<<<");
}
}
@Override
public void run(ApplicationArguments args) {
if (appStarted.compareAndSet(false, true)) {
cronTaskConfigRefresh();
}
}
}
当然,也可以把这部分代码直接整合到 SchedulingConfiguration 中,但是为了方便扩展,这里还是将执行与触发分离了。毕竟除了通过定时任务触发刷新,还可以在界面上通过按钮手动触发刷新,或者通过消息机制回调刷新。这一部分就请大家根据实际业务情况来自由发挥了.
我们创建一个原型工程和三个简单的定时任务来验证下,第一个任务是执行周期固定的任务,假设它的 Cron 表达式永远不会发生变化,像这样:
@Service
public class CronTaskBar implements IPollableService {
@Override
public void poll() {
System.out.println("Say Bar");
}
@Override
public String getCronExpression() {
return "0/1 * * * * ?";
}
}
第二个任务是一个经常更换执行周期的任务,我们用一个随机数发生器来模拟它的善变:
@Service
public class CronTaskFoo implements IPollableService {
private static final Random random = new SecureRandom();
@Override
public void poll() {
System.out.println("Say Foo");
}
@Override
public String getCronExpression() {
return "0/" + (random.nextInt(9) + 1) + " * * * * ?";
}
}
第三个任务就厉害了,它仿佛就像一个电灯的开关,在启用和禁用中反复横跳:
@Service
public class CronTaskUnavailable implements IPollableService {
private String cronExpression = "-";
private static final Map<String, String> map = new HashMap<>();
static {
map.put("-", "0/1 * * * * ?");
map.put("0/1 * * * * ?", "-");
}
@Override
public void poll() {
System.out.println("Say Unavailable");
}
@Override
public String getCronExpression() {
return (cronExpression = map.get(cronExpression));
}
}
如果上面的步骤都做对了,日志里应该能看到类似这样的输出:
定时调度任务动态加载开始>>>>>>
定时任务[CronTaskBar]的任务表达式未发生变化,无需刷新
定时任务[CronTaskFoo]已加载,当前任务表达式为[0/6 * * * * ?]
定时任务[CronTaskUnavailable]的任务表达式配置为禁用,将被不会被调度执行
定时调度任务动态加载结束<<<<<<
Say Bar
Say Bar
Say Foo
Say Bar
Say Bar
Say Bar
定时调度任务动态加载开始>>>>>>
定时任务[CronTaskBar]的任务表达式未发生变化,无需刷新
定时任务[CronTaskFoo]已加载,当前任务表达式为[0/3 * * * * ?]
定时任务[CronTaskUnavailable]已加载,当前任务表达式为[0/1 * * * * ?]
定时调度任务动态加载结束<<<<<<
Say Unavailable
Say Bar
Say Unavailable
Say Bar
Say Foo
Say Unavailable
Say Bar
Say Unavailable
Say Bar
Say Unavailable
Say Bar
我们在上文通过定时刷新和重建任务的方式来实现了动态更改 Cron 表达式的需求,能够满足大部分的项目场景,而且没有引入 quartzs 等额外的中间件,可以说是十分的轻量和优雅了。当然,如果各位看官有更好的方法,还请不吝赐教.
最后此篇关于动态更改Spring定时任务Cron表达式的优雅方案的文章就讲到这里了,如果你想了解更多关于动态更改Spring定时任务Cron表达式的优雅方案的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我尝试阅读有关 Spring BOM、Spring Boot 和 Spring IO 的文档。 但是没有说明,我们应该如何一起使用它们? 在我的项目中,我们已经有了自己的 Parent POM ,所以
我正在开发的很酷的企业应用程序正在转向 Spring。这对所有团队来说都是非常酷和令人兴奋的练习,但也是一个巨大的压力源。我们所做的是逐渐将遗留组件移至 Spring 上下文。现在我们有一个 huuu
我正在尝试使用 @Scheduled 运行 Spring 批处理作业注释如下: @Scheduled(cron = "* * * * * ?") public void launchMessageDi
我对这两个概念有点困惑。阅读 Spring 文档,我发现,例如。 bean 工厂是 Spring 容器。我还读到“ApplicationContext 是 BeanFactory 的完整超集”。但两者
我们有一个使用 Spring BlazeDS 集成的应用程序。到目前为止,我们一直在使用 Spring 和 Flex,它运行良好。我们现在还需要添加一些 Spring MVC Controller 。
假设我有一个类(class) Person带属性name和 age ,它可以像这样用 Spring 配置: 我想要一个自定义的 Spring 模式元素,这很容易做到,允许我在我的 Sp
如何在 Java 中以编程方式使用 Spring Data 创建 MongoDB 复合索引? 使用 MongoTemplate 我可以创建一个这样的索引:mongoTemplate.indexOps(
我想使用 spring-complex-task 执行我的应用程序,并且我已经构建了复杂的 spring-batch Flow Jobs,它执行得非常好。 你能解释一下spring批处理流作业与spr
我实现了 spring-boot 应用程序,现在我想将它用作非 spring 应用程序的库。 如何初始化 lib 类,以便 Autowiring 的依赖项按预期工作?显然,如果我使用“new”创建类实
我刚开始学习 spring cloud security,我有一个基本问题。它与 Spring Security 有何不同?我们是否需要在 spring boot 上构建我们的应用程序才能使用 spr
有很多人建议我使用 Spring Boot 而不是 Spring 来开发 REST Web 服务。我想知道这两者到底有什么区别? 最佳答案 总之 Spring Boot 减少了编写大量配置和样板代码的
您能向我解释一下如何使用 Spring 正确构建 Web 应用程序吗?我知道 Spring 框架的最新版本是 4.0.0.RELEASE,但是 Spring Security 的最新版本是 3.2.0
我如何才能知道作为 Spring Boot 应用程序的一部分加载的所有 bean 的名称?我想在 main 方法中有一些代码来打印服务器启动后加载的 bean 的详细信息。 最佳答案 如spring-
我有一个使用 Spring 3.1 构建的 RESTful API,也使用 Spring Security。我有一个 Web 应用程序,也是一个 Spring 3.1 MVC 应用程序。我计划让移动客
升级到 Spring 5 后,我在 Spring Rabbit 和 Spring AMQP 中遇到错误。 两者现在都设置为 1.5.6.RELEASE 有谁知道哪些版本应该与 Spring 5 兼容?
我现在已经使用 Spring Framework 3.0.5 和 Spring Security 3.0.5 多次了。我知道Spring框架使用DI和AOP。我还知道 Spring Security
我收到错误 Unable to Location NamespaceHandler when using context:annotation-config running (java -jar) 由
在 Spring 应用程序中嵌入唯一版本号的策略是什么? 我有一个使用 Spring Boot 和 Spring Web 的应用程序。 它已经足够成熟,我想对其进行版本控制并在运行时看到它显示在屏幕上
我正在使用 spring data jpa 进行持久化。如果存在多个具有相同名称的实体,是否有一种方法可以将一个实体标记为默认值。类似@Primary注解的东西用来解决多个bean的依赖问题 @Ent
我阅读了 Spring 框架的 DAOSupport 类。但是我无法理解这些 DAOSuport 类的优点。在 DAOSupport 类中,我们调用 getXXXTemplate() 方法来获取特定的
我是一名优秀的程序员,十分优秀!