How to conditionally enable or disable scheduled jobs in Spring? - java

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.

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

#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...
}
}
}

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

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.

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

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

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 = "";
}
}

#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;
}
}

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.

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), Quartz: Cron expression that will never execute

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.

Related

spring boot - integration test autowired interface no such bean found

I have a spring-boot app that now needs to support multiple Object stores and selectively use the desired store based on the environment. Essentially what i have done is create an interface that each store repository then implements.
I have simplified the code for the examples.
I have created 2 beans for each store type based on the spring profile determining the env:
#Profile("env1")
#Bean
public store1Sdk buildClientStore1() {
return new store1sdk();
}
#Profile("env2")
#Bean
public store2Sdk buildClientStore2() {
return new store2sdk();
}
in the service layer I have autowired the interface and then in the repositories i have used #Profile to specify which instance of the interface to use.
public interface ObjectStore {
String download(String fileObjectKey);
...
}
#Service
public class ObjectHandlerService {
#Autowired
private ObjectStore objectStore;
public String getObject(String fileObjectKey) {
return objectStore.download(fileObjectKey);
}
...
}
#Repository
#Profile("env1")
public class Store1Repository implements ObjectStore {
#Autowired
private Store1Sdk store1client;
public String download(String fileObjectKey) {
return store1client.getObject(storeName, fileObjectKey);
}
}
When I start the application with the configured "env" this actually runs as expected. however when running the test I get the "no qualifying bean of type ObjectStore. expected at least 1 bean which qualifies as autowire candidate."
#ExtendWith({ SpringExtension.class })
#SpringBootTest(classes = Application.class)
#ActiveProfiles("env1,test")
public class ComposerServiceTest {
#Autowired
private ObjectHandlerService service;
#Test
void download_success() {
String response = service.getObject("testKey");
...
}
}
As noted in the #ActiveProfile on the test class there are some other environments e.g. dev,test,prod. I have tried playing around with Component scan, having impl and interface in the same package, etc, to no success. I feel like I am missing something obvious with the test setup. But could be something with my overall application config? my main aim with the solution is to avoid having something a long the lines of
if (store1Sdk != null) {
store1Sdk.download(fileObjectKey);
}
if (store2Sdk != null) {
store2Sdk.download(fileObjectKey);
}
Try #ActiveProfiles({"env1", "test"}).
Activate multiple profiles using #ActiveProfiles and specify profiles as an array.
this probrom because Store1Repository use #Profile("env1"), when you use #test,this class not invoke. try delete #Profile("env1") of Store1Repository.
if you use #test, both of store1Sdk/store2Sdk don't instanse, try add default instanse.eg:
#Bean
public store2Sdk buildClientStoreDefault() {
return new store2sdk();
}

Class based on factory class not available for autowiring

