使用@SpringJUnitConfig时Quartz动态触发器不触发,如何解决?
框架版本
- Spring-Boot 3.3.5
- Spring Boot Starter Quartz(集成Quartz 2.3.2)
- Testcontainers 1.20.1
- Jooq 3.19.13
代码示例
抽象测试基类
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers @JooqTest public class AbstractQuartzTest { public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16.4-alpine").withDatabaseName("mjga"); @DynamicPropertySource static void postgresProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); registry.add("spring.flyway.locations", () -> "classpath:db/migration/test"); } static { postgres.start(); } }
Quartz配置Bean
@Bean("emailJobSchedulerFactory") public SchedulerFactoryBean emailJobSchedulerFactory(DataSource dataSource) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setSchedulerName("email-scheduler"); schedulerFactory.setDataSource(dataSource); Properties props = getCommonProps(); props.setProperty("org.quartz.threadPool.threadCount", "10"); schedulerFactory.setQuartzProperties(props); return schedulerFactory; }
测试类(问题场景)
// 使用@SpringJUnitConfig时触发器不生效,但@SpringBootTest可正常运行! @SpringJUnitConfig(classes = QuartzConfig.class) public class SendEmailJobTest extends AbstractQuartzTest { private Boolean executed; @Autowired @Qualifier("emailJobSchedulerFactory") private SchedulerFactoryBean emailJobSchedulerFactory; @Test public void emailJobScheduler_givenDynamicJobAndTrigger_shouldRunJobAtDesiredTime() throws Exception { Scheduler emailJobScheduler = emailJobSchedulerFactory.getScheduler(); assertTrue(emailJobScheduler.isStarted()); JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("userEmail", "Gh273@gmail.com"); JobDetail jobDetail = newJob(EmailJob.class) .withIdentity("email-job", "customer-service") .usingJobData(jobDataMap) .build(); Trigger trigger = newTrigger() .withIdentity("email-trigger", "customer-service") .startAt(futureDate(1, DateBuilder.IntervalUnit.SECOND)) .build(); emailJobScheduler .getListenerManager() .addJobListener( new JobListener() { @Override public String getName() { return "TestJobListener"; } @Override public void jobToBeExecuted(JobExecutionContext context) {} @Override public void jobExecutionVetoed(JobExecutionContext context) {} @Override public void jobWasExecuted( JobExecutionContext context, JobExecutionException jobException) { executed = Boolean.TRUE; } }); emailJobScheduler.scheduleJob(jobDetail, trigger); assertTrue(emailJobScheduler.isStarted()); await() .atMost(3, java.util.concurrent.TimeUnit.SECONDS) .untilAsserted(() -> assertThat(executed).isTrue()); } }
错误信息
由于jobWasExecuted从未执行,executed变量始终为null,但替换为@SpringBootTest后可正常运行。
错误栈:
Expecting actual not to be null java.lang.AssertionError: Expecting actual not to be null at com.yy.xx.integration.quartz.SendEmailJobTest.lambda$emailJobScheduler_givenDynamicJobAndTrigger_shouldRunJobAtDesiredTime$0(SendEmailJobTest.java:71) at org.awaitility.core.AssertionCondition.lambda$new$0(AssertionCondition.java:53) at org.awaitility.core.ConditionAwaiter$ConditionPoller.call(ConditionAwaiter.java:248) at org.awaitility.core.ConditionAwaiter$ConditionPoller.call(ConditionAwaiter.java:235) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) at java.base/java.lang.Thread.run(Thread.java:1583)
解决方法
1. 补充必要的自动配置注解
@SpringJUnitConfig仅加载指定配置类,缺少Quartz和DataSource的自动装配逻辑,需添加以下注解:
@AutoConfigureDataSource:确保数据源正确初始化@EnableAutoConfiguration:引入Quartz核心自动配置(可排除无关配置加快启动)
2. 确保Scheduler正确启动
显式设置SchedulerFactoryBean自动启动,或在测试中手动启动Scheduler,避免环境差异导致启动失败。
3. 修复线程安全问题
原测试中executed为普通Boolean,多线程下存在可见性问题,改用AtomicBoolean保证线程安全。
修改后的测试类示例
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers @SpringJUnitConfig(classes = QuartzConfig.class) @AutoConfigureDataSource @EnableAutoConfiguration(exclude = {FlywayAutoConfiguration.class, JooqAutoConfiguration.class}) // 排除不需要的自动配置 public class SendEmailJobTest extends AbstractQuartzTest { private final AtomicBoolean executed = new AtomicBoolean(false); @Autowired @Qualifier("emailJobSchedulerFactory") private SchedulerFactoryBean emailJobSchedulerFactory; @Test public void emailJobScheduler_givenDynamicJobAndTrigger_shouldRunJobAtDesiredTime() throws Exception { Scheduler emailJobScheduler = emailJobSchedulerFactory.getScheduler(); // 显式启动Scheduler,避免环境差异 if (!emailJobScheduler.isStarted()) { emailJobScheduler.start(); } JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("userEmail", "Gh273@gmail.com"); JobDetail jobDetail = newJob(EmailJob.class) .withIdentity("email-job", "customer-service") .usingJobData(jobDataMap) .build(); Trigger trigger = newTrigger() .withIdentity("email-trigger", "customer-service") .startAt(futureDate(1, DateBuilder.IntervalUnit.SECOND)) .build(); emailJobScheduler.getListenerManager().addJobListener(new JobListener() { @Override public String getName() { return "TestJobListener"; } @Override public void jobToBeExecuted(JobExecutionContext context) {} @Override public void jobExecutionVetoed(JobExecutionContext context) {} @Override public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { executed.set(true); } }); emailJobScheduler.scheduleJob(jobDetail, trigger); await() .atMost(3, TimeUnit.SECONDS) .untilAsserted(() -> assertThat(executed.get()).isTrue()); } }
修改后的Quartz配置Bean
@Bean("emailJobSchedulerFactory") public SchedulerFactoryBean emailJobSchedulerFactory(DataSource dataSource) { SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean(); schedulerFactory.setSchedulerName("email-scheduler"); schedulerFactory.setDataSource(dataSource); schedulerFactory.setAutoStartup(true); // 显式开启自动启动 Properties props = getCommonProps(); props.setProperty("org.quartz.threadPool.threadCount", "10"); schedulerFactory.setQuartzProperties(props); return schedulerFactory; }
内容的提问来源于stack exchange,提问作者Chuck1sn
相关产品推荐
相关产品推荐

