jBilling scheduled plugin 报错:Failed to schedule pluggable ta
结论
调度失败主要是因为 cron_exp 不符合 Quartz Cron 表达式的格式。jBilling 的 AbstractCronTask 使用 Quartz 调度器,通常需要 6 个字段,也可以加上第 7 个年份字段。它不能直接使用 Linux crontab 常见的 5 字段格式。
每分钟执行一次,可以配置为:
0 * * * * ?
也可以写成:
0 0/1 * * * ?
原配置是:
* * * *
去掉空白后只有 4 个字段,Quartz 无法据此创建 CronTrigger,所以最终记录了 Failed to schedule pluggable task。
Quartz Cron 表达式格式
Quartz 常用的格式是:
秒 分 时 日 月 星期 [年]
因此,下面的表达式表示每分钟的第 0 秒执行一次:
0 * * * * ?
配置时需要注意:
- 第一个字段表示秒,不能直接套用 Linux
crontab的格式。 - “日”和”星期”通常不能同时设置具体条件,其中一个应使用
?。 - 末尾空格不能补足缺少的字段。
* * * * * ?表示每秒执行一次,不是每分钟执行一次。
插件参数应准确配置为:
cron_exp=0 * * * * ?
如果配置通过数据库或管理界面保存,还要检查实际存储的值,确认没有连同引号、换行或额外的转义字符一起保存。
建议调整的插件代码
下面的实现修正了 Logger 类型,也正确保留了线程的中断状态:
public class TestLongTimeRunningPlugin extends AbstractCronTask {
public static final String TASK_NAME =
TestLongTimeRunningPlugin.class.getCanonicalName();
private static final Logger LOG =
Logger.getLogger(TestLongTimeRunningPlugin.class);
private static final int TIME_MINUTES = 5;
public TestLongTimeRunningPlugin() {
}
@Override
public String getTaskName() {
return TASK_NAME;
}
@Override
public void execute(JobExecutionContext context)
throws JobExecutionException {
LOG.debug("Starting and waiting for "
+ TIME_MINUTES + " minutes");
try {
TimeUnit.MINUTES.sleep(TIME_MINUTES);
LOG.debug("Completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.warn("Plugin execution was interrupted", e);
}
}
}
当前代码不必显式声明无参数构造方法,因为 Java 已经为原类生成了隐式的无参数构造方法。不过,Quartz 必须能够实例化 Job。保留一个 public 无参数构造方法更直观,也可以避免以后增加其他构造方法时意外破坏调度。
原代码中的这一行:
Logger.getLogger(draftAPIgetProductCategories.class)
会让日志使用另一个类的分类名称。这个问题通常不会导致调度失败,但会让日志来源难以判断,因此应改为当前插件类。
防止同一个任务重叠执行
Cron 表达式只控制触发时间,并不保证上一次执行结束后才开始下一次执行。
如果任务每分钟触发一次,而每次执行需要 5 分钟,是否会出现并发执行,要看 jBilling 使用的 Quartz 版本和 Job 的并发配置。
Quartz 2.x 可以使用:
import org.quartz.DisallowConcurrentExecution;
@DisallowConcurrentExecution
public class TestLongTimeRunningPlugin extends AbstractCronTask {
// ...
}
这样,当同一个 JobDetail 的前一次执行尚未结束时,Quartz 不会再并发启动一次。
较老的 Quartz 1.x 通常通过 StatefulJob 标记:
import org.quartz.StatefulJob;
public class TestLongTimeRunningPlugin
extends AbstractCronTask
implements StatefulJob {
// ...
}
2013 年前后的 jBilling 版本可能使用 Quartz 1.x,具体写法仍要以项目的实际依赖为准。可以检查构建依赖中的 Quartz 版本:
- 如果存在
org.quartz.StatefulJob,通常使用 Quartz 1.x 的方式。 - 如果存在
org.quartz.DisallowConcurrentExecution,则使用 Quartz 2.x 注解。
没有确认版本前,不要混用这两种方式。
排查步骤
修改后如果仍然无法调度,可以按以下顺序检查:
- 将
cron_exp设置为0 * * * * ?。 - 确认数据库或管理界面保存的是纯表达式,不包含引号。
- 确认插件类是
public,并且有可访问的无参数构造方法。 - 确认部署包中包含最新编译的插件类。
- 调高
com.sapienter.jbilling.server.util.Bootstrap和 Quartz 的日志级别,查看当前 WARN 没有显示的具体异常。 - 修改插件配置后重新加载或重启 jBilling,避免旧的调度配置继续生效。
从现有日志判断,jBilling 已经找到插件类并开始实例化,因此类名和基本部署路径大概率没有问题。失败发生在创建调度任务时,应先修正 cron_exp。
备注:内容仅供参考。
