Merge branch '2.0.x'

pull/12082/merge
Stephane Nicoll 7 years ago
commit ed19f20ca0

@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.quartz;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Executor;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -64,8 +63,6 @@ public class QuartzAutoConfiguration {
private final List<SchedulerFactoryBeanCustomizer> customizers; private final List<SchedulerFactoryBeanCustomizer> customizers;
private final Executor taskExecutor;
private final JobDetail[] jobDetails; private final JobDetail[] jobDetails;
private final Map<String, Calendar> calendars; private final Map<String, Calendar> calendars;
@ -76,12 +73,11 @@ public class QuartzAutoConfiguration {
public QuartzAutoConfiguration(QuartzProperties properties, public QuartzAutoConfiguration(QuartzProperties properties,
ObjectProvider<List<SchedulerFactoryBeanCustomizer>> customizers, ObjectProvider<List<SchedulerFactoryBeanCustomizer>> customizers,
ObjectProvider<Executor> taskExecutor, ObjectProvider<JobDetail[]> jobDetails, ObjectProvider<JobDetail[]> jobDetails,
ObjectProvider<Map<String, Calendar>> calendars, ObjectProvider<Map<String, Calendar>> calendars,
ObjectProvider<Trigger[]> triggers, ApplicationContext applicationContext) { ObjectProvider<Trigger[]> triggers, ApplicationContext applicationContext) {
this.properties = properties; this.properties = properties;
this.customizers = customizers.getIfAvailable(); this.customizers = customizers.getIfAvailable();
this.taskExecutor = taskExecutor.getIfUnique();
this.jobDetails = jobDetails.getIfAvailable(); this.jobDetails = jobDetails.getIfAvailable();
this.calendars = calendars.getIfAvailable(); this.calendars = calendars.getIfAvailable();
this.triggers = triggers.getIfAvailable(); this.triggers = triggers.getIfAvailable();
@ -98,9 +94,6 @@ public class QuartzAutoConfiguration {
schedulerFactoryBean schedulerFactoryBean
.setQuartzProperties(asProperties(this.properties.getProperties())); .setQuartzProperties(asProperties(this.properties.getProperties()));
} }
if (this.taskExecutor != null) {
schedulerFactoryBean.setTaskExecutor(this.taskExecutor);
}
if (this.jobDetails != null && this.jobDetails.length > 0) { if (this.jobDetails != null && this.jobDetails.length > 0) {
schedulerFactoryBean.setJobDetails(this.jobDetails); schedulerFactoryBean.setJobDetails(this.jobDetails);
} }

@ -17,12 +17,9 @@
package org.springframework.boot.autoconfigure.quartz; package org.springframework.boot.autoconfigure.quartz;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.sql.DataSource; import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.After;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.quartz.Calendar; import org.quartz.Calendar;
@ -31,7 +28,6 @@ import org.quartz.JobDetail;
import org.quartz.JobExecutionContext; import org.quartz.JobExecutionContext;
import org.quartz.JobKey; import org.quartz.JobKey;
import org.quartz.Scheduler; import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder; import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger; import org.quartz.Trigger;
import org.quartz.TriggerBuilder; import org.quartz.TriggerBuilder;
@ -39,16 +35,16 @@ import org.quartz.TriggerKey;
import org.quartz.impl.calendar.MonthlyCalendar; import org.quartz.impl.calendar.MonthlyCalendar;
import org.quartz.impl.calendar.WeeklyCalendar; import org.quartz.impl.calendar.WeeklyCalendar;
import org.quartz.simpl.RAMJobStore; import org.quartz.simpl.RAMJobStore;
import org.quartz.simpl.SimpleThreadPool;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.rule.OutputCapture; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -56,13 +52,13 @@ import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.quartz.LocalDataSourceJobStore; import org.springframework.scheduling.quartz.LocalDataSourceJobStore;
import org.springframework.scheduling.quartz.LocalTaskExecutorThreadPool;
import org.springframework.scheduling.quartz.QuartzJobBean; import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
/** /**
* Tests for {@link QuartzAutoConfiguration}. * Tests for {@link QuartzAutoConfiguration}.
@ -75,163 +71,140 @@ public class QuartzAutoConfigurationTests {
@Rule @Rule
public OutputCapture output = new OutputCapture(); public OutputCapture output = new OutputCapture();
private ConfigurableApplicationContext context; private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.generate-unique-name=true")
@After .withConfiguration(AutoConfigurations.of(QuartzAutoConfiguration.class));
public void closeContext() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void withNoDataSource() throws Exception {
load();
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1);
Scheduler scheduler = this.context.getBean(Scheduler.class);
assertThat(scheduler.getMetaData().getJobStoreClass())
.isAssignableFrom(RAMJobStore.class);
}
@Test @Test
public void withDataSourceUseMemoryByDefault() throws Exception { public void withNoDataSource() {
load(new Class<?>[] { EmbeddedDataSourceConfiguration.class, this.contextRunner.run((context) -> {
DataSourceTransactionManagerAutoConfiguration.class }); assertThat(context).hasSingleBean(Scheduler.class);
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = context.getBean(Scheduler.class);
Scheduler scheduler = this.context.getBean(Scheduler.class); assertThat(scheduler.getMetaData().getJobStoreClass())
assertThat(scheduler.getMetaData().getJobStoreClass()) .isAssignableFrom(RAMJobStore.class);
.isAssignableFrom(RAMJobStore.class); });
} }
@Test @Test
public void withDataSource() throws Exception { public void withDataSourceUseMemoryByDefault() {
load(new Class<?>[] { QuartzJobsConfiguration.class, this.contextRunner.withConfiguration(AutoConfigurations.of(
EmbeddedDataSourceConfiguration.class, DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class }, DataSourceTransactionManagerAutoConfiguration.class)).run((context) -> {
"spring.quartz.job-store-type=jdbc"); assertThat(context).hasSingleBean(Scheduler.class);
testWithDataSource(); Scheduler scheduler = context.getBean(Scheduler.class);
assertThat(scheduler.getMetaData().getJobStoreClass())
.isAssignableFrom(RAMJobStore.class);
});
} }
@Test @Test
public void withDataSourceNoTransactionManager() throws Exception { public void withDataSource() {
load(new Class<?>[] { QuartzJobsConfiguration.class, this.contextRunner.withUserConfiguration(QuartzJobsConfiguration.class)
EmbeddedDataSourceConfiguration.class }, .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
"spring.quartz.job-store-type=jdbc"); DataSourceTransactionManagerAutoConfiguration.class))
testWithDataSource(); .withPropertyValues("spring.quartz.job-store-type=jdbc")
} .run(assertDataSourceJobStore("dataSource"));
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 @Test
public void withMultipleTaskExecutors() throws Exception { public void withDataSourceNoTransactionManager() {
load(QuartzMultipleExecutorsConfiguration.class); this.contextRunner.withUserConfiguration(QuartzJobsConfiguration.class)
assertThat(this.context.getBeansOfType(Executor.class)).hasSize(2); .withConfiguration(AutoConfigurations.of(
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); DataSourceAutoConfiguration.class))
Scheduler scheduler = this.context.getBean(Scheduler.class); .withPropertyValues("spring.quartz.job-store-type=jdbc")
assertThat(scheduler.getMetaData().getThreadPoolClass()) .run(assertDataSourceJobStore("dataSource"));
.isEqualTo(SimpleThreadPool.class);
} }
@Test @Test
public void withMultipleTaskExecutorsWithPrimary() throws Exception { public void dataSourceWithQuartzDataSourceQualifierUsedWhenMultiplePresent() {
load(QuartzMultipleExecutorsWithPrimaryConfiguration.class); this.contextRunner
assertThat(this.context.getBeansOfType(Executor.class)).hasSize(2); .withUserConfiguration(QuartzJobsConfiguration.class,
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); MultipleDataSourceConfiguration.class)
Scheduler scheduler = this.context.getBean(Scheduler.class); .withPropertyValues("spring.quartz.job-store-type=jdbc")
assertThat(scheduler.getMetaData().getThreadPoolClass()) .run(assertDataSourceJobStore("quartzDataSource"));
.isEqualTo(LocalTaskExecutorThreadPool.class);
} }
@Test private ContextConsumer<AssertableApplicationContext> assertDataSourceJobStore(
public void withMultipleTaskExecutorsWithCustomizer() throws Exception { String datasourceName) {
load(QuartzMultipleExecutorsWithCustomizerConfiguration.class); return (context) -> {
assertThat(this.context.getBeansOfType(Executor.class)).hasSize(3); assertThat(context).hasSingleBean(Scheduler.class);
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); Scheduler scheduler = context.getBean(Scheduler.class);
Scheduler scheduler = this.context.getBean(Scheduler.class); assertThat(scheduler.getMetaData().getJobStoreClass())
assertThat(scheduler.getMetaData().getThreadPoolClass()) .isAssignableFrom(LocalDataSourceJobStore.class);
.isEqualTo(LocalTaskExecutorThreadPool.class); JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getBean(
datasourceName, 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 @Test
public void withConfiguredJobAndTrigger() throws Exception { public void withTaskExecutor() {
load(QuartzFullConfiguration.class, "test-name=withConfiguredJobAndTrigger"); this.contextRunner.withUserConfiguration(MockExecutorConfiguration.class)
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); .withPropertyValues(
Scheduler scheduler = this.context.getBean(Scheduler.class); "spring.quartz.properties.org.quartz.threadPool.threadCount=50")
assertThat(scheduler.getJobDetail(JobKey.jobKey("fooJob"))).isNotNull(); .run((context) -> {
assertThat(scheduler.getTrigger(TriggerKey.triggerKey("fooTrigger"))).isNotNull(); assertThat(context).hasSingleBean(Scheduler.class);
Thread.sleep(1000L); Scheduler scheduler = context.getBean(Scheduler.class);
this.output.expect(containsString("withConfiguredJobAndTrigger")); assertThat(scheduler.getMetaData().getThreadPoolSize()).isEqualTo(50);
this.output.expect(containsString("jobDataValue")); Executor executor = context.getBean(Executor.class);
verifyZeroInteractions(executor);
});
} }
@Test @Test
public void withConfiguredCalendars() throws Exception { public void withConfiguredJobAndTrigger() {
load(QuartzCalendarsConfiguration.class); this.contextRunner.withUserConfiguration(QuartzFullConfiguration.class)
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); .withPropertyValues("test-name=withConfiguredJobAndTrigger")
Scheduler scheduler = this.context.getBean(Scheduler.class); .run((context) -> {
assertThat(scheduler.getCalendar("weekly")).isNotNull(); assertThat(context).hasSingleBean(Scheduler.class);
assertThat(scheduler.getCalendar("monthly")).isNotNull(); Scheduler scheduler = 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 @Test
public void withQuartzProperties() throws Exception { public void withConfiguredCalendars() {
load("spring.quartz.properties.org.quartz.scheduler.instanceId=FOO"); this.contextRunner.withUserConfiguration(QuartzCalendarsConfiguration.class)
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); .run((context) -> {
Scheduler scheduler = this.context.getBean(Scheduler.class); assertThat(context).hasSingleBean(Scheduler.class);
assertThat(scheduler.getSchedulerInstanceId()).isEqualTo("FOO"); Scheduler scheduler = context.getBean(Scheduler.class);
assertThat(scheduler.getCalendar("weekly")).isNotNull();
assertThat(scheduler.getCalendar("monthly")).isNotNull();
});
} }
@Test @Test
public void withCustomizer() throws Exception { public void withQuartzProperties() {
load(QuartzCustomConfiguration.class); this.contextRunner.withPropertyValues(
assertThat(this.context.getBeansOfType(Scheduler.class)).hasSize(1); "spring.quartz.properties.org.quartz.scheduler.instanceId=FOO")
Scheduler scheduler = this.context.getBean(Scheduler.class); .run((context) -> {
assertThat(scheduler.getSchedulerName()).isEqualTo("fooScheduler"); assertThat(context).hasSingleBean(Scheduler.class);
Scheduler scheduler = context.getBean(Scheduler.class);
assertThat(scheduler.getSchedulerInstanceId()).isEqualTo("FOO");
});
} }
@Test @Test
public void dataSourceWithQuartzDataSourceQualifierUsedWhenMultiplePresent() { public void withCustomizer() {
load(MultipleDataSourceConfiguration.class, "spring.quartz.job-store-type=jdbc"); this.contextRunner.withUserConfiguration(QuartzCustomConfiguration.class)
} .run((context) -> {
assertThat(context).hasSingleBean(Scheduler.class);
private void load(String... environment) { Scheduler scheduler = context.getBean(Scheduler.class);
load(new Class<?>[0], environment); assertThat(scheduler.getSchedulerName()).isEqualTo("fooScheduler");
});
} }
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;
}
@Import(ComponentThatUsesScheduler.class) @Import(ComponentThatUsesScheduler.class)
@Configuration @Configuration
@ -292,51 +265,11 @@ public class QuartzAutoConfigurationTests {
} }
@Configuration @Configuration
protected static class QuartzExecutorConfiguration extends BaseQuartzConfiguration { protected static class MockExecutorConfiguration extends BaseQuartzConfiguration {
@Bean @Bean
public Executor executor() { public Executor executor() {
return Executors.newSingleThreadExecutor(); return mock(Executor.class);
}
}
@Configuration
protected static class QuartzMultipleExecutorsConfiguration
extends QuartzExecutorConfiguration {
@Bean
public Executor anotherExecutor() {
return Executors.newSingleThreadExecutor();
}
}
@Configuration
protected static class QuartzMultipleExecutorsWithPrimaryConfiguration
extends QuartzExecutorConfiguration {
@Bean
@Primary
public Executor primaryExecutor() {
return Executors.newSingleThreadExecutor();
}
}
@Configuration
protected static class QuartzMultipleExecutorsWithCustomizerConfiguration
extends QuartzMultipleExecutorsConfiguration {
@Bean
public Executor yetAnotherExecutor() {
return Executors.newSingleThreadExecutor();
}
@Bean
public SchedulerFactoryBeanCustomizer customizer() {
return (schedulerFactoryBean) -> schedulerFactoryBean
.setTaskExecutor(yetAnotherExecutor());
} }
} }
@ -358,15 +291,21 @@ public class QuartzAutoConfigurationTests {
@Bean @Bean
@Primary @Primary
public DataSource applicationDataSource() { public DataSource applicationDataSource() throws Exception {
return new HikariDataSource(); return createTestDataSource();
} }
@QuartzDataSource @QuartzDataSource
@Bean @Bean
public DataSource quartzDataSource() { public DataSource quartzDataSource() throws Exception {
return DataSourceBuilder.create().url("jdbc:hsqldb:mem:quartztest") return createTestDataSource();
.username("sa").build(); }
private DataSource createTestDataSource() throws Exception {
DataSourceProperties properties = new DataSourceProperties();
properties.setGenerateUniqueName(true);
properties.afterPropertiesSet();
return properties.initializeDataSourceBuilder().build();
} }
} }

@ -5876,6 +5876,10 @@ Quartz Scheduler configuration can be customized by using Quartz configuration p
()`spring.quartz.properties.*`) and `SchedulerFactoryBeanCustomizer` beans, which allow ()`spring.quartz.properties.*`) and `SchedulerFactoryBeanCustomizer` beans, which allow
programmatic `SchedulerFactoryBean` customization. programmatic `SchedulerFactoryBean` customization.
NOTE: In particular, an `Executor` bean is not associated with the scheduler as Quartz
offers a way to configure the scheduler via `spring.quartz.properties`. If you need
to customize the task executor, consider implementing `SchedulerFactoryBeanCustomizer`.
Jobs can define setters to inject data map properties. Regular beans can also be injected Jobs can define setters to inject data map properties. Regular beans can also be injected
in a similar manner, as shown in the following example: in a similar manner, as shown in the following example:

Loading…
Cancel
Save