I'm trying to learn the basics of Spring and have defined this class?
public class Processor {
public Processor() {
this.setup();
}
private void setup(){
//run some setup code
}
public String processString(String str) {
// process the string
return str;
}
}
I want to Spring enable this class so I use a factory bean:
Reading https://www.baeldung.com/spring-factorybean I use:
public class Processor implements FactoryBean<Processor> {
#Override
public Processor getObject() throws Exception {
return new Processor();
}
#Override
public Class<?> getObjectType() {
return Processor.class;
}
}
To Test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ProcessorFactory.class)
public class ProcessorTest {
#Autowired
private Processor processor;
#Test
public void testProcessor() {
//Some tests
}
}
This works as expected.
When I try to use
#Autowired
private Processor processor;
elsewhere in my project I receive compile-time error :
Could not autowire. No beans of 'Processor' type found.
Have I not setup the factory correctly? I should annotate the Processor object to indicate it is to be autowired ? Perhaps this is not a valid use case for Factory ?
In general, factory beans are pretty outdated, first spring versions indeed used this approach, but spring has evolved since that time.
The Factory bean itself should be registered in the spring configuration (the very first versions of spring used xml based configuration because Java Annotation did not exist at that time) so the tutorial contains the XML configuration example. Anyway, that's probably the reason of failure. In the test you should also specify the path to the xml configuration otherwise the factory bean won't be loaded.
You can use these factory beans (they're still supported) but they have the following downsides:
They couple your code to the spring framework
A lot of boilerplate (usually in typical application there can be hundreds of beans, so creating a Factory Bean for each one is an overkill).
So you can:
Instead of using Factory Beans, annotate the Processor with #Component annotation (or more specialized #Service).
Alternatively Use Java configuration:
#Configration
public class MyConfig {
#Bean
public Processor processor() {
// optionally slightly customize the creation of the class if required;
return new Processor();
}
}

Spring Boot application run method periodically

I am playing with a simple Spring Boot application and RabbitMQ.
However I cannot figure out how to run a method periodically.
Here is my Application class
#SpringBootApplication
public class SampleApp {
#Autowired
Sender sender;
public static void main(String[] args) {
SpringApplication.run(SampleApp.class, args);
}
#EventListener(ApplicationReadyEvent.class)
public void doSomethingAfterStartup() {
sender.sendMessage();
}
}
And the sendMessage method is defined as below
#Scheduled(fixedRate = 3000L)
public void sendMessage() {
log.info("Sending message...");
rabbitTemplate.convertAndSend("my-exchange", "my-routing-key", "TEST MESSAGE");
}
However this method is called only once, I can see only a single line in the console.
What I missed in my code?
Thanks.
Looks like you are missing #EnableScheduling:
#EnableScheduling
#SpringBootApplication
public class SampleApp {
...
}
Quoting the documentation:
Enables Spring's scheduled task execution capability, similar to functionality found in Spring's <task:*> XML namespace. To be used on #Configuration classes as follows:
#Configuration
#EnableScheduling
public class AppConfig {
// various #Bean definitions
}
This enables detection of #Scheduled annotations on any Spring-managed bean in the container.
I am usually using the Spring ThreadPoolTaskScheduler. You define it, as Bean for example then you wrap your method into a Runnable and you call it at intervals defined by a CronTrigger. The result can be retrieved using a ScheduledFuture
Check https://www.baeldung.com/spring-task-scheduler for a complete beginner tutorial.
Here's an alternative way if you don't want to rely on Spring's scheduler.
It's using rxjava2, here's an example:
#Component
public class MessagePublisher {
Sender sender;
Disposable d;
#Autowired
public Config(Sender sender) {
this.sender = sender;
this.d = doSomethingAfterStartup();
}
public Disposable doSomethingAfterStartup() {
return Observable.interval(3000, TimeUnit.MILLISECONDS).subscribe(tick -> {
sender.sendMessage();
});
}
}

Adding scheduler for #Scheduled annotation in spring without using xml annotations

I have several methods with the annotation #Scheduled. For each annotation or a group of them I want a different scheduler to be used. For example:
Group A has 3 methods with #Scheduled annotation which need to use Scheduler X.
Group B has 5 methods with #Scheduled annotation which need to use Scheduler Y.
From what I have read in Does spring #Scheduled annotated methods runs on different threads?, if the scheduler is not specified then only one of those methods will run at a time.
I know how this connection can be done using xml-based annotation. But is there a way that this can be done using Java-based annotation only?
It can be done using java config. But not using an annotation attributes.
You could have a look at the Spring API doc for some extended example.
For example:
#Configuration
#EnableScheduling
public class AppConfig implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
#Bean(destroyMethod="shutdown")
public Executor taskScheduler() {
return Executors.newScheduledThreadPool(42);
}
}
#Scheduled group is not yet supported. See this open issue.
If you want use more than one scheduler you have to create and configure them programmatically. For example:
#Configuration
#EnableScheduling
public class AppConfig implements SchedulingConfigurer {
[...]
#Bean(destroyMethod="shutdown", name = "taskSchedulerA")
public Executor taskSchedulerA() {
return Executors.newScheduledThreadPool(42);
}
#Bean(destroyMethod="shutdown", name = "taskSchedulerB")
public Executor taskSchedulerA() {
return Executors.newScheduledThreadPool(42);
}
}
#Service
public class MyService {
#Autowired #Qualifier("taskSchedulerA")
private Executor taskSchedulerA;
#Autowired #Qualifier("taskSchedulerB")
private Executor taskSchedulerB;
#PostConstruct
public void schedule(){
Executors.newScheduledThreadPool(42).schedule(new Runnable() {
#Override
public void run() {
functionOfGroupA();
}
} , ..);
}
}

How to silently cancel bean creation?

#Component
public class MyBean {
#Autowired
public MyBean(#Value("${optional:#{null}}") String optional) {
if (optional == null) {
// cancel bean creation?
}
}
}
How to silently cancel bean creation? I could throw a RuntimeException, but I don't want this cancellation to be considered as an error: the bean must just not be created, the application initialization must go on.
Here you can make use of #Conditional
Step 1- Implement Condition.matches so as to specify when should the bean be created or not.
public class SomeCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return System.getProperty("optional") != null;
}
}
The condition class is referred as direct class (not as spring bean) so it can't use the #Value property injection. See here for alternative
Step 2 - In the configuration class specify the above class as condition to decide the bean creation
#Configuration
public class SomeAppConfig {
#Bean
#Condition(SomeCondition.class)
public MyBean myBean() {
return new MyBean();
}
}
P.S.: I have assumed that you use Java config.
It's not possible.
If you don't create a bean you can't use it. It's just a simple Java class and the property used/autowired in the bean are just useless.
However, you may configure profile base configuration beans for different kind of environments like 'Dev', 'Test' or 'Production'.
https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/

Categories