How to conditionally enable or disable scheduled jobs in Spring?
JavaSpringCronScheduled TasksJava Problem Overview
I am defining scheduled jobs with cron style patterns in Spring, using the @Scheduled
annotation.
The cron pattern is stored in a config properties file. Actually there are two properties files: one default config, and one profile config that is environment dependent (e.g. dev, test, prod customer 1, prod customer 2 etc.) and overrides some of the default values.
I configured a property placeholder bean in my spring context which allows me to use ${}
style placeholders to import values from my properties files.
The job beans looks like this:
@Component
public class ImagesPurgeJob implements Job {
private Logger logger = Logger.getLogger(this.getClass());
@Override
@Transactional(readOnly=true)
@Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
public void execute() {
//Do something
//can use DAO or other autowired beans here
}
}
Relevant parts of my context XML :
<!-- Enable configuration of scheduled tasks via annotations -->
<task:annotation-driven/>
<!-- Load configuration files and allow '${}' style placeholders -->
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config/default-config.properties</value>
<value>classpath:config/environment-config.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="ignoreResourceNotFound" value="false"/>
</bean>
I really like this. It's quite simple and clean with minimal XML.
However I have one more requirement: some of these jobs can be totally disabled in some cases.
So, before I used Spring to manage them I created them manually and there is a boolean parameter along with the cron parameter in the config files, to specify if the job has to be enabled or not:
jobs.mediafiles.imagesPurgeJob.enable=true or false
jobs.mediafiles.imagesPurgeJob.schedule=0 0 0/12 * * ?
How can I use this parameter in Spring to conditionally create or just plainly ignore the bean, depending on this config parameter?
One obvious workaround would be to define a cron pattern that would never evaluate, so the job is never executed. But the bean would still be created and the config would be a bit obscure, so I feel there must be a better solution.
Java Solutions
Solution 1 - Java
The most efficient way to disable @Scheduled
in Spring is to set cron expression to -
@Scheduled(cron = "-")
public void autoEvictAllCache() {
LOGGER.info("Refresing the Cache Start :: " + new Date());
activeMQUtility.sendToTopicCacheEviction("ALL");
LOGGER.info("Refresing the Cache Complete :: " + new Date());
}
From the docs:
> CRON_DISABLED
>
> public static final String CRON_DISABLED
> A special cron
> expression value that indicates a disabled trigger: "-". This is
> primarily meant for use with ${...} placeholders, allowing for
> external disabling of corresponding scheduled methods.
>
> Since:
> 5.1 See Also: ScheduledTaskRegistrar.CRON_DISABLED
Solution 2 - Java
@Component
public class ImagesPurgeJob implements Job {
private Logger logger = Logger.getLogger(this.getClass());
@Value("${jobs.mediafiles.imagesPurgeJob.enable}")
private boolean imagesPurgeJobEnable;
@Override
@Transactional(readOnly=true)
@Scheduled(cron = "${jobs.mediafiles.imagesPurgeJob.schedule}")
public void execute() {
//Do something
//can use DAO or other autowired beans here
if(imagesPurgeJobEnable){
Do your conditional job here...
}
}
}
Solution 3 - Java
You can group schedule methods by conditions into number of services and init them like this:
@Service
@ConditionalOnProperty("yourConditionPropery")
public class SchedulingService {
@Scheduled
public void task1() {...}
@Scheduled
public void task2() {...}
}
Solution 4 - Java
Spring Boot provides @ConditionalOnProperty, which would be perfect if you were using Spring Boot. This annotation is a specialization of @Conditional, introduced with Spring 4.0.0.
Assuming you're just using "regular" spring and not Spring Boot, you could create your own Condition implementation for use with @Conditional that would mimic Spring Boot's @ConditionalOnProperty.
Solution 5 - Java
If you are looking to toggle @EnableScheduling from a property you can do this in Spring Boot by moving the @EnableScheduling annotation to a configuration class and use @ConditionalOnProperty as follows:
@Configuration
@EnableScheduling
@ConditionalOnProperty(prefix = "com.example.scheduling", name="enabled", havingValue="true", matchIfMissing = true)
public class SchedulingConfiguration {
}
This will disable scheduling for the application. This may be useful in a situation where you want to be able to run the application once or scheduled depending on how it's being started.
From wilkinsona's comment on here: https://github.com/spring-projects/spring-boot/issues/12682
Solution 6 - Java
Your question states to condition the actual creation of the bean. You can do this easily with this parameter by using @Profile if you are using at least Spring 3.1.
See the documentation here: http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/context/annotation/Profile.html
Solution 7 - Java
You can also create a Bean based on condition and that Bean can have a Scheduled method.
@Component
@Configuration
@EnableScheduling
public class CustomCronComponent {
@Bean
@ConditionalOnProperty(value = "my.cron.enabled", matchIfMissing = true, havingValue = "true")
public MyCronTask runMyCronTask() {
return new MyCronTask();
}
}
and
@Component
public class MyCronTask {
@Scheduled(cron = "${my.cron.expression}")
public void run() {
String a = "";
}
}
Solution 8 - Java
@Component
public class CurrencySyncServiceImpl implements CurrencySyncService {
private static Boolean isEnableSync;
/**
* Currency Sync FixedDelay in minutes
*/
private static Integer fixedDelay;
@Transactional
@Override
@Scheduled(fixedDelayString = "#{${currency.sync.fixedDelay}*60*1000}")
public void sync() {
if(CurrencySyncServiceImpl.isEnableSync) {
//Do something
//you can use DAO or other autowired beans here.
}
}
@Value("${currency.sync.fixedDelay}")
public void setFixedDelay(Integer fixedDelay) {
CurrencySyncServiceImpl.fixedDelay = fixedDelay;
}
@Value("${currency.sync.isEnable}")
public void setIsEnableSync(Boolean isEnableSync) {
CurrencySyncServiceImpl.isEnableSync = isEnableSync;
}
}
Solution 9 - Java
Please see my answer in another question. I think this is the best way to solve it. How to stop a scheduled task that was started using @Scheduled annotation?
Define a custom annotation like below.
@Documented
@Retention (RUNTIME)
@Target(ElementType.TYPE)
public @interface ScheduledSwitch {
// do nothing
}
Define a class implements org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.
public class ScheduledAnnotationBeanPostProcessorCustom
extends ScheduledAnnotationBeanPostProcessor {
@Value(value = "${prevent.scheduled.tasks:false}")
private boolean preventScheduledTasks;
private Map<Object, String> beans = new HashMap<>();
private final ReentrantLock lock = new ReentrantLock(true);
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
ScheduledSwitch switch = AopProxyUtils.ultimateTargetClass(bean)
.getAnnotation(ScheduledSwitch.class);
if (null != switch) {
beans.put(bean, beanName);
if (preventScheduledTasks) {
return bean;
}
}
return super.postProcessAfterInitialization(bean, beanName);
}
public void stop() {
lock.lock();
try {
for (Map.Entry<Object, String> entry : beans.entrySet()) {
postProcessBeforeDestruction(entry.getKey(), entry.getValue());
}
} finally {
lock.unlock();
}
}
public void start() {
lock.lock();
try {
for (Map.Entry<Object, String> entry : beans.entrySet()) {
if (!requiresDestruction(entry.getKey())) {
super.postProcessAfterInitialization(
entry.getKey(), entry.getValue());
}
}
} finally {
lock.unlock();
}
}
}
Replace ScheduledAnnotationBeanPostProcessor bean by the custom bean in configuration.
@Configuration
public class ScheduledConfig {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor() {
return new ScheduledAnnotationBeanPostProcessorCustom();
}
}
Add @ScheduledSwitch annotation to the beans that you want to prevent or stop @Scheduled tasks.
Solution 10 - Java
I know my answer is a hack, but giving a valid cron expression that never executes may fix the issue (in the environment specific configuration), https://stackoverflow.com/questions/13835221/quartz-cron-expression-that-will-never-execute
Solution 11 - Java
We can disable the bean creation of the class having that scheduled methods using @Conditional
annotation. This is very similar to @ConditionalOnProperty
. This is used to conditionally spin up a bean on to the spring context. If we set the value to false
, then the bean will not be spun up and loaded to spring. Below is the code.
application.properties:
com.boot.enable.scheduling=enable
Condition:
public class ConditionalBeans implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "enabled".equalsIgnoreCase(context.getEnvironment().getProperty("com.boot.enable.scheduling"));
}
}
My schedule class
@Service
@Conditional(ConditionalSchedules.class)
public class PrintPeriodicallyService {
@Scheduled(fixedRate = 3000)
public void runEvery3Seconds() {
System.out.println("Current time : " + new Date().getTime());
}
}
This approach has a lot of flexibility where the condition generation is totally under our control.