基于JSR-352的WildFly批处理作业:上次结束后延迟调度实现
Great question! Your requirement breaks down into two core goals:
- Ensure only one job instance runs at a time (no concurrent executions)
- Trigger the next run after a fixed delay from the end of the previous execution (not rigid fixed intervals regardless of runtime)
Here are two reliable approaches using JBeret (WildFly's built-in JSR-352 implementation):
Approach 1: Container-Level Configuration (Simplest)
WildFly's Batch JBeret subsystem supports native scheduling that matches your needs perfectly. This requires no custom code—just straightforward configuration.
Step 1: Block Concurrent Job Runs
First, update your job XML to prevent overlapping executions using a JBeret extension property:
<job id="yourBatchJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0"> <!-- Your job steps go here --> <properties> <!-- Block new runs if an instance is already active --> <property name="allow-start-if-running" value="false"/> </properties> </job>
Step 2: Define a Fixed-Delay Schedule
Edit your WildFly configuration file (standalone.xml or domain.xml) to add a job schedule with fixed-delay (wait time after previous run ends) and concurrent="false" (double insurance against concurrency):
<subsystem xmlns="urn:jboss:domain:batch-jberet:1.0"> <job-schedules> <job-schedule name="yourJobSchedule" job-name="yourBatchJob"> <trigger> <!-- fixed-delay = milliseconds to wait after previous run completes --> <!-- concurrent="false" skips scheduled runs if the job is already running --> <simple start-time="now" fixed-delay="300000" concurrent="false"/> </trigger> </job-schedule> </job-schedules> <!-- Rest of your batch subsystem configuration --> </subsystem>
fixed-delay="300000"translates to a 5-minute delay after the previous job finishes- This setup ensures the scheduler only triggers a new run once the prior one has ended and the delay window passes
Approach 2: Programmatic Control (Flexible)
If you need dynamic behavior (e.g., adjusting delay based on job results, conditional scheduling), use an EJB Singleton + Job Listener combo.
Step 1: Implement a Job Completion Listener
This listener triggers the next scheduled run once the current job finishes:
import javax.batch.api.listener.JobListener; import javax.batch.runtime.BatchRuntime; import javax.batch.runtime.JobExecution; import javax.batch.runtime.JobOperator; import javax.naming.InitialContext; public class PostJobSchedulerListener implements JobListener { @Override public void beforeJob() {} @Override public void afterJob() { JobOperator jobOperator = BatchRuntime.getJobOperator(); long currentExecutionId = javax.batch.runtime.context.JobContext.getCurrentJobExecutionId(); JobExecution execution = jobOperator.getJobExecution(currentExecutionId); // Only schedule next run if current job completed successfully (adjust logic as needed) if (execution.getBatchStatus() == javax.batch.runtime.BatchStatus.COMPLETED) { try { MyJobScheduler scheduler = InitialContext.doLookup("java:module/MyJobScheduler"); scheduler.scheduleNextRun(); } catch (Exception e) { e.printStackTrace(); } } } }
Register this listener in your job XML:
<job id="yourBatchJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0"> <listeners> <listener ref="postJobSchedulerListener"/> </listeners> <!-- Your job steps and allow-start-if-running property --> </job>
Step 2: Create a Singleton EJB for Scheduling
This EJB manages the delay timer and ensures only one scheduler instance exists in the application:
import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.Singleton; import javax.ejb.Startup; import javax.ejb.Timeout; import javax.ejb.Timer; import javax.ejb.TimerService; import javax.batch.runtime.BatchRuntime; import javax.batch.runtime.JobOperator; import java.util.List; @Singleton @Startup // Remove this if you don't want the job to start automatically on app deployment public class MyJobScheduler { @Resource private TimerService timerService; private static final long DELAY_MS = 5 * 60 * 1000; // 5-minute delay private static final String JOB_NAME = "yourBatchJob"; @PostConstruct public void init() { // Start the first job run when the application deploys startJobIfNotRunning(); } public void scheduleNextRun() { // Cancel existing timers to avoid duplicate schedules for (Timer timer : timerService.getTimers()) { timer.cancel(); } // Create a new timer to trigger the next run after the delay timerService.createTimer(DELAY_MS, null); } @Timeout public void onTimer(Timer timer) { startJobIfNotRunning(); } private void startJobIfNotRunning() { JobOperator jobOperator = BatchRuntime.getJobOperator(); List<Long> runningExecutions = jobOperator.getRunningExecutions(JOB_NAME); if (runningExecutions.isEmpty()) { jobOperator.start(JOB_NAME, new java.util.Properties()); } } }
Key Notes
- Concurrency Safety: Both approaches use
allow-start-if-running="false"plus either scheduler-levelconcurrent="false"or runtime checks to eliminate overlapping jobs. - Failure Handling: Adjust the Job Listener logic to handle failed jobs (e.g., retry immediately, extend the delay, or skip scheduling entirely).
- WildFly Version: Ensure you're using WildFly 10+ (JBeret 1.3+) to access the
fixed-delayscheduler feature in the batch subsystem.
内容的提问来源于stack exchange,提问作者mohammad_1m2

