我正在尝试将石英纳入我的 Spring Boot 项目中,但有两个问题。 首先让我解释一下我的项目范围内石英的最终目标是什么,然后我将提出问题。
我的最终目标是设置一个计划函数(scheduleCronJob),每 20-30 秒检查一次我的自定义 CronJob 表并获取所有作业及其 cron 表达式 - 如果它们是新的或它们的表达式已更改,则应传播更改到石英桌上。
自定义 CronJob 模型的目的是在 GUI 上列出这些 cron 表达式,以便可以从那里控制它们。一旦启动更改,计划的 ScheduleCronJob 就会将其传播到石英表上并计划或重新计划作业。
这是我尝试在这个示例中仅实现一个的代码(RefPoint1):
@Transactional
@Scheduled(fixedRate = 20000)
public void scheduleCronJob() throws SchedulerException, ParseException {
//RefPoint1
String cronExpression = cronRepository.findMatchingCronByItsAliasName("job1").getCronExpression();
//RefPoint2
//String cronExpression = "54 7 2 1 *";
//RefPoint4
//String cronExpression = "54 7 2 9 *";
//RefPoint5
//String cronExpression = "* 7 * 8 * 3 * 9 * 1 * 8";
//System.out.println("Print expr " + cronExpression);
//RefPoint3
buildExpression(cronExpression);
scheduler.start();
System.out.println("Scheduler status: " + scheduler.isStarted());
JobDetail jobDetail = JobBuilder.newJob(CronQuartzJobClass.class)
.withIdentity("cronJob", "group1")
.build();
//RefPoint7
/*
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(8, 40))
.build();
*/
//RefPoint6
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("cronTrigger", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
// Check if the job exists before scheduling
if (!scheduler.checkExists(jobDetail.getKey())) {
scheduler.scheduleJob(jobDetail, trigger);
}
// Reschedule the job if it already exists
if (scheduler.checkExists(jobDetail.getKey())) {
scheduler.rescheduleJob(trigger.getKey(), trigger);
}
System.out.println("Scheduler status: " + scheduler.getJobGroupNames());
System.out.println("Cron job scheduled.");
}
我收到以下错误:
13:21:27.813 [scheduling-1] ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task
java.lang.RuntimeException: CronExpression '54 7 2 1 *' is invalid.
at org.quartz.CronScheduleBuilder.cronSchedule(CronScheduleBuilder.java:111)
at my.package.jobs.jobs.CronQuartzService.scheduleCronJob(CronQuartzService.java:126)
at my.package.jobs.jobs.CronQuartzService$$FastClassBySpringCGLIB$$a83b6317.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at my.package.jobs.jobs.CronQuartzService$$EnhancerBySpringCGLIB$$4c08f9cf.scheduleCronJob(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.text.ParseException: Unexpected end of expression.
at org.quartz.CronExpression.buildExpression(CronExpression.java:494)
at org.quartz.CronExpression.<init>(CronExpression.java:276)
at org.quartz.CronScheduleBuilder.cronSchedule(CronScheduleBuilder.java:107)
... 25 common frames omitted
我有点迷信,所以我注释了 RefPoint1 并取消注释 RefPoint2。但还是得到同样的结果。 CronGuru 说这个表达式本身没问题。 https://crontab.guru/#54_7_2_1_*
自从我的 POM 中包含了 spring-boot-starter-quarty 2.7.8
我想是时候深入研究了;
我接下来要做的是进入 CronExpression.class ,我基本上只是将 buildExpression() 函数(以及所有依赖的东西 - 但为了混乱起见暂时不会将它们粘贴到这里)复制到我的主类中,所以我可以在scheduleCronJob()中调用它并使用它并进行测试运行@RefPoint3。
这就是我的函数(定义了变量):
private static final long serialVersionUID = 12423409423L;
protected static final int SECOND = 0;
protected static final int MINUTE = 1;
protected static final int HOUR = 2;
protected static final int DAY_OF_MONTH = 3;
protected static final int MONTH = 4;
protected static final int DAY_OF_WEEK = 5;
protected static final int YEAR = 6;
protected static final int ALL_SPEC_INT = 99; // '*'
protected static final int NO_SPEC_INT = 98; // '?'
protected static final Integer ALL_SPEC = ALL_SPEC_INT;
protected static final Integer NO_SPEC = NO_SPEC_INT;
protected static final Map<String, Integer> monthMap = new HashMap<String, Integer>(20);
protected static final Map<String, Integer> dayMap = new HashMap<String, Integer>(60);
static {
monthMap.put("JAN", 0);
monthMap.put("FEB", 1);
monthMap.put("MAR", 2);
monthMap.put("APR", 3);
monthMap.put("MAY", 4);
monthMap.put("JUN", 5);
monthMap.put("JUL", 6);
monthMap.put("AUG", 7);
monthMap.put("SEP", 8);
monthMap.put("OCT", 9);
monthMap.put("NOV", 10);
monthMap.put("DEC", 11);
dayMap.put("SUN", 1);
dayMap.put("MON", 2);
dayMap.put("TUE", 3);
dayMap.put("WED", 4);
dayMap.put("THU", 5);
dayMap.put("FRI", 6);
dayMap.put("SAT", 7);
}
private final String cronExpression = "";
private TimeZone timeZone = null;
protected transient TreeSet<Integer> seconds;
protected transient TreeSet<Integer> minutes;
protected transient TreeSet<Integer> hours;
protected transient TreeSet<Integer> daysOfMonth;
protected transient TreeSet<Integer> months;
protected transient TreeSet<Integer> daysOfWeek;
protected transient TreeSet<Integer> years;
protected transient boolean lastdayOfWeek = false;
protected transient int nthdayOfWeek = 0;
protected transient boolean lastdayOfMonth = false;
protected transient boolean nearestWeekday = false;
protected transient int lastdayOffset = 0;
protected transient boolean expressionParsed = false;
public static final int MAX_YEAR = Calendar.getInstance().get(Calendar.YEAR) + 100;
public void buildExpression(String expression) throws ParseException {
expressionParsed = true;
System.out.println("Expression: " + expression);
try {
if (seconds == null) {
seconds = new TreeSet<Integer>();
}
if (minutes == null) {
minutes = new TreeSet<Integer>();
}
if (hours == null) {
hours = new TreeSet<Integer>();
}
if (daysOfMonth == null) {
daysOfMonth = new TreeSet<Integer>();
}
if (months == null) {
months = new TreeSet<Integer>();
}
if (daysOfWeek == null) {
daysOfWeek = new TreeSet<Integer>();
}
if (years == null) {
years = new TreeSet<Integer>();
}
int exprOn = SECOND;
StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
false);
System.out.println("Count token expression: " + exprsTok.countTokens() + " ExprsTok string: " + exprsTok.toString());
while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
System.out.println(" Has more tokens: " + exprsTok.hasMoreTokens() + " exprOn: " + exprOn);
String expr = exprsTok.nextToken().trim();
System.out.println(" trim " + exprsTok.nextToken().toString());
// throw an exception if L is used with other days of the month
if(exprOn == DAY_OF_MONTH && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
throw new ParseException("Support for specifying 'L' and 'LW' with other days of the month is not implemented", -1);
}
// throw an exception if L is used with other days of the week
if(exprOn == DAY_OF_WEEK && expr.indexOf('L') != -1 && expr.length() > 1 && expr.contains(",")) {
throw new ParseException("Support for specifying 'L' with other days of the week is not implemented", -1);
}
if(exprOn == DAY_OF_WEEK && expr.indexOf('#') != -1 && expr.indexOf('#', expr.indexOf('#') +1) != -1) {
throw new ParseException("Support for specifying multiple \"nth\" days is not implemented.", -1);
}
StringTokenizer vTok = new StringTokenizer(expr, ",");
while (vTok.hasMoreTokens()) {
String v = vTok.nextToken();
storeExpressionVals(0, v, exprOn);
}
exprOn++;
}
System.out.println("exprOn " + exprOn + " DAY_OF_WEEK " + DAY_OF_WEEK );
if (exprOn <= DAY_OF_WEEK) {
throw new ParseException("Unexpected end of expression.",
expression.length());
}
if (exprOn <= YEAR) {
storeExpressionVals(0, "*", YEAR);
}
TreeSet<Integer> dow = getSet(DAY_OF_WEEK);
TreeSet<Integer> dom = getSet(DAY_OF_MONTH);
// Copying the logic from the UnsupportedOperationException below
boolean dayOfMSpec = !dom.contains(NO_SPEC);
boolean dayOfWSpec = !dow.contains(NO_SPEC);
if (!dayOfMSpec || dayOfWSpec) {
if (!dayOfWSpec || dayOfMSpec) {
throw new ParseException(
"Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.", 0);
}
}
} catch (ParseException pe) {
throw pe;
} catch (Exception e) {
throw new ParseException("Illegal cron expression format ("
+ e.toString() + ")", 0);
}
}
因此,当我通过 RefPoint3 运行 RefPoint2 时,我得到了一个小调试窗口来检查发生了什么,因为 buildExpression() 至关重要。 这是我打印出来的:
Expression: 54 7 2 1 *
Count token expression: 5 ExprsTok string: java.util.StringTokenizer@359b5a19
Has more tokens: true exprOn: 0
trim 7
Has more tokens: true exprOn: 1
trim 1
Has more tokens: true exprOn: 2
在这里我不太确定我完全理解 while 循环中发生了什么!? 在第一次运行时,下一个元素会被修剪,在第二次运行时也会被修剪。 因此,根据这个给定的逻辑,7 和 1 就出局了。不知道为什么会这样?
但无论如何,为了确认这一点,我注释了 RefPoint2 并取消注释 RefPoint4。这就是我得到的:
Expression: 54 7 2 9 *
Count token expression: 5 ExprsTok string: java.util.StringTokenizer@7774e25f
Has more tokens: true exprOn: 0
trim 7
Has more tokens: true exprOn: 1
trim 9
Has more tokens: true exprOn: 2
此时我仍然不完全确定 while 循环内发生了什么,所以我决定使用 RefPoint5 来提供它,我取消注释并注释了 RefPoint4。 这就是我得到的:
Expression: * 7 * 8 * 3 * 9 * 1 * 8
Count token expression: 12 ExprsTok string: java.util.StringTokenizer@3bc2622f
Has more tokens: true exprOn: 0
trim 7
Has more tokens: true exprOn: 1
trim 8
Has more tokens: true exprOn: 2
trim 3
Has more tokens: true exprOn: 3
trim 9
Has more tokens: true exprOn: 4
trim 1
Has more tokens: true exprOn: 5
trim 8
这是第一次满足这个条件:
System.out.println("exprOn " + exprOn + " DAY_OF_WEEK " + DAY_OF_WEEK );
if (exprOn <= DAY_OF_WEEK) {
throw new ParseException("Unexpected end of expression.",
expression.length());
}
因为我得到:
exprOn 6 DAY_OF_WEEK 5
因此,第一次,只需在该字符串表达式中输入更多字符,我就设法让 buildExpression() 克服该条件 if(exprOn <= DAY_OF_WEEK).
也许有人可以解释一下这一点?为什么 while 循环会从表达式中减损(使用 .trim),然后检查同一表达式是否 <= DAY_OF_WEEK? Because it will certainly be <= than DAY_OF_WEEK.
无论如何,出于测试目的,我决定将 cronExpression 从游戏中删除。所以我注释了 RefPoint6 并取消注释 RefPoint7,这样我就可以在石英表中进行一些实际工作来检查这个东西是否有效。
所以一切都开始运行,这是启动时的控制台:
14:13:36.327 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
14:13:36.572 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
14:13:38.094 [main] INFO o.h.jpa.internal.util.LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
14:13:38.125 [main] INFO org.hibernate.Version - HHH000412: Hibernate ORM core version 5.6.14.Final
14:13:38.217 [main] INFO o.h.annotations.common.Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
14:13:38.294 [main] INFO org.hibernate.dialect.Dialect - HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL94Dialect
14:13:38.724 [main] INFO o.h.e.t.j.p.i.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
14:13:38.729 [main] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit 'default'
14:13:38.926 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
14:13:38.928 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
14:13:38.933 [main] INFO o.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
14:13:38.934 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
14:13:38.935 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
14:13:38.937 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
14:13:38.937 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
14:13:38.937 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
14:13:38.937 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
14:13:39.054 [main] WARN o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
14:13:39.245 [main] INFO s.d.s.w.PropertySourcedRequestMappingHandlerMapping - Mapped URL path [/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2Controller#getDocumentation(String, HttpServletRequest)]
14:13:39.368 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
14:13:39.370 [main] INFO o.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
14:13:39.370 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
14:13:39.392 [main] INFO o.s.s.quartz.LocalDataSourceJobStore - Using db table-based data access locking (synchronization).
14:13:39.395 [main] INFO o.s.s.quartz.LocalDataSourceJobStore - JobStoreCMT initialized.
14:13:39.395 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'MYMACHINE'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.springframework.scheduling.quartz.LocalDataSourceJobStore' - which supports persistence. and is clustered.
14:13:39.395 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
14:13:39.395 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
14:13:39.395 [main] INFO org.quartz.core.QuartzScheduler - JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@3e2ee637
14:13:39.606 [main] INFO s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
14:13:39.612 [main] INFO o.s.s.quartz.SchedulerFactoryBean - Starting Quartz Scheduler now
14:13:39.748 [main] INFO org.quartz.core.QuartzScheduler - Scheduler quartzScheduler_$_MYMACHINE started.
14:13:39.757 [main] INFO h.ht.dps.wfm.jobs.Swagger2SpringBoot - Started Swagger2SpringBoot in 5.491 seconds (JVM running for 5.932)
14:13:39.775 [scheduling-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
Scheduler status: true
Scheduler status: [group1]
Cron job scheduled.
14:13:59.770 [scheduling-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
Scheduler status: true
Scheduler status: [group1]
Cron job scheduled.
14:14:19.775 [scheduling-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
Scheduler status: true
Scheduler status: [group1]
Cron job scheduled.
14:14:39.770 [scheduling-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
Scheduler status: true
Scheduler status: [group1]
Cron job scheduled.
但是我的石英表内没有任何传播:
除了这里: 在 qrtz_scheduler_state 表中。
我在启动时在控制台日志中发现奇怪的一件事是,首先它不是持久性的且非集群的
14:13:38.937 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
然后是持久化和集群化的:
4:13:39.395 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'WMAK90571701263619369'
Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
NOT STARTED.
Currently in standby mode.
Number of jobs executed: 0
Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
Using job-store 'org.springframework.scheduling.quartz.LocalDataSourceJobStore' - which supports persistence. and is clustered.
但是,当计划的 ScheduleCronJob() 每 20 秒运行一次时,它又是非集群的,并且显然是非持久性的。
14:27:59.802 [scheduling-1] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
Scheduler status: true
Scheduler status: [group1]
Cron job scheduled.
这些是我的应用程序属性
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=always
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.scheduler.clusterCheckinInterval=5000
我已将其设置为jdbc存储类型; clustered=true 并给它适当的委托类。
回顾一下;第一个问题是标准 cron 表达式无法通过 CronExpression.classgrinder 进行验证。第二个问题是,即使我删除显式的 cronExpression feed 并尝试让它被吞下,就像在这个例子中的小时和分钟一样,工作也不会被持久化到石英表中。
我无法完全理解的是 buildExpression() 中 while 函数的用途。在第二期中,我定义了应用程序属性,但似乎不知何故触手可及...:D 不知何故它恢复到默认的非持久非集群形式。
如果有人有任何线索或提示,我将不胜感激。另外,如果您需要更多数据 - 只需在下面喊,我可以提供。
您的格式 (
54 7 2 1 *
) 无效。你可以像这样使用0 54 7 2 1 ?
如果你想在spring-context下使用调度,你可以看看我的github小项目