From db060c847dde64a43e72ca432a7d071ec1754d13 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Mon, 5 Jun 2017 19:28:28 +0200 Subject: [PATCH 1/2] Ensure `QuartzDatabaseInitializer` is initialized before `Scheduler` If the auto-configured `Scheduler` instance backed by JDBC job store is used as a dependency in an application component, the initialization of `Scheduler` will be triggered before `QuartzDatabaseInitializer`. This will result in failure due to schema not being prepared in time for `Scheduler` to populate job details. This commit ensures `QuartzDatabaseInitializer` is initialized before the auto-configured `Scheduler` by introducing a dependency between the two. See gh-9411 --- .../quartz/QuartzAutoConfiguration.java | 45 ++++++++++++------- .../SchedulerDependsOnPostProcessor.java | 41 +++++++++++++++++ .../quartz/QuartzAutoConfigurationTests.java | 28 +++++++++--- .../spring-boot-sample-quartz/pom.xml | 8 ++++ .../quartz/SampleQuartzApplication.java | 22 +++++++-- .../src/main/resources/application.properties | 2 + 6 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java create mode 100644 spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java index 1753e04761..763215958a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java @@ -33,6 +33,7 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; @@ -87,14 +88,6 @@ public class QuartzAutoConfiguration { this.applicationContext = applicationContext; } - @Bean - @ConditionalOnSingleCandidate(DataSource.class) - @ConditionalOnMissingBean - public QuartzDatabaseInitializer quartzDatabaseInitializer(DataSource dataSource, - ResourceLoader resourceLoader) { - return new QuartzDatabaseInitializer(dataSource, resourceLoader, this.properties); - } - @Bean @ConditionalOnMissingBean public SchedulerFactoryBean quartzScheduler() { @@ -137,24 +130,42 @@ public class QuartzAutoConfiguration { @Configuration @ConditionalOnSingleCandidate(DataSource.class) + @ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "jdbc") protected static class JdbcStoreTypeConfiguration { @Bean - public SchedulerFactoryBeanCustomizer dataSourceCustomizer( - QuartzProperties properties, DataSource dataSource, + public static InitializerSchedulerDependencyPostProcessor initializerSchedulerDependencyPostProcessor() { + return new InitializerSchedulerDependencyPostProcessor(); + } + + @Bean + @ConditionalOnMissingBean + public QuartzDatabaseInitializer quartzDatabaseInitializer(DataSource dataSource, + ResourceLoader resourceLoader, QuartzProperties properties) { + return new QuartzDatabaseInitializer(dataSource, resourceLoader, properties); + } + + @Bean + public SchedulerFactoryBeanCustomizer dataSourceCustomizer(DataSource dataSource, ObjectProvider transactionManager) { return schedulerFactoryBean -> { - if (properties.getJobStoreType() == JobStoreType.JDBC) { - schedulerFactoryBean.setDataSource(dataSource); - PlatformTransactionManager txManager = transactionManager - .getIfUnique(); - if (txManager != null) { - schedulerFactoryBean.setTransactionManager(txManager); - } + schedulerFactoryBean.setDataSource(dataSource); + PlatformTransactionManager txManager = transactionManager.getIfUnique(); + if (txManager != null) { + schedulerFactoryBean.setTransactionManager(txManager); } }; } + private static class InitializerSchedulerDependencyPostProcessor + extends SchedulerDependsOnPostProcessor { + + InitializerSchedulerDependencyPostProcessor() { + super("quartzDatabaseInitializer"); + } + + } + } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java new file mode 100644 index 0000000000..fc2b387063 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import org.quartz.Scheduler; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; + +/** + * {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all + * {@link Scheduler} beans should "depend on" one or more specific beans. + * + * @author Vedran Pavic + * @since 2.0.0 + * @see BeanDefinition#setDependsOn(String[]) + */ +public class SchedulerDependsOnPostProcessor + extends AbstractDependsOnBeanFactoryPostProcessor { + + public SchedulerDependsOnPostProcessor(String... dependsOn) { + super(Scheduler.class, SchedulerFactoryBean.class, dependsOn); + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java index 06665d1e15..ef95c44480 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java @@ -174,7 +174,7 @@ public class QuartzAutoConfigurationTests { @Test public void withCustomizer() throws Exception { - load(QuartzCustomConfig.class); + load(QuartzCustomConfiguration.class); assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); assertThat(scheduler.getSchedulerName()).isEqualTo("fooScheduler"); @@ -199,8 +199,17 @@ public class QuartzAutoConfigurationTests { this.context = ctx; } + protected static class BaseQuartzConfiguration { + + @Bean + public ComponentThatUsesScheduler component() { + return new ComponentThatUsesScheduler(); + } + + } + @Configuration - protected static class QuartzJobsConfiguration { + protected static class QuartzJobsConfiguration extends BaseQuartzConfiguration { @Bean public JobDetail fooJob() { @@ -217,7 +226,7 @@ public class QuartzAutoConfigurationTests { } @Configuration - protected static class QuartzFullConfiguration { + protected static class QuartzFullConfiguration extends BaseQuartzConfiguration { @Bean public JobDetail fooJob() { @@ -237,7 +246,7 @@ public class QuartzAutoConfigurationTests { } @Configuration - protected static class QuartzCalendarsConfiguration { + protected static class QuartzCalendarsConfiguration extends BaseQuartzConfiguration { @Bean public Calendar weekly() { @@ -252,7 +261,7 @@ public class QuartzAutoConfigurationTests { } @Configuration - protected static class QuartzExecutorConfiguration { + protected static class QuartzExecutorConfiguration extends BaseQuartzConfiguration { @Bean public Executor executor() { @@ -262,7 +271,7 @@ public class QuartzAutoConfigurationTests { } @Configuration - protected static class QuartzCustomConfig { + protected static class QuartzCustomConfiguration extends BaseQuartzConfiguration { @Bean public SchedulerFactoryBeanCustomizer customizer() { @@ -272,6 +281,13 @@ public class QuartzAutoConfigurationTests { } + public static class ComponentThatUsesScheduler { + + @Autowired + private Scheduler scheduler; + + } + public static class FooJob extends QuartzJobBean { @Autowired diff --git a/spring-boot-samples/spring-boot-sample-quartz/pom.xml b/spring-boot-samples/spring-boot-sample-quartz/pom.xml index a161afa0be..b37ab99ace 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/pom.xml +++ b/spring-boot-samples/spring-boot-sample-quartz/pom.xml @@ -23,6 +23,14 @@ org.springframework.boot spring-boot-starter-quartz + + org.springframework.boot + spring-boot-starter-jdbc + + + com.h2database + h2 + diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java index 0836fa78a4..8d5f6711be 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java +++ b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java @@ -18,26 +18,40 @@ package sample.quartz; import org.quartz.JobBuilder; import org.quartz.JobDetail; +import org.quartz.Scheduler; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication -public class SampleQuartzApplication { +public class SampleQuartzApplication implements CommandLineRunner { + + @Autowired + private Scheduler scheduler; public static void main(String[] args) { SpringApplication.run(SampleQuartzApplication.class, args); } + @Override + public void run(String... args) throws Exception { + Trigger trigger = TriggerBuilder.newTrigger().forJob(sampleJobDetail()) + .withIdentity("startTrigger").usingJobData("name", "Boot").startNow() + .build(); + + this.scheduler.scheduleJob(trigger); + } + @Bean public JobDetail sampleJobDetail() { - return JobBuilder.newJob().ofType(SampleJob.class).withIdentity("sampleJob") - .usingJobData("name", "World") - .storeDurably().build(); + return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob") + .usingJobData("name", "World").storeDurably().build(); } @Bean diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties new file mode 100644 index 0000000000..59e9a98a3d --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.quartz.job-store-type=jdbc +spring.quartz.jdbc.initialize-schema=true From 7f420d1268ea4a8ef717910a0c8e35368609f2dd Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 6 Jun 2017 10:42:14 +0200 Subject: [PATCH 2/2] Polish contribution Closes gh-9411 --- .../quartz/QuartzAutoConfiguration.java | 38 +++++++++-------- .../SchedulerDependsOnPostProcessor.java | 41 ------------------- .../quartz/QuartzAutoConfigurationTests.java | 15 ++++--- .../spring-boot-sample-quartz/pom.xml | 8 ---- .../quartz/SampleQuartzApplication.java | 17 +------- .../src/main/resources/application.properties | 2 - 6 files changed, 31 insertions(+), 90 deletions(-) delete mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java delete mode 100644 spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java index 763215958a..38307f3a5a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java @@ -29,11 +29,11 @@ import org.quartz.Scheduler; import org.quartz.Trigger; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; @@ -130,12 +130,22 @@ public class QuartzAutoConfiguration { @Configuration @ConditionalOnSingleCandidate(DataSource.class) - @ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "jdbc") protected static class JdbcStoreTypeConfiguration { @Bean - public static InitializerSchedulerDependencyPostProcessor initializerSchedulerDependencyPostProcessor() { - return new InitializerSchedulerDependencyPostProcessor(); + public SchedulerFactoryBeanCustomizer dataSourceCustomizer( + QuartzProperties properties, DataSource dataSource, + ObjectProvider transactionManager) { + return schedulerFactoryBean -> { + if (properties.getJobStoreType() == JobStoreType.JDBC) { + schedulerFactoryBean.setDataSource(dataSource); + PlatformTransactionManager txManager = transactionManager + .getIfUnique(); + if (txManager != null) { + schedulerFactoryBean.setTransactionManager(txManager); + } + } + }; } @Bean @@ -146,22 +156,16 @@ public class QuartzAutoConfiguration { } @Bean - public SchedulerFactoryBeanCustomizer dataSourceCustomizer(DataSource dataSource, - ObjectProvider transactionManager) { - return schedulerFactoryBean -> { - schedulerFactoryBean.setDataSource(dataSource); - PlatformTransactionManager txManager = transactionManager.getIfUnique(); - if (txManager != null) { - schedulerFactoryBean.setTransactionManager(txManager); - } - }; + public static DatabaseInitializerSchedulerDependencyPostProcessor databaseInitializerSchedulerDependencyPostProcessor() { + return new DatabaseInitializerSchedulerDependencyPostProcessor(); } - private static class InitializerSchedulerDependencyPostProcessor - extends SchedulerDependsOnPostProcessor { + private static class DatabaseInitializerSchedulerDependencyPostProcessor + extends AbstractDependsOnBeanFactoryPostProcessor { - InitializerSchedulerDependencyPostProcessor() { - super("quartzDatabaseInitializer"); + DatabaseInitializerSchedulerDependencyPostProcessor() { + super(Scheduler.class, SchedulerFactoryBean.class, + "quartzDatabaseInitializer"); } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java deleted file mode 100644 index fc2b387063..0000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerDependsOnPostProcessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.autoconfigure.quartz; - -import org.quartz.Scheduler; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; -import org.springframework.scheduling.quartz.SchedulerFactoryBean; - -/** - * {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all - * {@link Scheduler} beans should "depend on" one or more specific beans. - * - * @author Vedran Pavic - * @since 2.0.0 - * @see BeanDefinition#setDependsOn(String[]) - */ -public class SchedulerDependsOnPostProcessor - extends AbstractDependsOnBeanFactoryPostProcessor { - - public SchedulerDependsOnPostProcessor(String... dependsOn) { - super(Scheduler.class, SchedulerFactoryBean.class, dependsOn); - } - -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java index ef95c44480..4ff0d41b43 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfigurationTests.java @@ -50,11 +50,13 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.env.Environment; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.quartz.LocalDataSourceJobStore; import org.springframework.scheduling.quartz.LocalTaskExecutorThreadPool; import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -199,13 +201,10 @@ public class QuartzAutoConfigurationTests { this.context = ctx; } + @Import(ComponentThatUsesScheduler.class) + @Configuration protected static class BaseQuartzConfiguration { - @Bean - public ComponentThatUsesScheduler component() { - return new ComponentThatUsesScheduler(); - } - } @Configuration @@ -283,9 +282,13 @@ public class QuartzAutoConfigurationTests { public static class ComponentThatUsesScheduler { - @Autowired private Scheduler scheduler; + public ComponentThatUsesScheduler(Scheduler scheduler) { + Assert.notNull(scheduler, "Scheduler must not be null"); + this.scheduler = scheduler; + } + } public static class FooJob extends QuartzJobBean { diff --git a/spring-boot-samples/spring-boot-sample-quartz/pom.xml b/spring-boot-samples/spring-boot-sample-quartz/pom.xml index b37ab99ace..a161afa0be 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/pom.xml +++ b/spring-boot-samples/spring-boot-sample-quartz/pom.xml @@ -23,14 +23,6 @@ org.springframework.boot spring-boot-starter-quartz - - org.springframework.boot - spring-boot-starter-jdbc - - - com.h2database - h2 - diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java index 8d5f6711be..54f82f1734 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java +++ b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleQuartzApplication.java @@ -18,36 +18,21 @@ package sample.quartz; import org.quartz.JobBuilder; import org.quartz.JobDetail; -import org.quartz.Scheduler; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; @SpringBootApplication -public class SampleQuartzApplication implements CommandLineRunner { - - @Autowired - private Scheduler scheduler; +public class SampleQuartzApplication { public static void main(String[] args) { SpringApplication.run(SampleQuartzApplication.class, args); } - @Override - public void run(String... args) throws Exception { - Trigger trigger = TriggerBuilder.newTrigger().forJob(sampleJobDetail()) - .withIdentity("startTrigger").usingJobData("name", "Boot").startNow() - .build(); - - this.scheduler.scheduleJob(trigger); - } - @Bean public JobDetail sampleJobDetail() { return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob") diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties deleted file mode 100644 index 59e9a98a3d..0000000000 --- a/spring-boot-samples/spring-boot-sample-quartz/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -spring.quartz.job-store-type=jdbc -spring.quartz.jdbc.initialize-schema=true