You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:17:03