From 59a15b259c42973e5ac55c6bc2b3e21fab9165cf Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 30 May 2017 18:32:59 +0200 Subject: [PATCH] Polish "Add Quartz Scheduler support" Closes gh-4299 --- .../autoconfigure/quartz/JobStoreType.java | 37 +++++ .../quartz/QuartzAutoConfiguration.java | 44 ++--- .../quartz/QuartzDatabaseInitializer.java | 4 +- .../quartz/QuartzProperties.java | 65 ++++---- .../SchedulerFactoryBeanCustomizer.java | 4 + ...itional-spring-configuration-metadata.json | 4 + .../quartz/QuartzAutoConfigurationTests.java | 150 +++++++++++++----- .../appendix-application-properties.adoc | 5 +- .../main/asciidoc/spring-boot-features.adoc | 72 ++++++--- .../spring-boot-sample-quartz/README.adoc | 2 +- .../main/java/sample/quartz/SampleJob.java | 27 +++- .../quartz/SampleQuartzApplication.java | 25 ++- 12 files changed, 315 insertions(+), 124 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/JobStoreType.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/JobStoreType.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/JobStoreType.java new file mode 100644 index 00000000000..e7673d42882 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/JobStoreType.java @@ -0,0 +1,37 @@ +/* + * 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; + +/** + * Define the supported Quartz {@code JobStore}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public enum JobStoreType { + + /** + * Store jobs in memory. + */ + MEMORY, + + /** + * Store jobs in the database. + */ + JDBC + +} 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 1748a4e3d80..284db53cf8a 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 @@ -28,21 +28,18 @@ import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.ResourceLoader; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; @@ -51,6 +48,7 @@ import org.springframework.transaction.PlatformTransactionManager; * {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler. * * @author Vedran Pavic + * @author Stephane Nicoll * @since 2.0.0 */ @Configuration @@ -59,7 +57,7 @@ import org.springframework.transaction.PlatformTransactionManager; @EnableConfigurationProperties(QuartzProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) -public class QuartzAutoConfiguration implements ApplicationContextAware { +public class QuartzAutoConfiguration { private final QuartzProperties properties; @@ -73,23 +71,25 @@ public class QuartzAutoConfiguration implements ApplicationContextAware { private final Trigger[] triggers; - private ApplicationContext applicationContext; + private final ApplicationContext applicationContext; public QuartzAutoConfiguration(QuartzProperties properties, ObjectProvider> customizers, ObjectProvider taskExecutor, ObjectProvider jobDetails, ObjectProvider> calendars, - ObjectProvider triggers) { + ObjectProvider triggers, + ApplicationContext applicationContext) { this.properties = properties; this.customizers = customizers.getIfAvailable(); this.taskExecutor = taskExecutor.getIfAvailable(); this.jobDetails = jobDetails.getIfAvailable(); this.calendars = calendars.getIfAvailable(); this.triggers = triggers.getIfAvailable(); + this.applicationContext = applicationContext; } @Bean - @ConditionalOnBean(DataSource.class) + @ConditionalOnSingleCandidate(DataSource.class) @ConditionalOnMissingBean public QuartzDatabaseInitializer quartzDatabaseInitializer(DataSource dataSource, ResourceLoader resourceLoader) { @@ -98,7 +98,7 @@ public class QuartzAutoConfiguration implements ApplicationContextAware { @Bean @ConditionalOnMissingBean - public SchedulerFactoryBean schedulerFactoryBean() { + public SchedulerFactoryBean quartzScheduler() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setJobFactory(new AutowireCapableBeanJobFactory( this.applicationContext.getAutowireCapableBeanFactory())); @@ -122,12 +122,6 @@ public class QuartzAutoConfiguration implements ApplicationContextAware { return schedulerFactoryBean; } - @Override - public void setApplicationContext(ApplicationContext applicationContext) - throws BeansException { - this.applicationContext = applicationContext; - } - private Properties asProperties(Map source) { Properties properties = new Properties(); properties.putAll(source); @@ -136,7 +130,6 @@ public class QuartzAutoConfiguration implements ApplicationContextAware { private void customize(SchedulerFactoryBean schedulerFactoryBean) { if (this.customizers != null) { - AnnotationAwareOrderComparator.sort(this.customizers); for (SchedulerFactoryBeanCustomizer customizer : this.customizers) { customizer.customize(schedulerFactoryBean); } @@ -144,15 +137,22 @@ public class QuartzAutoConfiguration implements ApplicationContextAware { } @Configuration - @ConditionalOnBean(DataSource.class) - protected static class QuartzSchedulerDataSourceConfiguration { + @ConditionalOnSingleCandidate(DataSource.class) + protected static class JdbcStoreTypeConfiguration { @Bean - public SchedulerFactoryBeanCustomizer dataSourceCustomizer(DataSource dataSource, - PlatformTransactionManager transactionManager) { + public SchedulerFactoryBeanCustomizer dataSourceCustomizer( + QuartzProperties properties, DataSource dataSource, + ObjectProvider transactionManager) { return schedulerFactoryBean -> { - schedulerFactoryBean.setDataSource(dataSource); - schedulerFactoryBean.setTransactionManager(transactionManager); + if (properties.getJobStoreType() == JobStoreType.JDBC) { + schedulerFactoryBean.setDataSource(dataSource); + PlatformTransactionManager txManager = + transactionManager.getIfUnique(); + if (txManager != null) { + schedulerFactoryBean.setTransactionManager(txManager); + } + } }; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java index 38d3b290056..63c3d52ab40 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzDatabaseInitializer.java @@ -41,12 +41,12 @@ public class QuartzDatabaseInitializer extends AbstractDatabaseInitializer { @Override protected boolean isEnabled() { - return this.properties.getInitializer().isEnabled(); + return this.properties.getJdbc().isInitializeSchema(); } @Override protected String getSchemaLocation() { - return this.properties.getSchema(); + return this.properties.getJdbc().getSchema(); } @Override diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java index 7570e01a35d..7f601444d3d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzProperties.java @@ -25,60 +25,69 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Configuration properties for the Quartz Scheduler integration. * * @author Vedran Pavic + * @author Stephane Nicoll * @since 2.0.0 */ @ConfigurationProperties("spring.quartz") public class QuartzProperties { - private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/quartz/impl/" - + "jdbcjobstore/tables_@@platform@@.sql"; - - private final Initializer initializer = new Initializer(); + /** + * Quartz job store type. + */ + private JobStoreType jobStoreType = JobStoreType.MEMORY; /** * Additional Quartz Scheduler properties. */ - private Map properties = new HashMap<>(); + private final Map properties = new HashMap<>(); - /** - * Path to the SQL file to use to initialize the database schema. - */ - private String schema = DEFAULT_SCHEMA_LOCATION; + private final Jdbc jdbc = new Jdbc(); - public Initializer getInitializer() { - return this.initializer; + public JobStoreType getJobStoreType() { + return this.jobStoreType; + } + + public void setJobStoreType(JobStoreType jobStoreType) { + this.jobStoreType = jobStoreType; } public Map getProperties() { return this.properties; } - public void setProperties(Map properties) { - this.properties = properties; + public Jdbc getJdbc() { + return this.jdbc; } - public String getSchema() { - return this.schema; - } + public static class Jdbc { - public void setSchema(String schema) { - this.schema = schema; - } - - public class Initializer { + private static final String DEFAULT_SCHEMA_LOCATION = "classpath:org/quartz/impl/" + + "jdbcjobstore/tables_@@platform@@.sql"; /** - * Create the required Quartz Scheduler tables on startup if necessary. Enabled - * automatically if the schema is configured. + * Path to the SQL file to use to initialize the database schema. */ - private boolean enabled = true; + private String schema = DEFAULT_SCHEMA_LOCATION; - public boolean isEnabled() { - return this.enabled && QuartzProperties.this.getSchema() != null; + /** + * Create the required Quartz Scheduler tables on startup. + */ + private boolean initializeSchema; + + public String getSchema() { + return this.schema; } - public void setEnabled(boolean enabled) { - this.enabled = enabled; + public void setSchema(String schema) { + this.schema = schema; + } + + public boolean isInitializeSchema() { + return this.initializeSchema; + } + + public void setInitializeSchema(boolean initializeSchema) { + this.initializeSchema = initializeSchema; } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java index 09a7b2fa2eb..d1d7492e7f7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/SchedulerFactoryBeanCustomizer.java @@ -28,6 +28,10 @@ import org.springframework.scheduling.quartz.SchedulerFactoryBean; */ public interface SchedulerFactoryBeanCustomizer { + /** + * Customize the {@link SchedulerFactoryBean}. + * @param schedulerFactoryBean the scheduler to customize + */ void customize(SchedulerFactoryBean schedulerFactoryBean); } diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 42d7e3c3e2a..356299c4b0d 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -357,6 +357,10 @@ "name": "spring.mvc.locale-resolver", "defaultValue": "accept-header" }, + { + "name": "spring.quartz.job-store-type", + "defaultValue": "memory" + }, { "name": "spring.rabbitmq.cache.connection.mode", "defaultValue": "channel" 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 17f21fbfc0a..bbfe90ba70a 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 @@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.quartz; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import javax.sql.DataSource; + import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -30,6 +32,7 @@ import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobKey; import org.quartz.Scheduler; +import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.Trigger; import org.quartz.TriggerBuilder; @@ -43,13 +46,16 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerA import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.test.rule.OutputCapture; import org.springframework.boot.test.util.TestPropertyValues; +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.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.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.containsString; @@ -58,6 +64,7 @@ import static org.hamcrest.CoreMatchers.containsString; * Tests for {@link QuartzAutoConfiguration}. * * @author Vedran Pavic + * @author Stephane Nicoll */ public class QuartzAutoConfigurationTests { @@ -67,7 +74,7 @@ public class QuartzAutoConfigurationTests { @Rule public OutputCapture output = new OutputCapture(); - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + private ConfigurableApplicationContext context; @After public void closeContext() { @@ -77,89 +84,123 @@ public class QuartzAutoConfigurationTests { } @Test - public void withDatabase() throws Exception { - registerAndRefresh(EmbeddedDataSourceConfiguration.class, - DataSourceTransactionManagerAutoConfiguration.class, - QuartzAutoConfiguration.class); + public void withNoDataSource() throws Exception { + load(); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); - - assertThat(scheduler).isNotNull(); - assertThat(scheduler.getMetaData().getJobStoreClass()) - .isAssignableFrom(LocalDataSourceJobStore.class); - } - - @Test - public void withNoDatabase() throws Exception { - registerAndRefresh(QuartzAutoConfiguration.class); - Scheduler scheduler = this.context.getBean(Scheduler.class); - - assertThat(scheduler).isNotNull(); assertThat(scheduler.getMetaData().getJobStoreClass()) .isAssignableFrom(RAMJobStore.class); } @Test - public void withTaskExecutor() throws Exception { - registerAndRefresh(QuartzAutoConfiguration.class, - QuartzExecutorConfiguration.class); + public void withDataSourceUseMemoryByDefault() throws Exception { + load(new Class[] { EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class }); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); + assertThat(scheduler.getMetaData().getJobStoreClass()) + .isAssignableFrom(RAMJobStore.class); + } - assertThat(scheduler).isNotNull(); + @Test + public void withDataSource() throws Exception { + load(new Class[] { QuartzJobsConfiguration.class, + EmbeddedDataSourceConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class }, + "spring.quartz.job-store-type=jdbc", + "spring.quartz.jdbc.initialize-schema=true"); + testWithDataSource(); + } + + @Test + public void withDataSourceNoTransactionManager() throws Exception { + load(new Class[] { QuartzJobsConfiguration.class, + EmbeddedDataSourceConfiguration.class }, + "spring.quartz.job-store-type=jdbc", + "spring.quartz.jdbc.initialize-schema=true"); + testWithDataSource(); + } + + private void testWithDataSource() throws SchedulerException { + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); + Scheduler scheduler = this.context.getBean(Scheduler.class); + assertThat(scheduler.getMetaData().getJobStoreClass()) + .isAssignableFrom(LocalDataSourceJobStore.class); + JdbcTemplate jdbcTemplate = new JdbcTemplate( + this.context.getBean(DataSource.class)); + assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM QRTZ_JOB_DETAILS", + Integer.class)).isEqualTo(2); + assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM QRTZ_SIMPLE_TRIGGERS", + Integer.class)).isEqualTo(0); + } + + @Test + public void withTaskExecutor() throws Exception { + load(QuartzExecutorConfiguration.class); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); + Scheduler scheduler = this.context.getBean(Scheduler.class); assertThat(scheduler.getMetaData().getThreadPoolClass()) .isEqualTo(LocalTaskExecutorThreadPool.class); } @Test public void withConfiguredJobAndTrigger() throws Exception { - TestPropertyValues.of("test-name=withConfiguredJobAndTrigger") - .applyTo(this.context); - registerAndRefresh(QuartzAutoConfiguration.class, QuartzJobConfiguration.class); + load(QuartzFullConfiguration.class, "test-name=withConfiguredJobAndTrigger"); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); - assertThat(scheduler.getJobDetail(JobKey.jobKey("fooJob"))).isNotNull(); assertThat(scheduler.getTrigger(TriggerKey.triggerKey("fooTrigger"))).isNotNull(); Thread.sleep(1000L); this.output.expect(containsString("withConfiguredJobAndTrigger")); + this.output.expect(containsString("jobDataValue")); } @Test public void withConfiguredCalendars() throws Exception { - registerAndRefresh(QuartzAutoConfiguration.class, - QuartzCalendarsConfiguration.class); + load(QuartzCalendarsConfiguration.class); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); - assertThat(scheduler.getCalendar("weekly")).isNotNull(); assertThat(scheduler.getCalendar("monthly")).isNotNull(); } @Test public void withQuartzProperties() throws Exception { - TestPropertyValues - .of("spring.quartz.properties.org.quartz.scheduler.instanceId=FOO") - .applyTo(this.context); - registerAndRefresh(QuartzAutoConfiguration.class); + load("spring.quartz.properties.org.quartz.scheduler.instanceId=FOO"); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); - - assertThat(scheduler).isNotNull(); assertThat(scheduler.getSchedulerInstanceId()).isEqualTo("FOO"); } @Test public void withCustomizer() throws Exception { - registerAndRefresh(QuartzAutoConfiguration.class, QuartzCustomConfig.class); + load(QuartzCustomConfig.class); + assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = this.context.getBean(Scheduler.class); - - assertThat(scheduler).isNotNull(); assertThat(scheduler.getSchedulerName()).isEqualTo("fooScheduler"); } - private void registerAndRefresh(Class... annotatedClasses) { - this.context.register(annotatedClasses); - this.context.refresh(); + private void load(String... environment) { + load(new Class[0], environment); + } + + private void load(Class config, String... environment) { + load(new Class[] { config }, environment); + } + + private void load(Class[] configs, String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + TestPropertyValues.of(environment).applyTo(ctx); + if (!ObjectUtils.isEmpty(configs)) { + ctx.register(configs); + } + ctx.register(QuartzAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; } @Configuration - protected static class QuartzJobConfiguration { + protected static class QuartzJobsConfiguration { @Bean public JobDetail fooJob() { @@ -167,6 +208,24 @@ public class QuartzAutoConfigurationTests { .storeDurably().build(); } + @Bean + public JobDetail barJob() { + return JobBuilder.newJob().ofType(FooJob.class).withIdentity("barJob") + .storeDurably().build(); + } + + } + + @Configuration + protected static class QuartzFullConfiguration { + + @Bean + public JobDetail fooJob() { + return JobBuilder.newJob().ofType(FooJob.class).withIdentity("fooJob") + .usingJobData("jobDataKey", "jobDataValue") + .storeDurably().build(); + } + @Bean public Trigger fooTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() @@ -219,10 +278,17 @@ public class QuartzAutoConfigurationTests { @Autowired private Environment env; + private String jobDataKey; + @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { - System.out.println(this.env.getProperty("test-name", "unknown")); + System.out.println(this.env.getProperty("test-name", "unknown") + " - " + + this.jobDataKey); + } + + public void setJobDataKey(String jobDataKey) { + this.jobDataKey = jobDataKey; } } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 473789aeebe..67367e74c62 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -130,9 +130,10 @@ content into your application; rather pick only the properties that you need. spring.profiles.include= # Unconditionally activate the specified comma separated profiles (or list of profiles if using YAML). # QUARTZ SCHEDULER ({sc-spring-boot-autoconfigure}/quartz/QuartzProperties.{sc-ext}[QuartzProperties]) - spring.quartz.initializer.enabled=true # Create the required Quartz Scheduler tables on startup if necessary. Enabled automatically if the schema is configured. + spring.quartz.job-store-type=memory # Quartz job store type. spring.quartz.properties.*= # Additional Quartz Scheduler properties. - spring.quartz.schema=classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql # Path to the SQL file to use to initialize the database schema. + spring.quartz.jdbc.initialize-schema=false # Create the required Quartz Scheduler tables on startup. + spring.quartz.jdbc.schema=classpath:org/quartz/impl/jdbcjobstore/tables_@@platform@@.sql # Path to the SQL file to use to initialize the database schema. # Reactor spring.reactor.stacktrace-mode.enabled=false # Set whether Reactor should collect stacktrace information at runtime. diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index aa1a3daf66f..6b3b9580cf7 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5153,32 +5153,66 @@ caching is enabled. [[boot-features-quartz]] == Quartz Scheduler +Spring Boot offers several conveniences for working with the Quartz scheduler, including +the `spring-boot-starter-quartz` '`Starter`'. If Quartz is available, a `Scheduler` will +be auto-configured (via the `SchedulerFactoryBean` abstraction). -If Quartz Scheduler and the relevant libraries (as defined by `spring-boot-starter-quartz`) -are on the classpath, Spring Boot will auto-configure a `SchedulerFactoryBean` which -provides `Scheduler` instance that you can inject in your application. +Beans of the following types will be automatically picked up and associated with the +the `Scheduler`: -Beans of following types will be automatically picked up and added to -`SchedulerFactoryBean`: - -* `JobDetail` +* `JobDetail`: defines a particular Job. `JobDetail` instance can easily be built with +the `JobBuilder` API * `Calendar` -* `Trigger` +* `Trigger`: defines when a particular job is triggered -By default, an in-memory `JobStore` will be used. However, if `DataSource` bean is -available in your application, Quartz Scheduler will be configured with a persistent -`JobStore`. +By default, an in-memory `JobStore` will be used. However, it is possible to configure +a JDBC-based store if a `DataSource` bean is available in your application and if the +`spring.quartz.job-store-type` property is configured accordingly: -When using a persistent `JobStore`, Quartz database schema can be initialized using -`QuartzDatabaseInitializer` if the location of schema script is configured using -`spring.quartz.schema` property. +[source,properties,indent=0] +---- + spring.quartz.job-store-type=jdbc +---- -Quartz Scheduler configuration can also be customized using Quartz configuration properties -(see `spring.quartz.properties.*`) and `SchedulerFactoryBeanCustomizer` beans which -allows programmatic `SchedulerFactoryBean` customization. +When the jdbc store is used, the schema can be initialized on startup: -Spring Boot also configures `JobFactory` that is `@Autowire` capable so you can easily -inject beans from `applicationContext` and use them in your Quartz jobs. +[source,properties,indent=0] +---- + spring.quartz.jdbc.initialize-schema=true +---- + +NOTE: The database is detected by default and initialized using the standard scripts +provided with the Quartz library. It is also possible to provide a custom script using the +`spring.quartz.jdbc.schema` property. + +Quartz Scheduler configuration can be customized using Quartz configuration properties (see +`spring.quartz.properties.*`) and `SchedulerFactoryBeanCustomizer` beans which allows +programmatic `SchedulerFactoryBean` customization. + +Job can define setters to inject data map properties. Regular beans can also be injected +in a similar manner: + +[source,java,indent=0] +---- + public class SampleJob extends QuartzJobBean { + + private MyService myService; + private String name; + + // Inject "MyService" bean + public void setMyService(MyService myService) { ... } + + // Inject the "name" job data property + public void setName(String name) { ... } + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + ... + } + + } +---- diff --git a/spring-boot-samples/spring-boot-sample-quartz/README.adoc b/spring-boot-samples/spring-boot-sample-quartz/README.adoc index 87e93024c82..e90a709db9c 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/README.adoc +++ b/spring-boot-samples/spring-boot-sample-quartz/README.adoc @@ -8,4 +8,4 @@ The sample uses Maven. It can be built and run from the command line: $ mvn spring-boot:run ---- -Console log will now show Hello message from SampleJob every 10 seconds. +Console log will now show "Hello World!" from `SampleJob` every 2 seconds. diff --git a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java index f5d82888836..b1d3db23cae 100644 --- a/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java +++ b/spring-boot-samples/spring-boot-sample-quartz/src/main/java/sample/quartz/SampleJob.java @@ -1,20 +1,39 @@ +/* + * 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 sample.quartz; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.scheduling.quartz.QuartzJobBean; public class SampleJob extends QuartzJobBean { + + private String name; - private static final Logger LOGGER = LoggerFactory.getLogger(SampleJob.class); + // Invoked if a Job data map entry with that name + public void setName(String name) { + this.name = name; + } @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { - LOGGER.info("Hello {}!", context.getJobDetail().getKey()); + System.out.println(String.format("Hello %s!", this.name)); } } 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 6c1530071fe..0836fa78a44 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 @@ -1,3 +1,19 @@ +/* + * 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 sample.quartz; import org.quartz.JobBuilder; @@ -18,17 +34,18 @@ public class SampleQuartzApplication { } @Bean - public JobDetail jobDetail() { + public JobDetail sampleJobDetail() { return JobBuilder.newJob().ofType(SampleJob.class).withIdentity("sampleJob") + .usingJobData("name", "World") .storeDurably().build(); } @Bean - public Trigger trigger() { + public Trigger sampleJobTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() - .withIntervalInSeconds(10).repeatForever(); + .withIntervalInSeconds(2).repeatForever(); - return TriggerBuilder.newTrigger().forJob(jobDetail()) + return TriggerBuilder.newTrigger().forJob(sampleJobDetail()) .withIdentity("sampleTrigger").withSchedule(scheduleBuilder).build(); }