Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to conditionally enable or disable scheduled jobs in Spring?

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.

like image 938
Pierre Henry Avatar asked Aug 23 '13 15:08

Pierre Henry


People also ask

How do I stop a scheduled spring job?

Another way to stop the scheduler would be manually canceling its Future. In the cases with multiple scheduler tasks, then we can maintain the Future map inside of the custom scheduler pool but cancel the corresponding scheduled Future based on scheduler class.

How do I start and stop a scheduler in spring boot?

The schedulers do not start or stop. In the real world, it is necessary to stop and restart the scheduler without restarting the spring boot application. The ScheduledAnnotationBeanPostProcessor class allows you to programmatically start and stop the scheduler without having to restart the spring boot application.

How do I enable scheduling in spring boot?

The @EnableScheduling annotation is used to enable the scheduler for your application. This annotation should be added into the main Spring Boot application class file. The @Scheduled annotation is used to trigger the scheduler for a specific time period.

How do I stop a scheduled cron job in Java?

Replace ScheduledAnnotationBeanPostProcessor bean by the custom bean in configuration. Add @ScheduledSwitch annotation to the beans that you want to prevent or stop @Scheduled tasks.


9 Answers

@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...

        }
    }
}
like image 63
Prabhakaran Ramaswamy Avatar answered Nov 03 '22 14:11

Prabhakaran Ramaswamy


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

like image 26
Vpn_talent Avatar answered Nov 03 '22 14:11

Vpn_talent


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() {...}

}
like image 23
Oleg Avatar answered Nov 03 '22 14:11

Oleg


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.

like image 24
tmullin Avatar answered Nov 03 '22 13:11

tmullin


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

like image 32
user3474985 Avatar answered Nov 03 '22 13:11

user3474985


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

like image 26
aweigold Avatar answered Nov 03 '22 14:11

aweigold


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 = "";
    }
}
like image 33
labm0nkey Avatar answered Nov 03 '22 14:11

labm0nkey


@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;
    }
}
like image 20
Vinit Solanki Avatar answered Nov 03 '22 13:11

Vinit Solanki


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.

like image 35
Bin Avatar answered Nov 03 '22 14:11

Bin